hxml tricks every haxe user should know

March 4th, 2013  |  Published in Uncategorized

Haxe projects usually are compiled with a hxml file, which basically contains compiler arguments. So most of the tricks I am going to show is actually compiler arguments tricks, which may also applicable to nmml file or IDE that does not use hxml (though they should).

1. Run project right after compilation

Being able to do so is critical to rapid development. The simple trick to use -cmd, which run the a command after successful compilation. For different target, you have to use different command:

  • Neko

    -neko Test.n
    -main Test
    -cmd neko Test.n

    Or there is actually a shortcut that performs exactly the same as above:
    -x Test

  • C++

    -cpp bin
    -main Test
    -cmd ./bin/Test

    Notice the resulting file will be Test-debug if you compile with -debug.

  • JavaScript

    -js Test.js
    -main Test
    -cmd phantomjs Test.js

    Here we use phantomjs to run the JS file, the trace will output to the terminal. In case it is something graphical, you have to launch a browser with a HTML file that loads the script.

  • PHP

    -php bin
    -main Test
    -cmd php bin/index.php

    I guess you have already know you can execute a php script this way?

  • Flash

    -swf Test.swf
    -main Test
    -cmd path/to/FlashDebugger Test.swf

    Or if you're on Mac, you can actually use -cmd open Test.swf, it will do the same as you double clicked on a swf file (or any other file!).

  • Java

    -java bin
    -main Test
    -cmd java -jar bin/bin.jar

  • C#

    -cs bin
    -main Test
    -cmd mono bin/bin/bin.exe

    Mac/Linux has to use Mono to execute it. Windows should able to run it with -cmd bin/bin/bin.exe (not tested though).

2. Commenting a hxml file

