From ca93a84e7faaa532e666774bcc91274074281286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aurel=20Bi=CC=81ly=CC=81?= Date: Thu, 27 Oct 2022 20:05:40 +0200 Subject: [PATCH] new version --- .gitignore | 2 +- haxelib.json | 5 +- src/ammer/Lib.hx | 23 + src/ammer/Lib.macro.baked.hx | 22 + src/ammer/Lib.macro.hx | 250 ++++++ src/ammer/Syntax.hx | 4 + src/ammer/Syntax.macro.hx | 90 ++ src/ammer/def/Enum.hx | 13 + src/ammer/def/Library.hx | 8 + src/ammer/def/Opaque.hx | 8 + src/ammer/def/Struct.hx | 8 + src/ammer/def/Sublibrary.hx | 8 + src/ammer/ffi/Alloc.hx | 7 + src/ammer/ffi/Array.hx | 8 + src/ammer/ffi/Bool.hx | 7 + src/ammer/ffi/Box.hx | 8 + src/ammer/ffi/Bytes.hx | 8 + src/ammer/ffi/Callback.hx | 25 + src/ammer/ffi/Deref.hx | 7 + src/ammer/ffi/FilePtr.hx | 77 ++ src/ammer/ffi/Float32.hx | 7 + src/ammer/ffi/Float64.hx | 7 + src/ammer/ffi/Haxe.hx | 8 + src/ammer/ffi/Int16.hx | 7 + src/ammer/ffi/Int32.hx | 7 + src/ammer/ffi/Int64.hx | 7 + src/ammer/ffi/Int8.hx | 7 + src/ammer/ffi/Size.hx | 4 + src/ammer/ffi/String.hx | 7 + src/ammer/ffi/This.hx | 7 + src/ammer/ffi/UInt16.hx | 7 + src/ammer/ffi/UInt32.hx | 7 + src/ammer/ffi/UInt64.hx | 7 + src/ammer/ffi/UInt8.hx | 7 + src/ammer/ffi/Unsupported.hx | 7 + src/ammer/ffi/Void.hx | 7 + src/ammer/internal/Ammer.hx | 527 ++++++++++++ src/ammer/internal/Bakery.hx | 514 +++++++++++ src/ammer/internal/Config.hx | 85 ++ src/ammer/internal/Entrypoint.hx | 899 ++++++++++++++++++++ src/ammer/internal/Fields.hx | 559 ++++++++++++ src/ammer/internal/FilePtrOutput.hx | 23 + src/ammer/internal/LibContext.hx | 181 ++++ src/ammer/internal/LibTypes.hx | 67 ++ src/ammer/internal/Meta.hx | 273 ++++++ src/ammer/internal/Reporting.hx | 110 +++ src/ammer/internal/Types.hx | 365 ++++++++ src/ammer/internal/Utils.hx | 191 +++++ src/ammer/internal/v1/AmmerBaked.hx | 41 + src/ammer/internal/v1/AmmerSetup.baked.hx | 28 + src/ammer/internal/v1/LibInfo.hx | 179 ++++ src/ammer/internal/v1/OsInfo.hx | 76 ++ test/native-gen/NativeGen.hx | 127 +++ test/native-gen/ammer/def/Enum.hx | 13 + test/native-gen/common-footer/native.h | 3 + test/native-gen/common-header/native.c | 7 + test/native-gen/common-header/native.h | 15 + test/native-gen/common-header/templates.cpp | 1 + test/native-gen/common-header/templates.hpp | 10 + test/native-gen/make.hxml | 4 + test/native-gen/test/Test.hx | 16 + test/native-src/Makefile.linux | 22 + test/native-src/Makefile.osx | 28 + test/native-src/Makefile.win | 21 + test/native-src/utf8.c | 26 + test/native-src/utf8.h | 12 + test/src/Main.hx | 12 + test/src/def/Native.hx | 15 + test/src/def/Templates.hx | 7 + test/src/test/Test.hx | 38 + test/src/test/TestArrays.hx | 70 ++ test/src/test/TestBytes.hx | 65 ++ test/src/test/TestCInjection.hx | 34 + test/src/test/TestCallback.hx | 232 +++++ test/src/test/TestConstants.hx | 38 + test/src/test/TestCpp.hx | 71 ++ test/src/test/TestDatatypes.hx | 236 +++++ test/src/test/TestEnums.hx | 46 + test/src/test/TestHaxe.hx | 22 + test/src/test/TestHaxeRef.hx | 39 + test/src/test/TestMaths.hx | 157 ++++ test/src/test/TestSignature.hx | 135 +++ test/src/test/TestStrings.hx | 53 ++ 83 files changed, 6389 insertions(+), 2 deletions(-) create mode 100644 src/ammer/Lib.hx create mode 100644 src/ammer/Lib.macro.baked.hx create mode 100644 src/ammer/Lib.macro.hx create mode 100644 src/ammer/Syntax.hx create mode 100644 src/ammer/Syntax.macro.hx create mode 100644 src/ammer/def/Enum.hx create mode 100644 src/ammer/def/Library.hx create mode 100644 src/ammer/def/Opaque.hx create mode 100644 src/ammer/def/Struct.hx create mode 100644 src/ammer/def/Sublibrary.hx create mode 100644 src/ammer/ffi/Alloc.hx create mode 100644 src/ammer/ffi/Array.hx create mode 100644 src/ammer/ffi/Bool.hx create mode 100644 src/ammer/ffi/Box.hx create mode 100644 src/ammer/ffi/Bytes.hx create mode 100644 src/ammer/ffi/Callback.hx create mode 100644 src/ammer/ffi/Deref.hx create mode 100644 src/ammer/ffi/FilePtr.hx create mode 100644 src/ammer/ffi/Float32.hx create mode 100644 src/ammer/ffi/Float64.hx create mode 100644 src/ammer/ffi/Haxe.hx create mode 100644 src/ammer/ffi/Int16.hx create mode 100644 src/ammer/ffi/Int32.hx create mode 100644 src/ammer/ffi/Int64.hx create mode 100644 src/ammer/ffi/Int8.hx create mode 100644 src/ammer/ffi/Size.hx create mode 100644 src/ammer/ffi/String.hx create mode 100644 src/ammer/ffi/This.hx create mode 100644 src/ammer/ffi/UInt16.hx create mode 100644 src/ammer/ffi/UInt32.hx create mode 100644 src/ammer/ffi/UInt64.hx create mode 100644 src/ammer/ffi/UInt8.hx create mode 100644 src/ammer/ffi/Unsupported.hx create mode 100644 src/ammer/ffi/Void.hx create mode 100644 src/ammer/internal/Ammer.hx create mode 100644 src/ammer/internal/Bakery.hx create mode 100644 src/ammer/internal/Config.hx create mode 100644 src/ammer/internal/Entrypoint.hx create mode 100644 src/ammer/internal/Fields.hx create mode 100644 src/ammer/internal/FilePtrOutput.hx create mode 100644 src/ammer/internal/LibContext.hx create mode 100644 src/ammer/internal/LibTypes.hx create mode 100644 src/ammer/internal/Meta.hx create mode 100644 src/ammer/internal/Reporting.hx create mode 100644 src/ammer/internal/Types.hx create mode 100644 src/ammer/internal/Utils.hx create mode 100644 src/ammer/internal/v1/AmmerBaked.hx create mode 100644 src/ammer/internal/v1/AmmerSetup.baked.hx create mode 100644 src/ammer/internal/v1/LibInfo.hx create mode 100644 src/ammer/internal/v1/OsInfo.hx create mode 100644 test/native-gen/NativeGen.hx create mode 100644 test/native-gen/ammer/def/Enum.hx create mode 100644 test/native-gen/common-footer/native.h create mode 100644 test/native-gen/common-header/native.c create mode 100644 test/native-gen/common-header/native.h create mode 100644 test/native-gen/common-header/templates.cpp create mode 100644 test/native-gen/common-header/templates.hpp create mode 100644 test/native-gen/make.hxml create mode 100644 test/native-gen/test/Test.hx create mode 100644 test/native-src/Makefile.linux create mode 100644 test/native-src/Makefile.osx create mode 100644 test/native-src/Makefile.win create mode 100644 test/native-src/utf8.c create mode 100644 test/native-src/utf8.h create mode 100644 test/src/Main.hx create mode 100644 test/src/def/Native.hx create mode 100644 test/src/def/Templates.hx create mode 100644 test/src/test/Test.hx create mode 100644 test/src/test/TestArrays.hx create mode 100644 test/src/test/TestBytes.hx create mode 100644 test/src/test/TestCInjection.hx create mode 100644 test/src/test/TestCallback.hx create mode 100644 test/src/test/TestConstants.hx create mode 100644 test/src/test/TestCpp.hx create mode 100644 test/src/test/TestDatatypes.hx create mode 100644 test/src/test/TestEnums.hx create mode 100644 test/src/test/TestHaxe.hx create mode 100644 test/src/test/TestHaxeRef.hx create mode 100644 test/src/test/TestMaths.hx create mode 100644 test/src/test/TestSignature.hx create mode 100644 test/src/test/TestStrings.hx diff --git a/.gitignore b/.gitignore index 7215988..eb89364 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ *.o *.so -test/bin +/test/bin/** diff --git a/haxelib.json b/haxelib.json index 433fad2..4c2eadc 100644 --- a/haxelib.json +++ b/haxelib.json @@ -9,5 +9,8 @@ "classPath": "src/", "contributors": [ "Aurel300" - ] + ], + "dependencies": { + "ammer-core": "" + } } \ No newline at end of file diff --git a/src/ammer/Lib.hx b/src/ammer/Lib.hx new file mode 100644 index 0000000..3d5c7aa --- /dev/null +++ b/src/ammer/Lib.hx @@ -0,0 +1,23 @@ +// ammer-bake: ammer Lib true +package ammer; + +class Lib { + // struct methods + public static macro function allocStruct(cls:Class, ?initVals:{}):T; + public static macro function nullPtrStruct(cls:Class):T; + + // box methods + public static macro function allocBox(cls:Class, ?initVal:T):ammer.ffi.Box; + public static macro function nullPtrBox(cls:Class):ammer.ffi.Box; + + // array methods + public static macro function allocArray(cls:Class, size:Int, ?initVal:T):ammer.ffi.Array; + public static macro function nullPtrArray(cls:Class):ammer.ffi.Array; + + public static macro function vecToArrayCopy(vec:haxe.ds.Vector):ammer.ffi.Array; + public static macro function vecToArrayRef(vec:haxe.ds.Vector):ammer.ffi.ArrayRef; + public static macro function vecToArrayRefForce(vec:haxe.ds.Vector):ammer.ffi.ArrayRef; + + // Haxe ref methods + public static macro function createHaxeRef(cls:Class, e:T):ammer.ffi.Haxe; +} diff --git a/src/ammer/Lib.macro.baked.hx b/src/ammer/Lib.macro.baked.hx new file mode 100644 index 0000000..b76fcfa --- /dev/null +++ b/src/ammer/Lib.macro.baked.hx @@ -0,0 +1,22 @@ +// ammer-bake: ammer Lib.macro macro +package ammer; + +import haxe.macro.Context; +import haxe.macro.Context.currentPos; +import haxe.macro.Context.fatalError as fail; +import haxe.macro.Context.resolveType; +import haxe.macro.Expr; +import haxe.macro.Type; +import haxe.macro.TypeTools; +import ammer.internal.*; +import ammer.internal.v1.AmmerBaked.mergedInfo as info; + +using Lambda; + +class Lib { + static function withPos(pos:Position, f:()->T):T { + return f(); + } +// ammer-include: internal/Utils.hx lib-baked +// ammer-include: Lib.macro.hx lib-baked +} diff --git a/src/ammer/Lib.macro.hx b/src/ammer/Lib.macro.hx new file mode 100644 index 0000000..07c0d06 --- /dev/null +++ b/src/ammer/Lib.macro.hx @@ -0,0 +1,250 @@ +package ammer; + +import haxe.macro.Context; +import haxe.macro.Context.fatalError as fail; +import haxe.macro.Expr; +import haxe.macro.TypeTools; +import ammer.internal.*; +import ammer.internal.Ammer.mergedInfo as info; +import ammer.internal.Utils.access; +import ammer.internal.Utils.accessTp; +import ammer.internal.Utils.complexTypeExpr; +import ammer.internal.Utils.expectTypePath; +import ammer.internal.Utils.isNull; +import ammer.internal.Utils.triggerTyping; +import ammer.internal.Utils.typeId; +import ammer.internal.Utils.typeId2; + +using Lambda; + +class Lib { + static function withPos(pos:Position, f:()->T):T { + return Reporting.withPosition(pos, f); + } + + static function resolveType(ct:ComplexType, pos:Position):Type { + return Reporting.withPosition(cls.pos, () -> Reporting.resolveType(elCt, cls.pos)); + } + + // These methods are inserted into `Lib.macro.hx` when baking a library. + // This avoids code duplication/synchronisation issues. Importantly, the code + // is just string-pasted, so it is important that the `import`s that are + // in `Lib.macro.baked.hx` are sufficient for the code to work. + +// ammer-fragment-begin: lib-baked + public static function allocStruct(cls:Expr, ?initVals:Expr):Expr { + var ct = complexTypeExpr(cls); + var clsType = withPos(cls.pos, () -> triggerTyping(ct)); + clsType != null || throw fail("invalid type in allocStruct call", cls.pos); + var struct = info.structs[typeId(clsType)]; + struct != null || throw fail("not a struct type in allocStruct call", cls.pos); + struct.gen.alloc != null || throw fail("struct type was not marked with @:ammer.alloc", cls.pos); + var alloc = struct.gen.alloc; + if (isNull(initVals)) { + return macro @:privateAccess $p{access(clsType)}.$alloc(); + } + var assigns = (switch (initVals) { + case {expr: EObjectDecl(fields)}: + [ for (field in fields) macro @:pos(field.expr.pos) $p{["_allocated", field.field]} = $e{field.expr} ]; + case _: throw fail("expected initial values (e.g. {a: 1, b: 2, ...}) as second argument of allocStruct call", initVals.pos); + }); + return macro { + var _allocated = @:privateAccess $p{access(clsType)}.$alloc(); + $b{assigns}; + _allocated; + }; + } + + public static function nullPtrStruct(cls:Expr):Expr { + var ct = complexTypeExpr(cls); + var clsType = withPos(cls.pos, () -> triggerTyping(ct)); + clsType != null || throw fail("invalid type in nullPtrStruct call", cls.pos); + var typeId = typeId(clsType); + var struct = info.structs[typeId]; + //var opaque = info.opaques[typeId]; + //(struct != null || opaque != null) || throw fail("not a struct type or opaque type in nullPtrStruct call", cls.pos); + struct != null || throw fail("not a struct type in nullPtrStruct call", cls.pos); + struct.gen.nullPtr != null || throw fail("struct type was not marked with @:ammer.alloc", cls.pos); + var nullPtr = struct.gen.nullPtr; + return macro @:privateAccess $p{access(clsType)}.$nullPtr(); + } + + public static function allocBox(cls:Expr, ?initVal:Expr):Expr { + var elCt = complexTypeExpr(cls); + #if ammer + var elType = resolveType(elCt, cls.pos); + resolveType((macro : ammer.ffi.Box<$elCt>), cls.pos); + var box = info.boxes.byElementTypeId[typeId2(elType)]; + #else + // if baked, ammer.ffi.Box does not exist, only its monomorphisations + var box = info.boxes.byElementTypeId[typeIdCt(elCt)]; + #end + box != null || throw fail("not a known box type in allocBox call", cls.pos); + var tp = expectTypePath(box.boxCt); + if (isNull(initVal)) { + return macro @:privateAccess new $tp($e{box.alloc}); + } + return macro { + var _allocated = @:privateAccess new $tp($e{box.alloc}); + _allocated.set($initVal); + _allocated; + }; + } + + public static function nullPtrBox(cls:Expr):Expr { + var elCt = complexTypeExpr(cls); + #if ammer + var elType = resolveType(elCt, cls.pos); + resolveType((macro : ammer.ffi.Box<$elCt>), cls.pos); + var box = info.boxes.byElementTypeId[typeId2(elType)]; + #else + // if baked, ammer.ffi.Box does not exist, only its monomorphisations + var box = info.boxes.byElementTypeId[typeIdCt(elCt)]; + #end + box != null || throw fail("not a known box type in nullPtrBox call", cls.pos); + var tp = expectTypePath(box.boxCt); + return macro @:privateAccess $p{accessTp(tp)}.nullPtr(); + } + + public static function allocArray(cls:Expr, size:Expr, ?initVal:Expr):Expr { + var elCt = complexTypeExpr(cls); + #if ammer + var elType = resolveType(elCt, cls.pos); + resolveType((macro : ammer.ffi.Array<$elCt>), cls.pos); + var array = info.arrays.byElementTypeId[typeId2(elType)]; + #else + // if baked, ammer.ffi.Array does not exist, only its monomorphisations + var array = info.arrays.byElementTypeId[typeIdCt(elCt)]; + #end + array != null || throw fail("not a known array type in allocArray call", cls.pos); + var tp = expectTypePath(array.arrayCt); + if (isNull(initVal)) { + return macro { + var _size = $size; + @:privateAccess new $tp($e{array.alloc}); + } + } + return macro { + var _size = $size; + var _val = $initVal; + var _allocated = @:privateAccess new $tp($e{array.alloc}); + for (i in 0..._size) { + _allocated.set(i, _val); + } + _allocated; + }; + } + + public static function nullPtrArray(cls:Expr):Expr { + var elCt = complexTypeExpr(cls); + #if ammer + var elType = resolveType(elCt, cls.pos); + resolveType((macro : ammer.ffi.Array<$elCt>), cls.pos); + var array = info.arrays.byElementTypeId[typeId2(elType)]; + #else + // if baked, ammer.ffi.Array does not exist, only its monomorphisations + var array = info.arrays.byElementTypeId[typeIdCt(elCt)]; + #end + array != null || throw fail("not a known array type in allocArray call", cls.pos); + var tp = expectTypePath(array.arrayCt); + return macro @:privateAccess $p{accessTp(tp)}.nullPtr(); + } + + public static function vecToArrayCopy(vec:Expr):Expr { + var typed = withPos(vec.pos, () -> Context.typeExpr(vec)); + var elType = (switch (typed.t) { + case TInst(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; + case TAbstract(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; + case _: throw fail("argument should be a haxe.ds.Vector", vec.pos); + }); + var elCt = TypeTools.toComplexType(elType); + var stored = Context.storeTypedExpr(typed); + #if ammer + // if baked, ammer.ffi.Array does not exist, only its monomorphisations + resolveType((macro : ammer.ffi.Array<$elCt>), vec.pos); + #end + var array = info.arrays.byElementTypeId[typeId2(elType)]; + array != null || throw fail("not a known array type in vecToArrayCopy call", vec.pos); + var tp = expectTypePath(array.arrayCt); + return macro { + var _vec = $vec; + @:privateAccess new $tp($e{array.fromHaxeCopy}); + }; + } + + public static function vecToArrayRef(vec:Expr):Expr { + var typed = withPos(vec.pos, () -> Context.typeExpr(vec)); + var elType = (switch (typed.t) { + case TInst(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; + case TAbstract(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; + case _: throw fail("argument should be a haxe.ds.Vector", vec.pos); + }); + var elCt = TypeTools.toComplexType(elType); + var stored = Context.storeTypedExpr(typed); + #if ammer + // if baked, ammer.ffi.Array does not exist, only its monomorphisations + resolveType((macro : ammer.ffi.Array<$elCt>), vec.pos); + #end + var array = info.arrays.byElementTypeId[typeId2(elType)]; + array != null || throw fail("not a known array type in vecToArrayRef call", vec.pos); + var tp = expectTypePath(array.arrayRefCt); + if (array.fromHaxeRef != null) { + // if references are supported, create an array ref + return macro { + var _vec = $vec; + @:privateAccess new $tp($e{array.fromHaxeRef}, _vec); + }; + } + // if not, create a fake ref with the same API + return macro { + var _vec = $vec; + @:privateAccess new $tp($e{array.fromHaxeCopy}, _vec); + }; + } + + public static function vecToArrayRefForce(vec:Expr):Expr { + var typed = withPos(vec.pos, () -> Context.typeExpr(vec)); + var elType = (switch (typed.t) { + case TInst(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; + case TAbstract(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; + case _: throw fail("argument should be a haxe.ds.Vector", vec.pos); + }); + var elCt = TypeTools.toComplexType(elType); + var stored = Context.storeTypedExpr(typed); + #if ammer + // if baked, ammer.ffi.Array does not exist, only its monomorphisations + resolveType((macro : ammer.ffi.Array<$elCt>), vec.pos); + #end + var array = info.arrays.byElementTypeId[typeId2(elType)]; + array != null || throw fail("not a known array type in vecToArrayRefForce call", vec.pos); + var tp = expectTypePath(array.arrayRefCt); + array.fromHaxeRef != null || throw fail("platform does not support non-copy references to Vector", vec.pos); + return macro { + var _vec = $vec; + @:privateAccess new $tp($e{array.fromHaxeRef}, _vec); + }; + } + + public static function createHaxeRef(cls:Expr, e:Expr):Expr { + var elCt = complexTypeExpr(cls); + var elType = resolveType(elCt, cls.pos); + #if ammer + // if baked, ammer.ffi.Haxe does not exist, only its monomorphisations + resolveType((macro : ammer.ffi.Haxe<$elCt>), cls.pos); + #end + var elId = typeId2(elType); + if (info.haxeRefs.byElementTypeId.exists(elId)) { + var haxeRef = info.haxeRefs.byElementTypeId[elId]; + return macro { + var _hxval = $e; + $e{haxeRef.create}; + }; + } + info.haxeRefs.byElementTypeId.exists(".Any.Any") || throw 0; + return macro { + var _hxval = $e; + new ammer.internal.LibTypes.HaxeAnyRef<$elCt>($e{info.haxeRefs.byElementTypeId[".Any.Any"].create}); + }; + } +// ammer-fragment-end: lib-baked +} diff --git a/src/ammer/Syntax.hx b/src/ammer/Syntax.hx new file mode 100644 index 0000000..083ef67 --- /dev/null +++ b/src/ammer/Syntax.hx @@ -0,0 +1,4 @@ +package ammer; + +@:autoBuild(ammer.Syntax.build()) +interface Syntax {} diff --git a/src/ammer/Syntax.macro.hx b/src/ammer/Syntax.macro.hx new file mode 100644 index 0000000..0cd2f60 --- /dev/null +++ b/src/ammer/Syntax.macro.hx @@ -0,0 +1,90 @@ +package ammer; + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.ExprTools; + +class Syntax { + public static function build():Array { + return [ for (field in Context.getBuildFields()) { + pos: field.pos, + name: field.name, + meta: field.meta, + kind: (switch (field.kind) { + case FVar(t, e): FVar(t, e != null ? process(e) : null); + case FFun(f): FFun({ + ret: f.ret, + params: f.params, + expr: f.expr != null ? process(f.expr) : null, + args: f.args, + }); + case FProp(get, set, t, e): FProp(get, set, t, e != null ? process(e) : null); + }), + doc: field.doc, + access: field.access, + } ]; + } + + static function process(e:Expr):Expr { + return (switch (e) { + // TODO: support bytes for ref/copy as well + case macro @copy $expr: macro ammer.Lib.vecToArrayCopy($expr); + case macro @leak $expr: macro { var _ref = new ammer.ffi.Haxe($expr); _ref.incref(); _ref; } + // TODO: this @ret solution is not good; process typed fields instead? + // TODO: reduce duplication + // TODO: better tempvar names + case macro @ret $e{{expr: ECall(f, args)}}: + var block = []; + var frees = []; + args = [ for (idx => arg in args) switch (arg) { + case macro @ref $expr: + var tmp = '_syntax_arg$idx'; + block.push(macro var $tmp = ammer.Lib.vecToArrayRef($e{process(expr)})); + frees.push(macro $i{tmp}.unref()); + macro $i{tmp}.array; + case macro @copyfree $expr: + var tmp = '_syntax_arg$idx'; + block.push(macro var $tmp = ammer.Lib.vecToArrayCopy($e{process(expr)})); + frees.push(macro $i{tmp}.free()); + macro $i{tmp}; + case _: process(arg); + } ]; + if (block.length > 0) { + var call = {expr: ECall(process(f), args), pos: e.pos}; + block.push(macro var _ret = $call); + block.push(macro $b{frees}); + block.push(macro _ret); + macro $b{block}; + } else { + ExprTools.map(e, process); + } + case {expr: ECall(f, args)}: + var block = []; + var frees = []; + args = [ for (idx => arg in args) switch (arg) { + case macro @ref $expr: + var tmp = '_syntax_arg$idx'; + block.push(macro var $tmp = ammer.Lib.vecToArrayRef($e{process(expr)})); + frees.push(macro $i{tmp}.unref()); + macro $i{tmp}.array; + case macro @copyfree $expr: + var tmp = '_syntax_arg$idx'; + block.push(macro var $tmp = ammer.Lib.vecToArrayCopy($e{process(expr)})); + frees.push(macro $i{tmp}.free()); + macro $i{tmp}; + case _: process(arg); + } ]; + if (block.length > 0) { + var call = {expr: ECall(process(f), args), pos: e.pos}; + block.push(macro $call); + block.push(macro if (true) $b{frees}); + macro $b{block}; + } else { + ExprTools.map(e, process); + } + case macro @ref $expr: throw Context.error("@ref can only be used on function call arguments", e.pos); + case macro @copyfree $expr: throw Context.error("@copyfree can only be used on function call arguments", e.pos); + case _: ExprTools.map(e, process); + }); + } +} diff --git a/src/ammer/def/Enum.hx b/src/ammer/def/Enum.hx new file mode 100644 index 0000000..b952aac --- /dev/null +++ b/src/ammer/def/Enum.hx @@ -0,0 +1,13 @@ +package ammer.def; + +#if macro + +import haxe.macro.Expr; + +class Enum { + public static function build(name:String, ffi:Expr, lib:Expr):Array { + return ammer.internal.Entrypoint.buildEnum(name, ffi, lib); + } +} + +#end diff --git a/src/ammer/def/Library.hx b/src/ammer/def/Library.hx new file mode 100644 index 0000000..6ed82b1 --- /dev/null +++ b/src/ammer/def/Library.hx @@ -0,0 +1,8 @@ +package ammer.def; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildLibrary()) +class Library<@:const Name> {} + +#end diff --git a/src/ammer/def/Opaque.hx b/src/ammer/def/Opaque.hx new file mode 100644 index 0000000..7c5a530 --- /dev/null +++ b/src/ammer/def/Opaque.hx @@ -0,0 +1,8 @@ +package ammer.def; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildOpaque()) +class Opaque<@:const Name, Lib> {} + +#end diff --git a/src/ammer/def/Struct.hx b/src/ammer/def/Struct.hx new file mode 100644 index 0000000..c72b797 --- /dev/null +++ b/src/ammer/def/Struct.hx @@ -0,0 +1,8 @@ +package ammer.def; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildStruct()) +class Struct<@:const Name, Lib> {} + +#end diff --git a/src/ammer/def/Sublibrary.hx b/src/ammer/def/Sublibrary.hx new file mode 100644 index 0000000..a87662d --- /dev/null +++ b/src/ammer/def/Sublibrary.hx @@ -0,0 +1,8 @@ +package ammer.def; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildSublibrary()) +class Sublibrary {} + +#end diff --git a/src/ammer/ffi/Alloc.hx b/src/ammer/ffi/Alloc.hx new file mode 100644 index 0000000..91a87f1 --- /dev/null +++ b/src/ammer/ffi/Alloc.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Alloc {} + +#end diff --git a/src/ammer/ffi/Array.hx b/src/ammer/ffi/Array.hx new file mode 100644 index 0000000..52e2795 --- /dev/null +++ b/src/ammer/ffi/Array.hx @@ -0,0 +1,8 @@ +package ammer.ffi; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildArray()) +class Array {} + +#end diff --git a/src/ammer/ffi/Bool.hx b/src/ammer/ffi/Bool.hx new file mode 100644 index 0000000..3d7ca98 --- /dev/null +++ b/src/ammer/ffi/Bool.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Bool {} + +#end diff --git a/src/ammer/ffi/Box.hx b/src/ammer/ffi/Box.hx new file mode 100644 index 0000000..601897e --- /dev/null +++ b/src/ammer/ffi/Box.hx @@ -0,0 +1,8 @@ +package ammer.ffi; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildBox()) +class Box {} + +#end diff --git a/src/ammer/ffi/Bytes.hx b/src/ammer/ffi/Bytes.hx new file mode 100644 index 0000000..6cce86b --- /dev/null +++ b/src/ammer/ffi/Bytes.hx @@ -0,0 +1,8 @@ +package ammer.ffi; + +#if !macro + +@:build(ammer.internal.Entrypoint.buildBytes()) +class Bytes {} + +#end diff --git a/src/ammer/ffi/Callback.hx b/src/ammer/ffi/Callback.hx new file mode 100644 index 0000000..154c482 --- /dev/null +++ b/src/ammer/ffi/Callback.hx @@ -0,0 +1,25 @@ +package ammer.ffi; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildCallback()) +class Callback< + // function type as seen by the native library + // e.g. (Int32, Haxe<(Int) -> Int>) -> Int32 + CallbackType, + + // function type as seen by Haxe + // e.g. (Int) -> Int + FunctionType, + + // where to find the function in CallbackType + @:const CallTarget, + + // which arguments to pass through to the Haxe function + @:const CallArgs, + + // parent library + Lib +> {} + +#end diff --git a/src/ammer/ffi/Deref.hx b/src/ammer/ffi/Deref.hx new file mode 100644 index 0000000..c2a62f4 --- /dev/null +++ b/src/ammer/ffi/Deref.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Deref {} + +#end diff --git a/src/ammer/ffi/FilePtr.hx b/src/ammer/ffi/FilePtr.hx new file mode 100644 index 0000000..53f18b0 --- /dev/null +++ b/src/ammer/ffi/FilePtr.hx @@ -0,0 +1,77 @@ +package ammer.ffi; + +#if !macro + +class FilePtr extends ammer.def.Opaque<"FILE*", ammer.internal.LibTypes> { + public static final SEEK_SET:Int; + public static final SEEK_CUR:Int; + public static final SEEK_END:Int; + public static final BUFSIZ:Int; + public static final EOF:Int; + public static final _IOFBF:Int; + public static final _IOLBF:Int; + public static final _IONBF:Int; + public static final FILENAME_MAX:Int; + public static final FOPEN_MAX:Int; + public static final TMP_MAX:Int; + public static final L_tmpnam:Int; + + public static final stderr:FilePtr; + public static final stdin:FilePtr; + public static final stdout:FilePtr; + + public static function fopen(filename:String, mode:String):FilePtr; + public static function getchar():Int; + public static function perror(str:String):Void; + public static function putchar(char:Int):Int; + public static function puts(str:String):Int; + public static function remove(filename:String):Int; + public static function rename(oldname:String, newname:String):Int; + public static function tmpfile():FilePtr; + // public static function tmpnam(str:String):String; // deprecated + + public function fclose(_:This):Void; + public function feof(_:This):Bool; + public function ferror(_:This):Int; + public function fflush(_:This):Int; + public function fgetc(_:This):Int; + public function fgetpos(_:This, pos:FilePos):Int; + //fgets + //fprintf? + public function fputc(char:Int, _:This):Int; + public function fputs(str:String, _:This):Int; + public function fread(ptr:Bytes, size:Size, count:Size, _:This):Size; + public function freopen(filename:String, mode:String, _:This):FilePtr; + // public function fscanf(_:This, format: ...) + public function fseek(_:This, offset:Int64, origin:Int):Int; + public function fsetpos(_:This, pos:FilePos):Int; + public function ftell(_:This):Int64; + public function fwrite(ptr:Bytes, size:Size, count:Size, _:This):Size; + // getc -> fgetc + // gets // removed + // public static function printf(...) + public function putc(char:Int, _:This):Int; + public function rewind(_:This):Void; + // public static function scanf(...) + public function setbuf(_:This, @:ammer.c.cast("char*") buffer:Bytes):Void; + public function setvbuf(_:This, @:ammer.c.cast("char*") buffer:Bytes, mode:Int, size:Size):Int; + // public static function snprintf(...) + // public static function sprintf(...) + // public static function sscanf(...) + public function ungetc(char:Int, _:This):Int; + // public function vfprintf(...) + // public function vfscanf(...) + // public function vprintf(...) + // public function vscanf(...) + // public function vsnprintf(...) + // public function vsprintf(...) + // public function vsscanf(...) + + @:ammer.haxe public function output():haxe.io.Output return @:privateAccess new ammer.internal.FilePtrOutput(this); + //public function input():haxe.io.Input return @:privateAccess new ammer.internal.FilePtrInput(this); +} + +@:ammer.alloc +class FilePos extends ammer.def.Struct<"fpos_t", ammer.internal.LibTypes> {} + +#end diff --git a/src/ammer/ffi/Float32.hx b/src/ammer/ffi/Float32.hx new file mode 100644 index 0000000..19393db --- /dev/null +++ b/src/ammer/ffi/Float32.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Float32 {} + +#end diff --git a/src/ammer/ffi/Float64.hx b/src/ammer/ffi/Float64.hx new file mode 100644 index 0000000..e4df009 --- /dev/null +++ b/src/ammer/ffi/Float64.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Float64 {} + +#end diff --git a/src/ammer/ffi/Haxe.hx b/src/ammer/ffi/Haxe.hx new file mode 100644 index 0000000..3142f08 --- /dev/null +++ b/src/ammer/ffi/Haxe.hx @@ -0,0 +1,8 @@ +package ammer.ffi; + +#if !macro + +@:genericBuild(ammer.internal.Entrypoint.genericBuildHaxeRef()) +class Haxe {} + +#end diff --git a/src/ammer/ffi/Int16.hx b/src/ammer/ffi/Int16.hx new file mode 100644 index 0000000..b375ecb --- /dev/null +++ b/src/ammer/ffi/Int16.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Int16 {} + +#end diff --git a/src/ammer/ffi/Int32.hx b/src/ammer/ffi/Int32.hx new file mode 100644 index 0000000..0f289bb --- /dev/null +++ b/src/ammer/ffi/Int32.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Int32 {} + +#end diff --git a/src/ammer/ffi/Int64.hx b/src/ammer/ffi/Int64.hx new file mode 100644 index 0000000..ca0e644 --- /dev/null +++ b/src/ammer/ffi/Int64.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Int64 {} + +#end diff --git a/src/ammer/ffi/Int8.hx b/src/ammer/ffi/Int8.hx new file mode 100644 index 0000000..5abc94c --- /dev/null +++ b/src/ammer/ffi/Int8.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Int8 {} + +#end diff --git a/src/ammer/ffi/Size.hx b/src/ammer/ffi/Size.hx new file mode 100644 index 0000000..7e020a0 --- /dev/null +++ b/src/ammer/ffi/Size.hx @@ -0,0 +1,4 @@ +package ammer.ffi; + +// TODO: deal with this ... +typedef Size = ammer.ffi.Int32; diff --git a/src/ammer/ffi/String.hx b/src/ammer/ffi/String.hx new file mode 100644 index 0000000..481d020 --- /dev/null +++ b/src/ammer/ffi/String.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class String {} + +#end diff --git a/src/ammer/ffi/This.hx b/src/ammer/ffi/This.hx new file mode 100644 index 0000000..2195a7b --- /dev/null +++ b/src/ammer/ffi/This.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class This {} + +#end diff --git a/src/ammer/ffi/UInt16.hx b/src/ammer/ffi/UInt16.hx new file mode 100644 index 0000000..8583c10 --- /dev/null +++ b/src/ammer/ffi/UInt16.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class UInt16 {} + +#end diff --git a/src/ammer/ffi/UInt32.hx b/src/ammer/ffi/UInt32.hx new file mode 100644 index 0000000..4011d8c --- /dev/null +++ b/src/ammer/ffi/UInt32.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class UInt32 {} + +#end diff --git a/src/ammer/ffi/UInt64.hx b/src/ammer/ffi/UInt64.hx new file mode 100644 index 0000000..c6357e3 --- /dev/null +++ b/src/ammer/ffi/UInt64.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class UInt64 {} + +#end diff --git a/src/ammer/ffi/UInt8.hx b/src/ammer/ffi/UInt8.hx new file mode 100644 index 0000000..652f1ad --- /dev/null +++ b/src/ammer/ffi/UInt8.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class UInt8 {} + +#end diff --git a/src/ammer/ffi/Unsupported.hx b/src/ammer/ffi/Unsupported.hx new file mode 100644 index 0000000..c90c854 --- /dev/null +++ b/src/ammer/ffi/Unsupported.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Unsupported<@:const Expr> {} + +#end diff --git a/src/ammer/ffi/Void.hx b/src/ammer/ffi/Void.hx new file mode 100644 index 0000000..3578ac4 --- /dev/null +++ b/src/ammer/ffi/Void.hx @@ -0,0 +1,7 @@ +package ammer.ffi; + +#if !macro + +class Void {} + +#end diff --git a/src/ammer/internal/Ammer.hx b/src/ammer/internal/Ammer.hx new file mode 100644 index 0000000..621f7f5 --- /dev/null +++ b/src/ammer/internal/Ammer.hx @@ -0,0 +1,527 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Compiler; +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.PositionTools; +import haxe.macro.Type; +import haxe.io.Path; +import sys.io.File; +import sys.FileSystem; + +using StringTools; + +typedef AmmerConfig = { + buildPath:String, + outputPath:String, +}; + +typedef QueuedSubtype = { + subId:String, + lib:Type, + pos:Position, + process:(ctx:LibContext)->Any, + result:Null, + done:Bool, + prev:Null, + next:Null, +}; + +class Ammer { + public static var baseConfig(get, never):AmmerConfig; + public static var builder(get, never):ammer.core.Builder; + public static var platform(get, never):ammer.core.Platform; + public static var platformConfig(default, null):ammer.core.plat.BaseConfig; + + public static var mergedInfo = new ammer.internal.v1.LibInfo(); + public static var libraries:{ + byLibraryName:Map, + byTypeId:Map, + active:Array, + } = { + byLibraryName: [], + byTypeId: [], + active: [], + }; + + static var queuedSubtypes:{ + first:QueuedSubtype, + last:QueuedSubtype, + } = { + first: null, + last: null, + }; + static function enqueueSubtype(s:QueuedSubtype):Void { + s.prev == null || throw 0; + s.next == null || throw 0; + if (queuedSubtypes.last == null) { + queuedSubtypes.first == null || throw 0; + queuedSubtypes.first = s; + queuedSubtypes.last = s; + return; + } + queuedSubtypes.first != null || throw 0; + queuedSubtypes.last.next == null || throw 0; + s.prev = queuedSubtypes.last; + queuedSubtypes.last.next = s; + queuedSubtypes.last = s; + } + static function dequeueSubtype(s:QueuedSubtype):Void { + if (s.prev == null) { + queuedSubtypes.first = s.next; + } else { + s.prev.next = s.next; + } + if (s.next == null) { + queuedSubtypes.last = s.prev; + } else { + s.next.prev = s.prev; + } + } + + static var baseConfigL:AmmerConfig; + static var builderL:ammer.core.Builder; + static var platformL:ammer.core.Platform; + + static function get_baseConfig():AmmerConfig { + if (baseConfigL != null) + return baseConfigL; + return baseConfigL = { + buildPath: Config.getPath("ammer.buildPath", null, true), + outputPath: Config.getPath("ammer.outputPath", null, true), + }; + } + + static function get_builder():ammer.core.Builder { + if (builderL != null) + return builderL; + + // TODO: allow selection of toolchain, configuration, etc + return ammer.core.Builder.createCurrentBuilder(({} : ammer.core.build.BaseBuilderConfig)); + } + + static function get_platform():ammer.core.Platform { + if (platformL != null) + return platformL; + + Bakery.init(); + + function getPaths(key:String):Array { + var paths = Config.getStringArray(key, ";"); + if (paths == null) return []; + return paths.filter(v -> v != ""); + } + platformConfig = (switch (Context.definedValue("target.name")) { + case "cpp": ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + staticLink: Config.getBool("ammer.cpp.staticLink", false), + } : ammer.core.plat.Cpp.CppConfig); + case "cs": ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + } : ammer.core.plat.Cs.CsConfig); + // TODO: eval? + case "hl": ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + hlc: Config.getBool("ammer.hl.hlc", !Compiler.getOutput().endsWith(".hl")), + hlIncludePaths: getPaths("ammer.hl.includePaths"), + hlLibraryPaths: getPaths("ammer.hl.libraryPaths"), + } : ammer.core.plat.Hashlink.HashlinkConfig); + case "java": ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + javaIncludePaths: getPaths("ammer.java.includePaths"), + javaLibraryPaths: getPaths("ammer.java.libraryPaths"), + } : ammer.core.plat.Java.JavaConfig); + case "lua": ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + luaIncludePaths: getPaths("ammer.lua.includePaths"), + luaLibraryPaths: getPaths("ammer.lua.libraryPaths"), + } : ammer.core.plat.Lua.LuaConfig); + case "neko": ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + nekoIncludePaths: getPaths("ammer.neko.includePaths"), + nekoLibraryPaths: getPaths("ammer.neko.libraryPaths"), + } : ammer.core.plat.Neko.NekoConfig); + case "js": ({ + // TODO: (once implemented,) choose JS build system + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + nodeGypBinary: Config.getString("ammer.js.nodeGypBinary", "node-gyp"), + } : ammer.core.plat.Nodejs.NodejsConfig); + case "python": ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + pythonVersionMinor: Config.getInt("ammer.python.version", 8), + pythonIncludePaths: getPaths("ammer.python.includePaths"), + pythonLibraryPaths: getPaths("ammer.python.libraryPaths"), + } : ammer.core.plat.Python.PythonConfig); + + case _: ({ + buildPath: baseConfig.buildPath, + outputPath: baseConfig.outputPath, + } : ammer.core.plat.None.NoneConfig); + }); + + platformL = ammer.core.Platform.createCurrentPlatform(platformConfig); + Context.onAfterTyping(_ -> { + var program = platformL.finalise(); + builder.build(program); + }); + return platformL; + } + + public static function registerBakedLibraryV1(info:ammer.internal.v1.LibInfo):Void { + // TODO: also detect duplicate library names (add to libraries.byLibraryName ?) + Reporting.withCurrentPos(() -> { + final fail = Reporting.error; +// ammer-fragment-begin: register-v1 + // detect local path + var path = PositionTools.getInfos(info.herePos).file; + if (!Path.isAbsolute(path)) { + path = Path.join([Sys.getCwd(), path]); + } + path = Path.normalize(Path.directory(path)); + var binaryPath = Path.normalize(path + "/" + info.setupToBin); + + if (true) { //if (Context.defined('${info.name}_copy_binary')) { + var outputPath = Context.definedValue("ammer.outputPath"); + outputPath != null || throw fail("missing required define: ammer.outputPath"); + FileSystem.createDirectory(outputPath); + for (file in info.files) { + var dst = file.dst; + var dstFull = Path.normalize(Path.join([outputPath, dst])); + + var dstExists = FileSystem.exists(dstFull); + if (dstExists) { + !FileSystem.isDirectory(dstFull) || throw fail('$dst exists but is a directory'); + if (true) continue; + /* + // TODO: skip if configured (when running ammer proper?) + var digest = haxe.crypto.Sha256.make(File.getBytes(dstFull)).toHex(); + if (digest == file.digest) { + continue; + } else { + Sys.println('[ammer] $dst found with different contents (possibly an older version)'); + }*/ + } + + // at this point, either the required file does not exist + // or its digest does not match what we expect (currently disabled) + // -> look for a source + + var sourcesSkipped = []; + var sourcesAvailableLocally = []; + var sourcesAvailableOnline = []; + + for (source in file.sources) { + // skip sources which are not compatible with the current OS + var compatible = true; + if (source.os != null) { + var osInfo = ammer.internal.v1.OsInfo.info; + if (source.os != osInfo.os) compatible = false; + if (compatible && osInfo.architecture != null) { + if (source.architectures != null + && !source.architectures.contains(osInfo.architecture)) compatible = false; + } + if (compatible && osInfo.version != null) { + if (source.minVersion != null + && osInfo.versionCompare(osInfo.version, source.minVersion) < 0) compatible = false; + if (source.maxVersion != null + && osInfo.versionCompare(osInfo.version, source.maxVersion) > 0) compatible = false; + } + } + if (!compatible) { + sourcesSkipped.push(source); + continue; + } + + var srcFull = Path.normalize(Path.join([binaryPath, source.name])); + if (FileSystem.exists(srcFull)) { + sourcesAvailableLocally.push(source); + continue; + } + + if (source.downloadFrom != null) { + sourcesAvailableOnline.push(source); + continue; + } + + sourcesSkipped.push(source); + } + + // exactly one locally available, compatible source: copy it + if (!dstExists && sourcesAvailableLocally.length == 1) { + var source = sourcesAvailableLocally[0]; + var srcFull = Path.normalize(Path.join([binaryPath, source.name])); + Sys.println('[ammer] copying $srcFull -> $dstFull'); + File.copy(srcFull, dstFull); + continue; + } + + // at this point, we will prompt the user to choose an action, either + // because there are multiple locally available, compatible sources, + // or because a source will need to be downloaded + + Sys.println('[ammer] required file $dst not found for library ${info.name}'); + Sys.println(" options (press a key):"); + + var options = new Map(); + function option(char:Int, text:String, f:()->Void):Void { + Sys.println(' [${String.fromCharCode(char)}] $text'); + options[char] = f; + } + var yeses = "y0123456789".split(""); + function optionYes(text:String, f:()->Void):Void { + if (yeses.length == 0) { + Sys.println(' [ ] (too many options...) $text'); + } else { + var cc = yeses.shift().charCodeAt(0); + option(cc, text, f); + } + } + + function describe(source:ammer.internal.v1.LibInfo.LibInfoFileSource, online:Bool):String { + var infos = []; + if (online && source.downloadFrom != null) infos.push('URL: ${source.downloadFrom}'); + if (source.os != null) infos.push('OS: ${source.os}'); + if (source.minVersion != null) infos.push('OS version >= ${source.minVersion}'); + if (source.maxVersion != null) infos.push('OS version <= ${source.maxVersion}'); + if (source.architectures != null) infos.push('arch: ${source.architectures.join("/")}'); + if (infos.length == 0) return source.description; + return '${source.description} (${infos.join(", ")})'; + } + + var choiceDone = false; + var doDownload = null; + var doCopy = null; + if (dstExists) { + for (source in sourcesAvailableLocally) { + optionYes('override file using: ${describe(source, false)}', () -> { + doCopy = source; + }); + } + for (source in sourcesAvailableOnline) { + optionYes('download file now and override using: ${describe(source, true)}', () -> { + doDownload = source; + doCopy = source; + }); + } + option("s".code, "keep existing file", () -> {}); + } else { + for (source in sourcesAvailableLocally) { + optionYes('use: ${describe(source, false)}', () -> { + doCopy = source; + }); + } + for (source in sourcesAvailableOnline) { + optionYes('download file now: ${describe(source, true)}', () -> { + doDownload = source; + doCopy = source; + }); + } + if (sourcesAvailableLocally.length == 0 && sourcesAvailableOnline.length == 0) { + Sys.println(" This file is not available online: you may need to compile it locally."); + // TODO: add link to manual page (once it exists) + } + option("s".code, "ignore (program may not function correctly)", () -> {}); + } + if (sourcesSkipped.length > 0) { + option("i".code, 'show details of ${sourcesSkipped.length} skipped sources', () -> { + for (source in sourcesSkipped) { + Sys.println(' ${describe(source, true)}'); + } + choiceDone = false; + }); + } + option("q".code, "abort compilation", () -> throw fail("aborting")); + + while (!choiceDone) { + var choice = Sys.getChar(false); + if (!options.exists(choice)) continue; + choiceDone = true; + options[choice](); + } + + if (doDownload != null) { + Sys.println(" downloading file ..."); + var url = doDownload.downloadFrom; + var srcFull = Path.normalize(Path.join([binaryPath, doDownload.name])); + var followed = 0; + var downloaded = false; + while (!downloaded) { + var http = new haxe.Http(url); + var done = false; + var success = false; + try { + var status = "999"; + http.onBytes = (data) -> { + if (status.charAt(0) == "2") { + Sys.println(" file downloaded"); + /* + var digest = haxe.crypto.Sha256.make(data).toHex(); + if (digest != file.digest) { + Sys.println(" warning: file digest has changed (possibly a newer version)"); + } + */ + File.saveBytes(srcFull, data); + downloaded = true; + done = true; + success = true; + } else if (status.charAt(0) == "3") { + http.responseHeaders.exists("Location") || throw fail("redirect without Location header"); + Sys.println(" following redirect ..."); + url = http.responseHeaders["Location"]; + done = true; + success = true; + } + }; + http.onError = (msg) -> { + Sys.println(' download error: $msg'); + done = true; + success = false; + }; + http.onStatus = (s:Int) -> { + status = '$s'; + Sys.println(' status: $status'); + if (status.charAt(0) != "2" && status.charAt(0) != "3") { + done = true; + success = false; + } + } + http.request(false); + } catch (ex:Dynamic) { + Sys.println(' download error: $ex'); + done = true; + success = false; + } + // TODO: timeout? + while (!done) Sys.sleep(.25); + if (!success) fail("download error"); + followed++; + if (followed >= 5) fail("too many redirects"); + } + } + if (doCopy != null) { + var srcFull = Path.normalize(Path.join([binaryPath, doCopy.name])); + Sys.println('[ammer] copying $srcFull -> $dstFull'); + File.copy(srcFull, dstFull); + } + } + } + + function extend(target:Map, source:Map):Void { + for (k => v in source) { + if (target.exists(k)) throw fail("library replaces an existing type"); + target[k] = v; + } + } + extend(mergedInfo.arrays.byTypeId, info.arrays.byTypeId); + extend(mergedInfo.arrays.byElementTypeId, info.arrays.byElementTypeId); + extend(mergedInfo.boxes.byTypeId, info.boxes.byTypeId); + extend(mergedInfo.boxes.byElementTypeId, info.boxes.byElementTypeId); + extend(mergedInfo.callbacks.byTypeId, info.callbacks.byTypeId); + extend(mergedInfo.callbacks.byElementTypeId, info.callbacks.byElementTypeId); + extend(mergedInfo.enums, info.enums); + extend(mergedInfo.haxeRefs.byTypeId, info.haxeRefs.byTypeId); + extend(mergedInfo.haxeRefs.byElementTypeId, info.haxeRefs.byElementTypeId); + extend(mergedInfo.opaques, info.opaques); + extend(mergedInfo.structs, info.structs); + extend(mergedInfo.sublibraries, info.sublibraries); +// ammer-fragment-end: register-v1 + }); + } + + public static function initLibrary(lib:ClassType, name:String, options:LibContext.LibContextOptions):LibContext { + if (libraries.byLibraryName.exists(name)) + throw Reporting.error('duplicate definition of library "$name"'); + + var libId = Utils.typeId(lib); + var ctx = new LibContext(name, options); + ctx.isLibTypes = (libId == "ammer.internal.LibTypes.LibTypes"); + libraries.byLibraryName[name] = ctx; + libraries.byTypeId[libId] = ctx; + libraries.active.push(ctx); + + contextReady(lib, ctx); + + return ctx; + } + + public static function contextReady(lib:ClassType, ctx:LibContext):Void { + var libId = Utils.typeId(lib); + var curr = queuedSubtypes.first; + while (curr != null) { + var currId = Utils.typeId2(curr.lib); + if (Utils.typeId2(curr.lib) == libId) { + dequeueSubtype(curr); + curr.result = Reporting.withPosition(curr.pos, () -> curr.process(ctx)); + curr.done = true; + } + curr = curr.next; + } + } + + // TODO: support multiple libraries for a subtype + public static function initSubtype( + subId:String, + lib:Type, + process:(ctx:LibContext)->T, + ?processLibTypes:(ctx:LibContext)->T + ):T { + // enqueue deferred subtype processing + var queued:QueuedSubtype = { + subId: subId, + lib: lib, + pos: Reporting.currentPos(), + process: process, + result: null, + done: false, + prev: null, + next: null, + }; + enqueueSubtype(queued); + + // trigger typing of the library + var ctx = Types.resolveContext(lib); + ctx != null || { + trace("could not resolve context", subId, lib); + Context.fatalError("here", Context.currentPos()); + //throw 0; + }; + + if (queued.done) { + return queued.result; + } + + // if it was not processed, then either: + // - the library is currently being processed, or + // - the library has already finished processing + + if (!ctx.done) { + // currently being processed + dequeueSubtype(queued); + return process(ctx); + } else { + if (ctx.isLibTypes && processLibTypes != null) { + return processLibTypes(ctx); + } + + // already processed, report missing subtype link + // TODO: format types better + Reporting.error( + '$subId is declared as a subtype of library ${Utils.typeId2(lib)}, ' + + 'but the library was already fully processed. Please add a subtype ' + + 'link @:ammer.sub((_ : $subId)) to the library definition.'); + return null; + } + } +} + +#end diff --git a/src/ammer/internal/Bakery.hx b/src/ammer/internal/Bakery.hx new file mode 100644 index 0000000..38dd8bb --- /dev/null +++ b/src/ammer/internal/Bakery.hx @@ -0,0 +1,514 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Printer; +import haxe.macro.Type; +import haxe.macro.TypeTools; +import haxe.io.Path; +import sys.io.File; +import sys.FileSystem; +import ammer.core.utils.LineBuf; + +using Lambda; +using StringTools; + +// TODO: haxelib.json (extraParams for `--macro`) + +class Bakery { + public static final BAKE_PREFIX = "// ammer-bake: "; + static var ammerRootPath:String = { + var path = haxe.macro.PositionTools.getInfos((macro 0).pos).file; + if (!Path.isAbsolute(path)) { + path = Path.join([Sys.getCwd(), path]); + } + Path.normalize(Path.directory(Path.normalize(path)) + "/../"); + }; + + public static var isBaking = false; + public static var mainType:Type; + static var bakeOutput:String; + static var rootToBin:String; + static var fileSources:Array; + + static var downloadURL:Null; + static var description:Null; + static var os:Null; + static var architectures:Null>; + static var minVersion:Null; + static var maxVersion:Null; + + public static function init():Void { + if (isBaking) return; + if (!Config.getBool("ammer.bake", false)) return; + isBaking = true; + + var mainTypeStr = Config.getString("ammer.bake.mainType", null, true); + var mainTypePack = mainTypeStr.split("."); + mainType = Context.resolveType(TPath({ + // TODO: support subtypes? merge somehow with Utils.complexTypeExpr? + name: mainTypePack.pop(), + pack: mainTypePack, + }), Context.currentPos()); + + bakeOutput = Config.getPath("ammer.bake.output", null, true); + rootToBin = Config.getString("ammer.bake.rootToBin", null, true); + var sourceCtr = 0; + fileSources = [ while (true) { + var prefix = 'ammer.bake.fileSource.${sourceCtr++}'; + var anyData = false; + inline function c(x: Null):Null { + if (x != null) anyData = true; + return x; + } + var source:ammer.internal.v1.LibInfo.LibInfoFileSource = { + name: c(Config.getString('$prefix.name', null)), + downloadFrom: c(Config.getString('$prefix.downloadFrom', null)), + description: c(Config.getString('$prefix.description', null)), + os: c(Config.getString('$prefix.os', null)), + architectures: c(Config.getStringArray('$prefix.architectures', ",", null)), + minVersion: c(Config.getString('$prefix.minVersion', null)), + maxVersion: c(Config.getString('$prefix.maxVersion', null)), + }; + if (!anyData) break; + source; + } ]; + + Context.onAfterTyping(writeFiles); + } + + static function writeFiles(_):Void { + FileSystem.createDirectory(bakeOutput); + var printer = new Printer(" "); + var modules = BakeryOutput.withBufs(); + + var extraParams:String; + var targetId:String; + var targetCondition:String; + var targetDescription:String; + switch (Ammer.platform.kind) { + case Cpp: + extraParams = "cpp"; + targetId = "cpp"; + targetCondition = 'Context.definedValue("target.name") == "cpp"'; + targetDescription = "Haxe/C++"; + case Cs: + extraParams = "cs"; + targetId = "cs"; + targetCondition = 'Context.definedValue("target.name") == "cs"'; + targetDescription = "Haxe/C#"; + case Eval: + extraParams = "eval"; + targetId = "eval"; + targetCondition = 'Context.definedValue("target.name") == "eval"'; + targetDescription = "Haxe interpreter"; + case Hashlink: + // var platformConfig = (cast Ammer.platformConfig : ammer.core.plat.Hashlink.HashlinkConfig); + // TODO: HL/C? + extraParams = "hl"; + targetId = "hl"; + targetCondition = 'Context.definedValue("target.name") == "hl"'; + targetDescription = "Haxe/HashLink"; + case Java: + // var platformConfig = (cast Ammer.platformConfig : ammer.core.plat.Java.JavaConfig); + // TODO: JVM? + extraParams = "java"; + targetId = "java"; + targetCondition = 'Context.definedValue("target.name") == "java"'; + targetDescription = "Haxe/Java"; + case Lua: + extraParams = "lua"; + targetId = "lua"; + targetCondition = 'Context.definedValue("target.name") == "lua"'; + targetDescription = "Haxe/Lua"; + case Neko: + extraParams = "neko"; + targetId = "neko"; + targetCondition = 'Context.definedValue("target.name") == "neko"'; + targetDescription = "Haxe/Neko"; + case Nodejs: + extraParams = "js && nodejs"; + targetId = "nodejs"; + targetCondition = 'Context.definedValue("target.name") == "js" && Context.defined("nodejs")'; + targetDescription = "Haxe/Node.js"; + case Python: + extraParams = "python"; + targetId = "python"; + targetCondition = 'Context.definedValue("target.name") == "python"'; + targetDescription = "Haxe/Python"; + case None: Context.fatalError("cannot bake without selecting platform", Context.currentPos()); + } + + // TODO: use as fragment in AmmerBaked? + var osName = (switch (Sys.systemName()) { + case "Windows": "win"; + case "Linux": "linux"; + case "BSD": "bsd"; + case "Mac": "mac"; + case _: "unknown"; // TODO: throw? + }); + var extensionDll = (switch (Sys.systemName()) { + case "Windows": "dll"; + case "Mac": "dylib"; + case _: "so"; + }); + var prefixLib = (switch (Sys.systemName()) { + case "Windows": ""; + case _: "lib"; + }); + + for (t in Utils.modifiedTypes) { + switch (t.t.kind) { + case KAbstractImpl(_.get() => abs): + var typeId = Utils.typeId(abs); + var output = modules.outputSub(abs.pack, abs.module.split(".").pop(), abs.name); + output + .ail("#if !macro"); + var isEnum = false; + for (meta in t.t.meta.get()) { + if (meta.name == ":build" || meta.name == ":autoBuild") + continue; + if (meta.name.startsWith(":ammer.")) + continue; + if (meta.name == ":enum") { + isEnum = true; + continue; + } + output.ail(printer.printMetadata(meta)); + } + output + .ai('${isEnum ? "enum " : ""}abstract ${abs.name}(${printer.printComplexType(TypeTools.toComplexType(abs.type))})') + .map(abs.from, t -> t.field == null ? ' from ${printer.printComplexType(TypeTools.toComplexType(t.t))}' : "") + .map(abs.to, t -> t.field == null ? ' to ${printer.printComplexType(TypeTools.toComplexType(t.t))}' : "") + .al(' {') + .lmap(t.fields, field -> printer.printField(field) + ";") + .ail("}") + .ail("#end /*!macro*/"); + case KNormal: + var typeId = Utils.typeId(t.t); + var isLibrary = Ammer.libraries.byTypeId.exists(typeId); + var output = modules.outputSub(t.t.pack, t.t.module.split(".").pop(), t.t.name); + output + .ail("#if !macro"); + for (meta in t.t.meta.get()) { + if (meta.name == ":build" || meta.name == ":autoBuild") + continue; + if (meta.name.startsWith(":ammer.")) + continue; + output.ail(printer.printMetadata(meta)); + } + output + .ail('${t.t.isExtern ? "extern " : ""}class ${t.t.name} {') + .lmap(t.fields, field -> printer.printField(field) + ";") + .ail("}") + .ail("#end /*!macro*/"); + + if (isLibrary) { + var outputMacro = modules.outputSub(t.t.pack, t.t.module.split(".").pop() + ".macro", t.t.name); + var ctx = Ammer.libraries.byTypeId[typeId]; + + if (typeId != "ammer.internal.LibTypes.LibTypes") { + function sortedKeys(map:Map):Array { + var keys = [ for (k in map.keys()) k ]; + keys.sort(Reflect.compare); + return keys; + } + var sortedArrayNames = sortedKeys(ctx.info.arrays.byElementTypeId); + var sortedBoxNames = sortedKeys(ctx.info.boxes.byElementTypeId); + var sortedCallbackNames = sortedKeys(ctx.info.callbacks.byElementTypeId); + var sortedEnumNames = sortedKeys(ctx.info.enums); + var sortedHaxeRefNames = sortedKeys(ctx.info.haxeRefs.byElementTypeId); + var sortedOpaqueNames = sortedKeys(ctx.info.opaques); + var sortedStructNames = sortedKeys(ctx.info.structs); + var sortedSublibraryNames = sortedKeys(ctx.info.sublibraries); + + // TODO: share this with ammer-core somehow? or else make outputPathRelative private again + //var outputPathRelative = ctx.library.outputPathRelative(); + //var sourcePath = 'prebuilt-$$osName-$targetId.bin'; + var destPath = (switch (Ammer.platform.kind) { + case Cs: 'ammer_${ctx.name}.dll'; + case Hashlink: 'ammer_${ctx.name}.hdll'; // TODO: HL/C uses the standard DLL naming + case Neko: 'ammer_${ctx.name}.ndll'; + case Nodejs: 'ammer_${ctx.name}.node'; + case Python: 'ammer_${ctx.name}.$${osName == "win" ? "pyd" : "so"}'; + case _: '$${prefixLib}ammer_${ctx.name}.$${extensionDll}'; + }); + //var outputPathRelative = Ammer.baseConfig.outputPath + "/" + destPath; + + function printString(s:String):String { + // not quote safe! + return s == null ? "null" : '"$s"'; + } + function printStringArray(s:Array):String { + return s == null ? "null" : '[${s.map(printString).join(", ")}]'; + } + function printExpr(e:Expr):String { + return e == null ? "null" : '(macro ${printer.printExpr(e)})'; + } + function printCt(ct:ComplexType):String { + return '(macro : ${printer.printComplexType(ct)})'; + } + /*function printType(t:Type):String { + return 'ComplexTypeTools.toType((macro : ${printer.printComplexType(TypeTools.toComplexType(t))}))'; + }*/ + + outputMacro.a( + File.getContent('$ammerRootPath/internal/v1/AmmerSetup.baked.hx') + .replace("/*libinfo*/", new LineBuf() + .al("// ammer-bake-common") + .ail('/*ammer-bake-common-start*/ if ($targetCondition) {').i() + .ail('info.name = "${ctx.name}";') + .ail('info.arrays.byElementTypeId = [').i() + .lmap(sortedArrayNames, name -> { + var info = ctx.info.arrays.byElementTypeId[name]; + '"$name" => {\n' + + 'arrayCt: ${printCt(info.arrayCt)},\n' + + 'arrayRefCt: ${printCt(info.arrayRefCt)},\n' + + 'alloc: ${printExpr(info.alloc)},\n' + + 'fromHaxeCopy: ${printExpr(info.fromHaxeCopy)},\n' + + 'fromHaxeRef: ${printExpr(info.fromHaxeRef)},\n' + + '},'; + }) + .d().ail('];') + .ail('info.boxes.byElementTypeId = [').i() + .lmap(sortedBoxNames, name -> { + var info = ctx.info.boxes.byElementTypeId[name]; + '"$name" => {\n' + + 'boxCt: ${printCt(info.boxCt)},\n' + + 'alloc: ${printExpr(info.alloc)},\n' + + '},'; + }) + .d().ail('];') + .ail('info.callbacks.byElementTypeId = [').i() + .lmap(sortedCallbackNames, name -> { + var info = ctx.info.callbacks.byElementTypeId[name]; + '"$name" => {\n' + + 'isGlobal: ${info.isGlobal},\n' + + 'callbackCt: ${printCt(info.callbackCt)},\n' + + 'funCt: ${printCt(info.funCt)},\n' + + 'callbackName: ${printString(info.callbackName)},\n' + + '},'; + }) + .d().ail('];') + .ail('info.enums = [').i() + .lmap(sortedEnumNames, name -> { + var info = ctx.info.enums[name]; + '"$name" => {},\n'; + }) + .d().ail('];') + .ail('info.haxeRefs.byElementTypeId = [').i() + .lmap(sortedHaxeRefNames, name -> { + var info = ctx.info.haxeRefs.byElementTypeId[name]; + '"$name" => {' + + 'create: ${printExpr(info.create)},\n' + + '},\n'; + }) + .d().ail('];') + .ail('info.opaques = [').i() + .lmap(sortedOpaqueNames, name -> { + var info = ctx.info.opaques[name]; + '"$name" => {\n' + + 'opaqueName: "${info.opaqueName}",\n' + + '},'; + }) + .d().ail('];') + .ail('info.structs = [').i() + .lmap(sortedStructNames, name -> { + var info = ctx.info.structs[name]; + '"$name" => {\n' + + 'alloc: ${info.alloc},\n' + + 'gen: {\n' + + 'alloc: ${printString(info.gen.alloc)},\n' + + 'free: ${printString(info.gen.free)},\n' + + 'nullPtr: ${printString(info.gen.nullPtr)},\n' + + '},\n' + + 'structName: "${info.structName}",\n' + + '},'; + }) + .d().ail('];') + .ail('info.sublibraries = [').i() + .lmap(sortedSublibraryNames, name -> { + var info = ctx.info.sublibraries[name]; + '"$name" => {},\n'; + }) + .d().ail('];') + .ail('info.setupToBin = "${t.t.pack.map(_ -> "..").concat(rootToBin.split("/")).join("/")}";') + .ifi(destPath != null) + .ail("info.files.push({").i() + .ail('dst: \'$destPath\',') + .ail("sources: [").i() + .map(fileSources, source -> new LineBuf().ail("{").i() + .ail('name: ${printString(source.name)},') + //.ail('digest: ${printString(haxe.crypto.Sha256.make(File.getBytes('$bakeOutput/$rootToBin/${extensions(outputPathRelative)}')).toHex())},') + .ail('description: "pre-compiled library for $targetDescription",') + .ail('downloadFrom: ${printString(source.downloadFrom)},') + .ail('os: ${printString(source.os)},') + .ail('architectures: ${printStringArray(source.architectures)},') + .ail('minVersion: ${printString(source.minVersion)},') + .ail('maxVersion: ${printString(source.maxVersion)},') + .d().ail("},").done()) + .d().ail("],") + .d().ail("});") + .ifd() + .d().ail("/*ammer-bake-common-end*/ }") + .done()) + ); + } + outputMacro.al('class ${t.t.name} {}'); + } + case _: throw 0; + } + } + for (t in ammer.core.utils.TypeUtils.definedTypes) { + modules.outputSub(t.pack, t.name, t.name) + .ail(printer.printTypeDefinition(t, false)); + } + for (t in Utils.definedTypes) { + modules.outputSub(t.pack, t.name, t.name) + .ail(printer.printTypeDefinition(t, false)); + } + + modules.outputCombined(extraParams, bakeOutput); + + // TODO: paths (handle backslashes etc on Windows) + // This is a little "preprocessor" system to reduce code duplication. + // It isn't great! + FileSystem.createDirectory('$bakeOutput/ammer/internal/v1'); + for (req in [ + ["internal/v1/AmmerBaked.hx", "internal/v1/AmmerBaked.hx"], + ["internal/v1/LibInfo.hx", "internal/v1/LibInfo.hx"], + ["internal/v1/OsInfo.hx", "internal/v1/OsInfo.hx"], + ["Lib.hx", "Lib.hx"], + ["Lib.macro.baked.hx", "Lib.macro.hx"], + ["internal/FilePtrOutput.hx", "internal/FilePtrOutput.hx"], + ]) { + var content = File.getContent('$ammerRootPath/${req[0]}') + .split("\n") + .map(l -> { + var lt = l.trim(); + if (!lt.startsWith("// ammer-include: ")) return l; + var params = lt.substr("// ammer-include: ".length).split(" "); + var refContent = File.getContent('$ammerRootPath/${params[0]}'); + var spl = refContent.split('// ammer-fragment-begin: ${params[1]}'); + spl.length == 2 || throw 'expected fragment begin ${params[1]} in ${params[0]}'; + spl = spl[1].split('// ammer-fragment-end: ${params[1]}'); + spl.length == 2 || throw 'expected fragment end ${params[1]} in ${params[0]}'; + spl[0]; + }) + .join("\n"); + File.saveContent('$bakeOutput/ammer/${req[1]}', content); + } + } + + public static function multiBake(platforms:Array, output:String):Void { + var modules = new BakeryOutput( + () -> { + platforms: ([] : Map), + common: ([] : Array), + }, + mod -> { + var platNames = [ for (k in mod.platforms.keys()) k ]; + platNames.sort(Reflect.compare); + var merged = new LineBuf(); + for (p in platNames) { + merged + .al('#if (${p})') + .al(mod.platforms[p] + .replace("// ammer-bake-common", mod.common.join("\n"))) + .al('#end /*(${p})*/'); + } + merged.done(); + } + ); + + function walkPath(base:String, ext:String):Void { + var path = '$base$ext'; + if (FileSystem.isDirectory(path)) { + FileSystem.readDirectory(path).iter(f -> { + if (f.startsWith(".")) return; + walkPath(base, '$ext/$f'); + }); + } else { + if (!path.endsWith(".hx")) return; + var content = File.getContent(path); + if (!content.startsWith(BAKE_PREFIX)) + throw Context.fatalError('unexpected file in bake path: $path', Context.currentPos()); + var lines = content.split("\n"); + var bakeParams = lines[0].substr(BAKE_PREFIX.length).split(" "); + + var bakePack = bakeParams[0].split("."); + var bakeModule = bakeParams[1]; + var bakeExtra = bakeParams.slice(2).join(" "); + if (path.endsWith(".macro.hx")) bakeExtra = "true"; // join macro files + + var content = []; + var bakeCommon = []; + var isCommon = false; + for (lnum => line in lines) { + if (line.contains("/*ammer-bake-common-start*/")) isCommon = true; + if (isCommon) bakeCommon.push(line); + else if (lnum >= 2) { + // remove bake prefix, package, and common lines (will be merged later) + content.push(line); + } + if (line.contains("/*ammer-bake-common-end*/")) isCommon = false; + } + + var outputSub = modules.outputSub(bakePack, bakeModule, "true"); + outputSub.platforms[bakeExtra] = content.join("\n"); + outputSub.common = outputSub.common.concat(bakeCommon); + } + } + platforms.iter(p -> walkPath(p, "")); + + modules.outputCombined("combined", output); + } +} + +class BakeryOutput { + public static function withBufs():BakeryOutput { + return new BakeryOutput(() -> new LineBuf(), buf -> buf.done()); + } + + var modules:Map> = []; + var create:()->T; + var finish:T->String; + + public function new(create:()->T, finish:T->String) { + this.create = create; + this.finish = finish; + } + + public function outputSub(pack:Array, module:String, sub:String):T { + var mid = pack.concat([module]).join("."); + if (!modules.exists(mid)) modules[mid] = new Map(); + if (!modules[mid].exists(sub)) modules[mid][sub] = create(); + return modules[mid][sub]; + } + + public function outputCombined(extraParams:String, outputPath:String):Void { + for (module => types in modules) { + var pack = module.split("."); + var module = pack.pop(); + if (module == "macro") { + module = '${pack.pop()}.$module'; + } + var subNames = [ for (k in types.keys()) k ]; + subNames.sort(Reflect.compare); + var moduleOutput = new LineBuf() + .ail('${Bakery.BAKE_PREFIX}${pack.join(".")} $module $extraParams') + .ail('package ${pack.join(".")};'); + for (subName in subNames) { + moduleOutput.al(finish(types[subName])); + } + + var out = outputPath + "/" + pack.join("/"); + FileSystem.createDirectory(out); + File.saveContent('${out}/${module}.hx', moduleOutput.done()); + } + } +} + +#end diff --git a/src/ammer/internal/Config.hx b/src/ammer/internal/Config.hx new file mode 100644 index 0000000..7fe79f3 --- /dev/null +++ b/src/ammer/internal/Config.hx @@ -0,0 +1,85 @@ +package ammer.internal; + +#if macro + +import haxe.io.Path; +import haxe.macro.Compiler; +import haxe.macro.Context; + +class Config { + static final BOOL_YES = ["yes", "y", "true", "1", "on"]; + static final BOOL_NO = ["no", "n", "false", "0", "off"]; + + public static function hasDefine(key:String):Bool { + return Context.defined(key); + } + + /** + Gets a compile-time define by `key`. If the specified key is not defined, + return the value `dv`, or throw an error if `doThrow` is `true`. + **/ + public static function getString(key:String, ?dv:String, ?doThrow:Bool = false):String { + if (Context.defined(key)) + return Context.definedValue(key); + if (doThrow) + Context.fatalError('missing required define: $key', Context.currentPos()); + return dv; + } + + public static function getStringArray(key:String, sep:String, ?dv:Array, ?doThrow:Bool = false):Array { + if (Context.defined(key)) + return Context.definedValue(key).split(sep); + if (doThrow) + Context.fatalError('missing required define: $key', Context.currentPos()); + return dv; + } + + /** + Gets a boolean from the compile-time define `key`. + **/ + public static function getBool(key:String, ?dv:Bool, ?doThrow:Bool = false):Bool { + if (Context.defined(key)) { + if (BOOL_YES.indexOf(Context.definedValue(key)) != -1) + return true; + if (BOOL_NO.indexOf(Context.definedValue(key)) != -1) + return false; + Context.fatalError('invalid define (should be yes or no): $key', Context.currentPos()); + } + if (doThrow) + Context.fatalError('missing required define: $key', Context.currentPos()); + return dv; + } + + public static function getInt(key:String, ?dv:Int, ?doThrow:Bool = false):Int { + if (Context.defined(key)) + return Std.parseInt(Context.definedValue(key)); + if (doThrow) + Context.fatalError('missing required define: $key', Context.currentPos()); + return dv; + } + + /** + Gets a path from the compile-time define `key`. If the path is relative, + resolve it relative to the current working directory. + **/ + public static function getPath(key:String, ?dv:String, ?doThrow:Bool = false):String { + var p = getString(key, dv, doThrow); + if (p != null && !Path.isAbsolute(p)) + p = Path.join([Sys.getCwd(), p]); + return p; + } + + public static function getEnum(key:String, map:Map, ?dv:T, ?doThrow:Bool = false):T { + var p = getString(key, null, doThrow); + if (p == null) + return dv; + if (!map.exists(p)) { + var keys = [for (k in map.keys()) k]; + keys.sort(Reflect.compare); + Context.fatalError('invalid define (should be one of ${keys.join(", ")})', Context.currentPos()); + } + return map[p]; + } +} + +#end diff --git a/src/ammer/internal/Entrypoint.hx b/src/ammer/internal/Entrypoint.hx new file mode 100644 index 0000000..eaa0d36 --- /dev/null +++ b/src/ammer/internal/Entrypoint.hx @@ -0,0 +1,899 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; +import haxe.macro.TypeTools; +import ammer.core.utils.TypeUtils; + +using Lambda; + +/** + Various entrypoints into `ammer` processing from user-defined types extending + the marker types from `ammer.def.*`, as well as some standard library types. + + The methods in this class can be categorised as follows: + - `genericBuild...` methods for `ammer.ffi.*` types. + (`...Array`, `...Box`, `...HaxeRef`) + These are invoked via `@:genericBuild` on marker types in the `ammer.ffi.*` + package. The method creates a monomorphisation of the requested type (e.g. + an array of 32-bit integers). + - `genericBuild...` methods for `ammer.def.*` types. + (`...Library`, `...Opaque`, `...Struct`, `...Sublibrary`) + These exist to attach a marker type to be used as a superclass for the + user-declared type. No processing is performed here except for validating + the type parameters. + - `autoBuild...` methods for "processed" types. + (`...Library`, `...Opaque`, `...Struct`, `...Sublibrary`) + These are invoked via `@:autoBuild` on the type resulting from the previous + category. The actual processing of user-declared types is performed here. + - `build...` methods for `ammer.def.*` types. + (`...Enum`) + These are invoked with a direct `@:build` metadata on a user-declared type. +**/ +class Entrypoint { + public static function genericBuildArray():ComplexType { + return Reporting.withCurrentPos(() -> switch (Context.getLocalType()) { + // ammer.ffi.Array + case TInst(_, [el]): + var el = TypeTools.followWithAbstracts(el); + var elId = Utils.typeId2(el); + if (Ammer.mergedInfo.arrays.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.arrays.byElementTypeId[elId].arrayCt; + } + Reporting.log('enter genericBuildArray $elId', "stage-ffi"); + + Ammer.initSubtype('ammer.ffi.Array<$elId>', el, ctx -> { + // was it initialised in the meanwhile? + if (Ammer.mergedInfo.arrays.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.arrays.byElementTypeId[elId].arrayCt; + } + Reporting.log('process genericBuildArray $elId', "stage-ffi"); + + var elRes = Types.resolveType(el, ctx); + + (elRes.wrap == null && elRes.unwrap == null) || throw Reporting.error("unsupported array element type"); + var elCt = elRes.haxeType; + var marshal = ctx.marshal.arrayPtr(elRes.marshal); + + var arrayTp:TypePath = { + name: 'Array_${elRes.marshal.mangled}', + pack: ["ammer", "gen"], + }; + var arrayCt = TPath(arrayTp); + + // force the box type to exist + var refElCt = elRes.marshal.arrayType != null ? elRes.marshal.arrayType : elCt; + var refEl = Context.resolveType(refElCt, Reporting.currentPos()); + var refElId = Utils.typeId2(refEl); + //Context.resolveType((macro : ammer.ffi.Box<$refElCt>), Reporting.currentPos()); + //var elBoxTp = Utils.expectTypePath(Ammer.mergedInfo.boxes.byElementTypeId[refElId].boxCt); + //var elBoxCt = TPath(elBoxTp); + + var defArray = macro class { + private var _ammer_native:Any; + private function new(native:Any) { + this._ammer_native = native; + } + public function get(index:Int):$elCt { + return $e{marshal.get(macro _ammer_native, macro index)}; + } + public function set(index:Int, val:$elCt):Void { + $e{marshal.set(macro _ammer_native, macro index, macro val)}; + } + //public function ref(index:Int):$elBoxCt { + // return @:privateAccess new $elBoxTp($e{marshal.ref(macro _ammer_native, macro index)}); + //} + public function free():Void { + $e{marshal.free(macro _ammer_native)}; + } + private static function nullPtr():$arrayCt { + return new $arrayTp($e{marshal.nullPtr}); + } + }; + defArray.name = arrayTp.name; + defArray.pack = arrayTp.pack; + Utils.defineType(defArray); + + // TODO: use abstract + var unref = marshal.fromHaxeRef != null + ? macro _ammer_core_native.unref() + : macro { + for (i in 0...vector.length) { + vector[i] = $e{marshal.get(macro _ammer_core_native, macro i)}; + } + }; + var defArrayRef = macro class { + private var _ammer_core_native:Dynamic; + private var vector:haxe.ds.Vector<$elCt>; + private function new( + _ammer_core_native:Dynamic, + vector:haxe.ds.Vector<$elCt> + ) { + this._ammer_core_native = _ammer_core_native; + this.vector = vector; + } + public var array(get, never):$arrayCt; + private inline function get_array():$arrayCt { + return @:privateAccess new $arrayTp(_ammer_core_native.ptr); + } + public function unref():Void { + if (_ammer_core_native != null) { + $unref; + _ammer_core_native = null; + } + } + }; + defArrayRef.name = 'ArrayRef_${elRes.marshal.mangled}'; + defArrayRef.pack = ["ammer", "gen"]; + var arrayRefCt = TPath({ + name: defArrayRef.name, + pack: defArrayRef.pack, + }); + Utils.defineType(defArrayRef); + + var array = { + arrayCt: arrayCt, + arrayRefCt: arrayRefCt, + alloc: marshal.alloc(macro _size), + fromHaxeCopy: marshal.fromHaxeCopy(macro _vec), + fromHaxeRef: marshal.fromHaxeRef != null ? marshal.fromHaxeRef(macro _vec) : null, + + elementType: el, + arrayMarshal: marshal, + }; + + Ammer.mergedInfo.arrays.byTypeId['ammer.gen.${defArray.name}.${defArray.name}'] = array; + Ammer.mergedInfo.arrays.byElementTypeId[elId] = array; + ctx.info.arrays.byTypeId['ammer.gen.${defArray.name}.${defArray.name}'] = array; + ctx.info.arrays.byElementTypeId[elId] = array; + + Reporting.log('exit genericBuildArray $elId', "stage-ffi"); + arrayCt; + }); + case _: throw 0; + }); + } + + public static function genericBuildBox():ComplexType { + return Reporting.withCurrentPos(() -> switch (Context.getLocalType()) { + // ammer.ffi.Box + case TInst(_, [el]): + var elId = Utils.typeId2(el); + if (Ammer.mergedInfo.boxes.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.boxes.byElementTypeId[elId].boxCt; + } + Reporting.log('enter genericBuildBox $elId', "stage-ffi"); + + Ammer.initSubtype('ammer.ffi.Box<$elId>', el, ctx -> { + // was it initialised in the meanwhile? + if (Ammer.mergedInfo.boxes.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.boxes.byElementTypeId[elId].boxCt; + } + Reporting.log('process genericBuildBox $elId', "stage-ffi"); + + var elRes = Types.resolveType(el, ctx); + + var elCt = elRes.haxeType; + var marshal = ctx.marshal.boxPtr(elRes.marshal); + + var boxTp:TypePath = { + name: 'Box_${elRes.marshal.mangled}', + pack: ["ammer", "gen"], + }; + var boxCt = TPath(boxTp); + var defBox = macro class { + private var _ammer_native:Any; + private function new(native:Any) { + this._ammer_native = native; + } + public function get():$elCt { + return $e{Utils.exprMap(marshal.get(macro _ammer_native), elRes.wrap)}; + } + public function set(val:$elCt):Void { + $e{marshal.set(macro _ammer_native, Utils.exprMap(macro val, elRes.unwrap))}; + } + public function free():Void { + $e{marshal.free(macro _ammer_native)}; + } + private static function nullPtr():$boxCt { + return new $boxTp($e{marshal.nullPtr}); + } + }; + defBox.name = boxTp.name; + defBox.pack = boxTp.pack; + Utils.defineType(defBox); + + var box = { + elementType: el, + boxCt: boxCt, + alloc: marshal.alloc, + boxMarshal: marshal, + }; + Ammer.mergedInfo.boxes.byTypeId['ammer.gen.${defBox.name}.${defBox.name}'] = box; + Ammer.mergedInfo.boxes.byElementTypeId[elId] = box; + ctx.info.boxes.byTypeId['ammer.gen.${defBox.name}.${defBox.name}'] = box; + ctx.info.boxes.byElementTypeId[elId] = box; + + Reporting.log('exit genericBuildBox $elId', "stage-ffi"); + boxCt; + }); + case _: throw 0; + }); + } + + public static function buildBytes():Array { + var pos = Context.currentPos(); + return Reporting.withPosition(pos, () -> { + var implType = Context.getLocalClass().get(); + + Reporting.log("enter buildBytes", "stage-ffi"); + + var lib = Context.resolveType((macro : ammer.internal.LibTypes), pos); + var fields:Array = Ammer.initSubtype(Utils.typeId(implType), lib, ctx -> { + Reporting.log("process buildBytes", "stage-ffi"); + var marshal = ctx.marshal.bytes(); + var toHaxeRef = marshal.toHaxeRef != null + ? macro return $e{marshal.toHaxeRef(macro _ammer_native, macro size)} + : macro throw "platform does not support non-copy references to Bytes"; + var fromHaxeRef = marshal.fromHaxeRef != null + ? macro return @:privateAccess new ammer.ffi.BytesRef($e{marshal.fromHaxeRef(macro bytes)}, bytes) + : macro return @:privateAccess new ammer.ffi.BytesRef($e{marshal.fromHaxeCopy(macro bytes)}, bytes); + var fromHaxeRefForce = marshal.fromHaxeRef != null + ? macro return @:privateAccess new ammer.ffi.BytesRef($e{marshal.fromHaxeRef(macro bytes)}, bytes) + : macro throw "platform does not support non-copy references from Bytes"; + var fields = (macro class { + private var _ammer_native:Any; + private function new(native:Any) { + this._ammer_native = native; + } + + public function get8(index:Int):Int return $e{marshal.get8(macro _ammer_native, macro index)}; + public function get16(index:Int):Int return $e{marshal.get16(macro _ammer_native, macro index)}; + public function get32(index:Int):Int return $e{marshal.get32(macro _ammer_native, macro index)}; + public function get64(index:Int):haxe.Int64 return $e{marshal.get64(macro _ammer_native, macro index)}; + + public function set8(index:Int, val:Int):Void $e{marshal.set8(macro _ammer_native, macro index, macro val)}; + public function set16(index:Int, val:Int):Void $e{marshal.set16(macro _ammer_native, macro index, macro val)}; + public function set32(index:Int, val:Int):Void $e{marshal.set32(macro _ammer_native, macro index, macro val)}; + public function set64(index:Int, val:haxe.Int64):Void $e{marshal.set64(macro _ammer_native, macro index, macro val)}; + + public function get16be(index:Int):Int return $e{marshal.get16be(macro _ammer_native, macro index)}; + public function get32be(index:Int):Int return $e{marshal.get32be(macro _ammer_native, macro index)}; + public function get64be(index:Int):haxe.Int64 return $e{marshal.get64be(macro _ammer_native, macro index)}; + public function set16be(index:Int, val:Int):Void $e{marshal.set16be(macro _ammer_native, macro index, macro val)}; + public function set32be(index:Int, val:Int):Void $e{marshal.set32be(macro _ammer_native, macro index, macro val)}; + public function set64be(index:Int, val:haxe.Int64):Void $e{marshal.set64be(macro _ammer_native, macro index, macro val)}; + + public function get16le(index:Int):Int return $e{marshal.get16le(macro _ammer_native, macro index)}; + public function get32le(index:Int):Int return $e{marshal.get32le(macro _ammer_native, macro index)}; + public function get64le(index:Int):haxe.Int64 return $e{marshal.get64le(macro _ammer_native, macro index)}; + public function set16le(index:Int, val:Int):Void $e{marshal.set16le(macro _ammer_native, macro index, macro val)}; + public function set32le(index:Int, val:Int):Void $e{marshal.set32le(macro _ammer_native, macro index, macro val)}; + public function set64le(index:Int, val:haxe.Int64):Void $e{marshal.set64le(macro _ammer_native, macro index, macro val)}; + + public static function alloc(size:Int):ammer.ffi.Bytes return new ammer.ffi.Bytes($e{marshal.alloc(macro size)}); + public static function zalloc(size:Int):ammer.ffi.Bytes return new ammer.ffi.Bytes($e{marshal.zalloc(macro size)}); + public static function nullPtr():ammer.ffi.Bytes return new ammer.ffi.Bytes($e{marshal.nullPtr}); + public function free():Void { + $e{marshal.free(macro _ammer_native)}; + _ammer_native = null; + } + public function copy(size:Int):Bytes return new ammer.ffi.Bytes($e{marshal.copy(macro _ammer_native, macro size)}); + public static function blit(source:Bytes, sourcepos:Int, dest:Bytes, destpos:Int, size:Int):Void { + $e{marshal.blit(macro source._ammer_native, macro sourcepos, macro dest._ammer_native, macro destpos, macro size)}; + } + public function offset(pos:Int):Bytes return new ammer.ffi.Bytes($e{marshal.offset(macro _ammer_native, macro pos)}); + + public function toHaxeCopy(size:Int):haxe.io.Bytes return $e{marshal.toHaxeCopy(macro _ammer_native, macro size)}; + public static function fromHaxeCopy(bytes:haxe.io.Bytes):Bytes return new ammer.ffi.Bytes($e{marshal.fromHaxeCopy(macro bytes)}); + public function toHaxeRef(size:Int):haxe.io.Bytes $toHaxeRef; + public static function fromHaxeRef(bytes:haxe.io.Bytes):ammer.ffi.BytesRef $fromHaxeRef; + public static function fromHaxeRefForce(bytes:haxe.io.Bytes):ammer.ffi.BytesRef $fromHaxeRefForce; + }).fields; + + // TODO: use abstract + var ptr = marshal.fromHaxeRef != null + ? macro _ammer_core_native.ptr + : macro _ammer_core_native; + var unref = marshal.fromHaxeRef != null + ? macro _ammer_core_native.unref() + : macro { + for (i in 0...hbytes.length) { + hbytes.set(i, $e{marshal.get8(ptr, macro i)}); + } + $e{marshal.free(ptr)}; + }; + var defBytesRef = macro class { + private var _ammer_core_native:Dynamic; + private var hbytes:haxe.io.Bytes; + private function new( + _ammer_core_native:Dynamic, + hbytes:haxe.io.Bytes + ) { + this._ammer_core_native = _ammer_core_native; + this.hbytes = hbytes; + } + public var bytes(get, never):ammer.ffi.Bytes; + private inline function get_bytes():ammer.ffi.Bytes { + return @:privateAccess new ammer.ffi.Bytes($ptr); + } + public function unref():Void { + if (_ammer_core_native != null) { + $unref; + _ammer_core_native = null; + } + } + }; + defBytesRef.name = "BytesRef"; + defBytesRef.pack = ["ammer", "ffi"]; + var bytesRefCt = TPath({ + name: defBytesRef.name, + pack: defBytesRef.pack, + }); + Utils.defineType(defBytesRef); + + fields; + }); + + Reporting.log("exit buildBytes", "stage-ffi"); + Utils.modifyType(implType, fields); + }); + } + + public static function genericBuildCallback():ComplexType { + var pos = Context.currentPos(); + return Reporting.withCurrentPos(() -> switch (Context.getLocalType()) { + // ammer.ffi.Callback + case TInst(_, [cbType, fnType, callTarget, callArgs, lib]): + switch (fnType) { + case TFun(_, _): + case _: throw Reporting.error("ammer.ffi.Callback second type parameter should be a function type"); + } + + var isGlobal = false; + var callTarget:Expr = (switch (callTarget) { + case TInst(_.get() => {kind: KExpr({expr: EConst(CString("global"))})}, []): + isGlobal = true; + null; + case TInst(_.get() => {kind: KExpr({expr: EArrayDecl([expr])})}, []): + expr; + case _: + throw Reporting.error("ammer.ffi.Callback third type parameter should be an array with a single expression or \"global\""); + }); + var callArgs = Utils.exprArrayOfType(callArgs); + callArgs != null + || throw Reporting.error("ammer.ffi.Callback fourth type parameter should be an array of expressions"); + + var printer = new haxe.macro.Printer(); + var elId = StringTools.hex(haxe.crypto.Crc32.make(haxe.io.Bytes.ofString(ammer.core.utils.Mangle.parts([ + Utils.typeId2(cbType), + Utils.typeId2(fnType), + printer.printExpr(callTarget), + printer.printExpr(macro $a{callArgs}), + ]))), 8); + if (Ammer.mergedInfo.callbacks.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.callbacks.byElementTypeId[elId].callbackCt; + } + Reporting.log('enter genericBuildCallback $elId', "stage"); + + var callbackTp:TypePath = { + name: 'Callback_$elId', + pack: ["ammer", "gen"], + }; + var callbackCt = TPath(callbackTp); + var funCt = TypeTools.toComplexType(fnType); + var callback:ammer.internal.v1.LibInfo.LibInfoCallback = { + isGlobal: isGlobal, + callbackCt: callbackCt, + funCt: funCt, + callbackName: null, + }; + var typeId = Utils.typeIdTp(callbackTp); + + Ammer.initSubtype('ammer.ffi.Callback<$elId>', lib, ctx -> { + // was it initialised in the meanwhile? + if (Ammer.mergedInfo.callbacks.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.callbacks.byElementTypeId[elId].callbackCt; + } + Reporting.log('process genericBuildCallback $elId', "stage"); + callback.ctx = ctx; + Ammer.mergedInfo.callbacks.byTypeId[typeId] = callback; + Ammer.mergedInfo.callbacks.byElementTypeId[elId] = callback; + ctx.info.callbacks.byTypeId[typeId] = callback; + ctx.info.callbacks.byElementTypeId[elId] = callback; + + var cbArgsRes; + var cbRetRes; + switch (cbType) { + case TFun(args, ret): + cbArgsRes = args.map(arg -> Types.resolveType(arg.t, ctx)); + cbRetRes = Types.resolveType(ret, ctx); + case _: throw Reporting.error("ammer.ffi.Callback first type parameter should be a function type"); + } + + var invoke = []; + for (idx => arg in cbArgsRes) { + if (arg.wrap != null) { + var ident = 'arg$idx'; + invoke.push(macro var $ident = $e{Utils.exprMap(macro $i{ident}, arg.wrap)}); + } + } + invoke.push(isGlobal + ? (macro return @:privateAccess $p{Utils.accessTp(callbackTp)}.stored.value($a{callArgs})) + : (macro return $e{callTarget}.value($a{callArgs}))); + callback.callbackName = ctx.library.addStaticCallback( + cbRetRes.marshal, + cbArgsRes.map(arg -> arg.marshal), + macro $b{invoke} + ); + + var defCallback = isGlobal + ? (macro class { + static var stored:ammer.ffi.Haxe<$funCt> = null; + public static function store(val:$funCt):Void { + if (stored != null) stored.decref(); + stored = ammer.Lib.createHaxeRef((_ : $funCt), val); + stored.incref(); + } + }) + : (macro class {}); + defCallback.name = callbackTp.name; + defCallback.pack = callbackTp.pack; + Utils.defineType(defCallback); + + Reporting.log('exit genericBuildCallback $elId', "stage"); + callbackCt; + }); + case _: throw 0; + }); + } + + public static function genericBuildHaxeRef():ComplexType { + var pos = Context.currentPos(); + return Reporting.withPosition(pos, () -> switch (Context.getLocalType()) { + // ammer.ffi.HaxeRef + case TInst(_, [el]): + var elId = Utils.typeId2(el); + if (Ammer.mergedInfo.haxeRefs.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.haxeRefs.byElementTypeId[elId].marshal.refCt; + } + Reporting.log('enter genericBuildHaxeRef $elId', "stage-ffi"); + + Ammer.initSubtype('ammer.ffi.Haxe<$elId>', el, ctx -> { + // was it initialised in the meanwhile? + if (Ammer.mergedInfo.haxeRefs.byElementTypeId.exists(elId)) { + return Ammer.mergedInfo.haxeRefs.byElementTypeId[elId].marshal.refCt; + } + Reporting.log('process genericBuildHaxeRef $elId', "stage-ffi"); + + var elCt = TypeTools.toComplexType(el); + var marshal = ctx.marshal.haxePtr(elCt); + var create = marshal.create(macro _hxval); + var haxeRef = { + ctx: ctx, + create: create, + elementType: el, + marshal: marshal, + }; + + var typeId = Utils.typeIdTp(ammer.core.utils.TypeUtils.complexTypeToPath(haxeRef.marshal.refCt)); + Ammer.mergedInfo.haxeRefs.byTypeId[typeId] = haxeRef; + Ammer.mergedInfo.haxeRefs.byElementTypeId[elId] = haxeRef; + ctx.info.haxeRefs.byTypeId[typeId] = haxeRef; + ctx.info.haxeRefs.byElementTypeId[elId] = haxeRef; + + Reporting.log('exit genericBuildHaxeRef $elId', "stage-ffi"); + haxeRef.marshal.refCt; + }, ctxLibTypes -> { + var elCt = TypeTools.toComplexType(el); + Ammer.mergedInfo.haxeRefs.byElementTypeId.exists(".Any.Any") || throw 0; + //var anyRefCt = Ammer.haxes.byElementTypeId[".Any.Any"].marshal.refCt; + + // TODO: emit warning? + Reporting.log('exit genericBuildHaxeRef $elId (as HaxeAnyRef)', "stage-ffi"); + (macro : ammer.internal.LibTypes.HaxeAnyRef<$elCt>); + }); + case _: throw 0; + }); + } + + public static function genericBuildLibrary():ComplexType { + return Reporting.withCurrentPos(() -> switch (Context.getLocalType()) { + // ammer.def.Library + case TInst(_, [tp1]): + var e1 = Utils.exprOfType(tp1); + (e1 != null && Utils.stringOfParam(tp1) != null) + || throw Reporting.error("ammer.def.Library: type parameter should be a string literal"); + TPath({ + pack: ["ammer", "internal"], + name: "Entrypoint", + sub: "LibraryProcessed", + params: [TPExpr(e1)], + }); + case _: throw 0; + }); + } + + public static function genericBuildOpaque():ComplexType { + return Reporting.withCurrentPos(() -> switch (Context.getLocalType()) { + // ammer.def.Opaque + case TInst(_, [tp1, tp2]): + var e1 = Utils.exprOfType(tp1); + (e1 != null && Utils.stringOfParam(tp1) != null) + || throw Reporting.error("ammer.def.Opaque: first type parameter should be a string literal"); + Utils.classOfParam(tp2) != null + || throw Reporting.error("ammer.def.Opaque: second type parameter should be the parent library"); + TPath({ + pack: ["ammer", "internal"], + name: "Entrypoint", + sub: "OpaqueProcessed", + params: [TPExpr(e1), TPType(TypeTools.toComplexType(tp2))], + }); + case _: throw 0; + }); + } + + public static function genericBuildStruct():ComplexType { + return Reporting.withCurrentPos(() -> switch (Context.getLocalType()) { + // ammer.def.Struct + case TInst(_, params = [tp1, tp2]): + var e1 = Utils.exprOfType(tp1); + (e1 != null && Utils.stringOfParam(tp1) != null) + || throw Reporting.error("ammer.def.Opaque: first type parameter should be a string literal"); + Utils.classOfParam(tp2) != null + || throw Reporting.error("ammer.def.Opaque: second type parameter should be the parent library"); + TPath({ + pack: ["ammer", "internal"], + name: "Entrypoint", + sub: "StructProcessed", + params: [TPExpr(e1), TPType(TypeTools.toComplexType(tp2))], + }); + case _: throw 0; + }); + } + + public static function genericBuildSublibrary():ComplexType { + return Reporting.withCurrentPos(() -> switch (Context.getLocalType()) { + // ammer.def.Sublibrary + case TInst(_, [tp1]): + Utils.classOfParam(tp1) != null + || throw Reporting.error("ammer.def.Sublibrary: type parameter should be the parent library"); + TPath({ + pack: ["ammer", "internal"], + name: "Entrypoint", + sub: "SublibraryProcessed", + params: [TPType(TypeTools.toComplexType(tp1))], + }); + case _: throw 0; + }); + } + + public static function buildEnum(name:String, el:Expr, lib:Expr):Array { + return Reporting.withCurrentPos(() -> { + var implType = Context.getLocalClass().get(); + var absType = (switch (implType.kind) { + case KAbstractImpl(_.get() => abs): abs; + case _: throw Reporting.error("ammer.def.Enum.build should be applied onto an enum abstract"); + }); + var implId = Utils.typeId(absType); + Reporting.log('enter buildEnum $implId "$name"', "stage"); + + var haxeEl = absType.type; + + var elCt = Utils.complexTypeExpr(el); + elCt != null || throw Reporting.error("ammer.def.Enum.build second argument should be an ammer FFI type"); + var el = Context.resolveType(elCt, Reporting.currentPos()); + + var libCt = Utils.complexTypeExpr(lib); + libCt != null || throw Reporting.error("ammer.def.Enum.build third argument should be the parent library"); + var lib = Context.resolveType(libCt, Reporting.currentPos()); + + var options:ammer.internal.v1.LibInfo.LibInfoEnum = {}; + Ammer.mergedInfo.enums[implId] = options; + + var implFields = Context.getBuildFields(); + var fields:Array = Ammer.initSubtype(implId, lib, ctx -> { + Reporting.log('process buildEnum $implId "$name"', "stage"); + + var rawElRes = Types.resolveType(el, ctx); + var elRes = ctx.marshal.enumInt(name, rawElRes.marshal); + switch (elRes.mangled) { + case "u1" | "i8" | "i16" | "i32" | "u8" | "u16" | "u32": + // TODO: other types: (though floats and strings don't make much sense for enums) + // "i64" | "u64" + // "f32" | "f64" + // "s" + case _: throw Reporting.error("ammer.def.Enum.build first argument can only be a primitve type"); + } + options.marshal = elRes; + + var extract = new ammer.core.utils.LineBuf() + .ail('#include ') + .ail('int main() {') + .i(); + var extractedFields = []; + var dbgCtr = 0; + for (field in implFields) switch (field.kind) { + case FVar(_, null) if (!field.access.contains(AStatic)): + var native = field.name; + for (meta in Meta.extract(field.meta, Meta.ENUM_FIELD)) switch (meta) { + case PMNative(name): native = name; + case _: throw 0; + } + extract.ail('printf("%d\\n", $native);'); + extractedFields.push(field); + case _: throw Reporting.error("unexpected field"); + } + extract.d().ail("}"); + + var data = ctx.prebuildImmediate(extract.done()).split("\n"); + [ for (field in implFields) { + var index = extractedFields.indexOf(field); + if (index == -1) { + field; + } else { + var val = Std.parseInt(data[index]); + Utils.updateField(field, FVar(null, macro $v{val})); + } + } ]; + // TODO: re-process through Fields? + }); + + Reporting.log('exit buildEnum $implId "$name" (${fields.length} fields)', "stage"); + Utils.modifyType(implType, fields); + }); + } + + public static function autoBuildLibrary():Array { + return Reporting.withCurrentPos(() -> { + var implType = Context.getLocalClass().get(); + var implId = Utils.typeId(implType); + var libname = Utils.stringOfParam(implType.superClass.params[0]); + libname != null || throw 0; + + Reporting.log('enter autoBuildLibrary $implId "$libname"', "stage"); + + var options:LibContext.LibContextOptions = { + name: libname, + headers: [], + defines: [], + includePaths: [], + libraryPaths: [], + frameworks: [], + language: C, + linkNames: [], + }; + var optionsLinkNameDefault = true; + var fieldOptions:ammer.internal.v1.LibInfo.LibInfoLibrary = {}; + var subtypes = []; + + for (meta in Meta.extract(implType.meta.get(), Meta.LIBRARY_CLASS)) switch (meta) { + case PMLib_Define(v): options.defines.push(v); + case PMLib_Framework(v): options.frameworks.push(v); + case PMLib_Frameworks(v): options.frameworks = options.frameworks.concat(v); + case PMLib_IncludePath(v): options.includePaths.push(v); + case PMLib_IncludePaths(v): options.includePaths = options.includePaths.concat(v); + case PMLib_Language(v): options.language = v; + case PMLib_LibraryPath(v): options.libraryPaths.push(v); + case PMLib_LibraryPaths(v): options.libraryPaths = options.libraryPaths.concat(v); + case PMLib_LinkName(v): optionsLinkNameDefault = false; options.linkNames.push(v); + case PMLib_LinkNames(v): optionsLinkNameDefault = false; options.linkNames = options.linkNames.concat(v); + case PMLib_Headers_Include(v): options.headers.push(IncludeLocal(v)); + case PMLib_Headers_Import(v): options.headers.push(ImportLocal(v)); + case PMLib_Headers_IncludeLocal(v): options.headers.push(IncludeLocal(v)); + case PMLib_Headers_ImportLocal(v): options.headers.push(ImportLocal(v)); + case PMLib_Headers_IncludeGlobal(v): options.headers.push(IncludeGlobal(v)); + case PMLib_Headers_ImportGlobal(v): options.headers.push(ImportGlobal(v)); + case PMNativePrefix(v): fieldOptions.nativePrefix = v; + case PMSub(v): subtypes.push(v); + case _: throw 0; + } + if (optionsLinkNameDefault) options.linkNames.push(libname); + + var isLibTypes = (implId == "ammer.internal.LibTypes.LibTypes"); + var fields:Array; + var implFields = Context.getBuildFields(); + if (isLibTypes && Bakery.isBaking) { + // When baking, `LibTypes` is treated as a sublibrary of the main + // library. As a result, its methods are compiled into the main library + // and it is not necessary to distribute an additional dynamic library + // with the program. When multiple baked libraries are used together, + // one `LibTypes` version is selected according to the order in which + // the baked libraries are introduced in the compiler invocation. + Reporting.log('processing autoBuildLibrary $implId as a sublibrary for the main type', "stage"); + + var options:ammer.internal.v1.LibInfo.LibInfoSublibrary = {}; + Ammer.mergedInfo.sublibraries[implId] = options; + + fields = Ammer.initSubtype(implId, Bakery.mainType, ctx -> { + Reporting.log('process autoBuildLibrary $implId "$libname"', "stage"); + options.ctx = ctx; + ctx.info.sublibraries[implId] = options; + + // TODO: this is a bit hacky; it exists so that `Types.resolveContext` + // finds the main library instead of `LibTypes`. + Ammer.libraries.byLibraryName["libtypes"] = ctx; + Ammer.libraries.byTypeId[implId] = ctx; + + Ammer.contextReady(implType, ctx); + subtypes.iter(Utils.triggerTyping); + Fields.process( + implFields, + ctx, + FCSublibrary(options) + ); + }); + } else { + var ctx = Ammer.initLibrary(implType, libname, options); + subtypes.iter(Utils.triggerTyping); + fields = Fields.process( + implFields, + ctx, + FCLibrary(fieldOptions) + ); + ctx.finalise(); + } + + Reporting.log('exit autoBuildLibrary $implId "$libname" (${fields.length} fields)', "stage"); + Utils.modifyType(implType, fields); + }); + } + + public static function autoBuildOpaque():Array { + return Reporting.withCurrentPos(() -> { + var implRef = Context.getLocalClass(); + var implType = implRef.get(); + var implId = Utils.typeId(implType); + var opaqueName = Utils.stringOfParam(implType.superClass.params[0]); + opaqueName != null || throw 0; + + Reporting.log('enter autoBuildOpaque $implId "$opaqueName"', "stage"); + + implType.params.length == 0 || throw Reporting.error("ammer opaque types cannot have type parameters"); + + var lib = Utils.classOfParam(implType.superClass.params[1]); + var options:ammer.internal.v1.LibInfo.LibInfoOpaque = { + implType: TInst(implRef, []), + opaqueName: opaqueName, + }; + Ammer.mergedInfo.opaques[implId] = options; + + for (meta in Meta.extract(implType.meta.get(), Meta.OPAQUE_CLASS)) switch (meta) { + case PMNativePrefix(v): options.nativePrefix = v; + case _: throw 0; + } + + var implFields = Context.getBuildFields(); + var fields:Array = Ammer.initSubtype(implId, implType.superClass.params[1], ctx -> { + Reporting.log('process autoBuildOpaque $implId "$opaqueName"', "stage"); + options.ctx = ctx; + ctx.info.opaques[implId] = options; + Fields.process( + implFields, + ctx, + FCOpaque(options) + ); + }); + + Reporting.log('exit autoBuildOpaque $implId "$opaqueName" (${fields.length} fields)', "stage"); + Utils.modifyType(implType, fields); + }); + } + + public static function autoBuildStruct():Array { + return Reporting.withCurrentPos(() -> { + var implRef = Context.getLocalClass(); + var implType = implRef.get(); + var implId = Utils.typeId(implType); + var structName = Utils.stringOfParam(implType.superClass.params[0]); + structName != null || throw 0; + + Reporting.log('enter autoBuildStruct $implId "$structName"', "stage"); + + implType.params.length == 0 || throw Reporting.error("ammer struct types cannot have type parameters"); + + var lib = Utils.classOfParam(implType.superClass.params[1]); + var alloc = false; + var options:ammer.internal.v1.LibInfo.LibInfoStruct = { + alloc: false, + gen: {}, + implType: TInst(implRef, []), + structName: structName, + }; + Ammer.mergedInfo.structs[implId] = options; + + for (meta in Meta.extract(implType.meta.get(), Meta.STRUCT_CLASS)) switch (meta) { + case PMAlloc: + options.alloc = true; + options.gen = { + alloc: "alloc", + free: "free", + nullPtr: "nullPtr", + }; + case PMGen_Alloc(v): + options.alloc = true; + options.gen.alloc = v; + case PMGen_Free(v): + options.alloc = true; + options.gen.free = v; + case PMGen_NullPtr(v): + options.alloc = true; + options.gen.nullPtr = v; + case PMNativePrefix(v): options.nativePrefix = v; + case PMSub(v): Utils.triggerTyping(v); // TODO: copy approach in Sublibrary + case _: throw 0; + } + + var implFields = Context.getBuildFields(); + var fields:Array = Ammer.initSubtype(implId, implType.superClass.params[1], ctx -> { + Reporting.log('process autoBuildStruct $implId "$structName"', "stage"); + options.ctx = ctx; + ctx.info.structs[implId] = options; + Fields.process( + implFields, + ctx, + FCStruct(options) + ); + }); + + Reporting.log('exit autoBuildStruct $implId "$structName" (${fields.length} fields)', "stage"); + Utils.modifyType(implType, fields); + }); + } + + public static function autoBuildSublibrary():Array { + return Reporting.withCurrentPos(() -> { + var implRef = Context.getLocalClass(); + var implType = implRef.get(); + var implId = Utils.typeId(implType); + Reporting.log('enter autoBuildSublibrary $implId', "stage"); + + var lib = Utils.classOfParam(implType.superClass.params[0]); + var options:ammer.internal.v1.LibInfo.LibInfoSublibrary = {}; + var subtypes = []; + Ammer.mergedInfo.sublibraries[implId] = options; + + for (meta in Meta.extract(implType.meta.get(), Meta.SUBLIBRARY_CLASS)) switch (meta) { + case PMNativePrefix(v): options.nativePrefix = v; + case PMSub(v): subtypes.push(v); + case _: throw 0; + } + + var implFields = Context.getBuildFields(); + var fields:Array = Ammer.initSubtype(implId, implType.superClass.params[0], ctx -> { + Reporting.log('process autoBuildSublibrary $implId', "stage"); + options.ctx = ctx; + ctx.info.sublibraries[implId] = options; + Ammer.contextReady(implType, ctx); + subtypes.iter(Utils.triggerTyping); + Fields.process( + implFields, + ctx, + FCSublibrary(options) + ); + }); + + Reporting.log('exit autoBuildSublibrary $implId (${fields.length} fields)', "stage"); + Utils.modifyType(implType, fields); + }); + } +} + +#else + +@:autoBuild(ammer.internal.Entrypoint.autoBuildLibrary()) +class LibraryProcessed<@:const Name> {} + +@:autoBuild(ammer.internal.Entrypoint.autoBuildOpaque()) +class OpaqueProcessed<@:const Name, Lib> {} + +@:autoBuild(ammer.internal.Entrypoint.autoBuildStruct()) +class StructProcessed<@:const Name, Lib> {} + +@:autoBuild(ammer.internal.Entrypoint.autoBuildSublibrary()) +class SublibraryProcessed {} + +#end diff --git a/src/ammer/internal/Fields.hx b/src/ammer/internal/Fields.hx new file mode 100644 index 0000000..840986b --- /dev/null +++ b/src/ammer/internal/Fields.hx @@ -0,0 +1,559 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; +import haxe.macro.TypeTools; + +using Lambda; +using StringTools; + +enum FieldContext { + FCLibrary(options:ammer.internal.v1.LibInfo.LibInfoLibrary); + FCOpaque(options:ammer.internal.v1.LibInfo.LibInfoOpaque); + FCStruct(options:ammer.internal.v1.LibInfo.LibInfoStruct); + FCSublibrary(options:ammer.internal.v1.LibInfo.LibInfoSublibrary); +} + +typedef CategorisedMethod = { + nativeName:String, + metas:Array, + field:Field, + fun:Function, +}; +typedef CategorisedVar = { + nativeName:String, + metas:Array, + field:Field, + ct:ComplexType, + isFinal:Bool, +}; + +class Fields { + static function categorise( + fields:Array, + fieldCtx:FieldContext + ):{ + staticMethods:Array, + staticVars:Array, + instanceMethods:Array, + instanceVars:Array, + passMethods:Array, + } { + var nativePrefix:String = null; + switch (fieldCtx) { + case FCLibrary(options): + nativePrefix = options.nativePrefix; + case FCOpaque(options): + nativePrefix = options.nativePrefix; + case FCStruct(options): + nativePrefix = options.nativePrefix; + case FCSublibrary(options): + nativePrefix = options.nativePrefix; + } + + var staticMethods = []; + var staticVars = []; + var instanceMethods = []; + var instanceVars = []; + var passMethods = []; + for (field in fields) Reporting.withPosition(field.pos, () -> { + var nativeName = nativePrefix != null + ? nativePrefix + field.name + : field.name; + + // validate field access modifiers + var isInstance = !field.access.contains(AStatic); + if (isInstance && !fieldCtx.match(FCStruct(_) | FCOpaque(_))) + return Reporting.error('non-static field ${field.name} only allowed in struct types and opaque types'); + + var isFinal = field.access.contains(AFinal); + + for (access in field.access) switch (access) { + case AOverride: Reporting.error('"override" not allowed in ammer definitions'); + case ADynamic: Reporting.error('"dynamic" not allowed in ammer definitions'); + case AInline: Reporting.error('"inline" not allowed in ammer definitions'); + case AMacro: Reporting.error('"macro" not allowed in ammer definitions'); + case AExtern: Reporting.error('"extern" not allowed in ammer definitions'); + case AAbstract: Reporting.error('"abstract" not allowed in ammer definitions'); + case AOverload: Reporting.error('"overload" not allowed in ammer definitions'); + case _: + } + + // parse metadata + var allowHaxe = false; + var defaultNativeName = true; + var metas = Meta.extract(field.meta, Meta.COMMON_FIELD, false); + for (meta in metas) switch (meta) { + case PMHaxe: allowHaxe = true; + case PMNative(name): defaultNativeName = false; nativeName = name; + case _: throw 0; + } + if (allowHaxe) { + defaultNativeName || throw Reporting.error("@:ammer.native has no effect on @:ammer.haxe fields"); + } + + // categorise + switch (field.kind) { + case FFun(f): + if (allowHaxe) { + f.expr != null || throw Reporting.error("function body required for @:ammer.haxe functions"); + passMethods.push({ + nativeName: null, + metas: metas, + field: field, + fun: f, + }); + } else { + f.expr == null || throw Reporting.error("function body not allowed (unless @:ammer.haxe is added)"); + f.ret != null || return Reporting.error("type annotation required for return type"); + !isFinal || return Reporting.error('"final" not allowed on methods'); + for (arg in f.args) arg.type != null || return Reporting.error("type annotation required for arguments"); + (isInstance ? instanceMethods : staticMethods).push({ + nativeName: nativeName, + metas: metas, + field: field, + fun: f, + }); + } + case FVar(ct, def): + !allowHaxe || return Reporting.error("@:ammer.haxe not yet supported on variable fields"); + ct != null || return Reporting.error("type annotation required"); + def == null || return Reporting.error("default values are not allowed"); + (isInstance ? instanceVars : staticVars).push({ + nativeName: nativeName, + metas: metas, + field: field, + ct: ct, + isFinal: isFinal, + }); + case _: return Reporting.error("invalid field kind"); + } + }); + return { + staticMethods: staticMethods, + staticVars: staticVars, + instanceMethods: instanceMethods, + instanceVars: instanceVars, + passMethods: passMethods, + }; + } + + public static function process( + fields:Array, + ctx:LibContext, + fieldCtx:FieldContext + ):Array { + var categorised = categorise(fields, fieldCtx); + var processed:Array = []; + + // create type representation + var libraryOptions:ammer.internal.v1.LibInfo.LibInfoLibrary; + var opaqueOptions:ammer.internal.v1.LibInfo.LibInfoOpaque; + var structOptions:ammer.internal.v1.LibInfo.LibInfoStruct; + var sublibraryOptions:ammer.internal.v1.LibInfo.LibInfoSublibrary; + switch (fieldCtx) { + case FCLibrary(options): + libraryOptions = options; + case FCOpaque(options): + // TODO: reduce duplication between opaque and struct cases + opaqueOptions = options; + options.marshal = ctx.marshal.opaque(options.opaqueName); + processed.push({ + name: "_ammer_native", + meta: [], + pos: Reporting.currentPos(), + kind: FVar(options.marshal.type.haxeType, null), + doc: null, + access: [APrivate], + }); + processed.push({ + name: "new", + meta: [], + pos: Reporting.currentPos(), + kind: FFun({ + ret: null, + expr: macro _ammer_native = native, + args: [{ name: "native", type: options.marshal.type.haxeType }], + }), + doc: null, + access: [APrivate], + }); + var implCt = TypeTools.toComplexType(options.implType); + var implTp = (switch (implCt) { + case TPath(tp): tp; + case _: throw 0; + }); + processed.push({ + name: "_ammer_lib_nullPtr", + meta: [], + pos: Reporting.currentPos(), + kind: FFun({ + ret: implCt, + expr: macro return new $implTp($e{options.marshal.nullPtr}), + args: [], + }), + doc: null, + access: [APrivate, AStatic], + }); + case FCStruct(options): + structOptions = options; + + // TODO: this is not great, is there better API design? + var structEmpty = ctx.marshal.structPtr(options.structName, [], false); + options.marshalOpaque = structEmpty.type; + options.marshalDeref = structEmpty.typeDeref; + + // TODO: store the fieldRefs? + var fieldRefs = categorised.instanceVars.map(f -> { + var fieldType = Types.resolveComplexType(f.ct, ctx); + var fref = ctx.marshal.fieldRef(f.nativeName, fieldType.marshal); + fref; + }); + options.marshal = ctx.marshal.structPtr( + options.structName, + fieldRefs, + options.gen.alloc != null + || options.gen.free != null + || options.gen.nullPtr != null + ); + processed.push({ + name: "_ammer_native", + meta: [], + pos: Reporting.currentPos(), + kind: FVar(options.marshal.type.haxeType, null), + doc: null, + access: [APrivate], + }); + processed.push({ + name: "new", + meta: [], + pos: Reporting.currentPos(), + kind: FFun({ + ret: null, + expr: macro _ammer_native = native, + args: [{ name: "native", type: options.marshal.type.haxeType }], + }), + doc: null, + access: [APrivate], + }); + var implCt = TypeTools.toComplexType(options.implType); + var implTp = (switch (implCt) { + case TPath(tp): tp; + case _: throw 0; + }); + + if (options.gen.alloc != null) { + processed.push({ + name: options.gen.alloc, + meta: [], + pos: Reporting.currentPos(), + kind: FFun({ + ret: implCt, + expr: macro return new $implTp($e{options.marshal.alloc}), + args: [], + }), + doc: null, + access: [APublic, AStatic], + }); + } + if (options.gen.free != null) { + processed.push({ + name: options.gen.free, + meta: [], + pos: Reporting.currentPos(), + kind: FFun({ + ret: (macro : Void), + expr: macro $e{options.marshal.free(macro _ammer_native)}, + args: [], + }), + doc: null, + access: [APublic], + }); + } + if (options.gen.nullPtr != null) { + processed.push({ + name: options.gen.nullPtr, + meta: [], + pos: Reporting.currentPos(), + kind: FFun({ + ret: implCt, + expr: macro return new $implTp($e{options.marshal.nullPtr}), + args: [], + }), + doc: null, + access: [APublic, AStatic], + }); + } + case FCSublibrary(options): + sublibraryOptions = options; + } + + // process fields + function processMethod(method:CategorisedMethod, isInstance:Bool):Void { + var argCount = method.fun.args.length; + + // process method metadata + var cPrereturn = ""; + var cReturn = "%CALL%"; + var derivedRet = null; + var derivedRetType = null; + for (meta in Meta.extract(method.field.meta, Meta.COMMON_METHOD)) switch (meta) { + case PMNative(name): // already processed in `categorised`, ignore + case PMC_MacroCall: // no-op + case PMC_Prereturn(expr): cPrereturn = expr; + case PMC_Return(expr): + // ret.mangled != "v" || throw Reporting.error(":ammer.c.return cannot be used on a method with `Void` return type"); + cReturn = expr; + case PMRet_Derive(expr, ct): + derivedRet = expr; + derivedRetType = ct; + case _: throw 0; + } + + // process argument metadata + var skipArgs = [ for (idx in 0...argCount) false ]; + var derivedHaxe = [ for (idx in 0...argCount) null ]; + var replaceHaxe = [ for (idx in 0...argCount) null ]; // TODO: messy! + var replaceArgType = [ for (idx in 0...argCount) null ]; + var cCast = [ for (idx in 0...argCount) null ]; // TODO: messy! + for (idx in 0...argCount) { + method.fun.args[idx].meta != null || continue; + for (meta in Meta.extract(method.fun.args[idx].meta, Meta.METHOD_ARG)) switch (meta) { + case PMC_Cast(to): cCast[idx] = to; + case PMSkip: skipArgs[idx] = true; + case PMDerive(e): derivedHaxe[idx] = e; + // TODO: c.derive + case _: throw 0; + } + } + var nonSkipCtr = 0; + var derivedC = [ for (idx in 0...argCount) skipArgs[idx] + ? null + : ((cCast[idx] != null ? '(${cCast[idx]})' : "") + '_arg${nonSkipCtr++}') ]; + var preExprs = []; + + // TODO: sanity checks, e.g. disallow skip and derive on the same argument + // TODO: ammer.argN... variant of metadata? + // TODO: ammer.ret... metadata + + // process special types + // TODO: retAlloc is not a very clean solution + var retAlloc = null; + function localResolve(res:Type, idx:Int):Types.ResolvedType { + if (Types.TYPES.this_.match(res)) { + isInstance || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); + (structOptions != null || opaqueOptions != null) || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); + idx != -1 || throw Reporting.error("ammer.ffi.This cannot be used as the return type"); + derivedHaxe[idx] = macro this; + res = structOptions != null ? structOptions.implType : opaqueOptions.implType; + } else if (Types.TYPES.derefThis.match(res)) { + isInstance || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); + (structOptions != null || opaqueOptions != null) || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); + idx != -1 || throw Reporting.error("ammer.ffi.This cannot be used as the return type"); + derivedHaxe[idx] = macro this; + var implCt = TypeTools.toComplexType(structOptions != null ? structOptions.implType : opaqueOptions.implType); + res = haxe.macro.ComplexTypeTools.toType((macro : ammer.ffi.Deref<$implCt>)); + } else { + switch (res) { + case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.callbacks.byTypeId.exists(id)): + var callback = Ammer.mergedInfo.callbacks.byTypeId[id]; + derivedC[idx] = callback.callbackName; + if (callback.isGlobal) { + var ident = 'arg$idx'; + preExprs.push(macro $p{Utils.accessTp(Utils.expectTypePath(callback.callbackCt))}.store($i{ident})); + replaceHaxe[idx] = macro 0; + replaceArgType[idx] = callback.funCt; + } else { + derivedHaxe[idx] = macro 0; + } + res = Types.TYPES.i32.type; + case TInst(Utils.typeId(_.get()) => "ammer.ffi.Alloc.Alloc", params): + idx == -1 || throw Reporting.error("ammer.ffi.Alloc cannot be used as an argument type"); + params.length == 1 || throw Reporting.error("ammer.ffi.Alloc should have one type parameter"); + var type = Utils.classOfParam(params[0]).get(); + var id = Utils.typeId(type); + Ammer.mergedInfo.structs[id].marshal != null || throw Reporting.error("type parameter should be a struct"); + Ammer.mergedInfo.structs[id].alloc || throw Reporting.error("struct is not allocatable"); + var ct = TypeTools.toComplexType(params[0]); + var tp = (switch (ct) { + case TPath(tp): tp; + case _: throw 0; + }); + retAlloc = Ammer.mergedInfo.structs[id].structName; + res = params[0]; + case TInst(Utils.typeId(_.get()) => "ammer.ffi.Unsupported.Unsupported", [val]): + // TODO: allow ammer.ffi.Unsupported<""> for ignored return values + idx != -1 || throw Reporting.error("ammer.ffi.Unsupported cannot be used as the return type"); + var expr = Utils.stringOfParam(val); + expr != null || throw Reporting.error("ammer.def.Unsupported type parameter should be a string"); + derivedHaxe[idx] = (macro 0); + derivedC[idx] = expr; + res = Types.TYPES.i32.type; + case TType(_): return localResolve(TypeTools.follow(res, true), idx); + case _: + } + } + return Types.resolveType(res, ctx); + } + function localResolveCt(ct:ComplexType, idx:Int):Types.ResolvedType { + var res = Reporting.resolveType(ct, Reporting.currentPos()); + return localResolve(res, idx); + } + + // resolve types + var ret = localResolveCt(method.fun.ret, -1); + var args = method.fun.args.mapi((idx, arg) -> skipArgs[idx] ? null : localResolveCt(arg.type, idx)); + + // create native representation + var nativeCall = '${method.nativeName}(${[ for (idx in 0...argCount) if (!skipArgs[idx]) derivedC[idx] ].join(", ")})'; + var native = ctx.library.addFunction( + ret.marshal, + [ for (idx in 0...argCount) if (!skipArgs[idx]) args[idx].marshal ], + cPrereturn + "\n" + (retAlloc != null + // TODO: configure malloc, memcpy + ? '_return = ($retAlloc*)malloc(sizeof($retAlloc)); +$retAlloc retval = ${cReturn.replace("%CALL%", nativeCall)}; +memcpy(_return, &retval, sizeof($retAlloc));' + : '${ret.marshal.mangled != "v" ? "_return = " : ""}${cReturn.replace("%CALL%", nativeCall)};'), + { + comment: 'original field name: ${method.field.name}', + } + ); + + // create Haxe call + var call:Expr = { + expr: ECall( + native, + [ for (idx in 0...argCount) if (!skipArgs[idx]) { + var expr = derivedHaxe[idx] != null + ? derivedHaxe[idx] + : (replaceHaxe[idx] != null + ? replaceHaxe[idx] + : { expr: EConst(CIdent('arg$idx')), pos: method.field.pos }); + Utils.exprMap(expr, args[idx].unwrap); + } ] + ), + pos: method.field.pos, + }; + var finalExprs = derivedRet != null + ? [macro (var ret = $e{Utils.exprMap(call, ret.wrap)}), macro return $derivedRet] + : [macro return $e{Utils.exprMap(call, ret.wrap)}]; + processed.push(Utils.updateField(method.field, FFun({ + ret: derivedRetType != null ? derivedRetType : ret.haxeType, + expr: macro $b{preExprs.concat(finalExprs)}, + args: [ for (idx in 0...argCount) { + derivedHaxe[idx] == null || continue; + ({name: 'arg$idx', type: skipArgs[idx] + ? method.fun.args[idx].type + : (replaceArgType[idx] != null + ? replaceArgType[idx] + : args[idx].haxeType)}:FunctionArg); + } ], + }))); + } + + for (method in categorised.staticMethods) + Reporting.withPosition(method.field.pos, () -> processMethod(method, false)); + for (method in categorised.instanceMethods) + Reporting.withPosition(method.field.pos, () -> processMethod(method, true)); + for (method in categorised.passMethods) + processed.push(method.field); + + // TODO: reduce duplication? + for (v in categorised.staticVars) Reporting.withPosition(v.field.pos, () -> { + var fieldType = Types.resolveComplexType(v.ct, ctx); + var marshal = ctx.marshal.fieldRef(v.nativeName, fieldType.marshal); + var getter = ctx.library.addFunction( + marshal.type, + [], + '_return = ${v.nativeName};' + ); + if (v.isFinal) { + processed.push({ + name: 'get_${v.field.name}', + pos: v.field.pos, + kind: FFun({ + ret: fieldType.haxeType, + expr: macro return $e{Utils.exprMap(macro $getter(), fieldType.wrap)}, + args: [], + }), + access: [APrivate, AStatic], + }); + v.field.access.remove(AFinal); // Haxe#8859 + processed.push(Utils.updateField(v.field, FProp("get", "never", fieldType.haxeType, null))); + } else { + processed.push({ + name: 'get_${v.field.name}', + pos: v.field.pos, + kind: FFun({ + ret: fieldType.haxeType, + expr: macro return $e{Utils.exprMap(macro $getter(), fieldType.wrap)}, + args: [], + }), + access: [APrivate, AStatic], + }); + var setter = ctx.library.addFunction( + ctx.marshal.void(), + [marshal.type], + '${v.nativeName} = _arg0;' + ); + processed.push({ + name: 'set_${v.field.name}', + pos: v.field.pos, + kind: FFun({ + ret: fieldType.haxeType, + expr: macro { + $setter($e{Utils.exprMap(macro val, fieldType.unwrap)}); + return val; + }, + args: [{ + name: "val", + type: fieldType.haxeType, + }], + }), + access: [APrivate, AStatic], + }); + processed.push(Utils.updateField(v.field, FProp("get", "set", fieldType.haxeType, null))); + } + }); + + for (v in categorised.instanceVars) Reporting.withPosition(v.field.pos, () -> { + var fieldType = Types.resolveComplexType(v.ct, ctx); + var marshal = ctx.marshal.fieldRef(v.nativeName, fieldType.marshal); + processed.push({ + name: 'get_${v.field.name}', + pos: v.field.pos, + kind: FFun({ + ret: fieldType.haxeType, + expr: macro return $e{Utils.exprMap(structOptions.marshal.fieldGet[v.nativeName](macro _ammer_native), fieldType.wrap)}, + args: [], + }), + access: [APrivate], + }); + processed.push({ + name: 'set_${v.field.name}', + pos: v.field.pos, + kind: FFun({ + ret: fieldType.haxeType, + expr: macro { + $e{structOptions.marshal.fieldSet[v.nativeName](macro _ammer_native, Utils.exprMap(macro val, fieldType.unwrap))}; + return val; + }, + args: [{ + name: "val", + type: fieldType.haxeType, + }], + }), + access: [APrivate], + }); + processed.push(Utils.updateField(v.field, FProp("get", "set", fieldType.haxeType, null))); + }); + + return processed; + } +} + +#end diff --git a/src/ammer/internal/FilePtrOutput.hx b/src/ammer/internal/FilePtrOutput.hx new file mode 100644 index 0000000..96af5ce --- /dev/null +++ b/src/ammer/internal/FilePtrOutput.hx @@ -0,0 +1,23 @@ +// ammer-bake: ammer.internal FilePtrOutput !macro +package ammer.internal; + +import ammer.ffi.FilePtr; + +class FilePtrOutput extends haxe.io.Output { + var file:FilePtr; + function new(file:FilePtr) { + this.file = file; + } + + override public function writeByte(c:Int):Void file.fputc(c); + override public function writeBytes(s:haxe.io.Bytes, pos:Int, len:Int):Int { + if (pos < 0 || len < 0 || pos + len > s.length) + throw haxe.io.Error.OutsideBounds; + var bytesRef = ammer.ffi.Bytes.fromHaxeRef(s.sub(pos, len)); + var ret = file.fwrite(bytesRef.bytes.offset(pos), 1, len); + bytesRef.unref(); + return ret; + } + override public function flush():Void file.fflush(); + override public function close():Void file.fclose(); // TODO: only close output? +} diff --git a/src/ammer/internal/LibContext.hx b/src/ammer/internal/LibContext.hx new file mode 100644 index 0000000..56797f9 --- /dev/null +++ b/src/ammer/internal/LibContext.hx @@ -0,0 +1,181 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; + +typedef LibContextOptions = { + name:String, + headers:Array, + defines:Array, + includePaths:Array, + libraryPaths:Array, + frameworks:Array, + language:ammer.core.LibraryLanguage, + linkNames:Array, +}; + +class LibContext { + public var name:String; + public var libraryOptions:ammer.core.LibraryConfig; + public var library:ammer.core.Library; + public var marshal:ammer.core.Marshal; + public var headers:Array; + public var prebuild:Array<{ + code:String, + process:String->Void, + }> = []; + public var done:Bool = false; + + public var info = new ammer.internal.v1.LibInfo(); + public var infoV1:Null; + + public var isLibTypes:Bool = false; + + public function new(name:String, options:LibContextOptions) { + this.name = name; + + var libName = 'ammer_${options.name}'; + libraryOptions = (switch (Context.definedValue("target.name")) { + case "cpp": ({name: libName} : ammer.core.plat.Cpp.CppLibraryConfig); + case "cs": ({name: libName} : ammer.core.plat.Cs.CsLibraryConfig); + case "hl": ({name: libName} : ammer.core.plat.Hashlink.HashlinkLibraryConfig); + case "java": ({ + name: libName, + jvm: Context.defined("jvm"), + } : ammer.core.plat.Java.JavaLibraryConfig); + case "lua": ({name: libName} : ammer.core.plat.Lua.LuaLibraryConfig); + case "neko": ({name: libName} : ammer.core.plat.Neko.NekoLibraryConfig); + case "js": ({name: libName} : ammer.core.plat.Nodejs.NodejsLibraryConfig); + case "python": ({name: libName} : ammer.core.plat.Python.PythonLibraryConfig); + + case _: ({name: libName} : ammer.core.plat.None.NoneLibraryConfig); + }); + + libraryOptions.language = options.language; + libraryOptions.defines = options.defines; + libraryOptions.includePaths = options.includePaths; + libraryOptions.libraryPaths = options.libraryPaths; + libraryOptions.frameworks = options.frameworks; + libraryOptions.linkNames = options.linkNames; + libraryOptions.pos = Reporting.currentPos(); + + // process configuration defines + // TODO: -D for defines + var prefix = 'ammer.lib.${options.name}'; + if (Config.hasDefine('$prefix.frameworks')) + libraryOptions.frameworks = Config.getStringArray('$prefix.frameworks', ","); + if (Config.hasDefine('$prefix.linkNames')) + libraryOptions.linkNames = Config.getStringArray('$prefix.linkNames', ","); + if (Config.hasDefine('$prefix.includePaths')) + libraryOptions.includePaths = Config.getStringArray('$prefix.includePaths', ","); + if (Config.hasDefine('$prefix.libraryPaths')) + libraryOptions.libraryPaths = Config.getStringArray('$prefix.libraryPaths', ","); + if (Config.hasDefine('$prefix.language')) + libraryOptions.language = Config.getEnum('$prefix.language', [ + "c" => ammer.core.LibraryLanguage.C, + "cpp" => Cpp, + "objc" => ObjectiveC, + "objcpp" => ObjectiveCpp, + ], C); + + // process includes + if (Config.hasDefine('$prefix.headers') + || Config.hasDefine('$prefix.headers.includeLocal') + || Config.hasDefine('$prefix.headers.includeGlobal') + || Config.hasDefine('$prefix.headers.importLocal') + || Config.hasDefine('$prefix.headers.importGlobal')) { + headers = []; + if (Config.hasDefine('$prefix.headers')) + headers = headers.concat(Config.getStringArray('$prefix.headers', ",").map(ammer.core.SourceInclude.IncludeLocal)); + if (Config.hasDefine('$prefix.headers.includeLocal')) + headers = headers.concat(Config.getStringArray('$prefix.headers.includeLocal', ",").map(ammer.core.SourceInclude.IncludeLocal)); + if (Config.hasDefine('$prefix.headers.includeGlobal')) + headers = headers.concat(Config.getStringArray('$prefix.headers.includeGlobal', ",").map(ammer.core.SourceInclude.IncludeGlobal)); + if (Config.hasDefine('$prefix.headers.importLocal')) + headers = headers.concat(Config.getStringArray('$prefix.headers.importLocal', ",").map(ammer.core.SourceInclude.ImportLocal)); + if (Config.hasDefine('$prefix.headers.importGlobal')) + headers = headers.concat(Config.getStringArray('$prefix.headers.importGlobal', ",").map(ammer.core.SourceInclude.ImportGlobal)); + } else { + headers = options.headers; + } + + library = Ammer.platform.createLibrary(libraryOptions); + for (header in headers) library.addInclude(header); + marshal = library.marshal(); + } + + public function prebuildImmediate(code:String):String { + // TODO: cache + var ret:String = null; + var program = new ammer.core.utils.LineBuf() + .lmap(headers, header -> header.toCode()) + .a(code) + .done(); + Ammer.builder.build(new ammer.core.build.BuildProgram([ + BOAlways(File('${Ammer.baseConfig.buildPath}/ammer_${name}'), EnsureDirectory), + BOAlways( + File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.c'), // TODO: append cache key or delete? + WriteContent(program) + ), + BODependent( + File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.o'), + File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.c'), + CompileObject(C, { + defines: libraryOptions.defines, + includePaths: libraryOptions.includePaths, + }) + ), + BODependent( + File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild'), + File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.o'), + LinkExecutable(C, { + defines: libraryOptions.defines, + libraryPaths: libraryOptions.libraryPaths, // unnecessary? + libraries: [], + linkName: null, + }) + ), + BOAlways( + File(""), + Command('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild', [], (code, process) -> { + code == 0 || throw 0; + ret = process.stdout.readAll().toString(); + }) + ), + ])); + return ret; + } + + public function finalise():Void { + Ammer.libraries.active.remove(this); + + if (prebuild.length > 0) { + // var program = new ammer.core.utils.LineBuf(); + // program + // .ail("int main() {}"); + // Ammer.builder.build(new ammer.core.BuildProgram([ + // BOAlways(File('${Ammer.baseConfig.buildPath}/ammer_${name}'), EnsureDirectory), + // BOAlways( + // File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.c'), + // WriteContent(program.done()) + // ), + // ])); + } + + // the latest version is always generated, older ones can be generated on + // demand by baked libraries using defines + //if (Context.defined("ammer_baked_v1_needed")) { infoVX = ... } + infoV1 = info; + done = true; + Ammer.platform.addLibrary(library); + } + + public function toString():String { + return 'LibContext($name)'; + } +} + +#end diff --git a/src/ammer/internal/LibTypes.hx b/src/ammer/internal/LibTypes.hx new file mode 100644 index 0000000..1b1daf3 --- /dev/null +++ b/src/ammer/internal/LibTypes.hx @@ -0,0 +1,67 @@ +package ammer.internal; + +#if !macro + +// TODO: version LibTypes as well? + +@:ammer.lib.linkNames([]) +@:ammer.sub((_ : ammer.ffi.Bytes)) +@:ammer.sub((_ : ammer.ffi.FilePtr)) +@:ammer.sub((_ : ammer.ffi.FilePtr.FilePos)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Box)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Array)) +//@:ammer.sub((_ : ammer.ffi.Array)) +@:ammer.sub((_ : ammer.ffi.Haxe)) +class LibTypes extends ammer.def.Library<"libtypes"> {} + +abstract HaxeAnyRef({ + var value(get, never):Any; + function incref():Void; + function decref():Void; +}/*ammer.ffi.Haxe*/)/* to ammer.ffi.Haxe*/ { + public inline function new(r:ammer.ffi.Haxe) { + this = r; + } + + public var value(get, never):T; + inline function get_value():T { + return (cast this.value : T); + } + //inline function set_value(value:T):T { + // return (cast (this.value = value) : T); + //} + + public inline function incref():Void this.incref(); + public inline function decref():Void this.decref(); + + public inline function toNative():Any return this; +} + +#else +class LibTypes {} +class AmmerSetup { + public static function init():Void {} +} +#end diff --git a/src/ammer/internal/Meta.hx b/src/ammer/internal/Meta.hx new file mode 100644 index 0000000..2ee40fd --- /dev/null +++ b/src/ammer/internal/Meta.hx @@ -0,0 +1,273 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Expr; + +using Lambda; +using StringTools; + +// TODO: parse meta positions as well for more precise errors? +typedef MetaParser = (args:Array)->Null; + +enum ParsedMeta { + // c.* + PMC_Cast(_:String); + PMC_MacroCall; + PMC_Prereturn(_:String); + PMC_Return(_:String); + // gen.* + PMGen_Alloc(_:String); + PMGen_Free(_:String); + PMGen_NullPtr(_:String); + // lib.* + PMLib_Define(_:String); + // TODO: PMLib_Defines(_:Array); + PMLib_Framework(_:String); + PMLib_Frameworks(_:Array); + PMLib_IncludePath(_:String); + PMLib_IncludePaths(_:Array); + PMLib_Language(_:ammer.core.LibraryLanguage); + PMLib_LibraryPath(_:String); + PMLib_LibraryPaths(_:Array); + PMLib_LinkName(_:String); + PMLib_LinkNames(_:Array); + PMLib_Headers_Include(_:String); + PMLib_Headers_Import(_:String); + PMLib_Headers_IncludeLocal(_:String); + PMLib_Headers_ImportLocal(_:String); + PMLib_Headers_IncludeGlobal(_:String); + PMLib_Headers_ImportGlobal(_:String); + // ret.* + PMRet_Derive(e:Expr, ct:ComplexType); + // other + PMAlloc; // TODO: rename to something better? + PMDerive(_:Expr); + PMHaxe; + PMNative(_:String); + PMNativePrefix(_:String); + PMSkip; + PMSub(_:ComplexType); +} + +class Meta { + static function parseComplexType(e:Expr, arg:Int):ComplexType { + var ct = Utils.complexTypeExpr(e); + if (ct == null) + throw 'expected reference to type using (_ : path.to.Type) syntax (argument ${arg + 1})'; + return ct; + } + + static function parseExpr(e:Expr, arg:Int):Expr { + return e; + } + + static function parseString(e:Expr, arg:Int):String { + return (switch (e.expr) { + case EConst(CString(v)): v; + case _: throw 'expected string constant (argument ${arg + 1})'; + }); + } + + static function parseStringArray(e:Expr, arg:Int):Array { + return (switch (e.expr) { + case EArrayDecl(vs): [ for (v in vs) switch (v.expr) { + case EConst(CString(v)): v; + case _: throw 'expected string constant (argument ${arg + 1})'; + } ]; + case _: throw 'expected array of string constants (argument ${arg + 1})'; + }); + } + + static function parseEnum(map:Map):(e:Expr, arg:Int)->T { + return (e:Expr, arg:Int) -> { + switch (e.expr) { + case EConst(CIdent(id)): + if (!map.exists(id)) { + var keys = [for (k in map.keys()) k]; + keys.sort(Reflect.compare); + throw 'invalid value, should be one of ${keys.join(", ")} (argument ${arg + 1})'; + } + map[id]; + case _: throw 'expected identifier (argument ${arg + 1})'; + } + }; + } + + static function parser0(v:ParsedMeta):MetaParser { + return (args) -> { + if (args.length != 0) + throw 'expected no arguments (${args.length} provided)'; + v; + }; + } + + static function parser1(p1:(Expr, Int)->T1, f:(T1)->ParsedMeta):MetaParser { + return (args) -> { + if (args.length != 1) + throw 'expected 1 argument (${args.length} provided)'; + f(p1(args[0], 0)); + }; + } + + static function parser2(p1:(Expr, Int)->T1, p2:(Expr, Int)->T2, f:(T1, T2)->ParsedMeta):MetaParser { + return (args) -> { + if (args.length != 2) + throw 'expected 2 arguments (${args.length} provided)'; + f(p1(args[0], 0), p2(args[1], 1)); + }; + } + + /** + Metadata allowed for the class defining a library. + **/ + public static final LIBRARY_CLASS = [ + "lib.define" => parser1(parseString, PMLib_Define), + "lib.framework" => parser1(parseString, PMLib_Framework), + "lib.frameworks" => parser1(parseStringArray, PMLib_Frameworks), + "lib.includePath" => parser1(parseString, PMLib_IncludePath), + "lib.includePaths" => parser1(parseStringArray, PMLib_IncludePaths), + "lib.libraryPath" => parser1(parseString, PMLib_LibraryPath), + "lib.libraryPaths" => parser1(parseStringArray, PMLib_LibraryPaths), + "lib.language" => parser1(parseEnum([ + "C" => ammer.core.LibraryLanguage.C, + "Cpp" => Cpp, + "ObjC" => ObjectiveC, + "ObjCpp" => ObjectiveCpp, + ]), PMLib_Language), + "lib.linkName" => parser1(parseString, PMLib_LinkName), + "lib.linkNames" => parser1(parseStringArray, PMLib_LinkNames), + "lib.headers.include" => parser1(parseString, PMLib_Headers_Include), + "lib.headers.import" => parser1(parseString, PMLib_Headers_Import), + "lib.headers.includeLocal" => parser1(parseString, PMLib_Headers_IncludeLocal), + "lib.headers.importLocal" => parser1(parseString, PMLib_Headers_ImportLocal), + "lib.headers.includeGlobal" => parser1(parseString, PMLib_Headers_IncludeGlobal), + "lib.headers.importGlobal" => parser1(parseString, PMLib_Headers_ImportGlobal), + "nativePrefix" => parser1(parseString, PMNativePrefix), + "sub" => parser1(parseComplexType, PMSub), + ]; + + /** + Metadata allowed for the class defining a sublibrary. + **/ + public static final SUBLIBRARY_CLASS = [ + "nativePrefix" => parser1(parseString, PMNativePrefix), + "sub" => parser1(parseComplexType, PMSub), // TODO: disallow? + ]; + + public static final COMMON_METHOD = [ + "haxe" => parser0(PMHaxe), + "native" => parser1(parseString, PMNative), + "macroCall" => parser0(PMC_MacroCall), + "c.macroCall" => parser0(PMC_MacroCall), + "c.prereturn" => parser1(parseString, PMC_Prereturn), + "c.return" => parser1(parseString, PMC_Return), + // "cpp.constructor", + // "cpp.member", + "ret.derive" => parser2(parseExpr, parseComplexType, PMRet_Derive), + ]; + + /** + Metadata allowed for a method of a library. + **/ + public static final LIBRARY_METHOD = COMMON_METHOD; + + /** + Metadata allowed for a method of a struct. + **/ + public static final STRUCT_METHOD = COMMON_METHOD; + + /** + Metadata allowed for a variable of a struct. + **/ + public static final STRUCT_VAR = [ + "haxe" => parser0(PMHaxe), + "native" => parser1(parseString, PMNative), + // TODO: get/set/ref + ]; + + public static final COMMON_FIELD = [ + "haxe" => parser0(PMHaxe), + "native" => parser1(parseString, PMNative), + ]; + + public static final ENUM_FIELD = [ + "native" => parser1(parseString, PMNative), + ]; + + // /** + // Metadata allowed for a variable of a library. + // **/ + // public static final LIBRARY_VARIABLE = [ + // "native", + // ]; + + /** + Metadata allowed for the class defining a struct type. + **/ + public static final STRUCT_CLASS = [ + "alloc" => parser0(PMAlloc), + "nativePrefix" => parser1(parseString, PMNativePrefix), + "sub" => parser1(parseComplexType, PMSub), + // "struct", // TODO: deprecation warning? + "gen.alloc" => parser1(parseString, PMGen_Alloc), + "gen.free" => parser1(parseString, PMGen_Free), + "gen.nullPtr" => parser1(parseString, PMGen_NullPtr), + ]; + + /** + Metadata allowed for the class defining an opaque type. + **/ + public static final OPAQUE_CLASS = [ + "nativePrefix" => parser1(parseString, PMNativePrefix), + //"sub" => parser1(parseComplexType, PMSub), + ]; + + public static final METHOD_ARG = [ + "c.cast" => parser1(parseString, PMC_Cast), + "skip" => parser0(PMSkip), + "derive" => parser1(parseExpr, PMDerive), + ]; + + /** + Iterate through the given `metas`. Any entries that do not start with + `:ammer` will be ignored. + + If `strict` is `true`, all `:ammer.*` metadata must be present in the + `parsers` map to be accepted; an error is thrown if not. + + If `strict` is `false`, `:ammer.*` metadata which are not present in the + `parsers` map are ignored. + **/ + public static function extract( + metas:Metadata, + parsers:Map, + strict:Bool = true + ):Array { + var ret = []; + for (meta in metas) { + if (!meta.name.startsWith(":ammer.")) + continue; + var id = meta.name.substr(":ammer.".length); + Reporting.withPosition(meta.pos, () -> { + var parser = parsers[id]; + if (parser == null) { + if (strict) { + var ids = [ for (k => _ in parsers) k ]; + ids.sort(Reflect.compare); + Reporting.error('unsupported or incorrectly specified ammer metadata ${meta.name} (should be one of ${ids.join(", ")})'); + } + return; + } + try { + ret.push(parser(meta.params)); + } catch (error:String) { + Reporting.error('cannot parse ammer metadata ${meta.name}: $error'); + } + }); + } + return ret; + } +} + +#end diff --git a/src/ammer/internal/Reporting.hx b/src/ammer/internal/Reporting.hx new file mode 100644 index 0000000..a974f9f --- /dev/null +++ b/src/ammer/internal/Reporting.hx @@ -0,0 +1,110 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; +using haxe.macro.PositionTools; + +class Reporting { + public static var positionStack:Array = []; + static var debugStreams:Map; + static var imports:Map, + usings:Array, + module:String, + }> = []; + + static function shouldEmit(stream:String):Bool { + if (debugStreams == null) { + debugStreams = [ + "stage" => false, + "stage-ffi" => false, + ]; + switch (Config.getString("ammer.debug")) { + case null: + case "all": for (k in debugStreams.keys()) debugStreams[k] = true; + case s: for (k in s.split(",")) debugStreams[k] = true; + } + } + return debugStreams[stream]; + } + + public static function pushPosition(pos:Position):Void { + positionStack.push(pos); + } + public static function popPosition():Void { + positionStack.length > 0 || throw 0; + positionStack.pop(); + } + public static function withPosition(pos:Position, f:()->T):T { + pushPosition(pos); + var ret = f(); + // TODO: throw does not reset the stack, implement catchable errors + popPosition(); + return ret; + } + public static function withCurrentPos(f:()->T, recordImports:Bool = true):T { + if (recordImports) { + var filename = Context.currentPos().getInfos().file; + if (!imports.exists(filename)) { + imports[filename] = { + imports: [ for (imp in Context.getLocalImports()) { + var path = imp.path.map(p -> p.name).join("."); + switch (imp.mode) { + case INormal: path; + case IAsName(alias): '$path as $alias'; + case IAll: "*"; + }; + } ], + usings: [ for (use in Context.getLocalUsing()) { + var cls = use.get(); + cls.pack.concat([cls.module.split(".").pop(), cls.name]).join("."); + } ], + module: Context.getLocalModule(), + }; + } + } + return withPosition(Context.currentPos(), f); + } + + public static function currentPos():Position { + positionStack.length > 0 || throw 0; + return positionStack[positionStack.length - 1]; + } + + public static function log(msg:String, stream:String, ?pos:haxe.PosInfos):Void { + if (shouldEmit(stream)) + Sys.println('[ammer:$stream] $msg (${pos.fileName}:${pos.lineNumber})'); + } + + public static function warning(msg:String):Void { + Context.warning(msg, currentPos()); + } + public static function error(msg:String):Void { + Context.error(msg, currentPos()); + } + + public static function resolveType(ct:ComplexType, pos:Position):Type { + var curPos = Context.currentPos(); + var curFilename = curPos.getInfos().file; + var filename = pos.getInfos().file; + if (curFilename != filename) { + var importsForFile = imports[filename]; + importsForFile != null || throw 0; + return Context.withImports( + importsForFile.imports.concat([importsForFile.module]), + importsForFile.usings, + () -> Context.resolveType(ct, pos) + ); + } else { + return Context.resolveType(ct, pos); + } + } + + // TODO: catchable, emitted error that does not immediately abort? + // throw Reporting.error("...") ? +} + +#end diff --git a/src/ammer/internal/Types.hx b/src/ammer/internal/Types.hx new file mode 100644 index 0000000..4f7592a --- /dev/null +++ b/src/ammer/internal/Types.hx @@ -0,0 +1,365 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; +import haxe.macro.TypeTools; + +using Lambda; + +typedef ResolvedType = { + marshal:ammer.core.TypeMarshal, + haxeType:ComplexType, + wrap:NullExpr>, + unwrap:NullExpr>, +}; + +typedef CommonType = { + type:Type, + id:String, + match:(Type)->Bool, +}; + +class Types { + static function prepareType(type:Type):CommonType { + var ret = { + type: type, + id: null, + match: null, + }; + switch (type) { + case TInst(_.get() => a, []): + ret.id = '${a.pack.join(".")}.${a.module.split(".").pop()}.${a.name}'; + ret.match = (ctype:Type) -> switch (ctype) { + case TInst(_.get() => b, []): a.name == b.name && a.module == b.module; + case _: false; + }; + case TInst(_.get() => a, [TInst(_.get() => a2, [])]): + ret.id = '${a.pack.join(".")}.${a.module.split(".").pop()}.${a.name}'; + ret.match = (ctype:Type) -> switch (ctype) { + case TInst(_.get() => b, [TInst(_.get() => b2, [])]): + a.name == b.name && a.module == b.module + && a2.name == b2.name && a2.module == b2.module; + case _: false; + }; + case TAbstract(_.get() => a, []): + ret.id = '${a.pack.join(".")}.${a.module.split(".").pop()}.${a.name}'; + ret.match = (ctype:Type) -> switch (ctype) { + case TAbstract(_.get() => b, []): a.name == b.name && a.module == b.module; + case _: false; + }; + case _: throw 'how to match $type ?'; + } + return ret; + } + + public static var TYPES = { + var herePos = (macro null).pos; + function c(ct:ComplexType, enable:Bool = true):CommonType { + if (!enable) return null; + // do not follow type: on some targets, some primitive types are typedefs + return prepareType(Context.resolveType(ct, herePos)); + } + var hasSingle = (switch (Context.definedValue("target.name")) { + case "cpp" | "cs" | "hl" | "java": true; + case _: false; + }); + { + void: c((macro : ammer.ffi.Void)), hxVoid: c((macro : Void)), + bool: c((macro : ammer.ffi.Bool)), hxBool: c((macro : Bool)), + u8: c((macro : ammer.ffi.UInt8)), + u16: c((macro : ammer.ffi.UInt16)), + u32: c((macro : ammer.ffi.UInt32)), hxU32: c((macro : UInt)), + u64: c((macro : ammer.ffi.UInt64)), + i8: c((macro : ammer.ffi.Int8)), + i16: c((macro : ammer.ffi.Int16)), + i32: c((macro : ammer.ffi.Int32)), hxI32: c((macro : Int)), + i64: c((macro : ammer.ffi.Int64)), hxI64: c((macro : haxe.Int64)), + f32: c((macro : ammer.ffi.Float32)), hxF32: c((macro : Single), hasSingle), + f64: c((macro : ammer.ffi.Float64)), hxF64: c((macro : Float)), + string: c((macro : ammer.ffi.String)), hxString: c((macro : String)), + bytes: /*c((macro : ammer.ffi.Bytes)),*/ (null : CommonType), + hxBytes: c((macro : haxe.io.Bytes)), //? + this_: c((macro : ammer.ffi.This)), + derefThis: c((macro : ammer.ffi.Deref)), + }; + }; + static function initTypes():Void { + if (TYPES == null || TYPES.bytes != null) return; + var herePos = (macro null).pos; + function c(ct:ComplexType):CommonType { + return prepareType(Context.resolveType(ct, herePos)); + } + TYPES.bytes = c((macro : ammer.ffi.Bytes)); + } + + public static function resolveComplexType( + ct:ComplexType, + lib:LibContext + ):ResolvedType { + return resolveType(Reporting.resolveType(ct, Reporting.currentPos()), lib); + } + + static function resolveContexts(type:Type):Array { + //var followedType = TypeTools.follow(type); + var ret:Array = []; + function c(target:CommonType):Bool { + if (target == null) return false; + return target.match(type); + } + initTypes(); + // ammer.ffi.* types + c(TYPES.void) + || c(TYPES.bool) + || c(TYPES.u8) + || c(TYPES.u16) + || c(TYPES.u32) + || c(TYPES.u64) + || c(TYPES.i8) + || c(TYPES.i16) + || c(TYPES.i32) + || c(TYPES.i64) + || c(TYPES.f32) + || c(TYPES.f64) + || c(TYPES.string) + || c(TYPES.bytes) + // Haxe shortcuts + || c(TYPES.hxVoid) + || c(TYPES.hxBool) + || c(TYPES.hxU32) + || c(TYPES.hxI32) + || c(TYPES.hxI64) + || c(TYPES.hxF32) + || c(TYPES.hxF64) + || c(TYPES.hxString) + //|| c(TYPES.hxBytes) + || { + ret = (switch (type) { + case TInst(Utils.typeId(_.get()) => id, []): + if (Ammer.mergedInfo.structs.exists(id)) { + Ammer.mergedInfo.structs[id].ctx != null || throw "context for struct not initialised yet"; + [Ammer.mergedInfo.structs[id].ctx]; + } else if (Ammer.mergedInfo.opaques.exists(id)) [Ammer.mergedInfo.opaques[id].ctx]; + else if (Ammer.mergedInfo.sublibraries.exists(id)) [Ammer.mergedInfo.sublibraries[id].ctx]; + else if (Ammer.mergedInfo.arrays.byTypeId.exists(id)) throw 0; // Ammer.arrays.byTypeId[id].ctx; + else if (Ammer.mergedInfo.boxes.byTypeId.exists(id)) throw 0; // Ammer.boxes.byTypeId[id].ctx; + else if (Ammer.mergedInfo.callbacks.byTypeId.exists(id)) [Ammer.mergedInfo.callbacks.byTypeId[id].ctx]; + // TODO: enums ? + else if (Ammer.mergedInfo.haxeRefs.byTypeId.exists(id)) [Ammer.mergedInfo.haxeRefs.byTypeId[id].ctx]; + else if (Ammer.libraries.byTypeId.exists(id)) [Ammer.libraries.byTypeId[id]]; + else []; + case TInst(Utils.typeId(_.get()) => "ammer.ffi.Deref.Deref", [type]): + resolveContexts(type); + case TAbstract(_, []): + var next = TypeTools.followWithAbstracts(type, true); + if (Utils.typeId2(type) != Utils.typeId2(next)) resolveContexts(next); + else []; + case TFun(args, ret): + var ret = []; + var retMap:Map = []; + for (arg in args) { + for (ctx in resolveContexts(arg.t)) { + if (retMap.exists(ctx.name)) continue; + retMap[ctx.name] = true; + ret.push(ctx); + } + } + ret; + case TType(_): return resolveContexts(TypeTools.follow(type, true)); + case _: trace(type); throw 0; + }); + true; + }; + return ret; + } + + public static function resolveContext(type:Type):LibContext { + var ret = resolveContexts(type); + if (ret.length > 1) { + trace("contexts", ret); + throw "multiple contexts ..."; + } else if (ret.length == 0) { + return Ammer.libraries.byTypeId["ammer.internal.LibTypes.LibTypes"]; + } + return ret[0]; + } + + public static function resolveType( + type:Type, + lib:LibContext + ):ResolvedType { + var marshal = lib.marshal; + var ret:ResolvedType = null; + function c( + target:CommonType, ffi:()->ammer.core.TypeMarshal, + ?haxeType:ComplexType, ?wrap:Expr->Expr, ?unwrap:Expr->Expr + ):Bool { + if (target == null) return false; + if (target.match(type)) { + var retMarshal = ffi(); + ret = { + marshal: retMarshal, + haxeType: haxeType != null ? haxeType : retMarshal.haxeType, + wrap: wrap, + unwrap: unwrap, + }; + return true; + } + return false; + } + initTypes(); + // check abstracts first: may unify with primitive types otherwise + (switch (type) { + case TAbstract(_.get() => abs, []): + // make sure build macros for the abstract get triggered + if (abs.impl != null) { + abs.impl.get(); + } + + var id = Utils.typeId(abs); + if (Ammer.mergedInfo.enums.exists(id)) { + ret = { + marshal: Ammer.mergedInfo.enums[id].marshal, + haxeType: Ammer.mergedInfo.enums[id].marshal.haxeType, + wrap: e -> e, + unwrap: e -> e, + }; + true; + } else { + false; + } + case _: false; + }) + // ammer.ffi.* types + || c(TYPES.void, marshal.void ) + || c(TYPES.bool, marshal.bool ) + || c(TYPES.u8, marshal.uint8 ) + || c(TYPES.u16, marshal.uint16 ) + || c(TYPES.u32, marshal.uint32 ) + || c(TYPES.u64, marshal.uint64 ) + || c(TYPES.i8, marshal.int8 ) + || c(TYPES.i16, marshal.int16 ) + || c(TYPES.i32, marshal.int32 ) + || c(TYPES.i64, marshal.int64 ) + || c(TYPES.f32, marshal.float32) + || c(TYPES.f64, marshal.float64) + || c(TYPES.string, marshal.string ) + || c(TYPES.bytes, () -> marshal.bytes().type, + (macro : ammer.ffi.Bytes), + e -> macro @:privateAccess new ammer.ffi.Bytes($e), + e -> macro @:privateAccess $e._ammer_native) + || c(TYPES.this_, () -> throw Reporting.error("ammer.ffi.This type not allowed here")) + // Haxe shortcuts + || c(TYPES.hxVoid, marshal.void ) + || c(TYPES.hxBool, marshal.bool ) + || c(TYPES.hxU32, marshal.uint32 ) + || c(TYPES.hxI32, marshal.int32 ) + || c(TYPES.hxI64, marshal.int64 ) + || c(TYPES.hxF32, marshal.float32) + || c(TYPES.hxF64, marshal.float64) + || c(TYPES.hxString, marshal.string ) + //|| c(TYPES.hxBytes, () -> marshal.bytes().type) + || { + // TODO: better handling of typarams, better errors... + // TODO: cache ResolvedTypes directly in the relevant info structs? + switch (type) { + case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.structs.exists(id)): + Ammer.mergedInfo.structs[id].marshalOpaque != null || throw 0; + var ct = TypeTools.toComplexType(type); + var tp = Utils.expectTypePath(ct); + ret = { + marshal: Ammer.mergedInfo.structs[id].marshalOpaque, + haxeType: ct, + wrap: e -> macro @:privateAccess new $tp($e), + unwrap: e -> macro @:privateAccess $e._ammer_native, + }; + true; + case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.opaques.exists(id)): + Ammer.mergedInfo.opaques[id].marshal != null || throw 0; + var ct = TypeTools.toComplexType(type); + var tp = Utils.expectTypePath(ct); + ret = { + marshal: Ammer.mergedInfo.opaques[id].marshal.type, + haxeType: ct, + wrap: e -> macro @:privateAccess new $tp($e), + unwrap: e -> macro @:privateAccess $e._ammer_native, + }; + true; + case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.arrays.byTypeId.exists(id)): + var array = Ammer.mergedInfo.arrays.byTypeId[id]; + var tp = Utils.expectTypePath(array.arrayCt); + ret = { + marshal: array.arrayMarshal.type, + haxeType: TypeTools.toComplexType(type), + wrap: e -> macro @:privateAccess new $tp($e), + unwrap: e -> macro @:privateAccess $e._ammer_native, + }; + true; + case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.boxes.byTypeId.exists(id)): + var box = Ammer.mergedInfo.boxes.byTypeId[id]; + var marshal = box.boxMarshal; + var tp = Utils.expectTypePath(box.boxCt); + ret = { + marshal: marshal.type, + haxeType: TypeTools.toComplexType(type), + wrap: e -> macro @:privateAccess new $tp($e), + unwrap: e -> macro @:privateAccess $e._ammer_native, + }; + true; + case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.haxeRefs.byTypeId.exists(id)): + var haxeRef = Ammer.mergedInfo.haxeRefs.byTypeId[id]; + ret = { + marshal: haxeRef.marshal.type, + haxeType: haxeRef.marshal.refCt, + wrap: e -> macro $e{haxeRef.marshal.restore(e)}, + unwrap: e -> macro $e{e}.handle, + }; + true; + case TAbstract(Utils.typeId(_.get()) => "ammer.internal.LibTypes.HaxeAnyRef", [el]): + // var elId = Utils.typeId2(el); + var ct = TypeTools.toComplexType(type); + var elCt = TypeTools.toComplexType(el); + var haxeRef = Ammer.mergedInfo.haxeRefs.byElementTypeId[".Any.Any"]; + haxeRef != null || throw 0; + var refCt = haxeRef.marshal.refCt; + ret = { + marshal: haxeRef.marshal.type, + haxeType: ct, // haxeRef.marshal.refCt, + wrap: e -> macro new ammer.internal.LibTypes.HaxeAnyRef<$elCt>($e{haxeRef.marshal.restore(e)}), + unwrap: e -> macro ($e{e}.toNative() : $refCt).handle, + }; + true; + case TInst(Utils.typeId(_.get()) => "ammer.ffi.Deref.Deref", [type = TInst(Utils.typeId(_.get()) => id, [])]) if (Ammer.mergedInfo.structs.exists(id)): + Ammer.mergedInfo.structs[id].marshalOpaque != null || throw 0; + var ct = TypeTools.toComplexType(type); + var tp = Utils.expectTypePath(ct); + ret = { + marshal: Ammer.mergedInfo.structs[id].marshalDeref, + haxeType: ct, + wrap: e -> macro @:privateAccess new $tp($e), + unwrap: e -> macro @:privateAccess $e._ammer_native, + }; + true; + case TAbstract(abs, _): + var next = TypeTools.followWithAbstracts(type, true); + if (Utils.typeId2(type) != Utils.typeId2(next)) return resolveType(next, lib); + false; + case TType(_): return resolveType(TypeTools.follow(type, true), lib); + case TInst(Utils.typeId(_.get()) => id, []): + trace("type id was", id); + false; + case _: false; + } + }; + if (ret == null) { + // TODO: error + trace("type:", type); + throw 0; + } + return ret; + } +} + +#end diff --git a/src/ammer/internal/Utils.hx b/src/ammer/internal/Utils.hx new file mode 100644 index 0000000..a4eb9e8 --- /dev/null +++ b/src/ammer/internal/Utils.hx @@ -0,0 +1,191 @@ +package ammer.internal; + +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.Type; +import ammer.internal.Reporting.currentPos; +import ammer.internal.Reporting.resolveType; + +using Lambda; + +class Utils { + // These methods are inserted into `Lib.macro.hx` when baking a library. + // This avoids code duplication/synchronisation issues. Importantly, the code + // is just string-pasted, so it is important that the `import`s that are + // in `Lib.macro.baked.hx` are sufficient for the code to work. + +// ammer-fragment-begin: lib-baked + public static function access(t:{pack:Array, module:String, name:String}, ?field:String):Array { + return t.pack.concat([t.module.split(".").pop(), t.name]).concat(field != null ? [field] : []); + } + + public static function accessTp(t:TypePath):Array { + return t.pack.concat([t.name]).concat(t.sub != null ? [t.sub] : []); + } + + public static function complexTypeExpr(e:Expr):Null { + function helper(e:Expr, fallback:Bool):Null { + return (switch (e.expr) { + case EParenthesis(e): complexTypeExpr(e); + case ECheckType(_, ct): ct; + case _ if (!fallback): + // TODO: kind of a hack + var str = new haxe.macro.Printer().printExpr(e); + helper(Context.parse('(_ : $str)', e.pos), true); + case _: null; + }); + } + return helper(e, false); + } + + public static function isNull(e:Expr):Bool { + return e == null || e.expr.match(EConst(CIdent("null"))); + } + + // FFI type resolution allows some convenience shortcuts (e.g. Haxe `Int` is + // the same as `ammer.ffi.Int32`). To avoid conflicts with multiple type IDs + // resolving to the same thing, this function normalises the Haxe shortcuts + // back to the `ammer.ffi.*` equivalents. + public static function normaliseTypeId(id:String):String { + return (switch (id) { + case ".StdTypes.Void": "ammer.ffi.Void.Void"; + case ".StdTypes.Bool": "ammer.ffi.Bool.Bool"; + case ".UInt.UInt": "ammer.ffi.UInt32.UInt32"; + case ".StdTypes.Int": "ammer.ffi.Int32.Int32"; + case "haxe.Int64.Int64": "ammer.ffi.Int64.Int64"; + case ".StdTypes.Single": "ammer.ffi.Float32.Float32"; + case ".StdTypes.Float": "ammer.ffi.Float64.Float64"; + case ".String.String": "ammer.ffi.String.String"; + + // platform specific + case "cs.StdTypes.UInt8": "ammer.ffi.UInt8.UInt8"; + case "hl.UI16.UI16": "ammer.ffi.UInt16.UInt16"; + case "java.StdTypes.Int8": "ammer.ffi.Int8.Int8"; + case "java.StdTypes.Char16": "ammer.ffi.UInt16.UInt16"; + case "java.StdTypes.Int16": "ammer.ffi.Int16.Int16"; + + case _: id; + }); + } + + public static function triggerTyping(ct:ComplexType):Null { + var type = resolveType(ct, currentPos()); + return (switch (type) { + case TInst(ref, _): ref.get(); + case _: null; + }); + } + + public static function typeId(t:{pack:Array, module:String, name:String}):String { + return normaliseTypeId('${t.pack.join(".")}.${t.module.split(".").pop()}.${t.name}'); + } + + public static function typeId2(t:Type):String { + return normaliseTypeId(switch (t) { + case TInst(_.get() => t, _): '${t.pack.join(".")}.${t.module.split(".").pop()}.${t.name}'; + case TAbstract(_.get() => t, _): '${t.pack.join(".")}.${t.module.split(".").pop()}.${t.name}'; + case TFun(args, ret): '(${args.map(arg -> typeId2(arg.t)).join(",")})->${typeId2(ret)}'; + case TType(_.get() => t, []): typeId2(t.type); + case TDynamic(_): ".Any.Any"; // ? + case _: trace(t); throw 0; + }); + } + + public static function typeIdCt(ct:ComplexType):String { + return normaliseTypeId(switch (ct) { + case TPath(tp): '${tp.pack.join(".")}.${tp.name}.${tp.sub == null ? tp.name : tp.sub}'; + case _: throw 0; + }); + } + + public static function expectTypePath(ct:ComplexType):TypePath { + return (switch (ct) { + case TPath(tp): tp; + case _: throw 0; + }); + } +// ammer-fragment-end: lib-baked + + public static function typeIdTp(tp:TypePath):String { + return normaliseTypeId('${tp.pack.join(".")}.${tp.name}.${tp.sub == null ? tp.name : tp.sub}'); + } + + public static function exprOfType(ty:Type):Null { + return (switch (ty) { + case TInst(_.get() => {kind: KExpr(expr)}, []): expr; + case _: null; + }); + } + + public static function exprArrayOfType(ty:Type):Null> { + return (switch (ty) { + case TInst(_.get() => {kind: KExpr({expr: EArrayDecl(exprs)})}, []): exprs; + case _: null; + }); + } + + public static function stringOfParam(ty:Type):Null { + return (switch (ty) { + case TInst(_.get() => {kind: KExpr({expr: EConst(CString(val))})}, []): val; + case _: null; + }); + } + + public static function stringArrayOfParam(ty:Type):Null> { + return (switch (ty) { + case TInst(_.get() => {kind: KExpr({expr: EArrayDecl(vals)})}, []): + [ for (val in vals) switch (val.expr) { + case EConst(CString(val)): val; + case _: return null; + } ]; + case _: null; + }); + } + + public static function classOfParam(tp:Type):Null> { + return (switch (tp) { + case TInst(lib, []): lib; + case _: null; + }); + } + + public static function funOfParam(tp:Type):Null<{args:Array<{t:Type, opt:Bool, name:String}>, ret:Type}> { + return (switch (tp) { + case TFun(args, ret): {args: args, ret: ret}; + case _: null; + }); + } + + public static function updateField(field:Field, kind:FieldType):Field { + return { + pos: field.pos, + name: field.name, + meta: [], + kind: kind, + doc: field.doc, + access: field.access, + }; + } + + public static function exprMap(e:Expr, op:NullExpr>):Expr { + if (op == null) + return e; + return op(e); + } + + public static var definedTypes:Array = []; + public static function defineType(tdef:TypeDefinition):Void { + definedTypes.push(tdef); + Context.defineType(tdef); + } + + public static var modifiedTypes:Array<{t:ClassType, fields:Array}> = []; + public static function modifyType(t:ClassType, fields:Array):Array { + modifiedTypes.push({t: t, fields: fields}); + return fields; + } +} + +#end diff --git a/src/ammer/internal/v1/AmmerBaked.hx b/src/ammer/internal/v1/AmmerBaked.hx new file mode 100644 index 0000000..0ff05da --- /dev/null +++ b/src/ammer/internal/v1/AmmerBaked.hx @@ -0,0 +1,41 @@ +// ammer-bake: ammer.internal.v1 AmmerBaked true +package ammer.internal.v1; + +#if macro + +import haxe.macro.Context; +import haxe.macro.PositionTools; +import haxe.io.Path; +import sys.FileSystem; +import sys.io.File; + +using StringTools; + +#if ammer +typedef AmmerBaked = Ammer; +#else +class AmmerBaked { + // copied from ammer.core.BuildProgram + // TODO: configurable MSVC and system + static var useMSVC = Sys.systemName() == "Windows"; + static var extensionDll = (switch (Sys.systemName()) { + case "Windows": "dll"; + case "Mac": "dylib"; + case _: "so"; + }); + static function extensions(path:String):String { + return path + .replace("%OBJ%", useMSVC ? "obj" : "o") + .replace("%LIB%", useMSVC ? "" : "lib") + .replace("%DLL%", extensionDll); + } + + public static var mergedInfo = new ammer.internal.v1.LibInfo(); + public static function registerBakedLibraryV1(info:ammer.internal.v1.LibInfo):Void { + var fail = Context.fatalError.bind(_, Context.currentPos()); + // ammer-include: internal/Ammer.hx register-v1 + } +} +#end + +#end diff --git a/src/ammer/internal/v1/AmmerSetup.baked.hx b/src/ammer/internal/v1/AmmerSetup.baked.hx new file mode 100644 index 0000000..b90e00c --- /dev/null +++ b/src/ammer/internal/v1/AmmerSetup.baked.hx @@ -0,0 +1,28 @@ +#if macro +import haxe.macro.Context; +import haxe.macro.ComplexTypeTools; +class AmmerSetup { + public static function init():Void { + var osName = (switch (Sys.systemName()) { + case "Windows": "win"; + case "Linux": "linux"; + case "BSD": "bsd"; + case "Mac": "mac"; + case _: "unknown"; + }); + var extensionDll = (switch (Sys.systemName()) { + case "Windows": "dll"; + case "Mac": "dylib"; + case _: "so"; + }); + var prefixLib = (switch (Sys.systemName()) { + case "Windows": ""; + case _: "lib"; + }); + var info = new ammer.internal.v1.LibInfo(); + info.herePos = (macro 0).pos; + /*libinfo*/ + ammer.internal.v1.AmmerBaked.registerBakedLibraryV1(info); + } +} +#end diff --git a/src/ammer/internal/v1/LibInfo.hx b/src/ammer/internal/v1/LibInfo.hx new file mode 100644 index 0000000..8bbd989 --- /dev/null +++ b/src/ammer/internal/v1/LibInfo.hx @@ -0,0 +1,179 @@ +// ammer-bake: ammer.internal.v1 LibInfo true +package ammer.internal.v1; + +#if macro + +import haxe.macro.Expr; +import haxe.macro.Type; + +typedef LibInfoArray = { + arrayCt:ComplexType, + arrayRefCt:ComplexType, + alloc:Expr, // == arrayMarshal.alloc(macro _size) + fromHaxeCopy:Expr, // == arrayMarshal.fromHaxeCopy(macro _vec) + fromHaxeRef:Null, // == arrayMarshal.fromHaxeRef(macro _vec) + + #if ammer + ?elementType:Type, + ?arrayMarshal:ammer.core.MarshalArray, + #end +}; + +typedef LibInfoBox = { + boxCt:ComplexType, + alloc:Expr, // == boxMarshal.alloc + + #if ammer + ?elementType:Type, + ?boxMarshal:ammer.core.MarshalBox, + #end +}; + +typedef LibInfoCallback = { + isGlobal:Bool, + callbackCt:ComplexType, + funCt:ComplexType, + callbackName:String, + + #if ammer + ?ctx:LibContext, + #end +}; + +typedef LibInfoEnum = { + #if ammer + ?marshal:ammer.core.TypeMarshal, + #end +}; + +typedef LibInfoHaxeRef = { + create:Expr, // == marshal.create(macro _hxval) + + #if ammer + ?ctx:LibContext, + ?elementType:Type, + ?marshal:ammer.core.MarshalHaxe, + #end +}; + +typedef LibInfoLibrary = { + #if ammer + ?nativePrefix:String, + #end +}; + +typedef LibInfoOpaque = { + opaqueName:String, + + #if ammer + ?ctx:LibContext, + ?implType:Type, + ?marshal:ammer.core.MarshalOpaque, + ?nativePrefix:String, + #end +}; + +typedef LibInfoStruct = { + alloc:Bool, + ?gen:{ + ?alloc:String, + ?free:String, + ?nullPtr:String, + }, + structName:String, + + #if ammer + ?ctx:LibContext, + ?implType:Type, + // used for recursive field refs + ?marshalOpaque:ammer.core.TypeMarshal, + ?marshalDeref:ammer.core.TypeMarshal, + ?marshal:ammer.core.MarshalStruct, + ?nativePrefix:String, + #end +}; + +typedef LibInfoSublibrary = { + #if ammer + ?ctx:LibContext, + ?nativePrefix:String, + #end +}; + +typedef LibInfoFileSource = { + // local filename + ?name:String, + + ?description:String, + + // hash of file + //?digest:String, + + // pre-baked release download info + // URL for automatic download + ?downloadFrom:String, + + // operating system + ?os:String, + + // supported architectures (an array to support fat binaries) + ?architectures:Array, + + // minimum OS version supported by file + ?minVersion:String, + + // maximum OS version supported by file + ?maxVersion:String, +}; + +typedef LibInfoFile = { + // destination filename (may contain %DLL% etc) + dst:String, + sources:Array, +}; + +class LibInfo { + public var name:String; + public var herePos:Position; + public var setupToBin:String; + + // String key is typeId of the implType + + public var arrays:{ + byTypeId:Map, + byElementTypeId:Map, + } = { + byTypeId: [], + byElementTypeId: [], + }; + public var boxes:{ + byTypeId:Map, + byElementTypeId:Map, + } = { + byTypeId: [], + byElementTypeId: [], + }; + public var callbacks:{ + byTypeId:Map, + byElementTypeId:Map, + } = { + byTypeId: [], + byElementTypeId: [], + }; + public var enums:Map = []; + public var haxeRefs:{ + byTypeId:Map, + byElementTypeId:Map, + } = { + byTypeId: [], + byElementTypeId: [], + }; + public var opaques:Map = []; + public var structs:Map = []; + public var sublibraries:Map = []; + public var files:Array = []; + + public function new() {} +} + +#end diff --git a/src/ammer/internal/v1/OsInfo.hx b/src/ammer/internal/v1/OsInfo.hx new file mode 100644 index 0000000..ed42eb5 --- /dev/null +++ b/src/ammer/internal/v1/OsInfo.hx @@ -0,0 +1,76 @@ +// ammer-bake: ammer.internal.v1 OsInfo true +package ammer.internal.v1; + +#if macro + +/** + Provides more complete info about the operating system. Possible values for + the data fields are as follows: + + - `os`: "windows", "mac", "linux" + - `version` (for "windows"): (TODO) + - `version` (for "mac"): "11.2.3", "10.9.5", etc + - `version` (for "linux"): (TODO) + - `architecture`: "x86_64", etc + + Keys are `null` when unknown. + + Additionally, `versionCompare` provides a function with the same signature + as `Reflect.compare` to compare two version strings for the given OS. +**/ +class OsInfo { + public static var info(get, never):OsInfo; + static var infoL:OsInfo; + static function get_info():OsInfo { + if (infoL != null) + return infoL; + return infoL = new OsInfo(); + } + + public var os(default, null):Null; + public var version(default, null):Null; + public var architecture(default, null):Null; + public var versionCompare(default, null):(a:String, b:String)->Int; + + function new() { + var osRaw = Sys.systemName(); + versionCompare = (a:String, b:String) -> Reflect.compare(a, b); + function run(cmd:String, args:Array):String { + var proc = new sys.io.Process(cmd, args); + var code = proc.exitCode(); + var stdout = proc.stdout.readAll().toString(); + proc.close(); + return stdout; + } + // simplified semver: only accepts X.Y.Z format + function semverCompare(a:String, b:String):Int { + var as = a.split(".").map(Std.parseInt); + var bs = b.split(".").map(Std.parseInt); + (as.length == 3 + && as[0] != null && as[0] >= 0 + && as[1] != null && as[1] >= 0 + && as[2] != null && as[2] >= 0) || throw 'invalid semver $a'; + (bs.length == 3 + && bs[0] != null && bs[0] >= 0 + && bs[1] != null && bs[1] >= 0 + && bs[2] != null && bs[2] >= 0) || throw 'invalid semver $b'; + for (i in 0...3) { + if (as[0] != bs[0]) + return as[0] < bs[0] ? -1 : 1; + } + return 0; + } + switch (osRaw) { + case "Mac": + os = "mac"; + version = run("sw_vers", ["-productVersion"]); + architecture = run("uname", ["-m"]); + versionCompare = semverCompare; + case _: + os = osRaw.toLowerCase(); + // TODO + } + } +} + +#end diff --git a/test/native-gen/NativeGen.hx b/test/native-gen/NativeGen.hx new file mode 100644 index 0000000..8c3624f --- /dev/null +++ b/test/native-gen/NativeGen.hx @@ -0,0 +1,127 @@ +#if macro + +import haxe.macro.Context; +import haxe.macro.Expr; +import sys.io.File; +import sys.FileSystem; + +using StringTools; + +class NativeGen { + public static function deleteFields():Array { + return []; + } + public static function generate():Void { + var pos = Context.currentPos(); + function params(isConst:Array):Array { + return [ for (i in 0...isConst.length) { + name: 'T$i', + meta: isConst[i] ? [{ + pos: pos, + name: ":const", + }] : [], + } ]; + } + Context.onTypeNotFound(rname -> { + if (rname == "Single") return { + name: rname, + pack: [], + kind: TDAlias((macro : Float)), + fields: [], + pos: pos, + }; + var pack = rname.split("."); + var name = pack.pop(); + if (pack.length > 0 && pack[0] == "ammer") { + if (rname == "ammer.Syntax") { + return { + name: name, + pack: pack, + kind: TDClass(null, [], true, false, false), + fields: [], + pos: pos, + }; + } + if (pack[1] == "def") { + return { + name: name, + pack: pack, + params: (switch (rname) { + case "ammer.def.Library": params([true]); + case "ammer.def.Struct": params([true, false]); + case "ammer.def.Sublibrary": params([false]); + case _: []; + }), + kind: TDClass(null, [], false, false, false), + meta: [{ + name: ":autoBuild", + params: [macro NativeGen.deleteFields()], + pos: pos, + }], + fields: [], + pos: pos, + }; + } + return { + name: name, + pack: pack, + params: [], + kind: TDClass(null, [], false, false, false), + fields: [], + pos: pos, + }; + } + return null; + }); + for (test in FileSystem.readDirectory("../src/test")) { + if (test.startsWith(".") || !test.endsWith(".hx")) continue; + Context.resolveType(TPath({ + name: test.substr(0, test.length - 3), + pack: ["test"], + }), pos); + } + Context.onAfterTyping(types -> { + var outputs:Map> = []; + for (common in FileSystem.readDirectory("common-header")) { + outputs[common] = ["(HEADER)" => File.getContent('common-header/$common')]; + } + for (t in types) switch (t) { + case TClassDecl(_.get() => cls = { pack: ["test"], name: _.startsWith("Test") => true }): + for (code in cls.meta.extract(":ammertest.code")) switch (code.params) { + case [{expr: EConst(CString(output))}, {expr: EMeta(_, {expr: EConst(CString(code))})}]: + code.startsWith("") || throw 0; + code = code.substr("".length); + code.endsWith("") || throw 0; + code = code.substr(0, code.length - "".length); + if (!outputs.exists(output)) outputs[output] = new Map(); + var typeId = cls.pack.concat([cls.name]).join("."); + outputs[output][typeId] = '// $typeId\n$code'; + case _: throw 0; + } + case _: + } + for (common in FileSystem.readDirectory("common-footer")) { + outputs[common]["(FOOTER)"] = File.getContent('common-footer/$common'); + } + var sortedOutputs = [ for (k in outputs.keys()) k ]; + sortedOutputs.sort(Reflect.compare); + for (output in sortedOutputs) { + var codes = outputs[output]; + var merged = new StringBuf(); + var sortedKeys = [ for (k in codes.keys()) k ]; + sortedKeys.sort((a, b) -> + a == "(HEADER)" ? -1 : + a == "(FOOTER)" ? 1 : + b == "(HEADER)" ? 1 : + b == "(FOOTER)" ? -1 : Reflect.compare(a, b)); + for (k in sortedKeys) { + merged.add(codes[k]); + } + Sys.println('$output ... ${sortedKeys.length}'); + File.saveContent('../native-src/$output', merged.toString()); + } + }); + } +} + +#end diff --git a/test/native-gen/ammer/def/Enum.hx b/test/native-gen/ammer/def/Enum.hx new file mode 100644 index 0000000..2076a85 --- /dev/null +++ b/test/native-gen/ammer/def/Enum.hx @@ -0,0 +1,13 @@ +package ammer.def; + +#if macro + +import haxe.macro.Expr; + +class Enum { + public static function build(name, ffi, lib):Array { + return null; + } +} + +#end diff --git a/test/native-gen/common-footer/native.h b/test/native-gen/common-footer/native.h new file mode 100644 index 0000000..d4aa34d --- /dev/null +++ b/test/native-gen/common-footer/native.h @@ -0,0 +1,3 @@ +#ifdef __cplusplus +} +#endif diff --git a/test/native-gen/common-header/native.c b/test/native-gen/common-header/native.c new file mode 100644 index 0000000..d147ba5 --- /dev/null +++ b/test/native-gen/common-header/native.c @@ -0,0 +1,7 @@ +#include +#include +#include +#include + +#include "native.h" +#include "utf8.h" diff --git a/test/native-gen/common-header/native.h b/test/native-gen/common-header/native.h new file mode 100644 index 0000000..5f4b362 --- /dev/null +++ b/test/native-gen/common-header/native.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #define LIB_EXPORT __declspec(dllexport) +#else + #define LIB_EXPORT +#endif + +#include +#include +#include diff --git a/test/native-gen/common-header/templates.cpp b/test/native-gen/common-header/templates.cpp new file mode 100644 index 0000000..bccf742 --- /dev/null +++ b/test/native-gen/common-header/templates.cpp @@ -0,0 +1 @@ +#include "templates.hpp" diff --git a/test/native-gen/common-header/templates.hpp b/test/native-gen/common-header/templates.hpp new file mode 100644 index 0000000..c52ad91 --- /dev/null +++ b/test/native-gen/common-header/templates.hpp @@ -0,0 +1,10 @@ +#pragma once + +#ifdef _WIN32 + #define LIB_EXPORT __declspec(dllexport) +#else + #define LIB_EXPORT +#endif + +#include +#include diff --git a/test/native-gen/make.hxml b/test/native-gen/make.hxml new file mode 100644 index 0000000..c5f9cf2 --- /dev/null +++ b/test/native-gen/make.hxml @@ -0,0 +1,4 @@ +-cp ../src +-cp . +--macro NativeGen.generate() +--no-output diff --git a/test/native-gen/test/Test.hx b/test/native-gen/test/Test.hx new file mode 100644 index 0000000..1912961 --- /dev/null +++ b/test/native-gen/test/Test.hx @@ -0,0 +1,16 @@ +package test; + +import haxe.io.Bytes; + +// some methods from Test class in Haxe unit test sources +@:autoBuild(NativeGen.deleteFields()) +class Test { + public function new() {} + function eq(v:T, v2:T, ?pos:haxe.PosInfos) {} + function feq(v:Float, v2:Float, ?pos:haxe.PosInfos) {} + function aeq(expected:Array, actual:Array, ?pos:haxe.PosInfos) {} + function beq(a:Bytes, b:Bytes, ?pos:haxe.PosInfos) {} + function t(v:Bool, ?pos:haxe.PosInfos) {} + function f(v:Bool, ?pos:haxe.PosInfos) {} + function noAssert(?pos:haxe.PosInfos) {} +} diff --git a/test/native-src/Makefile.linux b/test/native-src/Makefile.linux new file mode 100644 index 0000000..f2fb505 --- /dev/null +++ b/test/native-src/Makefile.linux @@ -0,0 +1,22 @@ +all: libnative.so libtemplates.so tmp.native.h + @: + +libnative.so: native.o utf8.o + gcc -shared -fPIC -o libnative.so native.o utf8.o -lc + +native.o: native.c + gcc -c -fPIC -o native.o native.c + +utf8.o: utf8.c + gcc -c -fPIC -o utf8.o utf8.c + +libtemplates.so: templates.o + g++ -shared -fPIC -o libtemplates.so templates.o -lc + +templates.o: templates.cpp + g++ -std=c++11 -c -fPIC -o templates.o templates.cpp + +tmp.native.h: native.h + cp native.h tmp.native.h + +.PHONY: all diff --git a/test/native-src/Makefile.osx b/test/native-src/Makefile.osx new file mode 100644 index 0000000..fb7ca8d --- /dev/null +++ b/test/native-src/Makefile.osx @@ -0,0 +1,28 @@ +all: libnative.dylib libnative.a libtemplates.dylib libtemplates.a tmp.native.h + @: + +libnative.a: native.o utf8.o + ar -rcs libnative.a native.o utf8.o + +native.o: native.c + gcc -o native.o -c native.c + +utf8.o: utf8.c + gcc -o utf8.o -c utf8.c + +libnative.dylib: native.c utf8.c + gcc -dynamiclib -o libnative.dylib native.c utf8.c + +libtemplates.a: templates.o + ar -rcs libtemplates.a templates.o + +templates.o: templates.cpp + g++ -std=c++11 -o templates.o -c templates.cpp + +libtemplates.dylib: templates.cpp + g++ -std=c++11 -dynamiclib -o libtemplates.dylib templates.cpp + +tmp.native.h: native.h + cp native.h tmp.native.h + +.PHONY: all diff --git a/test/native-src/Makefile.win b/test/native-src/Makefile.win new file mode 100644 index 0000000..7821b70 --- /dev/null +++ b/test/native-src/Makefile.win @@ -0,0 +1,21 @@ +all: native.dll templates.dll tmp.native.h + +native.dll: native.obj utf8.obj + cl /LD native.obj utf8.obj + +native.obj: native.c + cl /c native.c + +utf8.obj: utf8.c + cl /c utf8.c + +templates.dll: templates.obj + cl /LD templates.obj + +templates.obj: templates.cpp + cl /c templates.cpp + +tmp.native.h: native.h + copy native.h tmp.native.h + +.PHONY: all diff --git a/test/native-src/utf8.c b/test/native-src/utf8.c new file mode 100644 index 0000000..8f3d856 --- /dev/null +++ b/test/native-src/utf8.c @@ -0,0 +1,26 @@ +#include "utf8.h" + +LIB_EXPORT int utf8_decode(unsigned char **ptr) { + int cc1 = *((*ptr)++); if (cc1 < 0x80) return cc1; + int cc2 = *((*ptr)++) & 0x7F; if (cc1 < 0xE0) return ((cc1 & 0x3F) << 6) | cc2; + int cc3 = *((*ptr)++) & 0x7F; if (cc1 < 0xF0) return ((cc1 & 0x1F) << 12) | (cc2 << 6) | cc3; + int cc4 = *((*ptr)++) & 0x7F; return ((cc1 & 0x0F) << 18) | (cc2 << 12) | (cc3 << 6) | cc4; +} + +LIB_EXPORT void utf8_encode(unsigned char **ptr, int cc) { + if (cc <= 0x7F) { + *((*ptr)++) = cc; + } else if (cc <= 0x7FF) { + *((*ptr)++) = 0xC0 | (cc >> 6); + *((*ptr)++) = 0x80 | (cc & 0x3F); + } else if (cc <= 0xFFFF) { + *((*ptr)++) = 0xE0 | (cc >> 12); + *((*ptr)++) = 0x80 | ((cc >> 6) & 0x3F); + *((*ptr)++) = 0x80 | (cc & 0x3F); + } else { + *((*ptr)++) = 0xF0 | (cc >> 18); + *((*ptr)++) = 0x80 | ((cc >> 12) & 0x3F); + *((*ptr)++) = 0x80 | ((cc >> 6) & 0x3F); + *((*ptr)++) = 0x80 | (cc & 0x3F); + } +} diff --git a/test/native-src/utf8.h b/test/native-src/utf8.h new file mode 100644 index 0000000..fc885e9 --- /dev/null +++ b/test/native-src/utf8.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef _WIN32 + #define LIB_EXPORT __declspec(dllexport) +#else + #define LIB_EXPORT +#endif + +#include + +LIB_EXPORT int utf8_decode(unsigned char **ptr); +LIB_EXPORT void utf8_encode(unsigned char **ptr, int codepoint); diff --git a/test/src/Main.hx b/test/src/Main.hx new file mode 100644 index 0000000..6051bc7 --- /dev/null +++ b/test/src/Main.hx @@ -0,0 +1,12 @@ +import utest.Runner; +import utest.ui.Report; + +class Main { + public static function main():Void { + var runner = new Runner(); + runner.addCases(test); + // runner.onTestStart.add(test -> trace("running", test.fixture.method)); + Report.create(runner); + runner.run(); + } +} diff --git a/test/src/def/Native.hx b/test/src/def/Native.hx new file mode 100644 index 0000000..1d0ae66 --- /dev/null +++ b/test/src/def/Native.hx @@ -0,0 +1,15 @@ +package def; + +@:ammer.sub((_ : test.TestArrays.TestArraysNative)) +@:ammer.sub((_ : test.TestBytes.TestBytesNative)) +@:ammer.sub((_ : test.TestCInjection.TestCInjectionNative)) +@:ammer.sub((_ : test.TestCallback.TestCallbackNative)) +@:ammer.sub((_ : test.TestConstants.TestConstantsNative)) +@:ammer.sub((_ : test.TestDatatypes.TestDatatypesNative)) +@:ammer.sub((_ : test.TestEnums.TestEnumsNative)) +@:ammer.sub((_ : test.TestHaxe.TestHaxeNative)) +@:ammer.sub((_ : test.TestHaxeRef.TestHaxeRefNative)) +@:ammer.sub((_ : test.TestMaths.TestMathsNative)) +@:ammer.sub((_ : test.TestSignature.TestSignatureNative)) +@:ammer.sub((_ : test.TestStrings.TestStringsNative)) +class Native extends ammer.def.Library<"native"> {} diff --git a/test/src/def/Templates.hx b/test/src/def/Templates.hx new file mode 100644 index 0000000..19da0c9 --- /dev/null +++ b/test/src/def/Templates.hx @@ -0,0 +1,7 @@ +package def; + +import ammer.ffi.*; + +@:ammer.lib.language(Cpp) +@:ammer.sub((_ : test.TestCpp.TestCppNative)) +class Templates extends ammer.def.Library<"templates"> {} diff --git a/test/src/test/Test.hx b/test/src/test/Test.hx new file mode 100644 index 0000000..850c9ce --- /dev/null +++ b/test/src/test/Test.hx @@ -0,0 +1,38 @@ +package test; + +import utest.Assert; +import utest.Async; +import haxe.io.Bytes; + +// some methods from Test class in Haxe unit test sources +class Test implements utest.ITest { + public function new() {} + + function eq(v:T, v2:T, ?pos:haxe.PosInfos) { + Assert.equals(v, v2, pos); + } + + function feq(v:Float, v2:Float, ?pos:haxe.PosInfos) { + Assert.floatEquals(v, v2, pos); + } + + function aeq(expected:Array, actual:Array, ?pos:haxe.PosInfos) { + Assert.same(expected, actual, pos); + } + + function beq(a:Bytes, b:Bytes, ?pos:haxe.PosInfos) { + Assert.isTrue(a.compare(b) == 0, pos); + } + + function t(v:Bool, ?pos:haxe.PosInfos) { + Assert.isTrue(v, pos); + } + + function f(v:Bool, ?pos:haxe.PosInfos) { + Assert.isFalse(v, pos); + } + + function noAssert(?pos:haxe.PosInfos) { + t(true, pos); + } +} diff --git a/test/src/test/TestArrays.hx b/test/src/test/TestArrays.hx new file mode 100644 index 0000000..15007e6 --- /dev/null +++ b/test/src/test/TestArrays.hx @@ -0,0 +1,70 @@ +package test; + +@:ammertest.code("native.h", + LIB_EXPORT int take_array_fixed(int a[3]); + LIB_EXPORT int take_array(int *a, size_t b); + LIB_EXPORT void take_array_modify(int *a); +) +@:ammertest.code("native.c", + LIB_EXPORT int take_array_fixed(int a[3]) { + if (a[0] != 1 || a[1] != 2 || a[2] != 4) + return -1; + return a[0] + a[1] + a[2]; + } + LIB_EXPORT int take_array(int *a, size_t b) { + if (b != 3 || a[0] != 1 || a[1] != 2 || a[2] != 4) + return -1; + return a[0] + a[1] + a[2]; + } + LIB_EXPORT void take_array_modify(int *a) { + a[1] = 42; + } +) +class TestArraysNative extends ammer.def.Sublibrary { + public static function take_array_fixed(a:ammer.ffi.Array):Int; + public static function take_array( + @:ammer.skip _:haxe.ds.Vector, + @:ammer.derive(ammer.Lib.vecToArrayCopy(arg0)) _:ammer.ffi.Array, + @:ammer.derive(arg0.length) _:ammer.ffi.Size + ):Int; + public static function take_array_modify(a:ammer.ffi.Array):Void; +} + +class TestArrays extends Test implements ammer.Syntax { + function testArrays() { + var arr = ammer.Lib.allocArray(Int, 3); + arr.set(0, 1); + arr.set(1, 2); + arr.set(2, 4); + eq(TestArraysNative.take_array_fixed(arr), 7); + + var vec:haxe.ds.Vector = haxe.ds.Vector.fromArrayCopy([1, 2, 4]); + var arr = ammer.Lib.vecToArrayCopy(vec); + eq(TestArraysNative.take_array_fixed(arr), 7); + + eq(TestArraysNative.take_array(vec), 7); + + #if !(lua || neko || js || python) + eq(@ret TestArraysNative.take_array_fixed(@ref vec), 7); + + var arrRef = ammer.Lib.vecToArrayRefForce(vec); + eq(TestArraysNative.take_array_fixed(arrRef.array), 7); + TestArraysNative.take_array_modify(arrRef.array); + arrRef.unref(); + eq(vec[1], 42); + #end + + vec[1] = 2; + + TestArraysNative.take_array_modify(@copyfree vec); + eq(vec[1], 2); + + TestArraysNative.take_array_modify(@copy vec); + eq(vec[1], 2); + + #if !(lua || neko || js || python) + TestArraysNative.take_array_modify(@ref vec); + eq(vec[1], 42); + #end + } +} diff --git a/test/src/test/TestBytes.hx b/test/src/test/TestBytes.hx new file mode 100644 index 0000000..de76793 --- /dev/null +++ b/test/src/test/TestBytes.hx @@ -0,0 +1,65 @@ +package test; + +import haxe.io.Bytes as HB; +import ammer.ffi.Bytes as AB; + +@:ammertest.code("native.h", + LIB_EXPORT unsigned char *ident_bytes(unsigned char *a, size_t b); + LIB_EXPORT unsigned char *give_bytes(size_t len); +) +@:ammertest.code("native.c", + LIB_EXPORT unsigned char *ident_bytes(unsigned char *a, size_t b) { + return memcpy(malloc(b), a, b); + } + LIB_EXPORT unsigned char *give_bytes(size_t len) { + unsigned char *ret = malloc(len); + for (size_t i = 0; i < len; i++) { + ret[i] = i + 1; + } + return ret; + } +) +class TestBytesNative extends ammer.def.Sublibrary { + public static function ident_bytes(_:AB, _:Int):AB; + + @:ammer.native("ident_bytes") public static function ident_bytes_1( + @:ammer.skip _:HB, + @:ammer.derive(ammer.ffi.Bytes.fromHaxeCopy(arg0)) _:AB, + @:ammer.derive(arg0.length) _:Int + ):AB; + + @:ammer.ret.derive(ret.toHaxeCopy(arg1), (_ : HB)) + @:ammer.native("ident_bytes") public static function ident_bytes_2( + _:AB, + _:Int + ):AB; + + @:ammer.ret.derive(ret.toHaxeCopy(arg0.length), (_ : HB)) + @:ammer.native("ident_bytes") public static function ident_bytes_3( + @:ammer.skip _:HB, + @:ammer.derive(AB.fromHaxeCopy(arg0)) _:AB, + @:ammer.derive(arg0.length) _:Int + ):AB; + + @:ammer.ret.derive(ret.toHaxeCopy(arg0), (_ : HB)) + public static function give_bytes(len:Int):AB; +} + +class TestBytes extends Test { + function testIdent() { + beq(TestBytesNative.ident_bytes(AB.fromHaxeCopy(HB.ofHex("")), 0).toHaxeCopy(0), HB.ofHex("")); + beq(TestBytesNative.ident_bytes(AB.fromHaxeCopy(HB.ofHex("00")), 1).toHaxeCopy(1), HB.ofHex("00")); + + var b = HB.ofHex("0001FEFF"); + var c = HB.ofHex("AA01FEFF"); + beq(TestBytesNative.ident_bytes_1(b).toHaxeCopy(4), b); + beq(TestBytesNative.ident_bytes_2(AB.fromHaxeCopy(b), 4), b); + beq(TestBytesNative.ident_bytes_3(b), b); + } + + function testReturns() { + beq(TestBytesNative.give_bytes(0), HB.ofHex("")); + beq(TestBytesNative.give_bytes(1), HB.ofHex("01")); + beq(TestBytesNative.give_bytes(10), HB.ofHex("0102030405060708090A")); + } +} diff --git a/test/src/test/TestCInjection.hx b/test/src/test/TestCInjection.hx new file mode 100644 index 0000000..3d7cf99 --- /dev/null +++ b/test/src/test/TestCInjection.hx @@ -0,0 +1,34 @@ +package test; + +@:ammertest.code("native.h", + LIB_EXPORT void save_num(int); + LIB_EXPORT int get_saved_num(void); + LIB_EXPORT int *pointer_saved_num(void); +) +@:ammertest.code("native.c", + static int saved_num = 0; + LIB_EXPORT void save_num(int num) { + saved_num = num; + } + LIB_EXPORT int get_saved_num(void) { + return saved_num; + } + LIB_EXPORT int *pointer_saved_num(void) { + return &saved_num; + } +) +class TestCInjectionNative extends ammer.def.Sublibrary { + @:ammer.c.prereturn("save_num(5);") + public static function get_saved_num():Int; + + @:ammer.c.prereturn("save_num(11);") + @:ammer.c.return("*(%CALL%)") + public static function pointer_saved_num():Int; +} + +class TestCInjection extends Test { + function testInjection() { + eq(TestCInjectionNative.get_saved_num(), 5); + eq(TestCInjectionNative.pointer_saved_num(), 11); + } +} diff --git a/test/src/test/TestCallback.hx b/test/src/test/TestCallback.hx new file mode 100644 index 0000000..e6505f1 --- /dev/null +++ b/test/src/test/TestCallback.hx @@ -0,0 +1,232 @@ +package test; + +import ammer.ffi.Callback; +import ammer.ffi.Haxe; +import ammer.ffi.Int32; + +@:build(ammer.def.Enum.build("enum enum_constants_cb", ammer.ffi.Int32, TestCallbackNative)) +enum abstract NativeEnum2(Int) from Int to Int { + @:ammer.native("e_const_cb0") var EConst0; + @:ammer.native("e_const_cb1") var EConst1; + @:ammer.native("e_const_cb10") var EConst10; +} + +@:ammer.alloc +// TODO: this should be added automatically on cpp-static +@:headerCode('#include "/DevProjects/Repos/ammer/test/native-src/native.h"') +class NativeCallbackData extends ammer.def.Struct<"callback_data_t", TestCallbackNative> { + public var user_data:Haxe<(NativeCallbackData)->Int>; + public var foo:Int; +} + +@:ammer.sub((_ : test.TestCallback.NativeCallbackData)) +@:ammertest.code("native.h", + enum enum_constants_cb { + e_const_cb0 = 0, + e_const_cb1 = 1, + e_const_cb10 = 10 + }; + + typedef struct { + void *user_data; + int foo; + } callback_data_t; + LIB_EXPORT void save_func(int (* func)(int, int, void*), void *user_data); + LIB_EXPORT int call_func(void); + LIB_EXPORT int call_func_2(void *user_data, int (* func)(void *, const char *)); + LIB_EXPORT int call_func_3(void *user_data, int (* func)(callback_data_t *)); + LIB_EXPORT bool call_func_4(void *user_data, enum enum_constants_cb (* func)(void *, enum enum_constants_cb)); + + LIB_EXPORT void save_static_func(const char* (* func)(int, const char*)); + LIB_EXPORT const char* call_static_func(int, const char*); +) +@:ammertest.code("native.c", + static int (* cached_func)(int, int, void *); + static void *cached_user_data; + LIB_EXPORT void save_func(int (* func)(int, int, void *), void *user_data) { + cached_func = func; + cached_user_data = user_data; + } + LIB_EXPORT int call_func(void) { + return cached_func(1, 2, cached_user_data); + } + LIB_EXPORT int call_func_2(void *user_data, int (* func)(void *, const char *)) { + return func(user_data, "foobar") * 2; + } + static callback_data_t call_func_3_data; + LIB_EXPORT int call_func_3(void *user_data, int (* func)(callback_data_t *)) { + call_func_3_data.user_data = user_data; + call_func_3_data.foo = 59; + return func(&call_func_3_data); + } + LIB_EXPORT bool call_func_4(void *user_data, enum enum_constants_cb (* func)(void *, enum enum_constants_cb)) { + return func(user_data, e_const_cb1) == e_const_cb10; + } + + static const char* (* cached_static_func)(int, const char*); + LIB_EXPORT void save_static_func(const char* (* func)(int, const char*)) { + cached_static_func = func; + } + LIB_EXPORT const char* call_static_func(int a, const char* b) { + return cached_static_func(a, b); + } +) +class TestCallbackNative extends ammer.def.Sublibrary { + public static function save_func( + _:ammer.ffi.Callback< + (Int32, Int32, Haxe<(Int, Int)->Int>)->Int32, + (Int, Int)->Int, + [arg2], + [arg0, arg1], + TestCallbackNative + >, + _:Haxe<(Int, Int)->Int> + ):Void; + public static function call_func():Int; + public static function call_func_2( + _:Haxe<(String)->Int>, + _:Callback< + (Haxe<(String)->Int>, String)->Int32, + (String)->Int32, + [arg0], + [arg1], + TestCallbackNative + > + ):Int32; + public static function call_func_3( + _:Haxe<(NativeCallbackData)->Int>, + _:Callback< + (NativeCallbackData)->Int32, + (NativeCallbackData)->Int32, + [arg0.user_data], + [arg0], + TestCallbackNative + > + ):Int32; + public static function call_func_4( + _:Haxe<(NativeEnum2)->NativeEnum2>, + _:Callback< + (Haxe<(NativeEnum2)->NativeEnum2>, NativeEnum2)->NativeEnum2, + (NativeEnum2)->NativeEnum2, + [arg0], + [arg1], + TestCallbackNative + > + ):Bool; + + public static function save_static_func( + _:Callback< + (Int32, ammer.ffi.String) -> ammer.ffi.String, + (Int, String) -> String, + "global", + [arg0, arg1], + TestCallbackNative + > + ):Void; + public static function call_static_func(a:Int, b:String):String; +} + +class TestCallback extends Test implements ammer.Syntax { + var wasCalled = false; + var counterSet = false; + var callA = -1; + var callB = -1; + + function callback(a:Int, b:Int):Int { + wasCalled = true; + callA = a; + callB = b; + return a + b; + } + + function createClosure():((Int, Int)->Int) { + var counter = 0; + return ((a, b) -> { + counter++; + if (counter >= 3) + counterSet = true; + return a + b; + }); + } + + function testCallback() { + wasCalled = false; + var clRef = ammer.Lib.createHaxeRef((_ : (Int, Int)->Int), callback); + clRef.incref(); + TestCallbackNative.save_func(clRef); + eq(wasCalled, false); + eq(TestCallbackNative.call_func(), 3); + clRef.decref(); + eq(wasCalled, true); + eq(callA, 1); + eq(callB, 2); + + var clRef = ammer.Lib.createHaxeRef((_ : (Int, Int)->Int), (a:Int, b:Int) -> { + wasCalled = true; + callA = a; + callB = b; + a + b; + }); + wasCalled = false; + clRef.incref(); + TestCallbackNative.save_func(clRef); + eq(wasCalled, false); + eq(TestCallbackNative.call_func(), 3); + clRef.decref(); + eq(wasCalled, true); + eq(callA, 1); + eq(callB, 2); + + wasCalled = false; + var clRef = ammer.Lib.createHaxeRef((_ : (String)->Int), (x:String) -> { + wasCalled = true; + eq(x, "foobar"); + 2; + }); + clRef.incref(); + eq(TestCallbackNative.call_func_2(clRef), 4); + clRef.decref(); + eq(wasCalled, true); + + counterSet = false; + var clRef2 = ammer.Lib.createHaxeRef((_ : (Int, Int)->Int), createClosure()); + clRef2.incref(); + TestCallbackNative.save_func(clRef2); + eq(TestCallbackNative.call_func(), 3); + eq(TestCallbackNative.call_func(), 3); + eq(TestCallbackNative.call_func(), 3); + clRef2.decref(); + eq(counterSet, true); + + wasCalled = false; + var clRef = ammer.Lib.createHaxeRef((_ : NativeCallbackData -> Int), (data:NativeCallbackData) -> { + eq(data.foo, 59); + wasCalled = true; + 77; + }); + clRef.incref(); + eq(TestCallbackNative.call_func_3(clRef), 77); + clRef.decref(); + eq(wasCalled, true); + + wasCalled = false; + var clRef = ammer.Lib.createHaxeRef((_ : NativeEnum2 -> NativeEnum2), (data:NativeEnum2) -> { + eq(data, NativeEnum2.EConst1); + wasCalled = true; + NativeEnum2.EConst10; + }); + clRef.incref(); + TestCallbackNative.call_func_4(clRef); + clRef.decref(); + eq(wasCalled, true); + } + + static function staticCallback(a:Int, b:String):String { + return '$a$b'; + } + + function testStaticCallback():Void { + TestCallbackNative.save_static_func(staticCallback); + eq(TestCallbackNative.call_static_func(2, "foo"), "2foo"); + } +} diff --git a/test/src/test/TestConstants.hx b/test/src/test/TestConstants.hx new file mode 100644 index 0000000..42f2d3f --- /dev/null +++ b/test/src/test/TestConstants.hx @@ -0,0 +1,38 @@ +package test; + +@:ammertest.code("native.h", + #define DEFINE_INT 42 + #define DEFINE_INT_EXPR (8 * 9) + #define DEFINE_STRING "foo" + #define DEFINE_STRING_EXPR ("foo" "bar" "foo") + #define DEFINE_BOOL 1 + #define DEFINE_BOOL_EXPR ((1 == 1) ? 1 : 0) + #define DEFINE_FLOAT 5.3 + #define DEFINE_FLOAT_EXPR (5.3 * 2) +) +class TestConstantsNative extends ammer.def.Sublibrary { + @:ammer.native("DEFINE_INT") public static final define_int:Int; + @:ammer.native("DEFINE_INT_EXPR") public static final define_int_expr:Int; + @:ammer.native("DEFINE_STRING") public static final define_string:String; + @:ammer.native("DEFINE_STRING_EXPR") public static final define_string_expr:String; + @:ammer.native("DEFINE_BOOL") public static final define_bool:Bool; + @:ammer.native("DEFINE_BOOL_EXPR") public static final define_bool_expr:Bool; + @:ammer.native("DEFINE_FLOAT") public static final define_float:Float; + @:ammer.native("DEFINE_FLOAT_EXPR") public static final define_float_expr:Float; + + // TODO: test globals (public static var) + // TODO: add read-only/write-only meta +} + +class TestConstants extends Test { + function testDefines() { + eq(TestConstantsNative.define_int, 42); + eq(TestConstantsNative.define_int_expr, 72); + eq(TestConstantsNative.define_string, "foo"); + eq(TestConstantsNative.define_string_expr, "foobarfoo"); + eq(TestConstantsNative.define_bool, true); + eq(TestConstantsNative.define_bool_expr, true); + feq(TestConstantsNative.define_float, 5.3); + feq(TestConstantsNative.define_float_expr, 10.6); + } +} diff --git a/test/src/test/TestCpp.hx b/test/src/test/TestCpp.hx new file mode 100644 index 0000000..8733533 --- /dev/null +++ b/test/src/test/TestCpp.hx @@ -0,0 +1,71 @@ +package test; + +@:ammer.sub((_ : XTemplatesStruct)) +@:ammertest.code("templates.hpp", + template + LIB_EXPORT int_t templated_add_ints(int_t a, int_t b); + + LIB_EXPORT void cpp_nop(void); + + struct TemplatesStruct { + uint32_t member_int; + public: + TemplatesStruct() : member_int(5) {} + uint32_t add(uint32_t x); + }; +) +@:ammertest.code("templates.cpp", + template + LIB_EXPORT int_t templated_add_ints(int_t a, int_t b) { + return a + b; + } + + template LIB_EXPORT int templated_add_ints(int a, int b); + template LIB_EXPORT uint64_t templated_add_ints(uint64_t a, uint64_t b); + + LIB_EXPORT void cpp_nop(void) {} + + uint32_t TemplatesStruct::add(uint32_t x) { + return this->member_int + x; + } +) +class TestCppNative extends ammer.def.Sublibrary { + @:ammer.native("templated_add_ints") public static function templated_add_ints32(a:Int, b:Int):Int; + public static function cpp_nop():Void; +} + +@:ammer.alloc +class XTemplatesStruct extends ammer.def.Struct<"TemplatesStruct", def.Templates> { + public var member_int:UInt32; + + // @:ammer.native("TemplatesStruct") + // @:ammer.cpp.constructor + // public static function new_():XTemplatesStruct; + + // @:ammer.cpp.member + // public function add(x:UInt32):UInt32; +} + +class TestCpp extends Test { + function testTemplates() { + eq(TestCppNative.templated_add_ints32(0, 0), 0); + eq(TestCppNative.templated_add_ints32(1, 2), 3); + eq(TestCppNative.templated_add_ints32(-1, 1), 0); + eq(TestCppNative.templated_add_ints32(0xFFFFFFFF, 1), 0); + eq(TestCppNative.templated_add_ints32(0x7F000000, 0xFFFFFF), 0x7FFFFFFF); + eq(TestCppNative.templated_add_ints32(-0x7FFFFFFF, 0x7FFFFFFF), 0); + } + + function testCppLinkage() { + TestCppNative.cpp_nop(); + eq(1, 1); + } + /* + function testStructMembers() { + var obj = XTemplatesStruct.new_(); + eq(obj.member_int, 5); + obj.member_int = 7; + eq(obj.member_int, 7); + eq(obj.add(13), 20); + }*/ +} diff --git a/test/src/test/TestDatatypes.hx b/test/src/test/TestDatatypes.hx new file mode 100644 index 0000000..f016acf --- /dev/null +++ b/test/src/test/TestDatatypes.hx @@ -0,0 +1,236 @@ +package test; + +import ammer.ffi.This; + +// TODO: "opaque" is now a misnomer +@:ammer.nativePrefix("opaque_") +@:ammer.alloc +// TODO: this should be added automatically on cpp-static +@:headerCode('#include "/DevProjects/Repos/ammer/test/native-src/native.h"') +class NativeOpaque extends ammer.def.Struct<"opaque_type_t", def.Native> { + @:ammer.native("member_int") public var member_int:Int; + @:ammer.native("member_float") public var member_float:Float; + @:ammer.native("member_string") public var member_string:String; + /* + @:ammer.native("member_int_array_fixed") public var member_int_array_fixed:ammer.ffi.ArrayFixed; + #if (hl || cpp) + @:ammer.native("member_int_array") public var member_int_array:ammer.ffi.ArrayDynamic; + @:ammer.native("member_int_array_size") public var member_int_array_size:ammer.ffi.SizeOf<"member_int_array">; + @:ammer.native("member_string_array") public var member_string_array:ammer.ffi.ArrayDynamic; + @:ammer.native("member_string_array_size") public var member_string_array_size:ammer.ffi.SizeOf<"member_string_array">; + #end +*/ + public function get_int(_:This):Int; + public function get_float(_:This):Float; + public function get_string(_:This):String; + public function get_int_alt(_:Int, _:This, _:Int):Int; + //public function get_bytes(_:This, _:SizeOfReturn):Bytes; + + public function get_int_nested(_:ammer.ffi.Deref):Int; +} +/* +class NativeOpaque2 extends ammer.def.PointerNoStar<"opaque_type_ptr", def.Native> { + @:ammer.native("opaque_get_int") public function get_int(_:This):Int; +} +*/ + +@:ammer.sub((_ : test.TestDatatypes.NativeOpaque)) +// @:ammer.sub((_ : test.TestDatatypes.NativeOpaque2)) +@:ammertest.code("native.h", + typedef struct { + int member_int; + double member_float; + const char *member_string; + + int member_int_array_fixed[8]; + int *member_int_array; + int member_int_array_size; + const char **member_string_array; + int member_string_array_size; + } opaque_type_t; + typedef opaque_type_t *opaque_type_ptr; + + LIB_EXPORT opaque_type_ptr create_opaque(void); + LIB_EXPORT int opaque_get_int(opaque_type_ptr a); + LIB_EXPORT int opaque_get_int_nested(opaque_type_t a); + LIB_EXPORT double opaque_get_float(opaque_type_ptr a); + LIB_EXPORT const char *opaque_get_string(opaque_type_ptr a); + LIB_EXPORT int opaque_get_int_alt(int a, opaque_type_ptr b, int c); + LIB_EXPORT unsigned char *opaque_get_bytes(opaque_type_ptr a, size_t *b); + LIB_EXPORT void opaque_indirect(opaque_type_ptr *out); + LIB_EXPORT opaque_type_t create_opaque_noalloc(void); + LIB_EXPORT bool opaque_take_nested(opaque_type_t a); +) +@:ammertest.code("native.c", + LIB_EXPORT opaque_type_ptr create_opaque(void) { + opaque_type_ptr ret = malloc(sizeof(opaque_type_t)); + ret->member_int = 1; + ret->member_float = 2.0f; + ret->member_string = "3"; + for (int i = 0; i < 8; i++) { + ret->member_int_array_fixed[i] = 0xB0057ED + i; + } + ret->member_int_array = (int *)calloc(17, sizeof(int)); + ret->member_int_array_size = 17; + for (int i = 0; i < 17; i++) { + ret->member_int_array[i] = 0xB00573D + i; + } + ret->member_string_array = (const char **)calloc(3, sizeof(char *)); + ret->member_string_array_size = 3; + ret->member_string_array[0] = "arrfoo"; + ret->member_string_array[1] = "arrbar"; + ret->member_string_array[2] = "arrbaz"; + return ret; + } + LIB_EXPORT int opaque_get_int(opaque_type_ptr a) { + return a->member_int; + } + LIB_EXPORT int opaque_get_int_nested(opaque_type_t a) { + return a.member_int; + } + LIB_EXPORT double opaque_get_float(opaque_type_ptr a) { + return a->member_float; + } + LIB_EXPORT const char *opaque_get_string(opaque_type_ptr a) { + return a->member_string; + } + LIB_EXPORT int opaque_get_int_alt(int a, opaque_type_ptr b, int c) { + return a + b->member_int + c; + } + LIB_EXPORT unsigned char *opaque_get_bytes(opaque_type_ptr a, size_t *b) { + size_t len = strlen(a->member_string); + unsigned char *ret = malloc(len); + memcpy(ret, a->member_string, len); + *b = len; + return ret; + } + LIB_EXPORT void opaque_indirect(opaque_type_ptr *out) { + opaque_type_ptr ret = malloc(sizeof(opaque_type_t)); + ret->member_int = 10; + ret->member_float = 4.0f; + ret->member_string = "indirect"; + *out = ret; + } + LIB_EXPORT opaque_type_t create_opaque_noalloc(void) { + return (opaque_type_t){ + .member_int = 61, + .member_float = 5.2f, + .member_string = "noalloc", + .member_int_array_fixed = {9, 10, 11, 12, 13, 14, 15, 16}, + .member_int_array = NULL, + .member_int_array_size = 0, + .member_string_array = NULL, + .member_string_array_size = 0, + }; + } + LIB_EXPORT bool opaque_take_nested(opaque_type_t a) { + float diff = a.member_float - 5.4f; + return a.member_int == 62 + && (diff > -.0001f && diff < .0001f) + && strcmp(a.member_string, "noalloc") == 0; + //&& a.member_int_array_fixed[7] == 47 + //&& a.member_int_array == NULL + //&& a.member_string_array == NULL; + } +) +class TestDatatypesNative extends ammer.def.Sublibrary { + public static function create_opaque():NativeOpaque; + //@:ammer.native("create_opaque") public static function create_opaque2():NativeOpaque2; + + public static function opaque_indirect(_:ammer.ffi.Box):Void; + public static function create_opaque_noalloc():ammer.ffi.Alloc; + public static function opaque_take_nested(a:ammer.ffi.Deref):Bool; +} + +class TestDatatypes extends Test { + function testOpaque() { + var opaque = TestDatatypesNative.create_opaque(); + + eq(opaque.get_int(), 1); + feq(opaque.get_float(), 2.0); + eq(opaque.get_string(), "3"); + eq(opaque.get_int_alt(3, 4), 8); + /* + var opaque = TestDatatypesNative.create_opaque2(); + eq(opaque.get_int(), 1);*/ + } + + function testVariables() { + var opaque = TestDatatypesNative.create_opaque(); + opaque.member_int = 3; + eq(opaque.get_int(), 3); + opaque.member_int = 5; + eq(opaque.member_int, 5); + opaque.member_float = 3.12; + feq(opaque.get_float(), 3.12); + opaque.member_float = 5.12; + feq(opaque.member_float, 5.12); + // passing strings directly might be a bit dangerous + opaque.member_string = "foo"; + eq(opaque.get_string(), "foo"); + opaque.member_string = "bar"; + eq(opaque.member_string, "bar"); + //beq(opaque.get_bytes(), haxe.io.Bytes.ofHex("626172")); + } + + function testAlloc() { + var opaque = ammer.Lib.allocStruct(NativeOpaque); + opaque.member_int = 7; + eq(opaque.get_int(), 7); + opaque.member_int = 49; + eq(opaque.member_int, 49); + ammer.Lib.freeStruct(opaque); + } + + function testOutPointer() { + var opaqueBox = ammer.Lib.allocBox(NativeOpaque); + TestDatatypesNative.opaque_indirect(opaqueBox); + var opaque = opaqueBox.get(); + eq(opaque.member_int, 10); + feq(opaque.member_float, 4.0); + eq(opaque.member_string, "indirect"); + ammer.Lib.freeStruct(opaque); + } + + function testNested() { + var opaque = TestDatatypesNative.create_opaque_noalloc(); + eq(opaque.get_int(), 61); + feq(opaque.get_float(), 5.2); + eq(opaque.get_string(), "noalloc"); + //for (i in 0...8) { + // eq(opaque.member_int_array_fixed[i], 9 + i); + //} + opaque.member_int = 62; + eq(opaque.get_int_nested(), 62); + opaque.member_float = 5.4; + //opaque.member_int_array_fixed[7] = 47; + eq(TestDatatypesNative.opaque_take_nested(opaque), true); + ammer.Lib.freeStruct(opaque); + } + /* + function testArray() { + #if (hl || cpp) + var opaque = TestDatatypesNative.create_opaque(); + var arr = opaque.member_int_array_fixed; + eq(arr.length, 8); + for (i in 0...8) { + eq(arr[i], 0xB0057ED + i); + arr[i] = 0xDE7500B + i; + } + for (i in 0...8) { + eq(arr[i], 0xDE7500B + i); + } + var arr = opaque.member_int_array; + eq(arr.length, 17); + for (i in 0...17) { + eq(arr[i], 0xB00573D + i); + } + var arr = opaque.member_string_array; + eq(arr[0], "arrfoo"); + eq(arr[1], "arrbar"); + eq(arr[2], "arrbaz"); + #else + noAssert(); + #end + }*/ +} diff --git a/test/src/test/TestEnums.hx b/test/src/test/TestEnums.hx new file mode 100644 index 0000000..2cb53a5 --- /dev/null +++ b/test/src/test/TestEnums.hx @@ -0,0 +1,46 @@ +package test; + +@:build(ammer.def.Enum.build("enum enum_constants", ammer.ffi.Int32, def.Native)) +enum abstract NativeEnum(Int) from Int to Int { + @:ammer.native("e_const0") var EConst0; + @:ammer.native("e_const1") var EConst1; + @:ammer.native("e_const10") var EConst10; +} + +@:ammertest.code("native.h", + enum enum_constants { + e_const0 = 0, + e_const1 = 1, + e_const10 = 10 + }; + + //enum enum_flags { + // e_foo = 1, + // e_bar = 2, + // e_baz = 4 + //}; + + LIB_EXPORT bool take_enum(enum enum_constants a, enum enum_constants b, enum enum_constants c); + LIB_EXPORT enum enum_constants give_enum(void); +) +@:ammertest.code("native.c", + LIB_EXPORT bool take_enum(enum enum_constants a, enum enum_constants b, enum enum_constants c) { + return (a == e_const10) + && (b == e_const1) + && (c == e_const0); + } + LIB_EXPORT enum enum_constants give_enum(void) { + return e_const10; + } +) +class TestEnumsNative extends ammer.def.Sublibrary { + public static function take_enum(a:NativeEnum, b:NativeEnum, c:NativeEnum):Bool; + public static function give_enum():NativeEnum; +} + +class TestEnums extends Test { + function testEnums() { + eq(TestEnumsNative.take_enum(NativeEnum.EConst10, NativeEnum.EConst1, NativeEnum.EConst0), true); + eq(TestEnumsNative.give_enum(), NativeEnum.EConst10); + } +} diff --git a/test/src/test/TestHaxe.hx b/test/src/test/TestHaxe.hx new file mode 100644 index 0000000..5e08553 --- /dev/null +++ b/test/src/test/TestHaxe.hx @@ -0,0 +1,22 @@ +package test; + +@:ammertest.code("native.h", + LIB_EXPORT int func_under_haxe(int a, int b); +) +@:ammertest.code("native.c", + LIB_EXPORT int func_under_haxe(int a, int b) { + return a + b; + } +) +class TestHaxeNative extends ammer.def.Sublibrary { + private static function func_under_haxe(a:Int, b:Int):Int; + @:ammer.haxe public static function func(a:Int, b:Int):Int { + return 42 + func_under_haxe(a, b); + } +} + +class TestHaxe extends Test { + function testHaxe() { + eq(TestHaxeNative.func(1, 2), 45); + } +} diff --git a/test/src/test/TestHaxeRef.hx b/test/src/test/TestHaxeRef.hx new file mode 100644 index 0000000..bf4e8dd --- /dev/null +++ b/test/src/test/TestHaxeRef.hx @@ -0,0 +1,39 @@ +package test; + +import ammer.ffi.Haxe; + +@:structInit +class HaxeType { + public var val:Array; +} + +@:ammertest.code("native.h", + LIB_EXPORT void save_haxe(void* a); + LIB_EXPORT void *load_haxe(void); +) +@:ammertest.code("native.c", + static void *saved_haxe = 0; + LIB_EXPORT void save_haxe(void* a) { + saved_haxe = a; + } + LIB_EXPORT void *load_haxe(void) { + return saved_haxe; + } +) +@:ammer.sub((_ : ammer.ffi.Haxe)) +class TestHaxeRefNative extends ammer.def.Sublibrary { + public static function save_haxe(_:Haxe):Void; + public static function load_haxe():Haxe; +} + +class TestHaxeRef extends Test { + function testHaxe() { + function nested() { + TestHaxeRefNative.save_haxe(ammer.Lib.createHaxeRef(HaxeType, ({ + val: [1, 2, 3], + } : HaxeType))); + } + nested(); + aeq(TestHaxeRefNative.load_haxe().value.val, [1, 2, 3]); + } +} diff --git a/test/src/test/TestMaths.hx b/test/src/test/TestMaths.hx new file mode 100644 index 0000000..6f253ec --- /dev/null +++ b/test/src/test/TestMaths.hx @@ -0,0 +1,157 @@ +package test; + +import ammer.ffi.Int8; +import ammer.ffi.Int16; +import ammer.ffi.Int32; +import ammer.ffi.Int64; +import ammer.ffi.UInt8; +import ammer.ffi.UInt16; +import ammer.ffi.UInt32; +import ammer.ffi.UInt64; + +@:ammertest.code("native.h", + LIB_EXPORT int add_ints(int a, int b); + LIB_EXPORT unsigned int add_uints(unsigned int a, unsigned int b); + LIB_EXPORT float add_singles(float a, float b); + LIB_EXPORT double add_floats(double a, double b); + LIB_EXPORT bool logic_and(bool a, bool b); + LIB_EXPORT bool logic_or(bool a, bool b); + LIB_EXPORT int logic_ternary(bool a, int b, int c); + + LIB_EXPORT int8_t add_i8(int8_t a, int8_t b); + LIB_EXPORT int16_t add_i16(int16_t a, int16_t b); + LIB_EXPORT int32_t add_i32(int32_t a, int32_t b); + LIB_EXPORT int64_t add_i64(int64_t a, int64_t b); + LIB_EXPORT uint8_t add_u8(uint8_t a, uint8_t b); + LIB_EXPORT uint16_t add_u16(uint16_t a, uint16_t b); + LIB_EXPORT uint32_t add_u32(uint32_t a, uint32_t b); + LIB_EXPORT uint64_t add_u64(uint64_t a, uint64_t b); +) +@:ammertest.code("native.c", + LIB_EXPORT int add_ints(int a, int b) { + return a + b; + } + LIB_EXPORT unsigned int add_uints(unsigned int a, unsigned int b) { + return a + b; + } + LIB_EXPORT float add_singles(float a, float b) { + return a + b; + } + LIB_EXPORT double add_floats(double a, double b) { + return a + b; + } + LIB_EXPORT bool logic_and(bool a, bool b) { + return a && b; + } + LIB_EXPORT bool logic_or(bool a, bool b) { + return a || b; + } + LIB_EXPORT int logic_ternary(bool a, int b, int c) { + return a ? b : c; + } + + LIB_EXPORT int8_t add_i8(int8_t a, int8_t b) { + return a + b; + } + LIB_EXPORT int16_t add_i16(int16_t a, int16_t b) { + return a + b; + } + LIB_EXPORT int32_t add_i32(int32_t a, int32_t b) { + return a + b; + } + LIB_EXPORT int64_t add_i64(int64_t a, int64_t b) { + return a + b; + } + LIB_EXPORT uint8_t add_u8(uint8_t a, uint8_t b) { + return a + b; + } + LIB_EXPORT uint16_t add_u16(uint16_t a, uint16_t b) { + return a + b; + } + LIB_EXPORT uint32_t add_u32(uint32_t a, uint32_t b) { + return a + b; + } + LIB_EXPORT uint64_t add_u64(uint64_t a, uint64_t b) { + return a + b; + } +) +class TestMathsNative extends ammer.def.Sublibrary { + public static function add_ints(_:Int, _:Int):Int; + public static function add_uints(_:UInt, _:UInt):UInt; + #if !(lua || neko || js || python) + public static function add_singles(_:Single, _:Single):Single; + #end + public static function add_floats(_:Float, _:Float):Float; + public static function logic_and(_:Bool, _:Bool):Bool; + public static function logic_or(_:Bool, _:Bool):Bool; + public static function logic_ternary(_:Bool, _:Int, _:Int):Int; + + public static function add_i8(_:Int8, _:Int8):Int8; + public static function add_i16(_:Int16, _:Int16):Int16; + public static function add_i32(_:Int32, _:Int32):Int32; + public static function add_i64(_:Int64, _:Int64):Int64; + public static function add_u8(_:UInt8, _:UInt8):UInt8; + public static function add_u16(_:UInt16, _:UInt16):UInt16; + public static function add_u32(_:UInt32, _:UInt32):UInt32; + public static function add_u64(_:UInt64, _:UInt64):UInt64; +} + +class TestMaths extends Test { + function testInts() { + eq(TestMathsNative.add_ints(0, 0), 0); + eq(TestMathsNative.add_ints(1, 2), 3); + eq(TestMathsNative.add_ints(-1, 1), 0); + eq(TestMathsNative.add_ints(0xFFFFFFFF, 1), 0); + eq(TestMathsNative.add_ints(0x7F000000, 0xFFFFFF), 0x7FFFFFFF); + eq(TestMathsNative.add_ints(-0x7FFFFFFF, 0x7FFFFFFF), 0); + } + + function testUInts() { + eq(TestMathsNative.add_uints(0, 0), 0); + eq(TestMathsNative.add_uints(1, 2), 3); + eq(TestMathsNative.add_uints(0x7F000000, 0xFFFFFF), 0x7FFFFFFF); + // RHS is 0xFFFFFFFE but Haxe compiles the literal to -2 + eq(TestMathsNative.add_uints(0x7FFFFFFF, 0x7FFFFFFF), ((0x7FFFFFFF + 0x7FFFFFFF): UInt)); + } + + #if !(lua || neko || js || python) + function testSingles() { + eq(TestMathsNative.add_singles(0., 0.), (0.:Single)); + feq(TestMathsNative.add_singles(1., 2.), (3.:Single)); + feq(TestMathsNative.add_singles(-1., 1.), (0.:Single)); + feq(TestMathsNative.add_singles(-1e10, 1e10), (0.:Single)); + feq(TestMathsNative.add_singles(-1e10, 1e9), (-9e9:Single)); + } + #end + + function testFloats() { + eq(TestMathsNative.add_floats(0., 0.), 0.); + feq(TestMathsNative.add_floats(1., 2.), 3.); + feq(TestMathsNative.add_floats(-1., 1.), 0.); + feq(TestMathsNative.add_floats(-1e10, 1e10), 0.); + feq(TestMathsNative.add_floats(-1e10, 1e9), -9e9); + } + + function testBools() { + eq(TestMathsNative.logic_and(false, false), false); + eq(TestMathsNative.logic_and(true, false), false); + eq(TestMathsNative.logic_and(false, true), false); + eq(TestMathsNative.logic_and(true, true), true); + eq(TestMathsNative.logic_or(false, false), false); + eq(TestMathsNative.logic_or(true, false), true); + eq(TestMathsNative.logic_or(false, true), true); + eq(TestMathsNative.logic_or(true, true), true); + eq(TestMathsNative.logic_ternary(true, 3, 5), 3); + eq(TestMathsNative.logic_ternary(false, 3, 5), 5); + } + + function testBitWidths() { + eq(TestMathsNative.add_u8(142, 193), 79); + eq(TestMathsNative.add_u16(25679, 49565), 9708); + eq(TestMathsNative.add_u32(0xBF86404F, 0xBF863D5D), 0x7F0C7DAC); + var a = haxe.Int64.make(0xBBFBCDC4, 0x2397F34F); + var b = haxe.Int64.make(0x5ADF2061, 0x3E99B3E1); + var c = haxe.Int64.make(0x16DAEE25, 0x6231A730); + t(TestMathsNative.add_u64(a, b) == c); // see #10760 + } +} diff --git a/test/src/test/TestSignature.hx b/test/src/test/TestSignature.hx new file mode 100644 index 0000000..678dc17 --- /dev/null +++ b/test/src/test/TestSignature.hx @@ -0,0 +1,135 @@ +package test; + +import ammer.ffi.Unsupported; + +@:ammer.sub((_ : test.TestSignature.TestSignatureNative2)) +@:ammer.sub((_ : test.TestSignature.TestSignatureNative3)) +@:ammertest.code("native.h", + LIB_EXPORT int take_0(void); + LIB_EXPORT int take_1(int a1); + LIB_EXPORT int take_2(int a1, int a2); + LIB_EXPORT int take_3(int a1, int a2, int a3); + LIB_EXPORT int take_4(int a1, int a2, int a3, int a4); + LIB_EXPORT int take_5(int a1, int a2, int a3, int a4, int a5); + LIB_EXPORT int take_6(int a1, int a2, int a3, int a4, int a5, int a6); + LIB_EXPORT int take_7(int a1, int a2, int a3, int a4, int a5, int a6, int a7); + LIB_EXPORT int take_8(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8); + LIB_EXPORT int take_9(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); + LIB_EXPORT int take_10(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10); + LIB_EXPORT void nop(void); + LIB_EXPORT bool take_unsupported(void *a, double b); +) +@:ammertest.code("native.c", + LIB_EXPORT int take_0(void) { + return 0; + } + LIB_EXPORT int take_1(int a1) { + return 1; + } + LIB_EXPORT int take_2(int a1, int a2) { + return 2; + } + LIB_EXPORT int take_3(int a1, int a2, int a3) { + return 3; + } + LIB_EXPORT int take_4(int a1, int a2, int a3, int a4) { + return 4; + } + LIB_EXPORT int take_5(int a1, int a2, int a3, int a4, int a5) { + return 5; + } + LIB_EXPORT int take_6(int a1, int a2, int a3, int a4, int a5, int a6) { + return 6; + } + LIB_EXPORT int take_7(int a1, int a2, int a3, int a4, int a5, int a6, int a7) { + return 7; + } + LIB_EXPORT int take_8(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) { + return 8; + } + LIB_EXPORT int take_9(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { + return 9; + } + LIB_EXPORT int take_10(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10) { + return 10; + } + LIB_EXPORT void nop(void) {} + LIB_EXPORT bool take_unsupported(void *a, double b) { + return a == 0 && abs(b) < .0001; + } +) +class TestSignatureNative extends ammer.def.Sublibrary { + public static function take_0():Int; + public static function take_1(_:Int):Int; + public static function take_2(_:Int, _:Int):Int; + public static function take_3(_:Int, _:Int, _:Int):Int; + public static function take_4(_:Int, _:Int, _:Int, _:Int):Int; + public static function take_5(_:Int, _:Int, _:Int, _:Int, _:Int):Int; + public static function take_6(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; + public static function take_7(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; + public static function take_8(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; + public static function take_9(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; + public static function take_10(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; + public static function nop():Void; + public static function take_unsupported(_:Unsupported<"(void *)0">, _:Unsupported<"(double)0">):Bool; +} + +@:ammertest.code("native.h", + LIB_EXPORT int take_0alt(void); +) +@:ammertest.code("native.c", + LIB_EXPORT int take_0alt(void) { + return 0; + } +) +class TestSignatureNative2 extends ammer.def.Sublibrary { + public static function take_0():Int; + public static function take_0alt():Int; +} + +@:ammer.nativePrefix("prefixed_") +@:ammertest.code("native.h", + LIB_EXPORT void prefixed_nop2(void); +) +@:ammertest.code("native.c", + LIB_EXPORT void prefixed_nop2(void) {} +) +class TestSignatureNative3 extends ammer.def.Sublibrary { + public static function nop2():Void; + @:ammer.native("take_0") public static function take_0():Int; +} + +class TestSignature extends Test { + function testArgCount() { + eq(TestSignatureNative.take_0(), 0); + eq(TestSignatureNative.take_1(1), 1); + eq(TestSignatureNative.take_2(1, 2), 2); + eq(TestSignatureNative.take_3(1, 2, 3), 3); + eq(TestSignatureNative.take_4(1, 2, 3, 4), 4); + eq(TestSignatureNative.take_5(1, 2, 3, 4, 5), 5); + eq(TestSignatureNative.take_6(1, 2, 3, 4, 5, 6), 6); + eq(TestSignatureNative.take_7(1, 2, 3, 4, 5, 6, 7), 7); + eq(TestSignatureNative.take_8(1, 2, 3, 4, 5, 6, 7, 8), 8); + eq(TestSignatureNative.take_9(1, 2, 3, 4, 5, 6, 7, 8, 9), 9); + eq(TestSignatureNative.take_10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 10); + } + + function testVoid() { + TestSignatureNative.nop(); + noAssert(); + } + + function testMultipleClasses() { + eq(TestSignatureNative2.take_0(), 0); + eq(TestSignatureNative2.take_0alt(), 0); + } + + function testPrefix() { + TestSignatureNative3.nop2(); + eq(TestSignatureNative3.take_0(), 0); + } + + function testUnsupported() { + t(TestSignatureNative.take_unsupported()); + } +} diff --git a/test/src/test/TestStrings.hx b/test/src/test/TestStrings.hx new file mode 100644 index 0000000..0f4934f --- /dev/null +++ b/test/src/test/TestStrings.hx @@ -0,0 +1,53 @@ +package test; + +@:ammertest.code("native.h", + LIB_EXPORT const char *ident_string(const char *a); + LIB_EXPORT const char *rev_string(const char *a); + LIB_EXPORT bool check_string(const char *a, int id); +) +@:ammertest.code("native.c", + LIB_EXPORT const char *ident_string(const char *a) { + return strdup(a); + } + LIB_EXPORT const char *rev_string(const char *a) { + int len = strlen(a); + char *ret = malloc(len + 1); + int *cc = malloc(len * sizeof(int)); + int pos = 0; + while (*a != 0) cc[pos++] = utf8_decode((unsigned char **)&a); + char *retcur = ret; + while (pos > 0) utf8_encode((unsigned char **)&retcur, cc[--pos]); + *retcur = '\0'; + return ret; + } + LIB_EXPORT bool check_string(const char *a, int id) { + static const char *strings[] = { + "foo", + "\x42\xE0\xB2\xA0\xEA\xAF\x8D\xF0\x9F\x90\x84", + }; + return strcmp(a, strings[id]) == 0; + } +) +class TestStringsNative extends ammer.def.Sublibrary { + public static function ident_string(_:String):String; + public static function rev_string(_:String):String; + public static function check_string(_:String, _:Int):Bool; +} + +class TestStrings extends Test { + function testAscii() { + eq(TestStringsNative.ident_string(""), ""); + eq(TestStringsNative.ident_string("aaaa"), "aaaa"); + eq(TestStringsNative.rev_string(""), ""); + eq(TestStringsNative.rev_string("abc"), "cba"); + eq(TestStringsNative.check_string("foo", 0), true); + } + + #if !java + function testUnicode() { + eq(TestStringsNative.ident_string("\u0042\u0CA0\uABCD\u{1F404}"), "\u0042\u0CA0\uABCD\u{1F404}"); + eq(TestStringsNative.rev_string("\u0042\u0CA0\uABCD\u{1F404}"), "\u{1F404}\uABCD\u0CA0\u0042"); + eq(TestStringsNative.check_string("\u0042\u0CA0\uABCD\u{1F404}", 1), true); + } + #end +}