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.