Use a hash (i.e. #) to comment out the rest of the line. Despite of being useful to put documentation, it can let us switch between different compilation configurations:

-js Main.js
-main Main
### comment out one of the following logging levels
#-D my-log-error
#-D my-log-warning
-D my-log-info

And in the source file:

public function attack(target:Monster, power:Int):Void {
    if (target == null) {
        #if (my_log_error || my_log_warning || my_log_info)
        trace("target is null");
        #end
        return;
    }
    if (power <= 0) {
        #if (my_log_warning || my_log_info)
        trace("attack power should be positive");
        #end
        return;
    }
    mp -= power;
    target.hp -= power;
    #if my_log_info
    trace("attacked " + target + " by " + power);
    #end
}

3. Append extra compiler argument when using the command line

Another useful trick to switch between compilation configurations, is to simple supply more arguments after the hxml file. For example temporarily switch to debug mode:

haxe project.hxml -debug

4. Multiple compilations at once

The majority knows the existence of  --next, used for separating different builds:

all.hxml
-js script/MainPage.js
-main MainPage
-lib jQueryExtern
--next
-js script/ContactPage.js
-main ContactPage
-lib jQueryExtern
--next
-js script/AlbumPage.js
-main AlbumPage
-lib jQueryExtern

There is an --each option, which reduce the repeating params. It is used in the haxe compiler unit test hxml. Rewriting the above, we will get:

all.hxml
-lib jQueryExtern
--each
-js script/MainPage.js
-main MainPage
--next
-js script/ContactPage.js
-main ContactPage
--next
-js script/AlbumPage.js
-main AlbumPage

But really, separating each of them in different hxml is easier to maintain and has better compatibility with code completion (because many editors don't read hxml with --next very well). So we may use the following instead:

MainPage.hxml
-js script/MainPage.js
-main MainPage
-lib jQueryExtern

ContactPage.hxml
-js script/ContactPage.js
-main ContactPage
-lib jQueryExtern

AlbumPage.hxml
-js script/AlbumPage.js
-main AlbumPage
-lib jQueryExtern

all.hxml
MainPage.hxml
--next
ContactPage.hxml
--next
AlbumPage.hxml

Switch the hxml for code completion when working in different pages, and use the "all.hxml" to compile at once.

Tags: ,

Haxe tips: everything is an expression

October 14th, 2012  |  Published in Uncategorized

In Haxe, nearly everything is an expression. (Things that aren’t: import statement, class declaration etc, which are at module level). And every expression can be evaluated to a value.

A block is an expression that is evaluated to the last expression inside the block:

var v = {
    //some code
    123;
}
trace(v);//123

It can be used for list comprehension:

var oneToTen = {
	var a = [];
	for (i in 0...10) a.push(i+1);
	a;
}	
trace(oneToTen); //[1,2,3,4,5,6,7,8,9,10]

In reverse, we can notice many things actually use any expression instead of only a block.

Function declaration:

function hello() return "world";

For loop:

for (i in 0...5) trace(i); //0 1 2 3 4

And of course if-else:

if (a > 100)
    trace("a is more than 100");
else if (a > 50)
    trace("a is between 50 and 100");
else
    trace("a less than 50");

If-else itself is an expression too. So we can simplify the above to:

trace(if (a > 100) "a is more than 100" else if (a > 50) "a is between 50 and 100" else "a less than 50");

Of course we can use the equivalent ternary operator, but in my opinion it is a bit less readable:

trace(a > 100 ? "a is more than 100" : a > 50 ? "a is between 50 and 100" : "a less than 50");

Switch is useful to be used as an expression when working with enum:

enum Color {
	Gray(v:Int);
	Rgb(r:Int, g:Int, b:Int);
}
 
class Main {
	static function main():Void {
		var redColor = Rgb(255,0,0);
		var red = switch(redColor) {
			case Rgb(r,g,b): r;
			default: throw "not rgb color";
		};
		trace(red);//255
	}
}

And actually try-catch also returns a value:

var noException = try {
	//some code
	true;
} catch (exception:Dynamic) {
	false;
}

Do you find any other good use of anything as an expression?

Tags: ,

Haxe tips: Macro-Proxied Class = with macros for compile-time and implementation for run-time

July 17th, 2012  |  Published in Uncategorized

As mentioned in the previous post on overloading, I found a way to provide a reflection friendly, run-time implementation of a @:macro method or an extern class. The trick is simple: build the implementation with a @:native metadata.

Here is an implementation of using such I called macro-proxied class in the method overloading example:

//R.hx
#if macro 
import haxe.macro.Expr;
#end
 
#if !macro @:include("R") extern #end class R {
	/**
	 * Helper to get the real run-time implementation of this class.
	 * Only use at compile-time.
	 */
	inline static public var _impl = R_impl;
 
	/**
	 * Helper to mark an instance as the run-time implementation of this class.
	 * Only use at compile-time.
	 */
	@:macro function _to_impl(r:ExprOf<R>):ExprOf<R_impl> {
		return {
			expr: ECheckType(macro untyped $r, TPath({
				pack:[], 
				name:"R", 
				params: [], 
				sub: "R_impl"
			})),
			pos: r.pos
		}
	}
 
	/**
	 * Get a resource by its url.
	 * Returns a Xml if url ends with ".xml", a String otherwise.
	 */
	@:macro static public function get(url:ExprOf<String>):Expr {
		switch(url.expr) {
			case EConst(c): switch (c) {
				//url is a constant String, we can use optimized versions
				case CString(str):
					var dotPos = str.lastIndexOf(".");
					if (dotPos != -1) switch (str.substr(dotPos).toLowerCase()) {
						case ".xml": return macro R._impl.getXml($url);
						default:
					}
 
					return return macro R._impl.getText($url);
				default:
			}
			default:
		}
 
		//url is not known at compile-time, let the method check it at run-time.
		return macro R._impl.get($url);
	}
 
	/**
	 * Construct a resource loader.
	 */
	public function new():Void {}
 
	/**
	 * Function same as R.get(url). Just to demo the macro proxy on instance method.
	 */
	@:macro public function load(ethis:ExprOf<R>, url:ExprOf<String>):Expr {
		ethis = macro $ethis._to_impl();
		switch(url.expr) {
			case EConst(c): switch (c) {
				//url is a constant String, we can use optimized versions
				case CString(str):
					var dotPos = str.lastIndexOf(".");
					if (dotPos != -1) switch (str.substr(dotPos).toLowerCase()) {
						case ".xml": return macro $ethis.loadXml($url);
						default:
					}
 
					return return macro $ethis.loadText($url);
				default:
			}
			default:
		}
 
		//url is not known at compile-time, let the method check it at run-time.
		return macro $ethis.load($url);
	}
}
 
/**
 * Real implementation of R.
 */
@:native("R") // <-- here is the trick ;)
class R_impl {
 
	static public function get(url:String):Dynamic {		
		var dotPos = url.lastIndexOf(".");
		if (dotPos != -1) switch(url.substr(dotPos).toLowerCase()) {
			case ".xml": return getXml(url);
			default:
		}
 
		return getText(url);
	}
 
	public function new():Void { }
 
	public function load(url:String):Dynamic {
		return get(url);
	}
 
	//Below are the optimized overload cases. Skipped file extension check.
 
	static public function getText(url:String):String {
		return haxe.Http.requestUrl(url);
	}
 
	static public function getXml(url:String):Xml {
		return Xml.parse(getText(url));
	}
 
	public function loadText(url:String):String {
		return getText(url);
	}
 
	public function loadXml(url:String):Xml {
		return getXml(url);
	}
}

A simple test:

//Test.hx
class Test {
	static function main():Void {
 
		trace(Type.getClass(new R().load("http://blog.onthewings.net/sitemap.xml"))); //Test.hx:4: Xml
		trace(Type.getClass(R.get("http://blog.onthewings.net/sitemap.xml"))); //Test.hx:5: Xml
 
		trace(Type.getClass(new R().load("http://www.google.com/"))); //Test.hx:7: String
		trace(Type.getClass(R.get("http://www.google.com/"))); //Test.hx:8: String
 
		//call using variable, so that the compiler does not know it is a xml
		//just to show the overloading logic is working even at run-time
		var a = "http://blog.onthewings.net/sitemap.xml";
		trace(Type.getClass(new R().load(a))); //Test.hx:13: Xml
		trace(Type.getClass(R.get(a))); //Test.hx:14: Xml
 
	}
}

Abilities of Macro-Proxied Class

Macro-proxied class provides a compile-time pre-processing stage for the methods. We can remap method call to another optimized method base on compile-time checking on the method arguments. eg. method overloading as shown above.

There is an implementation at run-time, with class and method names remained the same. Thus reflection friendly.

Some ideas that may use macro-proxied class includes:

  • A FastMath class that optimize math operation when arguments are known at compile-time.
  • cpp wrapper(ndll) that can use method overloading like the native API.
  • jQuery plug-in that is used by “using”, for better variable argument length support.
  • SPOD, to merge the macro version to the original verion.
  • hxLINQ, like SPOD, to perform compile-time optimization on queries when possible.

Limitations of Macro-Proxied Class

Firstly, you cannot proxy the constructor, since the constructor(new) cannot be a @:macro method.

Secondly, care should be taken on handling inheritance. For the R example, if we want to extend R, the subclass should also be a macro-proxied class. Something like:

//SubR.hx
import R;
 
#if macro 
import haxe.macro.Expr;
#end
 
#if !macro @:include("SubR") extern #end class SubR extends R {
	inline static public var _impl = SubR_impl;
 
	@:macro override function _to_impl(r:ExprOf<SubR>):ExprOf<SubR_impl> {
		return {
			expr: ECheckType(macro untyped $r, TPath({
				pack:[], 
				name:"SubR", 
				params: [], 
				sub: "SubR_impl"
			})),
			pos: r.pos
		}
	}
 
	@:macro override public function load(ethis:ExprOf<R>, url:ExprOf<String>):Expr {
		var ethis = macro $ethis._to_impl();
		return macro $ethis.load($url);
	}
}
 
@:native("SubR")
class SubR_impl extends R_impl {
	override public function load(url:String):Dynamic {
		return "overrided!";
	}
 
	//Remember to override the optimized overload cases too!!
 
	override public function loadText(url:String):String {
		return load(url);
	}
 
	override public function loadXml(url:String):Xml {
		return load(url);
	}
}

Both the sub classes of a macro-proxied class have to be a macro-proxied class too since the signature of some methods at compile-time are changed to Array<Expr>->Expr. Inherited methods that are not macro-proxied cannot be macro-proxie. Also we have to think about the case when a SubR instance is stored in a R variable, would the macros or the run-time version of the methods work correctly?

Moreover, the structure of a macro-proxied class is not preserved. ie. R is not a structure type of { function load(url:String):Dynamic; }, which R_impl is, since load is not an instance method, but a macro method at compile-time. You can workaround this by casting (actually is an untyped expression) to R_impl by using _to_impl().

Lastly, again this trick should be refactored into some lib as it is a bit too complicated to write every time. I guess at least we should use a @:build macro to copy all the properties and methods(for the ones we do not want to override).

Tags: ,