Skip to content

Instantly share code, notes, and snippets.

@jcward
Last active September 12, 2020 10:58
Show Gist options
  • Save jcward/4669dd9f1e79948d6a5e to your computer and use it in GitHub Desktop.
Save jcward/4669dd9f1e79948d6a5e to your computer and use it in GitHub Desktop.
Haxe typed compile time constants example macro (parsing or evaluating)
-main Main
-lib hscript
-neko test.n
-D host="my.example.com"
-D port=8080
-D timestamp_expr=Date.now().getTime()
--next
-cmd neko test.n
# Note: Commandline escaping is different from build.hxml escaping
haxe -lib hscript -main Main -neko test.n -D host='"my.example.com"' -D port=8080 -D timestamp_expr="Date.now().getTime()"
// In a build system, I like to pass typed compile-time constants to my code
// via compiler defines. For example, a host String and a port Int. The
// following macros provide functionality to either parse or eval a compiler
// define as Haxe code. Since they operate at compile time and inject the
// resulting expressions into your code, this retains all type features --
// from type inference to type checking.
//
// Note that $type prints the type at compile time, while trace prints
// the value at runtime. Neko is called simply to demonstrate the latter.
import haxe.macro.Context;
class Main {
static function main()
{
// -D host='"my.example.com"'
var host = parseDefine("host");
#if !macro $type(host); #end // String
trace(host);
// -D port=8080
var port = parseDefine("port");
#if !macro $type(port); #end // Int
trace(port);
// parseDefine parses a define into an expression that evaluates at runtime:
// -D timestamp_expr="Date.now().getTime()"
var run_timestamp = parseDefine("timestamp_expr");
#if !macro $type(run_timestamp); #end // Float
trace(" Now timestamp: "+run_timestamp);
// evalDefine parses and then evaluates a define, resulting in an
// expression (often a constant) at runtime:
// -D timestamp_expr="Date.now().getTime()"
var build_timestamp = evalDefine("timestamp_expr");
#if !macro $type(build_timestamp); #end // Float
trace("Build timestamp: "+build_timestamp);
}
macro public static function parseDefine(key:String)
{
return Context.parse(Context.definedValue(key), Context.currentPos());
}
macro public static function evalDefine(key:String)
{
var e = new hscript.Parser().parseString(Context.definedValue(key));
var interp = new hscript.Interp();
// hscript: export some useful classes
interp.variables.set("Array", Array);
interp.variables.set("DateTools", DateTools);
interp.variables.set("Date", Date);
interp.variables.set("Math", Math);
interp.variables.set("StringTools", StringTools);
interp.variables.set("Sys", Sys);
interp.variables.set("Xml", Xml);
interp.variables.set("sys", {
"FileSystem": sys.FileSystem,
"io": {
"File": sys.io.File
},
"net": {
"Host": sys.net.Host
}
});
interp.variables.set("haxe", {
"Json": haxe.Json,
"Http": haxe.Http,
"Serializer": haxe.Serializer,
"Unserializer": haxe.Unserializer
});
return Context.makeExpr(interp.execute(e), Context.currentPos());
}
}
Thanks to Simn, nadako, dstrekelj, and grepsuzette for chatting on IRC about this.
@gamedevsam
Copy link

That is really cool!

@jcward
Copy link
Author

jcward commented Mar 16, 2016

[snip] @nadako and I discussed parsing vs evaluating. He mentions Context.eval was added at some point, but it was commented out for now (see HaxeFoundation/haxe#4443)

ETA: Ok, I've updated the gist to support either parseDefine or evalDefine, the latter currently relying on hscript to execute the code (since Context.eval is not yet available). Unfortunately it appears you have to manually add Haxe types to the scope of hscript, so be aware depending on what types your define snippets use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment