Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 :)

like image 403
chamberlainpi Avatar asked Oct 02 '15 14:10

chamberlainpi


People also ask

What is a Haxe macro?

hx of the Haxe Standard Library. Figure: The role of macros during compilation. A basic macro is a syntax-transformation. It receives zero or more expressions and also returns an expression. If a macro is called, it effectively inserts code at the place it was called from.

How do I initialize a macro?

Initialization macros are invoked from the command line by using the --macro callExpr(args) command. This registers a callback which the compiler invokes after creating its context, but before typing the argument to --main . This then allows configuring the compiler in some ways.


2 Answers

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
}
like image 75
Philippe Avatar answered Sep 17 '22 21:09

Philippe


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

like image 25
wighawag Avatar answered Sep 21 '22 21:09

wighawag