Small Haxe questions & answers

Sometimes I have one or other semi-obscure Haxe question, find nothing via search, and think "I feel like I asked about this before somewhere". And sometimes it turns out that yes, I did, but it usually takes a while to find what project houses the solution code, or where did I ask it.

This post is a small dump of such questions, mostly eventually self-answered.

Pattern-match on a nullable object

Context: I have enum cases of kind VarDecl(name:String, value:Null<Expr>) and wanted to match for specific value,

switch (expr.def) {
	case VarDecl(name, _.def => Some(...)): {
		// ...
	};
	// ...
	default:
}

but this would obviously crash if value is null.

Solution 1: match null with a preceding case:

switch (expr.def) {
	case VarDecl(name, null):
	case VarDecl(name, _.def => Some(...)): {
		// ...
	};
	// ...
	default:
}

downside: if you wanted code in default, you now need to move it in inline function so that you don't have to duplicate it.

Solution 2: add a "is not null?" parameter to your enum constructor

VarDecl(name:String, hasValue:Bool, value:Null<Expr>);
switch (expr.def) {
	case VarDecl(name, true, Some(...)): {
		// ...
	};
	// ...
	default:
}

downside: need to be able to change the enum itself (not always the case)

Raw JS regexp literals in Haxe code

Context: Sometimes you want actual RegExp literals on JS, as those will be managed by compiler (unlike instantiating RegExp, which compiles a new instance on every call), and to avoid \\ to represent a single backslash to match in source text.

Solution: As with any syntactic omission in Haxe, you can fix it with a macro

public static macro function rx(e:Expr) {
	switch (e.expr) {
		case EConst(CRegexp(s, o)): {
			var s = '/$s/$o';
			return macro (cast js.Syntax.code($v{s}):js.RegExp);
		};
		default: throw Context.error("Expected a regexp literal", e.pos);
	}
}

and then

var r = Macro.rx(~/(\w+)\\(\w+)/g).exec("a\\b");

will compile to

var r = /(\w+)\\(\w+)/g.exec("a\\b");

as you would expect.

Get full path to a Haxe library

Context: In my Haxe->GameMaker compiler I needed to call haxe.macro.Compiler.addClassPath to override standard library classes with GML-specific implementations, but it would take an absolute path.

Solution: haxe.macro.Context.resolvePath can resolve paths even if they are not correct class paths.

So, in my case, I had

haxelib.json
gml/Lib.hx
gml/(more target-specific wrappers and externs)
gml/std/Date.hx (override for std Date)
gml/std/haxe/CallStack.hx (override for std haxe.CallStack)

and did Compiler.addClassPath(Context.resolvePath("gml/std")).

Extract a ZIP archive on Haxe-JS

Context: Haxe has a set of standard classes for working with zlib, but they do not work very well (when at all) on JS.

Solution: There are pako externs, which is a compact JS library for the purpose, and these work perfectly fine.

Compress a ZIP archive on Haxe-JS

Context: While Haxe's zlib compressor classes don't work on JS (see above), you can still use haxe.zip.* to create uncompressed ZIP archives. Except standard Windows archive tool will deny to open it by default, saying "There is some data after the end of payload data"

Solution: You must trim your resulting Bytes to match BytesOutput.length, like so:

var output = new haxe.io.BytesOutput();
var writer = new haxe.zip.Writer(output);
var entries = new List();
var now = Date.now();
for (file in files) { // files is Array<{path:String,bytes:Bytes}>
	var bytes = file.bytes;
	entries.push({
		fileName: file.path,
		fileSize: bytes.length,
		fileTime: now,
		compressed: false,
		dataSize: bytes.length,
		data: bytes,
		crc32: haxe.crypto.Crc32.make(bytes)
	});
}
//
writer.write(entries);
var zipBytes = output.getBytes().sub(0, output.length);

TCP connection limit with Socket.select

Context: one of the projects I wrote some code for years ago had finally passed ~250 CCU earlier this year, and to their and mine surprise the server just got jammed when this happened.

Solution (?): apparently, on Unix (Mac/Linux/etc.) systems TCP clients count towards ulimit -n, and that's just about what would happen if you hit the resource limit.

Special thanks to: ReallyUniqueName

Get argument static field information in a macro

Context: I had

macroFunc(SomeClass.staticField)

and wanted to get information about that field

Solution: haxe.macro.Context.typeExpr can get you a TField, from which you can get a TTypeExpr, from which you can get the actual type and fields.

Special thanks to: nadako

Allow casting from any type to an abstract through a function

Context: While porting some legacy code from other language, I encountered a bulk of cases where the value had to be cast differently depending on source type, and with the source type not always being known compile-time. So the abstract had to accept any type for implicit cast, and figure out how to cast the value at runtime.

Solution: (rough mockup)

abstract WrapType(BaseType) from BaseType {
	@:from static inline function fromT1(v:T1):WrapType {
		// ...
	}
	@:from static inline function fromT2(v:T2):WrapType {
		// ...
	}
	@:from static function fromDyn(v:Dynamic):WrapType {
		if (Std.is(v, T1)) return fromT1(v);
		if (Std.is(v, T2)) return fromT2(v);
		// ...
		throw "Can't convert!";
	}
}

What's the name of automatic type detection algorithm used in Haxe

Answer: Type inference

Special thanks to: nadako

I keep mixing this up with type coercion for some reason.

Related posts:

Leave a Reply

Your email address will not be published. Required fields are marked *