In Haxe, how do you add Types/Classes to a Module with macros?
Asked Answered
D

2

5

I'd like to dynamically add some new Types to a given Module based on some files found in a directory.

I'm essentially trying to populate a bunch of @:file(...) embed classes at the bottom of a Module.

//This is the module I'm targeting to append embedded ByteArray subtypes (see below)
@:build(macros.AutoEmbed.build("some/folder/"))
class Embeds {
    //Empty on purpose, just let the Macro do its thing!
}


// At "macro-time", it should generate these:

@:file("some/folder/ui_main.xml")
class UI_MAIN_XML extends flash.utils.ByteArray { }

@:file("some/folder/config.template.json")
class CONFIG_TEMPLATE_JSON extends flash.utils.ByteArray { }

What I've been able to find so far is that I might have to alter the Embeds.hx module. So I looked into Context.getModule( Context.getLocalModule() ). I've also looked into TypeDefinition since it seems like the only way to define a new type from scratch.

The problem with Context.getModule(...) though is that it returns an array Array<Type>, not Array<TypeDefinition>, so I can't append new TypeDefinition to it (plus I have to figure out how to write those, ughh). That's probably a bad assumption on my part, but I thought by simply appending more TypeDefinition to it I could dynamically provide more types in the module.

I'm still very new to Macros as you can tell!

EDIT

It's true that I could just dynamically write/overwrite a new Embeds.hx file at compile-time with a FileSystem / File write solution, but that implies needing to compile at least once before your IDE's auto-completion can pickup the generated Embeds.* classes (FlashDevelop in my case). Plus anytime new files are dropped in the defined folder, same problem: you need to compile first before the IDE detects it. Yes, I really like auto-completion :)

Drive answered 2/10, 2015 at 14:13 Comment(0)
S
6

Starting from the build macro is good. You can build class fields and create types.

Here's a macro which will just generate one type and a corresponding field:

#if macro
import haxe.macro.Context;
import haxe.macro.Expr;

class AutoEmbed
{
    macro static public function build(folder:String):Array<Field>
    {
        var inClass = Context.getLocalClass().get();

        // explore folder and create those:

        var fileName = 'resource/strings.json';
        var name = fileName.split('/').pop().split('.').join('_');

        var valueExpr = makeType(inClass.pack, name, fileName);

        var field = {
            name: name,
            access: [APublic, AStatic, AInline],
            kind: FVar(null, valueExpr),
            pos: Context.currentPos()
        }

        return [field];
    }

    static function makeType(pack:Array<String>, name:String, fileName:String) 
    {
        var pos = Context.currentPos();
        var className = name.toUpperCase();

        var cdef = macro class Tmp extends haxe.io.BytesData { }
        cdef.pack = pack.copy();
        cdef.name = className;

        cdef.meta = [{
            name: ':file',
            params: [Context.makeExpr(fileName, pos)],
            pos: pos
        }];

        haxe.macro.Context.defineType(cdef);

        return {
            expr:EConst(CIdent(className)),
            pos:pos
        };
    }
}
#end

Now to use it:

trace(Embed.strings_json); // [ByteArray]

@:build(AutoEmbed.build('some/folder'))
class Embeds
{
    // generate field strings_json pointing to class STRINGS_JSON
}
Sponsor answered 2/10, 2015 at 21:16 Comment(2)
Nice and complete code-example! Thanks. Question though, where does the Tmp come from in the macro class Tmp extends haxe.io.BytesData part? And can I still extend flash.utils.ByteArray instead of haxe's version (not sure about the difference between the two to be honest...)?Drive
I use a dummy class name because I didn't figure how to use dynamic value here... BytesData is the same as Flash ByteArray - you can probably use it as well.Sponsor
S
4

You can use initialization macro : http://haxe.org/manual/macro-initialization.html to execute macro before typing occurs.

Then to actually create new classes/modules you can use Context.defineModule : http://api.haxe.org/haxe/macro/Context.html#defineModule

Sophi answered 2/10, 2015 at 18:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.