├── test ├── native-gen │ ├── common-header │ │ ├── templates.cpp │ │ ├── native.c │ │ ├── templates.hpp │ │ └── native.h │ ├── common-footer │ │ └── native.h │ ├── make.hxml │ ├── ammer │ │ └── def │ │ │ └── Enum.hx │ ├── test │ │ └── Test.hx │ └── NativeGen.hx ├── src │ ├── def │ │ ├── Templates.hx │ │ └── Native.hx │ ├── Main.hx │ └── test │ │ ├── TestHaxe.hx │ │ ├── Test.hx │ │ ├── TestCInjection.hx │ │ ├── TestHaxeRef.hx │ │ ├── TestEnums.hx │ │ ├── TestConstants.hx │ │ ├── TestStrings.hx │ │ ├── TestCpp.hx │ │ ├── TestArrays.hx │ │ ├── TestBytes.hx │ │ ├── TestSignature.hx │ │ ├── TestMaths.hx │ │ ├── TestCallback.hx │ │ └── TestDatatypes.hx └── native-src │ ├── utf8.h │ ├── Makefile.win │ ├── Makefile.linux │ ├── Makefile.osx │ └── utf8.c ├── hxformat.json ├── .gitignore ├── src └── ammer │ ├── ffi │ ├── Bool.hx │ ├── Int16.hx │ ├── Int32.hx │ ├── Int64.hx │ ├── Int8.hx │ ├── This.hx │ ├── UInt8.hx │ ├── Void.hx │ ├── Alloc.hx │ ├── Deref.hx │ ├── Float32.hx │ ├── Float64.hx │ ├── String.hx │ ├── UInt16.hx │ ├── UInt32.hx │ ├── UInt64.hx │ ├── Size.hx │ ├── Unsupported.hx │ ├── Bytes.hx │ ├── Box.hx │ ├── Array.hx │ ├── Haxe.hx │ ├── Callback.hx │ └── FilePtr.hx │ ├── Syntax.hx │ ├── def │ ├── Library.hx │ ├── Opaque.hx │ ├── Struct.hx │ ├── Sublibrary.hx │ └── Enum.hx │ ├── Lib.macro.baked.hx │ ├── internal │ ├── FilePtrOutput.hx │ ├── v1 │ │ ├── AmmerSetup.baked.hx │ │ ├── AmmerBaked.hx │ │ ├── RelativePathsHelper.hx │ │ ├── OsInfo.hx │ │ └── LibInfo.hx │ ├── LibTypes.hx │ ├── Config.hx │ ├── Reporting.hx │ ├── Utils.hx │ ├── LibContext.hx │ ├── Meta.hx │ ├── Types.hx │ ├── Ammer.hx │ ├── Fields.hx │ └── Bakery.hx │ ├── Lib.hx │ ├── Syntax.macro.hx │ └── Lib.macro.hx ├── haxelib.json └── LICENSE /test/native-gen/common-header/templates.cpp: -------------------------------------------------------------------------------- 1 | #include "templates.hpp" 2 | -------------------------------------------------------------------------------- /hxformat.json: -------------------------------------------------------------------------------- 1 | { 2 | "indentation": { 3 | "character": " " 4 | } 5 | } -------------------------------------------------------------------------------- /test/native-gen/common-footer/native.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | } 3 | #endif 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.hdll 2 | *.hl 3 | *.dylib 4 | *.a 5 | *.o 6 | *.so 7 | 8 | /test/bin/** 9 | -------------------------------------------------------------------------------- /src/ammer/ffi/Bool.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Bool {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Int16.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Int16 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Int32.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Int32 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Int64.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Int64 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Int8.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Int8 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/This.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class This {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/UInt8.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class UInt8 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Void.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Void {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /test/native-gen/make.hxml: -------------------------------------------------------------------------------- 1 | -cp ../src 2 | -cp . 3 | --macro NativeGen.generate() 4 | --no-output 5 | -------------------------------------------------------------------------------- /src/ammer/Syntax.hx: -------------------------------------------------------------------------------- 1 | package ammer; 2 | 3 | @:autoBuild(ammer.Syntax.build()) 4 | interface Syntax {} 5 | -------------------------------------------------------------------------------- /src/ammer/ffi/Alloc.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Alloc {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Deref.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Deref {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Float32.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Float32 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Float64.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Float64 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/String.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class String {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/UInt16.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class UInt16 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/UInt32.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class UInt32 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/UInt64.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class UInt64 {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Size.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | // TODO: deal with this ... 4 | typedef Size = ammer.ffi.Int32; 5 | -------------------------------------------------------------------------------- /src/ammer/ffi/Unsupported.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class Unsupported<@:const Expr> {} 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/ammer/ffi/Bytes.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | @:build(ammer.internal.Entrypoint.buildBytes()) 6 | class Bytes {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /src/ammer/ffi/Box.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildBox()) 6 | class Box {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /src/ammer/ffi/Array.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildArray()) 6 | class Array {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /src/ammer/ffi/Haxe.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildHaxeRef()) 6 | class Haxe {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /test/native-gen/common-header/native.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "native.h" 7 | #include "utf8.h" 8 | -------------------------------------------------------------------------------- /src/ammer/def/Library.hx: -------------------------------------------------------------------------------- 1 | package ammer.def; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildLibrary()) 6 | class Library<@:const Name> {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /src/ammer/def/Opaque.hx: -------------------------------------------------------------------------------- 1 | package ammer.def; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildOpaque()) 6 | class Opaque<@:const Name, Lib> {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /src/ammer/def/Struct.hx: -------------------------------------------------------------------------------- 1 | package ammer.def; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildStruct()) 6 | class Struct<@:const Name, Lib> {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /src/ammer/def/Sublibrary.hx: -------------------------------------------------------------------------------- 1 | package ammer.def; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildSublibrary()) 6 | class Sublibrary {} 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /test/src/def/Templates.hx: -------------------------------------------------------------------------------- 1 | package def; 2 | 3 | import ammer.ffi.*; 4 | 5 | @:ammer.lib.language(Cpp) 6 | @:ammer.sub((_ : test.TestCpp.TestCppNative)) 7 | class Templates extends ammer.def.Library<"templates"> {} 8 | -------------------------------------------------------------------------------- /test/native-gen/common-header/templates.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #define LIB_EXPORT __declspec(dllexport) 5 | #else 6 | #define LIB_EXPORT 7 | #endif 8 | 9 | #include 10 | #include 11 | -------------------------------------------------------------------------------- /test/native-gen/ammer/def/Enum.hx: -------------------------------------------------------------------------------- 1 | package ammer.def; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Expr; 6 | 7 | class Enum { 8 | public static function build(name, ffi, lib):Array { 9 | return null; 10 | } 11 | } 12 | 13 | #end 14 | -------------------------------------------------------------------------------- /src/ammer/def/Enum.hx: -------------------------------------------------------------------------------- 1 | package ammer.def; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Expr; 6 | 7 | class Enum { 8 | public static function build(name:String, ffi:Expr, lib:Expr):Array { 9 | return ammer.internal.Entrypoint.buildEnum(name, ffi, lib); 10 | } 11 | } 12 | 13 | #end 14 | -------------------------------------------------------------------------------- /test/native-gen/common-header/native.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #ifdef _WIN32 8 | #define LIB_EXPORT __declspec(dllexport) 9 | #else 10 | #define LIB_EXPORT 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /test/native-src/utf8.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #define LIB_EXPORT __declspec(dllexport) 5 | #else 6 | #define LIB_EXPORT 7 | #endif 8 | 9 | #include 10 | 11 | LIB_EXPORT int utf8_decode(unsigned char **ptr); 12 | LIB_EXPORT void utf8_encode(unsigned char **ptr, int codepoint); 13 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ammer", 3 | "url": "https://github.com/Aurel300/ammer", 4 | "license": "MIT", 5 | "tags": [ 6 | ], 7 | "description": "", 8 | "version": "0.1.0", 9 | "classPath": "src/", 10 | "contributors": [ 11 | "Aurel300" 12 | ], 13 | "dependencies": { 14 | "ammer-core": "" 15 | } 16 | } -------------------------------------------------------------------------------- /test/src/Main.hx: -------------------------------------------------------------------------------- 1 | import utest.Runner; 2 | import utest.ui.Report; 3 | 4 | class Main { 5 | public static function main():Void { 6 | var runner = new Runner(); 7 | runner.addCases(test); 8 | // runner.onTestStart.add(test -> trace("running", test.fixture.method)); 9 | Report.create(runner); 10 | runner.run(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/native-src/Makefile.win: -------------------------------------------------------------------------------- 1 | all: native.dll templates.dll tmp.native.h 2 | 3 | native.dll: native.obj utf8.obj 4 | cl /LD native.obj utf8.obj 5 | 6 | native.obj: native.c 7 | cl /c native.c 8 | 9 | utf8.obj: utf8.c 10 | cl /c utf8.c 11 | 12 | templates.dll: templates.obj 13 | cl /LD templates.obj 14 | 15 | templates.obj: templates.cpp 16 | cl /c templates.cpp 17 | 18 | tmp.native.h: native.h 19 | copy native.h tmp.native.h 20 | 21 | .PHONY: all 22 | -------------------------------------------------------------------------------- /test/native-src/Makefile.linux: -------------------------------------------------------------------------------- 1 | all: libnative.so libtemplates.so tmp.native.h 2 | @: 3 | 4 | libnative.so: native.o utf8.o 5 | gcc -shared -fPIC -o libnative.so native.o utf8.o -lc 6 | 7 | native.o: native.c 8 | gcc -c -fPIC -o native.o native.c 9 | 10 | utf8.o: utf8.c 11 | gcc -c -fPIC -o utf8.o utf8.c 12 | 13 | libtemplates.so: templates.o 14 | g++ -shared -fPIC -o libtemplates.so templates.o -lc 15 | 16 | templates.o: templates.cpp 17 | g++ -std=c++11 -c -fPIC -o templates.o templates.cpp 18 | 19 | tmp.native.h: native.h 20 | cp native.h tmp.native.h 21 | 22 | .PHONY: all 23 | -------------------------------------------------------------------------------- /src/ammer/ffi/Callback.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | @:genericBuild(ammer.internal.Entrypoint.genericBuildCallback()) 6 | class Callback< 7 | // function type as seen by the native library 8 | // e.g. (Int32, Haxe<(Int) -> Int>) -> Int32 9 | CallbackType, 10 | 11 | // function type as seen by Haxe 12 | // e.g. (Int) -> Int 13 | FunctionType, 14 | 15 | // where to find the function in CallbackType 16 | @:const CallTarget, 17 | 18 | // which arguments to pass through to the Haxe function 19 | @:const CallArgs, 20 | 21 | // parent library 22 | Lib 23 | > {} 24 | 25 | #end 26 | -------------------------------------------------------------------------------- /test/native-gen/test/Test.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import haxe.io.Bytes; 4 | 5 | // some methods from Test class in Haxe unit test sources 6 | @:autoBuild(NativeGen.deleteFields()) 7 | class Test { 8 | public function new() {} 9 | function eq(v:T, v2:T, ?pos:haxe.PosInfos) {} 10 | function feq(v:Float, v2:Float, ?pos:haxe.PosInfos) {} 11 | function aeq(expected:Array, actual:Array, ?pos:haxe.PosInfos) {} 12 | function beq(a:Bytes, b:Bytes, ?pos:haxe.PosInfos) {} 13 | function t(v:Bool, ?pos:haxe.PosInfos) {} 14 | function f(v:Bool, ?pos:haxe.PosInfos) {} 15 | function noAssert(?pos:haxe.PosInfos) {} 16 | } 17 | -------------------------------------------------------------------------------- /test/src/test/TestHaxe.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:ammertest.code("native.h", 4 | LIB_EXPORT int func_under_haxe(int a, int b); 5 | ) 6 | @:ammertest.code("native.c", 7 | LIB_EXPORT int func_under_haxe(int a, int b) { 8 | return a + b; 9 | } 10 | ) 11 | class TestHaxeNative extends ammer.def.Sublibrary { 12 | private static function func_under_haxe(a:Int, b:Int):Int; 13 | @:ammer.haxe public static function func(a:Int, b:Int):Int { 14 | return 42 + func_under_haxe(a, b); 15 | } 16 | } 17 | 18 | class TestHaxe extends Test { 19 | function testHaxe() { 20 | eq(TestHaxeNative.func(1, 2), 45); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ammer/Lib.macro.baked.hx: -------------------------------------------------------------------------------- 1 | // ammer-bake: ammer Lib.macro macro 2 | package ammer; 3 | 4 | import haxe.macro.Context; 5 | import haxe.macro.Context.currentPos; 6 | import haxe.macro.Context.fatalError as fail; 7 | import haxe.macro.Context.resolveType; 8 | import haxe.macro.Expr; 9 | import haxe.macro.Type; 10 | import haxe.macro.TypeTools; 11 | import ammer.internal.*; 12 | import ammer.internal.v1.AmmerBaked.mergedInfo as info; 13 | 14 | using Lambda; 15 | 16 | class Lib { 17 | static function withPos(pos:Position, f:()->T):T { 18 | return f(); 19 | } 20 | // ammer-include: internal/Utils.hx lib-baked 21 | // ammer-include: Lib.macro.hx lib-baked 22 | } 23 | -------------------------------------------------------------------------------- /test/native-src/Makefile.osx: -------------------------------------------------------------------------------- 1 | all: libnative.dylib libnative.a libtemplates.dylib libtemplates.a tmp.native.h 2 | @: 3 | 4 | libnative.a: native.o utf8.o 5 | ar -rcs libnative.a native.o utf8.o 6 | 7 | native.o: native.c 8 | gcc -o native.o -c native.c 9 | 10 | utf8.o: utf8.c 11 | gcc -o utf8.o -c utf8.c 12 | 13 | libnative.dylib: native.c utf8.c 14 | gcc -dynamiclib -o libnative.dylib native.c utf8.c 15 | 16 | libtemplates.a: templates.o 17 | ar -rcs libtemplates.a templates.o 18 | 19 | templates.o: templates.cpp 20 | g++ -std=c++11 -o templates.o -c templates.cpp 21 | 22 | libtemplates.dylib: templates.cpp 23 | g++ -std=c++11 -dynamiclib -o libtemplates.dylib templates.cpp 24 | 25 | tmp.native.h: native.h 26 | cp native.h tmp.native.h 27 | 28 | .PHONY: all 29 | -------------------------------------------------------------------------------- /test/src/def/Native.hx: -------------------------------------------------------------------------------- 1 | package def; 2 | 3 | @:ammer.sub((_ : test.TestArrays.TestArraysNative)) 4 | @:ammer.sub((_ : test.TestBytes.TestBytesNative)) 5 | @:ammer.sub((_ : test.TestCInjection.TestCInjectionNative)) 6 | @:ammer.sub((_ : test.TestCallback.TestCallbackNative)) 7 | @:ammer.sub((_ : test.TestConstants.TestConstantsNative)) 8 | @:ammer.sub((_ : test.TestDatatypes.TestDatatypesNative)) 9 | @:ammer.sub((_ : test.TestEnums.TestEnumsNative)) 10 | @:ammer.sub((_ : test.TestHaxe.TestHaxeNative)) 11 | @:ammer.sub((_ : test.TestHaxeRef.TestHaxeRefNative)) 12 | @:ammer.sub((_ : test.TestMaths.TestMathsNative)) 13 | @:ammer.sub((_ : test.TestSignature.TestSignatureNative)) 14 | @:ammer.sub((_ : test.TestStrings.TestStringsNative)) 15 | class Native extends ammer.def.Library<"native"> {} 16 | -------------------------------------------------------------------------------- /src/ammer/internal/FilePtrOutput.hx: -------------------------------------------------------------------------------- 1 | // ammer-bake: ammer.internal FilePtrOutput !macro 2 | package ammer.internal; 3 | 4 | import ammer.ffi.FilePtr; 5 | 6 | class FilePtrOutput extends haxe.io.Output { 7 | var file:FilePtr; 8 | function new(file:FilePtr) { 9 | this.file = file; 10 | } 11 | 12 | override public function writeByte(c:Int):Void file.fputc(c); 13 | override public function writeBytes(s:haxe.io.Bytes, pos:Int, len:Int):Int { 14 | if (pos < 0 || len < 0 || pos + len > s.length) 15 | throw haxe.io.Error.OutsideBounds; 16 | var bytesRef = ammer.ffi.Bytes.fromHaxeRef(s.sub(pos, len)); 17 | var ret = file.fwrite(bytesRef.bytes.offset(pos), 1, len); 18 | bytesRef.unref(); 19 | return ret; 20 | } 21 | override public function flush():Void file.fflush(); 22 | override public function close():Void file.fclose(); // TODO: only close output? 23 | } 24 | -------------------------------------------------------------------------------- /src/ammer/internal/v1/AmmerSetup.baked.hx: -------------------------------------------------------------------------------- 1 | #if macro 2 | import haxe.macro.Context; 3 | import haxe.macro.ComplexTypeTools; 4 | class /*libname*/_AmmerSetup { 5 | public static function init():Void { 6 | var osName = (switch (Sys.systemName()) { 7 | case "Windows": "win"; 8 | case "Linux": "linux"; 9 | case "BSD": "bsd"; 10 | case "Mac": "mac"; 11 | case _: "unknown"; 12 | }); 13 | var extensionDll = (switch (Sys.systemName()) { 14 | case "Windows": "dll"; 15 | case "Mac": "dylib"; 16 | case _: "so"; 17 | }); 18 | var prefixLib = (switch (Sys.systemName()) { 19 | case "Windows": ""; 20 | case _: "lib"; 21 | }); 22 | var info = new ammer.internal.v1.LibInfo(); 23 | info.herePos = (macro 0).pos; 24 | /*libinfo*/ 25 | ammer.internal.v1.AmmerBaked.registerBakedLibraryV1(info); 26 | } 27 | } 28 | #end 29 | -------------------------------------------------------------------------------- /test/src/test/Test.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import utest.Assert; 4 | import utest.Async; 5 | import haxe.io.Bytes; 6 | 7 | // some methods from Test class in Haxe unit test sources 8 | class Test implements utest.ITest { 9 | public function new() {} 10 | 11 | function eq(v:T, v2:T, ?pos:haxe.PosInfos) { 12 | Assert.equals(v, v2, pos); 13 | } 14 | 15 | function feq(v:Float, v2:Float, ?pos:haxe.PosInfos) { 16 | Assert.floatEquals(v, v2, pos); 17 | } 18 | 19 | function aeq(expected:Array, actual:Array, ?pos:haxe.PosInfos) { 20 | Assert.same(expected, actual, pos); 21 | } 22 | 23 | function beq(a:Bytes, b:Bytes, ?pos:haxe.PosInfos) { 24 | Assert.isTrue(a.compare(b) == 0, pos); 25 | } 26 | 27 | function t(v:Bool, ?pos:haxe.PosInfos) { 28 | Assert.isTrue(v, pos); 29 | } 30 | 31 | function f(v:Bool, ?pos:haxe.PosInfos) { 32 | Assert.isFalse(v, pos); 33 | } 34 | 35 | function noAssert(?pos:haxe.PosInfos) { 36 | t(true, pos); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/native-src/utf8.c: -------------------------------------------------------------------------------- 1 | #include "utf8.h" 2 | 3 | LIB_EXPORT int utf8_decode(unsigned char **ptr) { 4 | int cc1 = *((*ptr)++); if (cc1 < 0x80) return cc1; 5 | int cc2 = *((*ptr)++) & 0x7F; if (cc1 < 0xE0) return ((cc1 & 0x3F) << 6) | cc2; 6 | int cc3 = *((*ptr)++) & 0x7F; if (cc1 < 0xF0) return ((cc1 & 0x1F) << 12) | (cc2 << 6) | cc3; 7 | int cc4 = *((*ptr)++) & 0x7F; return ((cc1 & 0x0F) << 18) | (cc2 << 12) | (cc3 << 6) | cc4; 8 | } 9 | 10 | LIB_EXPORT void utf8_encode(unsigned char **ptr, int cc) { 11 | if (cc <= 0x7F) { 12 | *((*ptr)++) = cc; 13 | } else if (cc <= 0x7FF) { 14 | *((*ptr)++) = 0xC0 | (cc >> 6); 15 | *((*ptr)++) = 0x80 | (cc & 0x3F); 16 | } else if (cc <= 0xFFFF) { 17 | *((*ptr)++) = 0xE0 | (cc >> 12); 18 | *((*ptr)++) = 0x80 | ((cc >> 6) & 0x3F); 19 | *((*ptr)++) = 0x80 | (cc & 0x3F); 20 | } else { 21 | *((*ptr)++) = 0xF0 | (cc >> 18); 22 | *((*ptr)++) = 0x80 | ((cc >> 12) & 0x3F); 23 | *((*ptr)++) = 0x80 | ((cc >> 6) & 0x3F); 24 | *((*ptr)++) = 0x80 | (cc & 0x3F); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/src/test/TestCInjection.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:ammertest.code("native.h", 4 | LIB_EXPORT void save_num(int); 5 | LIB_EXPORT int get_saved_num(void); 6 | LIB_EXPORT int *pointer_saved_num(void); 7 | ) 8 | @:ammertest.code("native.c", 9 | static int saved_num = 0; 10 | LIB_EXPORT void save_num(int num) { 11 | saved_num = num; 12 | } 13 | LIB_EXPORT int get_saved_num(void) { 14 | return saved_num; 15 | } 16 | LIB_EXPORT int *pointer_saved_num(void) { 17 | return &saved_num; 18 | } 19 | ) 20 | class TestCInjectionNative extends ammer.def.Sublibrary { 21 | @:ammer.c.prereturn("save_num(5);") 22 | public static function get_saved_num():Int; 23 | 24 | @:ammer.c.prereturn("save_num(11);") 25 | @:ammer.c.return("*(%CALL%)") 26 | public static function pointer_saved_num():Int; 27 | } 28 | 29 | class TestCInjection extends Test { 30 | function testInjection() { 31 | eq(TestCInjectionNative.get_saved_num(), 5); 32 | eq(TestCInjectionNative.pointer_saved_num(), 11); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ammer/Lib.hx: -------------------------------------------------------------------------------- 1 | // ammer-bake: ammer Lib true 2 | package ammer; 3 | 4 | class Lib { 5 | // struct methods 6 | public static macro function allocStruct(cls:Class, ?initVals:{}):T; 7 | public static macro function nullPtrStruct(cls:Class):T; 8 | 9 | // box methods 10 | public static macro function allocBox(cls:Class, ?initVal:T):ammer.ffi.Box; 11 | public static macro function nullPtrBox(cls:Class):ammer.ffi.Box; 12 | 13 | // array methods 14 | public static macro function allocArray(cls:Class, size:Int, ?initVal:T):ammer.ffi.Array; 15 | public static macro function nullPtrArray(cls:Class):ammer.ffi.Array; 16 | 17 | public static macro function vecToArrayCopy(vec:haxe.ds.Vector):ammer.ffi.Array; 18 | public static macro function vecToArrayRef(vec:haxe.ds.Vector):ammer.ffi.ArrayRef; 19 | public static macro function vecToArrayRefForce(vec:haxe.ds.Vector):ammer.ffi.ArrayRef; 20 | 21 | // Haxe ref methods 22 | public static macro function createHaxeRef(cls:Class, e:T):ammer.ffi.Haxe; 23 | } 24 | -------------------------------------------------------------------------------- /test/src/test/TestHaxeRef.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import ammer.ffi.Haxe; 4 | 5 | @:structInit 6 | class HaxeType { 7 | public var val:Array; 8 | } 9 | 10 | @:ammertest.code("native.h", 11 | LIB_EXPORT void save_haxe(void* a); 12 | LIB_EXPORT void *load_haxe(void); 13 | ) 14 | @:ammertest.code("native.c", 15 | static void *saved_haxe = 0; 16 | LIB_EXPORT void save_haxe(void* a) { 17 | saved_haxe = a; 18 | } 19 | LIB_EXPORT void *load_haxe(void) { 20 | return saved_haxe; 21 | } 22 | ) 23 | @:ammer.sub((_ : ammer.ffi.Haxe)) 24 | class TestHaxeRefNative extends ammer.def.Sublibrary { 25 | public static function save_haxe(_:Haxe):Void; 26 | public static function load_haxe():Haxe; 27 | } 28 | 29 | class TestHaxeRef extends Test { 30 | function testHaxe() { 31 | function nested() { 32 | TestHaxeRefNative.save_haxe(ammer.Lib.createHaxeRef(HaxeType, ({ 33 | val: [1, 2, 3], 34 | } : HaxeType))); 35 | } 36 | nested(); 37 | aeq(TestHaxeRefNative.load_haxe().value.val, [1, 2, 3]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C)2019 Aurel Bílý 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/ammer/internal/v1/AmmerBaked.hx: -------------------------------------------------------------------------------- 1 | // ammer-bake: ammer.internal.v1 AmmerBaked true 2 | package ammer.internal.v1; 3 | 4 | #if macro 5 | 6 | import haxe.macro.Context; 7 | import haxe.macro.PositionTools; 8 | import haxe.io.Path; 9 | import sys.FileSystem; 10 | import sys.io.File; 11 | 12 | using StringTools; 13 | 14 | #if ammer 15 | typedef AmmerBaked = Ammer; 16 | #else 17 | class AmmerBaked { 18 | // copied from ammer.core.BuildProgram 19 | // TODO: configurable MSVC and system 20 | static var useMSVC = Sys.systemName() == "Windows"; 21 | static var extensionDll = (switch (Sys.systemName()) { 22 | case "Windows": "dll"; 23 | case "Mac": "dylib"; 24 | case _: "so"; 25 | }); 26 | static function extensions(path:String):String { 27 | return path 28 | .replace("%OBJ%", useMSVC ? "obj" : "o") 29 | .replace("%LIB%", useMSVC ? "" : "lib") 30 | .replace("%DLL%", extensionDll); 31 | } 32 | 33 | public static var mergedInfo = new ammer.internal.v1.LibInfo(); 34 | public static function registerBakedLibraryV1(info:ammer.internal.v1.LibInfo):Void { 35 | var fail = Context.fatalError.bind(_, Context.currentPos()); 36 | // ammer-include: internal/Ammer.hx register-v1 37 | } 38 | } 39 | #end 40 | 41 | #end 42 | -------------------------------------------------------------------------------- /src/ammer/internal/v1/RelativePathsHelper.hx: -------------------------------------------------------------------------------- 1 | // ammer-bake: ammer.internal.v1 RelativePathsHelper true 2 | package ammer.internal.v1; 3 | 4 | #if macro 5 | 6 | import haxe.macro.Compiler; 7 | import haxe.macro.Context; 8 | import haxe.macro.Expr; 9 | import haxe.macro.PositionTools; 10 | import haxe.io.Path; 11 | 12 | using StringTools; 13 | 14 | class RelativePathsHelper { 15 | public static function build(includePaths:Array, libraryPaths:Array):Array { 16 | if (includePaths.length == 0 && libraryPaths.length == 0) return null; 17 | 18 | var cls = Context.getLocalClass().get(); 19 | 20 | var outputPath = Compiler.getOutput(); 21 | var rootPath = PositionTools.getInfos(cls.pos).file; 22 | if (!Path.isAbsolute(rootPath)) rootPath = Path.join([Sys.getCwd(), rootPath]); 23 | rootPath = Path.normalize(Path.directory(rootPath) + "/" + cls.pack.map(_ -> "../").join("")); 24 | var xml = '' 25 | + includePaths.map(path -> '').join("") 26 | + '' 27 | + libraryPaths.map(path -> '').join("") 28 | + ''; 29 | 30 | cls.meta.add(":buildXml", [macro $v{xml}], cls.pos); 31 | return null; 32 | } 33 | } 34 | 35 | #end 36 | -------------------------------------------------------------------------------- /test/src/test/TestEnums.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:build(ammer.def.Enum.build("enum enum_constants", ammer.ffi.Int32, def.Native)) 4 | enum abstract NativeEnum(Int) from Int to Int { 5 | @:ammer.native("e_const0") var EConst0; 6 | @:ammer.native("e_const1") var EConst1; 7 | @:ammer.native("e_const10") var EConst10; 8 | } 9 | 10 | @:ammertest.code("native.h", 11 | enum enum_constants { 12 | e_const0 = 0, 13 | e_const1 = 1, 14 | e_const10 = 10 15 | }; 16 | 17 | //enum enum_flags { 18 | // e_foo = 1, 19 | // e_bar = 2, 20 | // e_baz = 4 21 | //}; 22 | 23 | LIB_EXPORT bool take_enum(enum enum_constants a, enum enum_constants b, enum enum_constants c); 24 | LIB_EXPORT enum enum_constants give_enum(void); 25 | ) 26 | @:ammertest.code("native.c", 27 | LIB_EXPORT bool take_enum(enum enum_constants a, enum enum_constants b, enum enum_constants c) { 28 | return (a == e_const10) 29 | && (b == e_const1) 30 | && (c == e_const0); 31 | } 32 | LIB_EXPORT enum enum_constants give_enum(void) { 33 | return e_const10; 34 | } 35 | ) 36 | class TestEnumsNative extends ammer.def.Sublibrary { 37 | public static function take_enum(a:NativeEnum, b:NativeEnum, c:NativeEnum):Bool; 38 | public static function give_enum():NativeEnum; 39 | } 40 | 41 | class TestEnums extends Test { 42 | function testEnums() { 43 | eq(TestEnumsNative.take_enum(NativeEnum.EConst10, NativeEnum.EConst1, NativeEnum.EConst0), true); 44 | eq(TestEnumsNative.give_enum(), NativeEnum.EConst10); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/src/test/TestConstants.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:ammertest.code("native.h", 4 | #define DEFINE_INT 42 5 | #define DEFINE_INT_EXPR (8 * 9) 6 | #define DEFINE_STRING "foo" 7 | #define DEFINE_STRING_EXPR ("foo" "bar" "foo") 8 | #define DEFINE_BOOL 1 9 | #define DEFINE_BOOL_EXPR ((1 == 1) ? 1 : 0) 10 | #define DEFINE_FLOAT 5.3 11 | #define DEFINE_FLOAT_EXPR (5.3 * 2) 12 | ) 13 | class TestConstantsNative extends ammer.def.Sublibrary { 14 | @:ammer.native("DEFINE_INT") public static final define_int:Int; 15 | @:ammer.native("DEFINE_INT_EXPR") public static final define_int_expr:Int; 16 | @:ammer.native("DEFINE_STRING") public static final define_string:String; 17 | @:ammer.native("DEFINE_STRING_EXPR") public static final define_string_expr:String; 18 | @:ammer.native("DEFINE_BOOL") public static final define_bool:Bool; 19 | @:ammer.native("DEFINE_BOOL_EXPR") public static final define_bool_expr:Bool; 20 | @:ammer.native("DEFINE_FLOAT") public static final define_float:Float; 21 | @:ammer.native("DEFINE_FLOAT_EXPR") public static final define_float_expr:Float; 22 | 23 | // TODO: test globals (public static var) 24 | // TODO: add read-only/write-only meta 25 | } 26 | 27 | class TestConstants extends Test { 28 | function testDefines() { 29 | eq(TestConstantsNative.define_int, 42); 30 | eq(TestConstantsNative.define_int_expr, 72); 31 | eq(TestConstantsNative.define_string, "foo"); 32 | eq(TestConstantsNative.define_string_expr, "foobarfoo"); 33 | eq(TestConstantsNative.define_bool, true); 34 | eq(TestConstantsNative.define_bool_expr, true); 35 | feq(TestConstantsNative.define_float, 5.3); 36 | feq(TestConstantsNative.define_float_expr, 10.6); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/src/test/TestStrings.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:ammertest.code("native.h", 4 | LIB_EXPORT const char *ident_string(const char *a); 5 | LIB_EXPORT const char *rev_string(const char *a); 6 | LIB_EXPORT bool check_string(const char *a, int id); 7 | ) 8 | @:ammertest.code("native.c", 9 | LIB_EXPORT const char *ident_string(const char *a) { 10 | return strdup(a); 11 | } 12 | LIB_EXPORT const char *rev_string(const char *a) { 13 | int len = strlen(a); 14 | char *ret = malloc(len + 1); 15 | int *cc = malloc(len * sizeof(int)); 16 | int pos = 0; 17 | while (*a != 0) cc[pos++] = utf8_decode((unsigned char **)&a); 18 | char *retcur = ret; 19 | while (pos > 0) utf8_encode((unsigned char **)&retcur, cc[--pos]); 20 | *retcur = '\0'; 21 | return ret; 22 | } 23 | LIB_EXPORT bool check_string(const char *a, int id) { 24 | static const char *strings[] = { 25 | "foo", 26 | "\x42\xE0\xB2\xA0\xEA\xAF\x8D\xF0\x9F\x90\x84", 27 | }; 28 | return strcmp(a, strings[id]) == 0; 29 | } 30 | ) 31 | class TestStringsNative extends ammer.def.Sublibrary { 32 | public static function ident_string(_:String):String; 33 | public static function rev_string(_:String):String; 34 | public static function check_string(_:String, _:Int):Bool; 35 | } 36 | 37 | class TestStrings extends Test { 38 | function testAscii() { 39 | eq(TestStringsNative.ident_string(""), ""); 40 | eq(TestStringsNative.ident_string("aaaa"), "aaaa"); 41 | eq(TestStringsNative.rev_string(""), ""); 42 | eq(TestStringsNative.rev_string("abc"), "cba"); 43 | eq(TestStringsNative.check_string("foo", 0), true); 44 | } 45 | 46 | #if !java 47 | function testUnicode() { 48 | eq(TestStringsNative.ident_string("\u0042\u0CA0\uABCD\u{1F404}"), "\u0042\u0CA0\uABCD\u{1F404}"); 49 | eq(TestStringsNative.rev_string("\u0042\u0CA0\uABCD\u{1F404}"), "\u{1F404}\uABCD\u0CA0\u0042"); 50 | eq(TestStringsNative.check_string("\u0042\u0CA0\uABCD\u{1F404}", 1), true); 51 | } 52 | #end 53 | } 54 | -------------------------------------------------------------------------------- /test/src/test/TestCpp.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:ammer.sub((_ : XTemplatesStruct)) 4 | @:ammertest.code("templates.hpp", 5 | template 6 | LIB_EXPORT int_t templated_add_ints(int_t a, int_t b); 7 | 8 | LIB_EXPORT void cpp_nop(void); 9 | 10 | struct TemplatesStruct { 11 | uint32_t member_int; 12 | public: 13 | TemplatesStruct() : member_int(5) {} 14 | uint32_t add(uint32_t x); 15 | }; 16 | ) 17 | @:ammertest.code("templates.cpp", 18 | template 19 | LIB_EXPORT int_t templated_add_ints(int_t a, int_t b) { 20 | return a + b; 21 | } 22 | 23 | template LIB_EXPORT int templated_add_ints(int a, int b); 24 | template LIB_EXPORT uint64_t templated_add_ints(uint64_t a, uint64_t b); 25 | 26 | LIB_EXPORT void cpp_nop(void) {} 27 | 28 | uint32_t TemplatesStruct::add(uint32_t x) { 29 | return this->member_int + x; 30 | } 31 | ) 32 | class TestCppNative extends ammer.def.Sublibrary { 33 | @:ammer.native("templated_add_ints") public static function templated_add_ints32(a:Int, b:Int):Int; 34 | public static function cpp_nop():Void; 35 | } 36 | 37 | @:ammer.alloc 38 | class XTemplatesStruct extends ammer.def.Struct<"TemplatesStruct", def.Templates> { 39 | public var member_int:UInt32; 40 | 41 | // @:ammer.native("TemplatesStruct") 42 | // @:ammer.cpp.constructor 43 | // public static function new_():XTemplatesStruct; 44 | 45 | // @:ammer.cpp.member 46 | // public function add(x:UInt32):UInt32; 47 | } 48 | 49 | class TestCpp extends Test { 50 | function testTemplates() { 51 | eq(TestCppNative.templated_add_ints32(0, 0), 0); 52 | eq(TestCppNative.templated_add_ints32(1, 2), 3); 53 | eq(TestCppNative.templated_add_ints32(-1, 1), 0); 54 | eq(TestCppNative.templated_add_ints32(0xFFFFFFFF, 1), 0); 55 | eq(TestCppNative.templated_add_ints32(0x7F000000, 0xFFFFFF), 0x7FFFFFFF); 56 | eq(TestCppNative.templated_add_ints32(-0x7FFFFFFF, 0x7FFFFFFF), 0); 57 | } 58 | 59 | function testCppLinkage() { 60 | TestCppNative.cpp_nop(); 61 | eq(1, 1); 62 | } 63 | /* 64 | function testStructMembers() { 65 | var obj = XTemplatesStruct.new_(); 66 | eq(obj.member_int, 5); 67 | obj.member_int = 7; 68 | eq(obj.member_int, 7); 69 | eq(obj.add(13), 20); 70 | }*/ 71 | } 72 | -------------------------------------------------------------------------------- /test/src/test/TestArrays.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:ammertest.code("native.h", 4 | LIB_EXPORT int take_array_fixed(int a[3]); 5 | LIB_EXPORT int take_array(int *a, size_t b); 6 | LIB_EXPORT void take_array_modify(int *a); 7 | ) 8 | @:ammertest.code("native.c", 9 | LIB_EXPORT int take_array_fixed(int a[3]) { 10 | if (a[0] != 1 || a[1] != 2 || a[2] != 4) 11 | return -1; 12 | return a[0] + a[1] + a[2]; 13 | } 14 | LIB_EXPORT int take_array(int *a, size_t b) { 15 | if (b != 3 || a[0] != 1 || a[1] != 2 || a[2] != 4) 16 | return -1; 17 | return a[0] + a[1] + a[2]; 18 | } 19 | LIB_EXPORT void take_array_modify(int *a) { 20 | a[1] = 42; 21 | } 22 | ) 23 | class TestArraysNative extends ammer.def.Sublibrary { 24 | public static function take_array_fixed(a:ammer.ffi.Array):Int; 25 | public static function take_array( 26 | @:ammer.skip _:haxe.ds.Vector, 27 | @:ammer.derive(ammer.Lib.vecToArrayCopy(arg0)) _:ammer.ffi.Array, 28 | @:ammer.derive(arg0.length) _:ammer.ffi.Size 29 | ):Int; 30 | public static function take_array_modify(a:ammer.ffi.Array):Void; 31 | } 32 | 33 | class TestArrays extends Test implements ammer.Syntax { 34 | function testArrays() { 35 | var arr = ammer.Lib.allocArray(Int, 3); 36 | arr.set(0, 1); 37 | arr.set(1, 2); 38 | arr.set(2, 4); 39 | eq(TestArraysNative.take_array_fixed(arr), 7); 40 | 41 | var vec:haxe.ds.Vector = haxe.ds.Vector.fromArrayCopy([1, 2, 4]); 42 | var arr = ammer.Lib.vecToArrayCopy(vec); 43 | eq(TestArraysNative.take_array_fixed(arr), 7); 44 | 45 | eq(TestArraysNative.take_array(vec), 7); 46 | 47 | #if !(lua || neko || js || python) 48 | eq(@ret TestArraysNative.take_array_fixed(@ref vec), 7); 49 | 50 | var arrRef = ammer.Lib.vecToArrayRefForce(vec); 51 | eq(TestArraysNative.take_array_fixed(arrRef.array), 7); 52 | TestArraysNative.take_array_modify(arrRef.array); 53 | arrRef.unref(); 54 | eq(vec[1], 42); 55 | #end 56 | 57 | vec[1] = 2; 58 | 59 | TestArraysNative.take_array_modify(@copyfree vec); 60 | eq(vec[1], 2); 61 | 62 | TestArraysNative.take_array_modify(@copy vec); 63 | eq(vec[1], 2); 64 | 65 | #if !(lua || neko || js || python) 66 | TestArraysNative.take_array_modify(@ref vec); 67 | eq(vec[1], 42); 68 | #end 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/src/test/TestBytes.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import haxe.io.Bytes as HB; 4 | import ammer.ffi.Bytes as AB; 5 | 6 | @:ammertest.code("native.h", 7 | LIB_EXPORT unsigned char *ident_bytes(unsigned char *a, size_t b); 8 | LIB_EXPORT unsigned char *give_bytes(size_t len); 9 | ) 10 | @:ammertest.code("native.c", 11 | LIB_EXPORT unsigned char *ident_bytes(unsigned char *a, size_t b) { 12 | return memcpy(malloc(b), a, b); 13 | } 14 | LIB_EXPORT unsigned char *give_bytes(size_t len) { 15 | unsigned char *ret = malloc(len); 16 | for (size_t i = 0; i < len; i++) { 17 | ret[i] = i + 1; 18 | } 19 | return ret; 20 | } 21 | ) 22 | class TestBytesNative extends ammer.def.Sublibrary { 23 | public static function ident_bytes(_:AB, _:Int):AB; 24 | 25 | @:ammer.native("ident_bytes") public static function ident_bytes_1( 26 | @:ammer.skip _:HB, 27 | @:ammer.derive(ammer.ffi.Bytes.fromHaxeCopy(arg0)) _:AB, 28 | @:ammer.derive(arg0.length) _:Int 29 | ):AB; 30 | 31 | @:ammer.ret.derive(ret.toHaxeCopy(arg1), (_ : HB)) 32 | @:ammer.native("ident_bytes") public static function ident_bytes_2( 33 | _:AB, 34 | _:Int 35 | ):AB; 36 | 37 | @:ammer.ret.derive(ret.toHaxeCopy(arg0.length), (_ : HB)) 38 | @:ammer.native("ident_bytes") public static function ident_bytes_3( 39 | @:ammer.skip _:HB, 40 | @:ammer.derive(AB.fromHaxeCopy(arg0)) _:AB, 41 | @:ammer.derive(arg0.length) _:Int 42 | ):AB; 43 | 44 | @:ammer.ret.derive(ret.toHaxeCopy(arg0), (_ : HB)) 45 | public static function give_bytes(len:Int):AB; 46 | } 47 | 48 | class TestBytes extends Test { 49 | function testIdent() { 50 | beq(TestBytesNative.ident_bytes(AB.fromHaxeCopy(HB.ofHex("")), 0).toHaxeCopy(0), HB.ofHex("")); 51 | beq(TestBytesNative.ident_bytes(AB.fromHaxeCopy(HB.ofHex("00")), 1).toHaxeCopy(1), HB.ofHex("00")); 52 | 53 | var b = HB.ofHex("0001FEFF"); 54 | var c = HB.ofHex("AA01FEFF"); 55 | beq(TestBytesNative.ident_bytes_1(b).toHaxeCopy(4), b); 56 | beq(TestBytesNative.ident_bytes_2(AB.fromHaxeCopy(b), 4), b); 57 | beq(TestBytesNative.ident_bytes_3(b), b); 58 | } 59 | 60 | function testReturns() { 61 | beq(TestBytesNative.give_bytes(0), HB.ofHex("")); 62 | beq(TestBytesNative.give_bytes(1), HB.ofHex("01")); 63 | beq(TestBytesNative.give_bytes(10), HB.ofHex("0102030405060708090A")); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ammer/internal/LibTypes.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if !macro 4 | 5 | // TODO: version LibTypes as well? 6 | 7 | @:ammer.lib.linkNames([]) 8 | @:ammer.sub((_ : ammer.ffi.Bytes)) 9 | @:ammer.sub((_ : ammer.ffi.FilePtr)) 10 | @:ammer.sub((_ : ammer.ffi.FilePtr.FilePos)) 11 | @:ammer.sub((_ : ammer.ffi.Box)) 12 | @:ammer.sub((_ : ammer.ffi.Box)) 13 | @:ammer.sub((_ : ammer.ffi.Box)) 14 | @:ammer.sub((_ : ammer.ffi.Box)) 15 | @:ammer.sub((_ : ammer.ffi.Box)) 16 | @:ammer.sub((_ : ammer.ffi.Box)) 17 | @:ammer.sub((_ : ammer.ffi.Box)) 18 | @:ammer.sub((_ : ammer.ffi.Box)) 19 | @:ammer.sub((_ : ammer.ffi.Box)) 20 | @:ammer.sub((_ : ammer.ffi.Box)) 21 | @:ammer.sub((_ : ammer.ffi.Box)) 22 | @:ammer.sub((_ : ammer.ffi.Box)) 23 | @:ammer.sub((_ : ammer.ffi.Array)) 24 | @:ammer.sub((_ : ammer.ffi.Array)) 25 | @:ammer.sub((_ : ammer.ffi.Array)) 26 | @:ammer.sub((_ : ammer.ffi.Array)) 27 | @:ammer.sub((_ : ammer.ffi.Array)) 28 | @:ammer.sub((_ : ammer.ffi.Array)) 29 | @:ammer.sub((_ : ammer.ffi.Array)) 30 | @:ammer.sub((_ : ammer.ffi.Array)) 31 | @:ammer.sub((_ : ammer.ffi.Array)) 32 | @:ammer.sub((_ : ammer.ffi.Array)) 33 | @:ammer.sub((_ : ammer.ffi.Array)) 34 | @:ammer.sub((_ : ammer.ffi.Array)) 35 | //@:ammer.sub((_ : ammer.ffi.Array)) 36 | @:ammer.sub((_ : ammer.ffi.Haxe)) 37 | class LibTypes extends ammer.def.Library<"libtypes"> {} 38 | 39 | abstract HaxeAnyRef({ 40 | var value(get, never):Any; 41 | function incref():Void; 42 | function decref():Void; 43 | }/*ammer.ffi.Haxe*/)/* to ammer.ffi.Haxe*/ { 44 | public inline function new(r:ammer.ffi.Haxe) { 45 | this = r; 46 | } 47 | 48 | public var value(get, never):T; 49 | inline function get_value():T { 50 | return (cast this.value : T); 51 | } 52 | //inline function set_value(value:T):T { 53 | // return (cast (this.value = value) : T); 54 | //} 55 | 56 | public inline function incref():Void this.incref(); 57 | public inline function decref():Void this.decref(); 58 | 59 | public inline function toNative():Any return this; 60 | } 61 | 62 | #else 63 | class LibTypes {} 64 | class LibTypes_LibTypes_AmmerSetup { 65 | public static function init():Void {} 66 | } 67 | #end 68 | -------------------------------------------------------------------------------- /src/ammer/internal/v1/OsInfo.hx: -------------------------------------------------------------------------------- 1 | // ammer-bake: ammer.internal.v1 OsInfo true 2 | package ammer.internal.v1; 3 | 4 | #if macro 5 | 6 | /** 7 | Provides more complete info about the operating system. Possible values for 8 | the data fields are as follows: 9 | 10 | - `os`: "windows", "mac", "linux" 11 | - `version` (for "windows"): (TODO) 12 | - `version` (for "mac"): "11.2.3", "10.9.5", etc 13 | - `version` (for "linux"): (TODO) 14 | - `architecture`: "x86_64", etc 15 | 16 | Keys are `null` when unknown. 17 | 18 | Additionally, `versionCompare` provides a function with the same signature 19 | as `Reflect.compare` to compare two version strings for the given OS. 20 | **/ 21 | class OsInfo { 22 | public static var info(get, never):OsInfo; 23 | static var infoL:OsInfo; 24 | static function get_info():OsInfo { 25 | if (infoL != null) 26 | return infoL; 27 | return infoL = new OsInfo(); 28 | } 29 | 30 | public var os(default, null):Null; 31 | public var version(default, null):Null; 32 | public var architecture(default, null):Null; 33 | public var versionCompare(default, null):(a:String, b:String)->Int; 34 | 35 | function new() { 36 | var osRaw = Sys.systemName(); 37 | versionCompare = (a:String, b:String) -> Reflect.compare(a, b); 38 | function run(cmd:String, args:Array):String { 39 | var proc = new sys.io.Process(cmd, args); 40 | var code = proc.exitCode(); 41 | var stdout = proc.stdout.readAll().toString(); 42 | proc.close(); 43 | return stdout; 44 | } 45 | // simplified semver: only accepts X.Y.Z format 46 | function semverCompare(a:String, b:String):Int { 47 | var as = a.split(".").map(Std.parseInt); 48 | var bs = b.split(".").map(Std.parseInt); 49 | (as.length == 3 50 | && as[0] != null && as[0] >= 0 51 | && as[1] != null && as[1] >= 0 52 | && as[2] != null && as[2] >= 0) || throw 'invalid semver $a'; 53 | (bs.length == 3 54 | && bs[0] != null && bs[0] >= 0 55 | && bs[1] != null && bs[1] >= 0 56 | && bs[2] != null && bs[2] >= 0) || throw 'invalid semver $b'; 57 | for (i in 0...3) { 58 | if (as[0] != bs[0]) 59 | return as[0] < bs[0] ? -1 : 1; 60 | } 61 | return 0; 62 | } 63 | switch (osRaw) { 64 | case "Mac": 65 | os = "mac"; 66 | version = run("sw_vers", ["-productVersion"]); 67 | architecture = run("uname", ["-m"]); 68 | versionCompare = semverCompare; 69 | case _: 70 | os = osRaw.toLowerCase(); 71 | // TODO 72 | } 73 | } 74 | } 75 | 76 | #end 77 | -------------------------------------------------------------------------------- /src/ammer/internal/Config.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.io.Path; 6 | import haxe.macro.Compiler; 7 | import haxe.macro.Context; 8 | 9 | class Config { 10 | static final BOOL_YES = ["yes", "y", "true", "1", "on"]; 11 | static final BOOL_NO = ["no", "n", "false", "0", "off"]; 12 | 13 | public static function hasDefine(key:String):Bool { 14 | return Context.defined(key); 15 | } 16 | 17 | /** 18 | Gets a compile-time define by `key`. If the specified key is not defined, 19 | return the value `dv`, or throw an error if `doThrow` is `true`. 20 | **/ 21 | public static function getString(key:String, ?dv:String, ?doThrow:Bool = false):String { 22 | if (Context.defined(key)) 23 | return Context.definedValue(key); 24 | if (doThrow) 25 | Context.fatalError('missing required define: $key', Context.currentPos()); 26 | return dv; 27 | } 28 | 29 | public static function getStringArray(key:String, sep:String, ?dv:Array, ?doThrow:Bool = false):Array { 30 | if (Context.defined(key)) 31 | return Context.definedValue(key).split(sep); 32 | if (doThrow) 33 | Context.fatalError('missing required define: $key', Context.currentPos()); 34 | return dv; 35 | } 36 | 37 | /** 38 | Gets a boolean from the compile-time define `key`. 39 | **/ 40 | public static function getBool(key:String, ?dv:Bool, ?doThrow:Bool = false):Bool { 41 | if (Context.defined(key)) { 42 | if (BOOL_YES.indexOf(Context.definedValue(key)) != -1) 43 | return true; 44 | if (BOOL_NO.indexOf(Context.definedValue(key)) != -1) 45 | return false; 46 | Context.fatalError('invalid define (should be yes or no): $key', Context.currentPos()); 47 | } 48 | if (doThrow) 49 | Context.fatalError('missing required define: $key', Context.currentPos()); 50 | return dv; 51 | } 52 | 53 | public static function getInt(key:String, ?dv:Int, ?doThrow:Bool = false):Int { 54 | if (Context.defined(key)) 55 | return Std.parseInt(Context.definedValue(key)); 56 | if (doThrow) 57 | Context.fatalError('missing required define: $key', Context.currentPos()); 58 | return dv; 59 | } 60 | 61 | /** 62 | Gets a path from the compile-time define `key`. If the path is relative, 63 | resolve it relative to the current working directory. 64 | **/ 65 | public static function getPath(key:String, ?dv:String, ?doThrow:Bool = false):String { 66 | var p = getString(key, dv, doThrow); 67 | if (p != null && !Path.isAbsolute(p)) 68 | p = Path.join([Sys.getCwd(), p]); 69 | return p; 70 | } 71 | 72 | public static function getEnum(key:String, map:Map, ?dv:T, ?doThrow:Bool = false):T { 73 | var p = getString(key, null, doThrow); 74 | if (p == null) 75 | return dv; 76 | if (!map.exists(p)) { 77 | var keys = [for (k in map.keys()) k]; 78 | keys.sort(Reflect.compare); 79 | Context.fatalError('invalid define (should be one of ${keys.join(", ")})', Context.currentPos()); 80 | } 81 | return map[p]; 82 | } 83 | } 84 | 85 | #end 86 | -------------------------------------------------------------------------------- /src/ammer/ffi/FilePtr.hx: -------------------------------------------------------------------------------- 1 | package ammer.ffi; 2 | 3 | #if !macro 4 | 5 | class FilePtr extends ammer.def.Opaque<"FILE*", ammer.internal.LibTypes> { 6 | public static final SEEK_SET:Int; 7 | public static final SEEK_CUR:Int; 8 | public static final SEEK_END:Int; 9 | public static final BUFSIZ:Int; 10 | public static final EOF:Int; 11 | public static final _IOFBF:Int; 12 | public static final _IOLBF:Int; 13 | public static final _IONBF:Int; 14 | public static final FILENAME_MAX:Int; 15 | public static final FOPEN_MAX:Int; 16 | public static final TMP_MAX:Int; 17 | public static final L_tmpnam:Int; 18 | 19 | public static final stderr:FilePtr; 20 | public static final stdin:FilePtr; 21 | public static final stdout:FilePtr; 22 | 23 | public static function fopen(filename:String, mode:String):FilePtr; 24 | public static function getchar():Int; 25 | public static function perror(str:String):Void; 26 | public static function putchar(char:Int):Int; 27 | public static function puts(str:String):Int; 28 | public static function remove(filename:String):Int; 29 | public static function rename(oldname:String, newname:String):Int; 30 | public static function tmpfile():FilePtr; 31 | // public static function tmpnam(str:String):String; // deprecated 32 | 33 | public function fclose(_:This):Void; 34 | public function feof(_:This):Bool; 35 | public function ferror(_:This):Int; 36 | public function fflush(_:This):Int; 37 | public function fgetc(_:This):Int; 38 | public function fgetpos(_:This, pos:FilePos):Int; 39 | //fgets 40 | //fprintf? 41 | public function fputc(char:Int, _:This):Int; 42 | public function fputs(str:String, _:This):Int; 43 | public function fread(ptr:Bytes, size:Size, count:Size, _:This):Size; 44 | public function freopen(filename:String, mode:String, _:This):FilePtr; 45 | // public function fscanf(_:This, format: ...) 46 | public function fseek(_:This, offset:Int64, origin:Int):Int; 47 | public function fsetpos(_:This, pos:FilePos):Int; 48 | public function ftell(_:This):Int64; 49 | public function fwrite(ptr:Bytes, size:Size, count:Size, _:This):Size; 50 | // getc -> fgetc 51 | // gets // removed 52 | // public static function printf(...) 53 | public function putc(char:Int, _:This):Int; 54 | public function rewind(_:This):Void; 55 | // public static function scanf(...) 56 | public function setbuf(_:This, @:ammer.c.cast("char*") buffer:Bytes):Void; 57 | public function setvbuf(_:This, @:ammer.c.cast("char*") buffer:Bytes, mode:Int, size:Size):Int; 58 | // public static function snprintf(...) 59 | // public static function sprintf(...) 60 | // public static function sscanf(...) 61 | public function ungetc(char:Int, _:This):Int; 62 | // public function vfprintf(...) 63 | // public function vfscanf(...) 64 | // public function vprintf(...) 65 | // public function vscanf(...) 66 | // public function vsnprintf(...) 67 | // public function vsprintf(...) 68 | // public function vsscanf(...) 69 | 70 | @:ammer.haxe public function output():haxe.io.Output return @:privateAccess new ammer.internal.FilePtrOutput(this); 71 | //public function input():haxe.io.Input return @:privateAccess new ammer.internal.FilePtrInput(this); 72 | } 73 | 74 | @:ammer.alloc 75 | class FilePos extends ammer.def.Struct<"fpos_t", ammer.internal.LibTypes> {} 76 | 77 | #end 78 | -------------------------------------------------------------------------------- /src/ammer/Syntax.macro.hx: -------------------------------------------------------------------------------- 1 | package ammer; 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Expr; 5 | import haxe.macro.ExprTools; 6 | 7 | class Syntax { 8 | public static function build():Array { 9 | return [ for (field in Context.getBuildFields()) { 10 | pos: field.pos, 11 | name: field.name, 12 | meta: field.meta, 13 | kind: (switch (field.kind) { 14 | case FVar(t, e): FVar(t, e != null ? process(e) : null); 15 | case FFun(f): FFun({ 16 | ret: f.ret, 17 | params: f.params, 18 | expr: f.expr != null ? process(f.expr) : null, 19 | args: f.args, 20 | }); 21 | case FProp(get, set, t, e): FProp(get, set, t, e != null ? process(e) : null); 22 | }), 23 | doc: field.doc, 24 | access: field.access, 25 | } ]; 26 | } 27 | 28 | static function process(e:Expr):Expr { 29 | return (switch (e) { 30 | // TODO: support bytes for ref/copy as well 31 | case macro @copy $expr: macro ammer.Lib.vecToArrayCopy($expr); 32 | case macro @leak $expr: macro { var _ref = new ammer.ffi.Haxe($expr); _ref.incref(); _ref; } 33 | // TODO: this @ret solution is not good; process typed fields instead? 34 | // TODO: reduce duplication 35 | // TODO: better tempvar names 36 | case macro @ret $e{{expr: ECall(f, args)}}: 37 | var block = []; 38 | var frees = []; 39 | args = [ for (idx => arg in args) switch (arg) { 40 | case macro @ref $expr: 41 | var tmp = '_syntax_arg$idx'; 42 | block.push(macro var $tmp = ammer.Lib.vecToArrayRef($e{process(expr)})); 43 | frees.push(macro $i{tmp}.unref()); 44 | macro $i{tmp}.array; 45 | case macro @copyfree $expr: 46 | var tmp = '_syntax_arg$idx'; 47 | block.push(macro var $tmp = ammer.Lib.vecToArrayCopy($e{process(expr)})); 48 | frees.push(macro $i{tmp}.free()); 49 | macro $i{tmp}; 50 | case _: process(arg); 51 | } ]; 52 | if (block.length > 0) { 53 | var call = {expr: ECall(process(f), args), pos: e.pos}; 54 | block.push(macro var _ret = $call); 55 | block.push(macro $b{frees}); 56 | block.push(macro _ret); 57 | macro $b{block}; 58 | } else { 59 | ExprTools.map(e, process); 60 | } 61 | case {expr: ECall(f, args)}: 62 | var block = []; 63 | var frees = []; 64 | args = [ for (idx => arg in args) switch (arg) { 65 | case macro @ref $expr: 66 | var tmp = '_syntax_arg$idx'; 67 | block.push(macro var $tmp = ammer.Lib.vecToArrayRef($e{process(expr)})); 68 | frees.push(macro $i{tmp}.unref()); 69 | macro $i{tmp}.array; 70 | case macro @copyfree $expr: 71 | var tmp = '_syntax_arg$idx'; 72 | block.push(macro var $tmp = ammer.Lib.vecToArrayCopy($e{process(expr)})); 73 | frees.push(macro $i{tmp}.free()); 74 | macro $i{tmp}; 75 | case _: process(arg); 76 | } ]; 77 | if (block.length > 0) { 78 | var call = {expr: ECall(process(f), args), pos: e.pos}; 79 | block.push(macro $call); 80 | block.push(macro if (true) $b{frees}); 81 | macro $b{block}; 82 | } else { 83 | ExprTools.map(e, process); 84 | } 85 | case macro @ref $expr: throw Context.error("@ref can only be used on function call arguments", e.pos); 86 | case macro @copyfree $expr: throw Context.error("@copyfree can only be used on function call arguments", e.pos); 87 | case _: ExprTools.map(e, process); 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ammer/internal/Reporting.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type; 8 | using haxe.macro.PositionTools; 9 | 10 | class Reporting { 11 | public static var positionStack:Array = []; 12 | static var debugStreams:Map; 13 | static var imports:Map, 15 | usings:Array, 16 | module:String, 17 | }> = []; 18 | 19 | static function shouldEmit(stream:String):Bool { 20 | if (debugStreams == null) { 21 | debugStreams = [ 22 | "stage" => false, 23 | "stage-ffi" => false, 24 | ]; 25 | switch (Config.getString("ammer.debug")) { 26 | case null: 27 | case "all": for (k in debugStreams.keys()) debugStreams[k] = true; 28 | case s: for (k in s.split(",")) debugStreams[k] = true; 29 | } 30 | } 31 | return debugStreams[stream]; 32 | } 33 | 34 | public static function pushPosition(pos:Position):Void { 35 | positionStack.push(pos); 36 | } 37 | public static function popPosition():Void { 38 | positionStack.length > 0 || throw 0; 39 | positionStack.pop(); 40 | } 41 | public static function withPosition(pos:Position, f:()->T):T { 42 | pushPosition(pos); 43 | var ret = f(); 44 | // TODO: throw does not reset the stack, implement catchable errors 45 | popPosition(); 46 | return ret; 47 | } 48 | public static function withCurrentPos(f:()->T, recordImports:Bool = true):T { 49 | if (recordImports) { 50 | var filename = Context.currentPos().getInfos().file; 51 | if (!imports.exists(filename)) { 52 | imports[filename] = { 53 | imports: [ for (imp in Context.getLocalImports()) { 54 | var path = imp.path.map(p -> p.name).join("."); 55 | switch (imp.mode) { 56 | case INormal: path; 57 | case IAsName(alias): '$path as $alias'; 58 | case IAll: "*"; 59 | }; 60 | } ], 61 | usings: [ for (use in Context.getLocalUsing()) { 62 | var cls = use.get(); 63 | cls.pack.concat([cls.module.split(".").pop(), cls.name]).join("."); 64 | } ], 65 | module: Context.getLocalModule(), 66 | }; 67 | } 68 | } 69 | return withPosition(Context.currentPos(), f); 70 | } 71 | 72 | public static function currentPos():Position { 73 | positionStack.length > 0 || throw 0; 74 | return positionStack[positionStack.length - 1]; 75 | } 76 | 77 | public static function log(msg:String, stream:String, ?pos:haxe.PosInfos):Void { 78 | if (shouldEmit(stream)) 79 | Sys.println('[ammer:$stream] $msg (${pos.fileName}:${pos.lineNumber})'); 80 | } 81 | 82 | public static function warning(msg:String):Void { 83 | Context.warning(msg, currentPos()); 84 | } 85 | public static function error(msg:String):Void { 86 | Context.error(msg, currentPos()); 87 | } 88 | 89 | public static function resolveType(ct:ComplexType, pos:Position):Type { 90 | var curPos = Context.currentPos(); 91 | var curFilename = curPos.getInfos().file; 92 | var filename = pos.getInfos().file; 93 | if (curFilename != filename) { 94 | var importsForFile = imports[filename]; 95 | importsForFile != null || throw 0; 96 | return Context.withImports( 97 | importsForFile.imports.concat([importsForFile.module]), 98 | importsForFile.usings, 99 | () -> Context.resolveType(ct, pos) 100 | ); 101 | } else { 102 | return Context.resolveType(ct, pos); 103 | } 104 | } 105 | 106 | // TODO: catchable, emitted error that does not immediately abort? 107 | // throw Reporting.error("...") ? 108 | } 109 | 110 | #end 111 | -------------------------------------------------------------------------------- /src/ammer/internal/v1/LibInfo.hx: -------------------------------------------------------------------------------- 1 | // ammer-bake: ammer.internal.v1 LibInfo true 2 | package ammer.internal.v1; 3 | 4 | #if macro 5 | 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type; 8 | 9 | typedef LibInfoArray = { 10 | arrayCt:ComplexType, 11 | arrayRefCt:ComplexType, 12 | alloc:Expr, // == arrayMarshal.alloc(macro _size) 13 | fromHaxeCopy:Expr, // == arrayMarshal.fromHaxeCopy(macro _vec) 14 | fromHaxeRef:Null, // == arrayMarshal.fromHaxeRef(macro _vec) 15 | 16 | #if ammer 17 | ?elementType:Type, 18 | ?arrayMarshal:ammer.core.MarshalArray, 19 | #end 20 | }; 21 | 22 | typedef LibInfoBox = { 23 | boxCt:ComplexType, 24 | alloc:Expr, // == boxMarshal.alloc 25 | 26 | #if ammer 27 | ?elementType:Type, 28 | ?boxMarshal:ammer.core.MarshalBox, 29 | #end 30 | }; 31 | 32 | typedef LibInfoCallback = { 33 | isGlobal:Bool, 34 | callbackCt:ComplexType, 35 | funCt:ComplexType, 36 | callbackName:String, 37 | 38 | #if ammer 39 | ?ctx:LibContext, 40 | #end 41 | }; 42 | 43 | typedef LibInfoEnum = { 44 | #if ammer 45 | ?marshal:ammer.core.TypeMarshal, 46 | #end 47 | }; 48 | 49 | typedef LibInfoHaxeRef = { 50 | create:Expr, // == marshal.create(macro _hxval) 51 | 52 | #if ammer 53 | ?ctx:LibContext, 54 | ?elementType:Type, 55 | ?marshal:ammer.core.MarshalHaxe, 56 | #end 57 | }; 58 | 59 | typedef LibInfoLibrary = { 60 | #if ammer 61 | ?nativePrefix:String, 62 | #end 63 | }; 64 | 65 | typedef LibInfoOpaque = { 66 | opaqueName:String, 67 | 68 | #if ammer 69 | ?ctx:LibContext, 70 | ?implType:Type, 71 | ?marshal:ammer.core.MarshalOpaque, 72 | ?nativePrefix:String, 73 | #end 74 | }; 75 | 76 | typedef LibInfoStruct = { 77 | alloc:Bool, 78 | ?gen:{ 79 | ?alloc:String, 80 | ?free:String, 81 | ?nullPtr:String, 82 | }, 83 | structName:String, 84 | 85 | #if ammer 86 | ?ctx:LibContext, 87 | ?implType:Type, 88 | // used for recursive field refs 89 | ?marshalOpaque:ammer.core.TypeMarshal, 90 | ?marshalDeref:ammer.core.TypeMarshal, 91 | ?marshal:ammer.core.MarshalStruct, 92 | ?nativePrefix:String, 93 | #end 94 | }; 95 | 96 | typedef LibInfoSublibrary = { 97 | #if ammer 98 | ?ctx:LibContext, 99 | ?nativePrefix:String, 100 | #end 101 | }; 102 | 103 | typedef LibInfoFileSource = { 104 | // local filename 105 | ?name:String, 106 | 107 | ?description:String, 108 | 109 | // hash of file 110 | //?digest:String, 111 | 112 | // pre-baked release download info 113 | // URL for automatic download 114 | ?downloadFrom:String, 115 | 116 | // operating system 117 | ?os:String, 118 | 119 | // supported architectures (an array to support fat binaries) 120 | ?architectures:Array, 121 | 122 | // minimum OS version supported by file 123 | ?minVersion:String, 124 | 125 | // maximum OS version supported by file 126 | ?maxVersion:String, 127 | }; 128 | 129 | typedef LibInfoFile = { 130 | // destination filename (may contain %DLL% etc) 131 | dst:String, 132 | sources:Array, 133 | }; 134 | 135 | class LibInfo { 136 | public var name:String; 137 | public var herePos:Position; 138 | public var setupToBin:String; 139 | 140 | // String key is typeId of the implType 141 | 142 | public var arrays:{ 143 | byTypeId:Map, 144 | byElementTypeId:Map, 145 | } = { 146 | byTypeId: [], 147 | byElementTypeId: [], 148 | }; 149 | public var boxes:{ 150 | byTypeId:Map, 151 | byElementTypeId:Map, 152 | } = { 153 | byTypeId: [], 154 | byElementTypeId: [], 155 | }; 156 | public var callbacks:{ 157 | byTypeId:Map, 158 | byElementTypeId:Map, 159 | } = { 160 | byTypeId: [], 161 | byElementTypeId: [], 162 | }; 163 | public var enums:Map = []; 164 | public var haxeRefs:{ 165 | byTypeId:Map, 166 | byElementTypeId:Map, 167 | } = { 168 | byTypeId: [], 169 | byElementTypeId: [], 170 | }; 171 | public var opaques:Map = []; 172 | public var structs:Map = []; 173 | public var sublibraries:Map = []; 174 | public var files:Array = []; 175 | 176 | public function new() {} 177 | } 178 | 179 | #end 180 | -------------------------------------------------------------------------------- /test/native-gen/NativeGen.hx: -------------------------------------------------------------------------------- 1 | #if macro 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Expr; 5 | import sys.io.File; 6 | import sys.FileSystem; 7 | 8 | using StringTools; 9 | 10 | class NativeGen { 11 | public static function deleteFields():Array { 12 | return []; 13 | } 14 | public static function generate():Void { 15 | var pos = Context.currentPos(); 16 | function params(isConst:Array):Array { 17 | return [ for (i in 0...isConst.length) { 18 | name: 'T$i', 19 | meta: isConst[i] ? [{ 20 | pos: pos, 21 | name: ":const", 22 | }] : [], 23 | } ]; 24 | } 25 | Context.onTypeNotFound(rname -> { 26 | if (rname == "Single") return { 27 | name: rname, 28 | pack: [], 29 | kind: TDAlias((macro : Float)), 30 | fields: [], 31 | pos: pos, 32 | }; 33 | var pack = rname.split("."); 34 | var name = pack.pop(); 35 | if (pack.length > 0 && pack[0] == "ammer") { 36 | if (rname == "ammer.Syntax") { 37 | return { 38 | name: name, 39 | pack: pack, 40 | kind: TDClass(null, [], true, false, false), 41 | fields: [], 42 | pos: pos, 43 | }; 44 | } 45 | if (pack[1] == "def") { 46 | return { 47 | name: name, 48 | pack: pack, 49 | params: (switch (rname) { 50 | case "ammer.def.Library": params([true]); 51 | case "ammer.def.Struct": params([true, false]); 52 | case "ammer.def.Sublibrary": params([false]); 53 | case _: []; 54 | }), 55 | kind: TDClass(null, [], false, false, false), 56 | meta: [{ 57 | name: ":autoBuild", 58 | params: [macro NativeGen.deleteFields()], 59 | pos: pos, 60 | }], 61 | fields: [], 62 | pos: pos, 63 | }; 64 | } 65 | return { 66 | name: name, 67 | pack: pack, 68 | params: [], 69 | kind: TDClass(null, [], false, false, false), 70 | fields: [], 71 | pos: pos, 72 | }; 73 | } 74 | return null; 75 | }); 76 | for (test in FileSystem.readDirectory("../src/test")) { 77 | if (test.startsWith(".") || !test.endsWith(".hx")) continue; 78 | Context.resolveType(TPath({ 79 | name: test.substr(0, test.length - 3), 80 | pack: ["test"], 81 | }), pos); 82 | } 83 | Context.onAfterTyping(types -> { 84 | var outputs:Map> = []; 85 | for (common in FileSystem.readDirectory("common-header")) { 86 | outputs[common] = ["(HEADER)" => File.getContent('common-header/$common')]; 87 | } 88 | for (t in types) switch (t) { 89 | case TClassDecl(_.get() => cls = { pack: ["test"], name: _.startsWith("Test") => true }): 90 | for (code in cls.meta.extract(":ammertest.code")) switch (code.params) { 91 | case [{expr: EConst(CString(output))}, {expr: EMeta(_, {expr: EConst(CString(code))})}]: 92 | code.startsWith("") || throw 0; 93 | code = code.substr("".length); 94 | code.endsWith("") || throw 0; 95 | code = code.substr(0, code.length - "".length); 96 | if (!outputs.exists(output)) outputs[output] = new Map(); 97 | var typeId = cls.pack.concat([cls.name]).join("."); 98 | outputs[output][typeId] = '// $typeId\n$code'; 99 | case _: throw 0; 100 | } 101 | case _: 102 | } 103 | for (common in FileSystem.readDirectory("common-footer")) { 104 | outputs[common]["(FOOTER)"] = File.getContent('common-footer/$common'); 105 | } 106 | var sortedOutputs = [ for (k in outputs.keys()) k ]; 107 | sortedOutputs.sort(Reflect.compare); 108 | for (output in sortedOutputs) { 109 | var codes = outputs[output]; 110 | var merged = new StringBuf(); 111 | var sortedKeys = [ for (k in codes.keys()) k ]; 112 | sortedKeys.sort((a, b) -> 113 | a == "(HEADER)" ? -1 : 114 | a == "(FOOTER)" ? 1 : 115 | b == "(HEADER)" ? 1 : 116 | b == "(FOOTER)" ? -1 : Reflect.compare(a, b)); 117 | for (k in sortedKeys) { 118 | merged.add(codes[k]); 119 | } 120 | Sys.println('$output ... ${sortedKeys.length}'); 121 | File.saveContent('../native-src/$output', merged.toString()); 122 | } 123 | }); 124 | } 125 | } 126 | 127 | #end 128 | -------------------------------------------------------------------------------- /test/src/test/TestSignature.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import ammer.ffi.Unsupported; 4 | 5 | @:ammer.sub((_ : test.TestSignature.TestSignatureNative2)) 6 | @:ammer.sub((_ : test.TestSignature.TestSignatureNative3)) 7 | @:ammertest.code("native.h", 8 | LIB_EXPORT int take_0(void); 9 | LIB_EXPORT int take_1(int a1); 10 | LIB_EXPORT int take_2(int a1, int a2); 11 | LIB_EXPORT int take_3(int a1, int a2, int a3); 12 | LIB_EXPORT int take_4(int a1, int a2, int a3, int a4); 13 | LIB_EXPORT int take_5(int a1, int a2, int a3, int a4, int a5); 14 | LIB_EXPORT int take_6(int a1, int a2, int a3, int a4, int a5, int a6); 15 | LIB_EXPORT int take_7(int a1, int a2, int a3, int a4, int a5, int a6, int a7); 16 | LIB_EXPORT int take_8(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8); 17 | LIB_EXPORT int take_9(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); 18 | 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); 19 | LIB_EXPORT void nop(void); 20 | LIB_EXPORT bool take_unsupported(void *a, double b); 21 | ) 22 | @:ammertest.code("native.c", 23 | LIB_EXPORT int take_0(void) { 24 | return 0; 25 | } 26 | LIB_EXPORT int take_1(int a1) { 27 | return 1; 28 | } 29 | LIB_EXPORT int take_2(int a1, int a2) { 30 | return 2; 31 | } 32 | LIB_EXPORT int take_3(int a1, int a2, int a3) { 33 | return 3; 34 | } 35 | LIB_EXPORT int take_4(int a1, int a2, int a3, int a4) { 36 | return 4; 37 | } 38 | LIB_EXPORT int take_5(int a1, int a2, int a3, int a4, int a5) { 39 | return 5; 40 | } 41 | LIB_EXPORT int take_6(int a1, int a2, int a3, int a4, int a5, int a6) { 42 | return 6; 43 | } 44 | LIB_EXPORT int take_7(int a1, int a2, int a3, int a4, int a5, int a6, int a7) { 45 | return 7; 46 | } 47 | LIB_EXPORT int take_8(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) { 48 | return 8; 49 | } 50 | LIB_EXPORT int take_9(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { 51 | return 9; 52 | } 53 | 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) { 54 | return 10; 55 | } 56 | LIB_EXPORT void nop(void) {} 57 | LIB_EXPORT bool take_unsupported(void *a, double b) { 58 | return a == 0 && abs(b) < .0001; 59 | } 60 | ) 61 | class TestSignatureNative extends ammer.def.Sublibrary { 62 | public static function take_0():Int; 63 | public static function take_1(_:Int):Int; 64 | public static function take_2(_:Int, _:Int):Int; 65 | public static function take_3(_:Int, _:Int, _:Int):Int; 66 | public static function take_4(_:Int, _:Int, _:Int, _:Int):Int; 67 | public static function take_5(_:Int, _:Int, _:Int, _:Int, _:Int):Int; 68 | public static function take_6(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; 69 | public static function take_7(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; 70 | public static function take_8(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; 71 | public static function take_9(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; 72 | public static function take_10(_:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int, _:Int):Int; 73 | public static function nop():Void; 74 | public static function take_unsupported(_:Unsupported<"(void *)0">, _:Unsupported<"(double)0">):Bool; 75 | } 76 | 77 | @:ammertest.code("native.h", 78 | LIB_EXPORT int take_0alt(void); 79 | ) 80 | @:ammertest.code("native.c", 81 | LIB_EXPORT int take_0alt(void) { 82 | return 0; 83 | } 84 | ) 85 | class TestSignatureNative2 extends ammer.def.Sublibrary { 86 | public static function take_0():Int; 87 | public static function take_0alt():Int; 88 | } 89 | 90 | @:ammer.nativePrefix("prefixed_") 91 | @:ammertest.code("native.h", 92 | LIB_EXPORT void prefixed_nop2(void); 93 | ) 94 | @:ammertest.code("native.c", 95 | LIB_EXPORT void prefixed_nop2(void) {} 96 | ) 97 | class TestSignatureNative3 extends ammer.def.Sublibrary { 98 | public static function nop2():Void; 99 | @:ammer.native("take_0") public static function take_0():Int; 100 | } 101 | 102 | class TestSignature extends Test { 103 | function testArgCount() { 104 | eq(TestSignatureNative.take_0(), 0); 105 | eq(TestSignatureNative.take_1(1), 1); 106 | eq(TestSignatureNative.take_2(1, 2), 2); 107 | eq(TestSignatureNative.take_3(1, 2, 3), 3); 108 | eq(TestSignatureNative.take_4(1, 2, 3, 4), 4); 109 | eq(TestSignatureNative.take_5(1, 2, 3, 4, 5), 5); 110 | eq(TestSignatureNative.take_6(1, 2, 3, 4, 5, 6), 6); 111 | eq(TestSignatureNative.take_7(1, 2, 3, 4, 5, 6, 7), 7); 112 | eq(TestSignatureNative.take_8(1, 2, 3, 4, 5, 6, 7, 8), 8); 113 | eq(TestSignatureNative.take_9(1, 2, 3, 4, 5, 6, 7, 8, 9), 9); 114 | eq(TestSignatureNative.take_10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 10); 115 | } 116 | 117 | function testVoid() { 118 | TestSignatureNative.nop(); 119 | noAssert(); 120 | } 121 | 122 | function testMultipleClasses() { 123 | eq(TestSignatureNative2.take_0(), 0); 124 | eq(TestSignatureNative2.take_0alt(), 0); 125 | } 126 | 127 | function testPrefix() { 128 | TestSignatureNative3.nop2(); 129 | eq(TestSignatureNative3.take_0(), 0); 130 | } 131 | 132 | function testUnsupported() { 133 | t(TestSignatureNative.take_unsupported()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/src/test/TestMaths.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import ammer.ffi.Int8; 4 | import ammer.ffi.Int16; 5 | import ammer.ffi.Int32; 6 | import ammer.ffi.Int64; 7 | import ammer.ffi.UInt8; 8 | import ammer.ffi.UInt16; 9 | import ammer.ffi.UInt32; 10 | import ammer.ffi.UInt64; 11 | 12 | @:ammertest.code("native.h", 13 | LIB_EXPORT int add_ints(int a, int b); 14 | LIB_EXPORT unsigned int add_uints(unsigned int a, unsigned int b); 15 | LIB_EXPORT float add_singles(float a, float b); 16 | LIB_EXPORT double add_floats(double a, double b); 17 | LIB_EXPORT bool logic_and(bool a, bool b); 18 | LIB_EXPORT bool logic_or(bool a, bool b); 19 | LIB_EXPORT int logic_ternary(bool a, int b, int c); 20 | 21 | LIB_EXPORT int8_t add_i8(int8_t a, int8_t b); 22 | LIB_EXPORT int16_t add_i16(int16_t a, int16_t b); 23 | LIB_EXPORT int32_t add_i32(int32_t a, int32_t b); 24 | LIB_EXPORT int64_t add_i64(int64_t a, int64_t b); 25 | LIB_EXPORT uint8_t add_u8(uint8_t a, uint8_t b); 26 | LIB_EXPORT uint16_t add_u16(uint16_t a, uint16_t b); 27 | LIB_EXPORT uint32_t add_u32(uint32_t a, uint32_t b); 28 | LIB_EXPORT uint64_t add_u64(uint64_t a, uint64_t b); 29 | ) 30 | @:ammertest.code("native.c", 31 | LIB_EXPORT int add_ints(int a, int b) { 32 | return a + b; 33 | } 34 | LIB_EXPORT unsigned int add_uints(unsigned int a, unsigned int b) { 35 | return a + b; 36 | } 37 | LIB_EXPORT float add_singles(float a, float b) { 38 | return a + b; 39 | } 40 | LIB_EXPORT double add_floats(double a, double b) { 41 | return a + b; 42 | } 43 | LIB_EXPORT bool logic_and(bool a, bool b) { 44 | return a && b; 45 | } 46 | LIB_EXPORT bool logic_or(bool a, bool b) { 47 | return a || b; 48 | } 49 | LIB_EXPORT int logic_ternary(bool a, int b, int c) { 50 | return a ? b : c; 51 | } 52 | 53 | LIB_EXPORT int8_t add_i8(int8_t a, int8_t b) { 54 | return a + b; 55 | } 56 | LIB_EXPORT int16_t add_i16(int16_t a, int16_t b) { 57 | return a + b; 58 | } 59 | LIB_EXPORT int32_t add_i32(int32_t a, int32_t b) { 60 | return a + b; 61 | } 62 | LIB_EXPORT int64_t add_i64(int64_t a, int64_t b) { 63 | return a + b; 64 | } 65 | LIB_EXPORT uint8_t add_u8(uint8_t a, uint8_t b) { 66 | return a + b; 67 | } 68 | LIB_EXPORT uint16_t add_u16(uint16_t a, uint16_t b) { 69 | return a + b; 70 | } 71 | LIB_EXPORT uint32_t add_u32(uint32_t a, uint32_t b) { 72 | return a + b; 73 | } 74 | LIB_EXPORT uint64_t add_u64(uint64_t a, uint64_t b) { 75 | return a + b; 76 | } 77 | ) 78 | class TestMathsNative extends ammer.def.Sublibrary { 79 | public static function add_ints(_:Int, _:Int):Int; 80 | public static function add_uints(_:UInt, _:UInt):UInt; 81 | #if !(lua || neko || js || python) 82 | public static function add_singles(_:Single, _:Single):Single; 83 | #end 84 | public static function add_floats(_:Float, _:Float):Float; 85 | public static function logic_and(_:Bool, _:Bool):Bool; 86 | public static function logic_or(_:Bool, _:Bool):Bool; 87 | public static function logic_ternary(_:Bool, _:Int, _:Int):Int; 88 | 89 | public static function add_i8(_:Int8, _:Int8):Int8; 90 | public static function add_i16(_:Int16, _:Int16):Int16; 91 | public static function add_i32(_:Int32, _:Int32):Int32; 92 | public static function add_i64(_:Int64, _:Int64):Int64; 93 | public static function add_u8(_:UInt8, _:UInt8):UInt8; 94 | public static function add_u16(_:UInt16, _:UInt16):UInt16; 95 | public static function add_u32(_:UInt32, _:UInt32):UInt32; 96 | public static function add_u64(_:UInt64, _:UInt64):UInt64; 97 | } 98 | 99 | class TestMaths extends Test { 100 | function testInts() { 101 | eq(TestMathsNative.add_ints(0, 0), 0); 102 | eq(TestMathsNative.add_ints(1, 2), 3); 103 | eq(TestMathsNative.add_ints(-1, 1), 0); 104 | eq(TestMathsNative.add_ints(0xFFFFFFFF, 1), 0); 105 | eq(TestMathsNative.add_ints(0x7F000000, 0xFFFFFF), 0x7FFFFFFF); 106 | eq(TestMathsNative.add_ints(-0x7FFFFFFF, 0x7FFFFFFF), 0); 107 | } 108 | 109 | function testUInts() { 110 | eq(TestMathsNative.add_uints(0, 0), 0); 111 | eq(TestMathsNative.add_uints(1, 2), 3); 112 | eq(TestMathsNative.add_uints(0x7F000000, 0xFFFFFF), 0x7FFFFFFF); 113 | // RHS is 0xFFFFFFFE but Haxe compiles the literal to -2 114 | eq(TestMathsNative.add_uints(0x7FFFFFFF, 0x7FFFFFFF), ((0x7FFFFFFF + 0x7FFFFFFF): UInt)); 115 | } 116 | 117 | #if !(lua || neko || js || python) 118 | function testSingles() { 119 | eq(TestMathsNative.add_singles(0., 0.), (0.:Single)); 120 | feq(TestMathsNative.add_singles(1., 2.), (3.:Single)); 121 | feq(TestMathsNative.add_singles(-1., 1.), (0.:Single)); 122 | feq(TestMathsNative.add_singles(-1e10, 1e10), (0.:Single)); 123 | feq(TestMathsNative.add_singles(-1e10, 1e9), (-9e9:Single)); 124 | } 125 | #end 126 | 127 | function testFloats() { 128 | eq(TestMathsNative.add_floats(0., 0.), 0.); 129 | feq(TestMathsNative.add_floats(1., 2.), 3.); 130 | feq(TestMathsNative.add_floats(-1., 1.), 0.); 131 | feq(TestMathsNative.add_floats(-1e10, 1e10), 0.); 132 | feq(TestMathsNative.add_floats(-1e10, 1e9), -9e9); 133 | } 134 | 135 | function testBools() { 136 | eq(TestMathsNative.logic_and(false, false), false); 137 | eq(TestMathsNative.logic_and(true, false), false); 138 | eq(TestMathsNative.logic_and(false, true), false); 139 | eq(TestMathsNative.logic_and(true, true), true); 140 | eq(TestMathsNative.logic_or(false, false), false); 141 | eq(TestMathsNative.logic_or(true, false), true); 142 | eq(TestMathsNative.logic_or(false, true), true); 143 | eq(TestMathsNative.logic_or(true, true), true); 144 | eq(TestMathsNative.logic_ternary(true, 3, 5), 3); 145 | eq(TestMathsNative.logic_ternary(false, 3, 5), 5); 146 | } 147 | 148 | function testBitWidths() { 149 | eq(TestMathsNative.add_u8(142, 193), 79); 150 | eq(TestMathsNative.add_u16(25679, 49565), 9708); 151 | eq(TestMathsNative.add_u32(0xBF86404F, 0xBF863D5D), 0x7F0C7DAC); 152 | var a = haxe.Int64.make(0xBBFBCDC4, 0x2397F34F); 153 | var b = haxe.Int64.make(0x5ADF2061, 0x3E99B3E1); 154 | var c = haxe.Int64.make(0x16DAEE25, 0x6231A730); 155 | t(TestMathsNative.add_u64(a, b) == c); // see #10760 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/ammer/internal/Utils.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type; 8 | import ammer.internal.Reporting.currentPos; 9 | import ammer.internal.Reporting.resolveType; 10 | 11 | using Lambda; 12 | 13 | class Utils { 14 | // These methods are inserted into `Lib.macro.hx` when baking a library. 15 | // This avoids code duplication/synchronisation issues. Importantly, the code 16 | // is just string-pasted, so it is important that the `import`s that are 17 | // in `Lib.macro.baked.hx` are sufficient for the code to work. 18 | 19 | // ammer-fragment-begin: lib-baked 20 | public static function access(t:{pack:Array, module:String, name:String}, ?field:String):Array { 21 | return t.pack.concat([t.module.split(".").pop(), t.name]).concat(field != null ? [field] : []); 22 | } 23 | 24 | public static function accessTp(t:TypePath):Array { 25 | return t.pack.concat([t.name]).concat(t.sub != null ? [t.sub] : []); 26 | } 27 | 28 | public static function complexTypeExpr(e:Expr):Null { 29 | function helper(e:Expr, fallback:Bool):Null { 30 | return (switch (e.expr) { 31 | case EParenthesis(e): complexTypeExpr(e); 32 | case ECheckType(_, ct): ct; 33 | case _ if (!fallback): 34 | // TODO: kind of a hack 35 | var str = new haxe.macro.Printer().printExpr(e); 36 | helper(Context.parse('(_ : $str)', e.pos), true); 37 | case _: null; 38 | }); 39 | } 40 | return helper(e, false); 41 | } 42 | 43 | public static function isNull(e:Expr):Bool { 44 | return e == null || e.expr.match(EConst(CIdent("null"))); 45 | } 46 | 47 | // FFI type resolution allows some convenience shortcuts (e.g. Haxe `Int` is 48 | // the same as `ammer.ffi.Int32`). To avoid conflicts with multiple type IDs 49 | // resolving to the same thing, this function normalises the Haxe shortcuts 50 | // back to the `ammer.ffi.*` equivalents. 51 | public static function normaliseTypeId(id:String):String { 52 | return (switch (id) { 53 | case ".StdTypes.Void": "ammer.ffi.Void.Void"; 54 | case ".StdTypes.Bool": "ammer.ffi.Bool.Bool"; 55 | case ".UInt.UInt": "ammer.ffi.UInt32.UInt32"; 56 | case ".StdTypes.Int": "ammer.ffi.Int32.Int32"; 57 | case "haxe.Int64.Int64": "ammer.ffi.Int64.Int64"; 58 | case ".StdTypes.Single": "ammer.ffi.Float32.Float32"; 59 | case ".StdTypes.Float": "ammer.ffi.Float64.Float64"; 60 | case ".String.String": "ammer.ffi.String.String"; 61 | 62 | // platform specific 63 | case "cs.StdTypes.UInt8": "ammer.ffi.UInt8.UInt8"; 64 | case "hl.UI16.UI16": "ammer.ffi.UInt16.UInt16"; 65 | case "java.StdTypes.Int8": "ammer.ffi.Int8.Int8"; 66 | case "java.StdTypes.Char16": "ammer.ffi.UInt16.UInt16"; 67 | case "java.StdTypes.Int16": "ammer.ffi.Int16.Int16"; 68 | 69 | case _: id; 70 | }); 71 | } 72 | 73 | public static function triggerTyping(ct:ComplexType):Null { 74 | var type = resolveType(ct, currentPos()); 75 | return (switch (type) { 76 | case TInst(ref, _): ref.get(); 77 | case _: null; 78 | }); 79 | } 80 | 81 | public static function typeId(t:{pack:Array, module:String, name:String}):String { 82 | return normaliseTypeId('${t.pack.join(".")}.${t.module.split(".").pop()}.${t.name}'); 83 | } 84 | 85 | public static function typeId2(t:Type):String { 86 | return normaliseTypeId(switch (t) { 87 | case TInst(_.get() => t, _): '${t.pack.join(".")}.${t.module.split(".").pop()}.${t.name}'; 88 | case TAbstract(_.get() => t, _): '${t.pack.join(".")}.${t.module.split(".").pop()}.${t.name}'; 89 | case TFun(args, ret): '(${args.map(arg -> typeId2(arg.t)).join(",")})->${typeId2(ret)}'; 90 | case TType(_.get() => t, []): typeId2(t.type); 91 | case TDynamic(_): ".Any.Any"; // ? 92 | case _: trace(t); throw 0; 93 | }); 94 | } 95 | 96 | public static function typeIdCt(ct:ComplexType):String { 97 | return normaliseTypeId(switch (ct) { 98 | case TPath(tp): '${tp.pack.join(".")}.${tp.name}.${tp.sub == null ? tp.name : tp.sub}'; 99 | case _: throw 0; 100 | }); 101 | } 102 | 103 | public static function expectTypePath(ct:ComplexType):TypePath { 104 | return (switch (ct) { 105 | case TPath(tp): tp; 106 | case _: throw 0; 107 | }); 108 | } 109 | // ammer-fragment-end: lib-baked 110 | 111 | public static function typeIdTp(tp:TypePath):String { 112 | return normaliseTypeId('${tp.pack.join(".")}.${tp.name}.${tp.sub == null ? tp.name : tp.sub}'); 113 | } 114 | 115 | public static function exprOfType(ty:Type):Null { 116 | return (switch (ty) { 117 | case TInst(_.get() => {kind: KExpr(expr)}, []): expr; 118 | case _: null; 119 | }); 120 | } 121 | 122 | public static function exprArrayOfType(ty:Type):Null> { 123 | return (switch (ty) { 124 | case TInst(_.get() => {kind: KExpr({expr: EArrayDecl(exprs)})}, []): exprs; 125 | case _: null; 126 | }); 127 | } 128 | 129 | public static function stringOfParam(ty:Type):Null { 130 | return (switch (ty) { 131 | case TInst(_.get() => {kind: KExpr({expr: EConst(CString(val))})}, []): val; 132 | case _: null; 133 | }); 134 | } 135 | 136 | public static function stringArrayOfParam(ty:Type):Null> { 137 | return (switch (ty) { 138 | case TInst(_.get() => {kind: KExpr({expr: EArrayDecl(vals)})}, []): 139 | [ for (val in vals) switch (val.expr) { 140 | case EConst(CString(val)): val; 141 | case _: return null; 142 | } ]; 143 | case _: null; 144 | }); 145 | } 146 | 147 | public static function classOfParam(tp:Type):Null> { 148 | return (switch (tp) { 149 | case TInst(lib, []): lib; 150 | case _: null; 151 | }); 152 | } 153 | 154 | public static function funOfParam(tp:Type):Null<{args:Array<{t:Type, opt:Bool, name:String}>, ret:Type}> { 155 | return (switch (tp) { 156 | case TFun(args, ret): {args: args, ret: ret}; 157 | case _: null; 158 | }); 159 | } 160 | 161 | public static function updateField(field:Field, kind:FieldType):Field { 162 | return { 163 | pos: field.pos, 164 | name: field.name, 165 | meta: [], 166 | kind: kind, 167 | doc: field.doc, 168 | access: field.access, 169 | }; 170 | } 171 | 172 | public static function exprMap(e:Expr, op:NullExpr>):Expr { 173 | if (op == null) 174 | return e; 175 | return op(e); 176 | } 177 | 178 | public static var definedTypes:Array = []; 179 | public static function defineType(tdef:TypeDefinition):Void { 180 | definedTypes.push(tdef); 181 | Context.defineType(tdef); 182 | } 183 | 184 | public static var modifiedTypes:Array<{t:ClassType, fields:Array}> = []; 185 | public static function modifyType(t:ClassType, fields:Array):Array { 186 | modifiedTypes.push({t: t, fields: fields}); 187 | return fields; 188 | } 189 | } 190 | 191 | #end 192 | -------------------------------------------------------------------------------- /test/src/test/TestCallback.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import ammer.ffi.Callback; 4 | import ammer.ffi.Haxe; 5 | import ammer.ffi.Int32; 6 | 7 | @:build(ammer.def.Enum.build("enum enum_constants_cb", ammer.ffi.Int32, TestCallbackNative)) 8 | enum abstract NativeEnum2(Int) from Int to Int { 9 | @:ammer.native("e_const_cb0") var EConst0; 10 | @:ammer.native("e_const_cb1") var EConst1; 11 | @:ammer.native("e_const_cb10") var EConst10; 12 | } 13 | 14 | @:ammer.alloc 15 | // TODO: this should be added automatically on cpp-static 16 | @:headerCode('#include "/DevProjects/Repos/ammer/test/native-src/native.h"') 17 | class NativeCallbackData extends ammer.def.Struct<"callback_data_t", TestCallbackNative> { 18 | public var user_data:Haxe<(NativeCallbackData)->Int>; 19 | public var foo:Int; 20 | } 21 | 22 | @:ammer.sub((_ : test.TestCallback.NativeCallbackData)) 23 | @:ammertest.code("native.h", 24 | enum enum_constants_cb { 25 | e_const_cb0 = 0, 26 | e_const_cb1 = 1, 27 | e_const_cb10 = 10 28 | }; 29 | 30 | typedef struct { 31 | void *user_data; 32 | int foo; 33 | } callback_data_t; 34 | LIB_EXPORT void save_func(int (* func)(int, int, void*), void *user_data); 35 | LIB_EXPORT int call_func(void); 36 | LIB_EXPORT int call_func_2(void *user_data, int (* func)(void *, const char *)); 37 | LIB_EXPORT int call_func_3(void *user_data, int (* func)(callback_data_t *)); 38 | LIB_EXPORT bool call_func_4(void *user_data, enum enum_constants_cb (* func)(void *, enum enum_constants_cb)); 39 | 40 | LIB_EXPORT void save_static_func(const char* (* func)(int, const char*)); 41 | LIB_EXPORT const char* call_static_func(int, const char*); 42 | ) 43 | @:ammertest.code("native.c", 44 | static int (* cached_func)(int, int, void *); 45 | static void *cached_user_data; 46 | LIB_EXPORT void save_func(int (* func)(int, int, void *), void *user_data) { 47 | cached_func = func; 48 | cached_user_data = user_data; 49 | } 50 | LIB_EXPORT int call_func(void) { 51 | return cached_func(1, 2, cached_user_data); 52 | } 53 | LIB_EXPORT int call_func_2(void *user_data, int (* func)(void *, const char *)) { 54 | return func(user_data, "foobar") * 2; 55 | } 56 | static callback_data_t call_func_3_data; 57 | LIB_EXPORT int call_func_3(void *user_data, int (* func)(callback_data_t *)) { 58 | call_func_3_data.user_data = user_data; 59 | call_func_3_data.foo = 59; 60 | return func(&call_func_3_data); 61 | } 62 | LIB_EXPORT bool call_func_4(void *user_data, enum enum_constants_cb (* func)(void *, enum enum_constants_cb)) { 63 | return func(user_data, e_const_cb1) == e_const_cb10; 64 | } 65 | 66 | static const char* (* cached_static_func)(int, const char*); 67 | LIB_EXPORT void save_static_func(const char* (* func)(int, const char*)) { 68 | cached_static_func = func; 69 | } 70 | LIB_EXPORT const char* call_static_func(int a, const char* b) { 71 | return cached_static_func(a, b); 72 | } 73 | ) 74 | class TestCallbackNative extends ammer.def.Sublibrary { 75 | public static function save_func( 76 | _:ammer.ffi.Callback< 77 | (Int32, Int32, Haxe<(Int, Int)->Int>)->Int32, 78 | (Int, Int)->Int, 79 | [arg2], 80 | [arg0, arg1], 81 | TestCallbackNative 82 | >, 83 | _:Haxe<(Int, Int)->Int> 84 | ):Void; 85 | public static function call_func():Int; 86 | public static function call_func_2( 87 | _:Haxe<(String)->Int>, 88 | _:Callback< 89 | (Haxe<(String)->Int>, String)->Int32, 90 | (String)->Int32, 91 | [arg0], 92 | [arg1], 93 | TestCallbackNative 94 | > 95 | ):Int32; 96 | public static function call_func_3( 97 | _:Haxe<(NativeCallbackData)->Int>, 98 | _:Callback< 99 | (NativeCallbackData)->Int32, 100 | (NativeCallbackData)->Int32, 101 | [arg0.user_data], 102 | [arg0], 103 | TestCallbackNative 104 | > 105 | ):Int32; 106 | public static function call_func_4( 107 | _:Haxe<(NativeEnum2)->NativeEnum2>, 108 | _:Callback< 109 | (Haxe<(NativeEnum2)->NativeEnum2>, NativeEnum2)->NativeEnum2, 110 | (NativeEnum2)->NativeEnum2, 111 | [arg0], 112 | [arg1], 113 | TestCallbackNative 114 | > 115 | ):Bool; 116 | 117 | public static function save_static_func( 118 | _:Callback< 119 | (Int32, ammer.ffi.String) -> ammer.ffi.String, 120 | (Int, String) -> String, 121 | "global", 122 | [arg0, arg1], 123 | TestCallbackNative 124 | > 125 | ):Void; 126 | public static function call_static_func(a:Int, b:String):String; 127 | } 128 | 129 | class TestCallback extends Test implements ammer.Syntax { 130 | var wasCalled = false; 131 | var counterSet = false; 132 | var callA = -1; 133 | var callB = -1; 134 | 135 | function callback(a:Int, b:Int):Int { 136 | wasCalled = true; 137 | callA = a; 138 | callB = b; 139 | return a + b; 140 | } 141 | 142 | function createClosure():((Int, Int)->Int) { 143 | var counter = 0; 144 | return ((a, b) -> { 145 | counter++; 146 | if (counter >= 3) 147 | counterSet = true; 148 | return a + b; 149 | }); 150 | } 151 | 152 | function testCallback() { 153 | wasCalled = false; 154 | var clRef = ammer.Lib.createHaxeRef((_ : (Int, Int)->Int), callback); 155 | clRef.incref(); 156 | TestCallbackNative.save_func(clRef); 157 | eq(wasCalled, false); 158 | eq(TestCallbackNative.call_func(), 3); 159 | clRef.decref(); 160 | eq(wasCalled, true); 161 | eq(callA, 1); 162 | eq(callB, 2); 163 | 164 | var clRef = ammer.Lib.createHaxeRef((_ : (Int, Int)->Int), (a:Int, b:Int) -> { 165 | wasCalled = true; 166 | callA = a; 167 | callB = b; 168 | a + b; 169 | }); 170 | wasCalled = false; 171 | clRef.incref(); 172 | TestCallbackNative.save_func(clRef); 173 | eq(wasCalled, false); 174 | eq(TestCallbackNative.call_func(), 3); 175 | clRef.decref(); 176 | eq(wasCalled, true); 177 | eq(callA, 1); 178 | eq(callB, 2); 179 | 180 | wasCalled = false; 181 | var clRef = ammer.Lib.createHaxeRef((_ : (String)->Int), (x:String) -> { 182 | wasCalled = true; 183 | eq(x, "foobar"); 184 | 2; 185 | }); 186 | clRef.incref(); 187 | eq(TestCallbackNative.call_func_2(clRef), 4); 188 | clRef.decref(); 189 | eq(wasCalled, true); 190 | 191 | counterSet = false; 192 | var clRef2 = ammer.Lib.createHaxeRef((_ : (Int, Int)->Int), createClosure()); 193 | clRef2.incref(); 194 | TestCallbackNative.save_func(clRef2); 195 | eq(TestCallbackNative.call_func(), 3); 196 | eq(TestCallbackNative.call_func(), 3); 197 | eq(TestCallbackNative.call_func(), 3); 198 | clRef2.decref(); 199 | eq(counterSet, true); 200 | 201 | wasCalled = false; 202 | var clRef = ammer.Lib.createHaxeRef((_ : NativeCallbackData -> Int), (data:NativeCallbackData) -> { 203 | eq(data.foo, 59); 204 | wasCalled = true; 205 | 77; 206 | }); 207 | clRef.incref(); 208 | eq(TestCallbackNative.call_func_3(clRef), 77); 209 | clRef.decref(); 210 | eq(wasCalled, true); 211 | 212 | wasCalled = false; 213 | var clRef = ammer.Lib.createHaxeRef((_ : NativeEnum2 -> NativeEnum2), (data:NativeEnum2) -> { 214 | eq(data, NativeEnum2.EConst1); 215 | wasCalled = true; 216 | NativeEnum2.EConst10; 217 | }); 218 | clRef.incref(); 219 | TestCallbackNative.call_func_4(clRef); 220 | clRef.decref(); 221 | eq(wasCalled, true); 222 | } 223 | 224 | static function staticCallback(a:Int, b:String):String { 225 | return '$a$b'; 226 | } 227 | 228 | function testStaticCallback():Void { 229 | TestCallbackNative.save_static_func(staticCallback); 230 | eq(TestCallbackNative.call_static_func(2, "foo"), "2foo"); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/ammer/internal/LibContext.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type; 8 | 9 | typedef LibContextOptions = { 10 | name:String, 11 | headers:Array, 12 | defines:Array, 13 | definesCodeOnly:Array, 14 | includePaths:Array<{rel:String, abs:String}>, 15 | libraryPaths:Array<{rel:String, abs:String}>, 16 | frameworks:Array, 17 | language:ammer.core.LibraryLanguage, 18 | linkNames:Array, 19 | }; 20 | 21 | class LibContext { 22 | public var name:String; 23 | public var originalOptions:LibContextOptions; 24 | public var libraryOptions:ammer.core.LibraryConfig; 25 | public var library:ammer.core.Library; 26 | public var marshal:ammer.core.Marshal; 27 | public var headers:Array; 28 | public var prebuild:Array<{ 29 | code:String, 30 | process:String->Void, 31 | }> = []; 32 | public var done:Bool = false; 33 | 34 | public var info = new ammer.internal.v1.LibInfo(); 35 | public var infoV1:Null; 36 | 37 | public var isLibTypes:Bool = false; 38 | 39 | public function new(name:String, options:LibContextOptions) { 40 | this.name = name; 41 | originalOptions = options; 42 | 43 | var libName = 'ammer_${options.name}'; 44 | libraryOptions = (switch (Context.definedValue("target.name")) { 45 | case "cpp": ({name: libName} : ammer.core.plat.Cpp.CppLibraryConfig); 46 | case "cs": ({name: libName} : ammer.core.plat.Cs.CsLibraryConfig); 47 | case "hl": ({name: libName} : ammer.core.plat.Hashlink.HashlinkLibraryConfig); 48 | case "java": ({ 49 | name: libName, 50 | jvm: Context.defined("jvm"), 51 | } : ammer.core.plat.Java.JavaLibraryConfig); 52 | case "lua": ({name: libName} : ammer.core.plat.Lua.LuaLibraryConfig); 53 | case "neko": ({name: libName} : ammer.core.plat.Neko.NekoLibraryConfig); 54 | case "js": ({name: libName} : ammer.core.plat.Nodejs.NodejsLibraryConfig); 55 | case "python": ({name: libName} : ammer.core.plat.Python.PythonLibraryConfig); 56 | 57 | case _: ({name: libName} : ammer.core.plat.None.NoneLibraryConfig); 58 | }); 59 | 60 | libraryOptions.language = options.language; 61 | libraryOptions.defines = options.defines; 62 | libraryOptions.definesCodeOnly = options.definesCodeOnly; 63 | libraryOptions.includePaths = options.includePaths.map(p -> p.abs); 64 | libraryOptions.libraryPaths = options.libraryPaths.map(p -> p.abs); 65 | libraryOptions.frameworks = options.frameworks; 66 | libraryOptions.linkNames = options.linkNames; 67 | libraryOptions.pos = Reporting.currentPos(); 68 | 69 | // process configuration defines 70 | // TODO: -D for defines 71 | var prefix = 'ammer.lib.${options.name}'; 72 | if (Config.hasDefine('$prefix.frameworks')) 73 | libraryOptions.frameworks = Config.getStringArray('$prefix.frameworks', ","); 74 | if (Config.hasDefine('$prefix.linkNames')) 75 | libraryOptions.linkNames = Config.getStringArray('$prefix.linkNames', ","); 76 | if (Config.hasDefine('$prefix.includePaths')) 77 | libraryOptions.includePaths = Config.getStringArray('$prefix.includePaths', ","); 78 | if (Config.hasDefine('$prefix.libraryPaths')) 79 | libraryOptions.libraryPaths = Config.getStringArray('$prefix.libraryPaths', ","); 80 | if (Config.hasDefine('$prefix.language')) 81 | libraryOptions.language = Config.getEnum('$prefix.language', [ 82 | "c" => ammer.core.LibraryLanguage.C, 83 | "cpp" => Cpp, 84 | "objc" => ObjectiveC, 85 | "objcpp" => ObjectiveCpp, 86 | ], C); 87 | 88 | // process includes 89 | if (Config.hasDefine('$prefix.headers') 90 | || Config.hasDefine('$prefix.headers.includeLocal') 91 | || Config.hasDefine('$prefix.headers.includeGlobal') 92 | || Config.hasDefine('$prefix.headers.importLocal') 93 | || Config.hasDefine('$prefix.headers.importGlobal')) { 94 | headers = []; 95 | if (Config.hasDefine('$prefix.headers')) 96 | headers = headers.concat(Config.getStringArray('$prefix.headers', ",").map(ammer.core.SourceInclude.IncludeLocal)); 97 | if (Config.hasDefine('$prefix.headers.includeLocal')) 98 | headers = headers.concat(Config.getStringArray('$prefix.headers.includeLocal', ",").map(ammer.core.SourceInclude.IncludeLocal)); 99 | if (Config.hasDefine('$prefix.headers.includeGlobal')) 100 | headers = headers.concat(Config.getStringArray('$prefix.headers.includeGlobal', ",").map(ammer.core.SourceInclude.IncludeGlobal)); 101 | if (Config.hasDefine('$prefix.headers.importLocal')) 102 | headers = headers.concat(Config.getStringArray('$prefix.headers.importLocal', ",").map(ammer.core.SourceInclude.ImportLocal)); 103 | if (Config.hasDefine('$prefix.headers.importGlobal')) 104 | headers = headers.concat(Config.getStringArray('$prefix.headers.importGlobal', ",").map(ammer.core.SourceInclude.ImportGlobal)); 105 | } else { 106 | headers = options.headers; 107 | } 108 | 109 | library = Ammer.platform.createLibrary(libraryOptions); 110 | for (header in headers) library.addInclude(header); 111 | marshal = library.marshal(); 112 | } 113 | 114 | public function prebuildImmediate(code:String):String { 115 | // TODO: cache 116 | var ret:String = null; 117 | var program = new ammer.core.utils.LineBuf() 118 | .lmap(headers, header -> header.toCode()) 119 | .a(code) 120 | .done(); 121 | Ammer.builder.build(new ammer.core.build.BuildProgram([ 122 | BOAlways(File('${Ammer.baseConfig.buildPath}/ammer_${name}'), EnsureDirectory), 123 | BOAlways( 124 | File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.c'), // TODO: append cache key or delete? 125 | WriteContent(program) 126 | ), 127 | BODependent( 128 | File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.o'), // TODO: should be .obj on MSVC 129 | File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.c'), 130 | CompileObject(C, { 131 | defines: libraryOptions.defines, 132 | includePaths: libraryOptions.includePaths, 133 | }) 134 | ), 135 | BODependent( 136 | File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild'), 137 | File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.o'), 138 | LinkExecutable(C, { 139 | defines: libraryOptions.defines, 140 | libraryPaths: libraryOptions.libraryPaths, // unnecessary? 141 | libraries: [], 142 | linkName: null, 143 | }) 144 | ), 145 | BOAlways( 146 | File(""), 147 | Command('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild', [], (code, process) -> { 148 | code == 0 || throw 0; 149 | ret = process.stdout.readAll().toString(); 150 | }) 151 | ), 152 | ])); 153 | return ret; 154 | } 155 | 156 | public function finalise():Void { 157 | Ammer.libraries.active.remove(this); 158 | 159 | if (prebuild.length > 0) { 160 | // var program = new ammer.core.utils.LineBuf(); 161 | // program 162 | // .ail("int main() {}"); 163 | // Ammer.builder.build(new ammer.core.BuildProgram([ 164 | // BOAlways(File('${Ammer.baseConfig.buildPath}/ammer_${name}'), EnsureDirectory), 165 | // BOAlways( 166 | // File('${Ammer.baseConfig.buildPath}/ammer_${name}/prebuild.c'), 167 | // WriteContent(program.done()) 168 | // ), 169 | // ])); 170 | } 171 | 172 | // the latest version is always generated, older ones can be generated on 173 | // demand by baked libraries using defines 174 | //if (Context.defined("ammer_baked_v1_needed")) { infoVX = ... } 175 | infoV1 = info; 176 | done = true; 177 | Ammer.platform.addLibrary(library); 178 | } 179 | 180 | public function toString():String { 181 | return 'LibContext($name)'; 182 | } 183 | } 184 | 185 | #end 186 | -------------------------------------------------------------------------------- /test/src/test/TestDatatypes.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import ammer.ffi.This; 4 | 5 | // TODO: "opaque" is now a misnomer 6 | @:ammer.nativePrefix("opaque_") 7 | @:ammer.alloc 8 | class NativeOpaque extends ammer.def.Struct<"opaque_type_t", def.Native> { 9 | @:ammer.native("member_int") public var member_int:Int; 10 | @:ammer.native("member_float") public var member_float:Float; 11 | @:ammer.native("member_string") public var member_string:String; 12 | /* 13 | @:ammer.native("member_int_array_fixed") public var member_int_array_fixed:ammer.ffi.ArrayFixed; 14 | #if (hl || cpp) 15 | @:ammer.native("member_int_array") public var member_int_array:ammer.ffi.ArrayDynamic; 16 | @:ammer.native("member_int_array_size") public var member_int_array_size:ammer.ffi.SizeOf<"member_int_array">; 17 | @:ammer.native("member_string_array") public var member_string_array:ammer.ffi.ArrayDynamic; 18 | @:ammer.native("member_string_array_size") public var member_string_array_size:ammer.ffi.SizeOf<"member_string_array">; 19 | #end 20 | */ 21 | public function get_int(_:This):Int; 22 | public function get_float(_:This):Float; 23 | public function get_string(_:This):String; 24 | public function get_int_alt(_:Int, _:This, _:Int):Int; 25 | //public function get_bytes(_:This, _:SizeOfReturn):Bytes; 26 | 27 | public function get_int_nested(_:ammer.ffi.Deref):Int; 28 | } 29 | /* 30 | class NativeOpaque2 extends ammer.def.PointerNoStar<"opaque_type_ptr", def.Native> { 31 | @:ammer.native("opaque_get_int") public function get_int(_:This):Int; 32 | } 33 | */ 34 | 35 | @:ammer.sub((_ : test.TestDatatypes.NativeOpaque)) 36 | // @:ammer.sub((_ : test.TestDatatypes.NativeOpaque2)) 37 | @:ammertest.code("native.h", 38 | typedef struct { 39 | int member_int; 40 | double member_float; 41 | const char *member_string; 42 | 43 | int member_int_array_fixed[8]; 44 | int *member_int_array; 45 | int member_int_array_size; 46 | const char **member_string_array; 47 | int member_string_array_size; 48 | } opaque_type_t; 49 | typedef opaque_type_t *opaque_type_ptr; 50 | 51 | LIB_EXPORT opaque_type_ptr create_opaque(void); 52 | LIB_EXPORT int opaque_get_int(opaque_type_ptr a); 53 | LIB_EXPORT int opaque_get_int_nested(opaque_type_t a); 54 | LIB_EXPORT double opaque_get_float(opaque_type_ptr a); 55 | LIB_EXPORT const char *opaque_get_string(opaque_type_ptr a); 56 | LIB_EXPORT int opaque_get_int_alt(int a, opaque_type_ptr b, int c); 57 | LIB_EXPORT unsigned char *opaque_get_bytes(opaque_type_ptr a, size_t *b); 58 | LIB_EXPORT void opaque_indirect(opaque_type_ptr *out); 59 | LIB_EXPORT opaque_type_t create_opaque_noalloc(void); 60 | LIB_EXPORT bool opaque_take_nested(opaque_type_t a); 61 | ) 62 | @:ammertest.code("native.c", 63 | LIB_EXPORT opaque_type_ptr create_opaque(void) { 64 | opaque_type_ptr ret = malloc(sizeof(opaque_type_t)); 65 | ret->member_int = 1; 66 | ret->member_float = 2.0f; 67 | ret->member_string = "3"; 68 | for (int i = 0; i < 8; i++) { 69 | ret->member_int_array_fixed[i] = 0xB0057ED + i; 70 | } 71 | ret->member_int_array = (int *)calloc(17, sizeof(int)); 72 | ret->member_int_array_size = 17; 73 | for (int i = 0; i < 17; i++) { 74 | ret->member_int_array[i] = 0xB00573D + i; 75 | } 76 | ret->member_string_array = (const char **)calloc(3, sizeof(char *)); 77 | ret->member_string_array_size = 3; 78 | ret->member_string_array[0] = "arrfoo"; 79 | ret->member_string_array[1] = "arrbar"; 80 | ret->member_string_array[2] = "arrbaz"; 81 | return ret; 82 | } 83 | LIB_EXPORT int opaque_get_int(opaque_type_ptr a) { 84 | return a->member_int; 85 | } 86 | LIB_EXPORT int opaque_get_int_nested(opaque_type_t a) { 87 | return a.member_int; 88 | } 89 | LIB_EXPORT double opaque_get_float(opaque_type_ptr a) { 90 | return a->member_float; 91 | } 92 | LIB_EXPORT const char *opaque_get_string(opaque_type_ptr a) { 93 | return a->member_string; 94 | } 95 | LIB_EXPORT int opaque_get_int_alt(int a, opaque_type_ptr b, int c) { 96 | return a + b->member_int + c; 97 | } 98 | LIB_EXPORT unsigned char *opaque_get_bytes(opaque_type_ptr a, size_t *b) { 99 | size_t len = strlen(a->member_string); 100 | unsigned char *ret = malloc(len); 101 | memcpy(ret, a->member_string, len); 102 | *b = len; 103 | return ret; 104 | } 105 | LIB_EXPORT void opaque_indirect(opaque_type_ptr *out) { 106 | opaque_type_ptr ret = malloc(sizeof(opaque_type_t)); 107 | ret->member_int = 10; 108 | ret->member_float = 4.0f; 109 | ret->member_string = "indirect"; 110 | *out = ret; 111 | } 112 | LIB_EXPORT opaque_type_t create_opaque_noalloc(void) { 113 | return (opaque_type_t){ 114 | .member_int = 61, 115 | .member_float = 5.2f, 116 | .member_string = "noalloc", 117 | .member_int_array_fixed = {9, 10, 11, 12, 13, 14, 15, 16}, 118 | .member_int_array = NULL, 119 | .member_int_array_size = 0, 120 | .member_string_array = NULL, 121 | .member_string_array_size = 0, 122 | }; 123 | } 124 | LIB_EXPORT bool opaque_take_nested(opaque_type_t a) { 125 | float diff = a.member_float - 5.4f; 126 | return a.member_int == 62 127 | && (diff > -.0001f && diff < .0001f) 128 | && strcmp(a.member_string, "noalloc") == 0; 129 | //&& a.member_int_array_fixed[7] == 47 130 | //&& a.member_int_array == NULL 131 | //&& a.member_string_array == NULL; 132 | } 133 | ) 134 | class TestDatatypesNative extends ammer.def.Sublibrary { 135 | public static function create_opaque():NativeOpaque; 136 | //@:ammer.native("create_opaque") public static function create_opaque2():NativeOpaque2; 137 | 138 | public static function opaque_indirect(_:ammer.ffi.Box):Void; 139 | public static function create_opaque_noalloc():ammer.ffi.Alloc; 140 | public static function opaque_take_nested(a:ammer.ffi.Deref):Bool; 141 | } 142 | 143 | class TestDatatypes extends Test { 144 | function testOpaque() { 145 | var opaque = TestDatatypesNative.create_opaque(); 146 | 147 | eq(opaque.get_int(), 1); 148 | feq(opaque.get_float(), 2.0); 149 | eq(opaque.get_string(), "3"); 150 | eq(opaque.get_int_alt(3, 4), 8); 151 | /* 152 | var opaque = TestDatatypesNative.create_opaque2(); 153 | eq(opaque.get_int(), 1);*/ 154 | } 155 | 156 | function testVariables() { 157 | var opaque = TestDatatypesNative.create_opaque(); 158 | opaque.member_int = 3; 159 | eq(opaque.get_int(), 3); 160 | opaque.member_int = 5; 161 | eq(opaque.member_int, 5); 162 | opaque.member_float = 3.12; 163 | feq(opaque.get_float(), 3.12); 164 | opaque.member_float = 5.12; 165 | feq(opaque.member_float, 5.12); 166 | // passing strings directly might be a bit dangerous 167 | opaque.member_string = "foo"; 168 | eq(opaque.get_string(), "foo"); 169 | opaque.member_string = "bar"; 170 | eq(opaque.member_string, "bar"); 171 | //beq(opaque.get_bytes(), haxe.io.Bytes.ofHex("626172")); 172 | } 173 | 174 | function testAlloc() { 175 | var opaque = NativeOpaque.alloc(); 176 | opaque.member_int = 7; 177 | eq(opaque.get_int(), 7); 178 | opaque.member_int = 49; 179 | eq(opaque.member_int, 49); 180 | opaque.free(); 181 | } 182 | 183 | function testOutPointer() { 184 | var opaqueBox = ammer.Lib.allocBox(NativeOpaque); 185 | TestDatatypesNative.opaque_indirect(opaqueBox); 186 | var opaque = opaqueBox.get(); 187 | eq(opaque.member_int, 10); 188 | feq(opaque.member_float, 4.0); 189 | eq(opaque.member_string, "indirect"); 190 | opaque.free(); 191 | } 192 | 193 | function testNested() { 194 | var opaque = TestDatatypesNative.create_opaque_noalloc(); 195 | eq(opaque.get_int(), 61); 196 | feq(opaque.get_float(), 5.2); 197 | eq(opaque.get_string(), "noalloc"); 198 | //for (i in 0...8) { 199 | // eq(opaque.member_int_array_fixed[i], 9 + i); 200 | //} 201 | opaque.member_int = 62; 202 | eq(opaque.get_int_nested(), 62); 203 | opaque.member_float = 5.4; 204 | //opaque.member_int_array_fixed[7] = 47; 205 | eq(TestDatatypesNative.opaque_take_nested(opaque), true); 206 | opaque.free(); 207 | } 208 | /* 209 | function testArray() { 210 | #if (hl || cpp) 211 | var opaque = TestDatatypesNative.create_opaque(); 212 | var arr = opaque.member_int_array_fixed; 213 | eq(arr.length, 8); 214 | for (i in 0...8) { 215 | eq(arr[i], 0xB0057ED + i); 216 | arr[i] = 0xDE7500B + i; 217 | } 218 | for (i in 0...8) { 219 | eq(arr[i], 0xDE7500B + i); 220 | } 221 | var arr = opaque.member_int_array; 222 | eq(arr.length, 17); 223 | for (i in 0...17) { 224 | eq(arr[i], 0xB00573D + i); 225 | } 226 | var arr = opaque.member_string_array; 227 | eq(arr[0], "arrfoo"); 228 | eq(arr[1], "arrbar"); 229 | eq(arr[2], "arrbaz"); 230 | #else 231 | noAssert(); 232 | #end 233 | }*/ 234 | } 235 | -------------------------------------------------------------------------------- /src/ammer/internal/Meta.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Expr; 6 | 7 | using Lambda; 8 | using StringTools; 9 | 10 | // TODO: parse meta positions as well for more precise errors? 11 | typedef MetaParser = (args:Array)->Null; 12 | 13 | enum ParsedMeta { 14 | // c.* 15 | PMC_Cast(_:String); 16 | PMC_MacroCall; 17 | PMC_Prereturn(_:String); 18 | PMC_Return(_:String); 19 | // gen.* 20 | PMGen_Alloc(_:String); 21 | PMGen_Free(_:String); 22 | PMGen_NullPtr(_:String); 23 | // lib.* 24 | PMLib_Define(_:String); 25 | PMLib_Define_CodeOnly(_:String); 26 | // TODO: PMLib_Defines(_:Array); 27 | PMLib_Framework(_:String); 28 | PMLib_Frameworks(_:Array); 29 | PMLib_IncludePath(_:String); 30 | PMLib_IncludePaths(_:Array); 31 | PMLib_Language(_:ammer.core.LibraryLanguage); 32 | PMLib_LibraryPath(_:String); 33 | PMLib_LibraryPaths(_:Array); 34 | PMLib_LinkName(_:String); 35 | PMLib_LinkNames(_:Array); 36 | PMLib_Headers_Include(_:String); 37 | PMLib_Headers_Import(_:String); 38 | PMLib_Headers_IncludeLocal(_:String); 39 | PMLib_Headers_ImportLocal(_:String); 40 | PMLib_Headers_IncludeGlobal(_:String); 41 | PMLib_Headers_ImportGlobal(_:String); 42 | // ret.* 43 | PMRet_Derive(e:Expr, ct:ComplexType); 44 | // other 45 | PMAlloc; // TODO: rename to something better? 46 | PMDerive(_:Expr); 47 | PMHaxe; 48 | PMNative(_:String); 49 | PMNativePrefix(_:String); 50 | PMSkip; 51 | PMSub(_:ComplexType); 52 | } 53 | 54 | class Meta { 55 | static function parseComplexType(e:Expr, arg:Int):ComplexType { 56 | var ct = Utils.complexTypeExpr(e); 57 | if (ct == null) 58 | throw 'expected reference to type using (_ : path.to.Type) syntax (argument ${arg + 1})'; 59 | return ct; 60 | } 61 | 62 | static function parseExpr(e:Expr, arg:Int):Expr { 63 | return e; 64 | } 65 | 66 | static function parseString(e:Expr, arg:Int):String { 67 | return (switch (e.expr) { 68 | case EConst(CString(v)): v; 69 | case _: throw 'expected string constant (argument ${arg + 1})'; 70 | }); 71 | } 72 | 73 | static function parseStringArray(e:Expr, arg:Int):Array { 74 | return (switch (e.expr) { 75 | case EArrayDecl(vs): [ for (v in vs) switch (v.expr) { 76 | case EConst(CString(v)): v; 77 | case _: throw 'expected string constant (argument ${arg + 1})'; 78 | } ]; 79 | case _: throw 'expected array of string constants (argument ${arg + 1})'; 80 | }); 81 | } 82 | 83 | static function parseEnum(map:Map):(e:Expr, arg:Int)->T { 84 | return (e:Expr, arg:Int) -> { 85 | switch (e.expr) { 86 | case EConst(CIdent(id)): 87 | if (!map.exists(id)) { 88 | var keys = [for (k in map.keys()) k]; 89 | keys.sort(Reflect.compare); 90 | throw 'invalid value, should be one of ${keys.join(", ")} (argument ${arg + 1})'; 91 | } 92 | map[id]; 93 | case _: throw 'expected identifier (argument ${arg + 1})'; 94 | } 95 | }; 96 | } 97 | 98 | static function parser0(v:ParsedMeta):MetaParser { 99 | return (args) -> { 100 | if (args.length != 0) 101 | throw 'expected no arguments (${args.length} provided)'; 102 | v; 103 | }; 104 | } 105 | 106 | static function parser1(p1:(Expr, Int)->T1, f:(T1)->ParsedMeta):MetaParser { 107 | return (args) -> { 108 | if (args.length != 1) 109 | throw 'expected 1 argument (${args.length} provided)'; 110 | f(p1(args[0], 0)); 111 | }; 112 | } 113 | 114 | static function parser2(p1:(Expr, Int)->T1, p2:(Expr, Int)->T2, f:(T1, T2)->ParsedMeta):MetaParser { 115 | return (args) -> { 116 | if (args.length != 2) 117 | throw 'expected 2 arguments (${args.length} provided)'; 118 | f(p1(args[0], 0), p2(args[1], 1)); 119 | }; 120 | } 121 | 122 | /** 123 | Metadata allowed for the class defining a library. 124 | **/ 125 | public static final LIBRARY_CLASS = [ 126 | "lib.define" => parser1(parseString, PMLib_Define), 127 | "lib.define.codeOnly" => parser1(parseString, PMLib_Define_CodeOnly), 128 | "lib.framework" => parser1(parseString, PMLib_Framework), 129 | "lib.frameworks" => parser1(parseStringArray, PMLib_Frameworks), 130 | "lib.includePath" => parser1(parseString, PMLib_IncludePath), 131 | "lib.includePaths" => parser1(parseStringArray, PMLib_IncludePaths), 132 | "lib.libraryPath" => parser1(parseString, PMLib_LibraryPath), 133 | "lib.libraryPaths" => parser1(parseStringArray, PMLib_LibraryPaths), 134 | "lib.language" => parser1(parseEnum([ 135 | "C" => ammer.core.LibraryLanguage.C, 136 | "Cpp" => Cpp, 137 | "ObjC" => ObjectiveC, 138 | "ObjCpp" => ObjectiveCpp, 139 | ]), PMLib_Language), 140 | "lib.linkName" => parser1(parseString, PMLib_LinkName), 141 | "lib.linkNames" => parser1(parseStringArray, PMLib_LinkNames), 142 | "lib.headers.include" => parser1(parseString, PMLib_Headers_Include), 143 | "lib.headers.import" => parser1(parseString, PMLib_Headers_Import), 144 | "lib.headers.includeLocal" => parser1(parseString, PMLib_Headers_IncludeLocal), 145 | "lib.headers.importLocal" => parser1(parseString, PMLib_Headers_ImportLocal), 146 | "lib.headers.includeGlobal" => parser1(parseString, PMLib_Headers_IncludeGlobal), 147 | "lib.headers.importGlobal" => parser1(parseString, PMLib_Headers_ImportGlobal), 148 | "nativePrefix" => parser1(parseString, PMNativePrefix), 149 | "sub" => parser1(parseComplexType, PMSub), 150 | ]; 151 | 152 | /** 153 | Metadata allowed for the class defining a sublibrary. 154 | **/ 155 | public static final SUBLIBRARY_CLASS = [ 156 | "nativePrefix" => parser1(parseString, PMNativePrefix), 157 | "sub" => parser1(parseComplexType, PMSub), // TODO: disallow? 158 | ]; 159 | 160 | public static final COMMON_METHOD = [ 161 | "haxe" => parser0(PMHaxe), 162 | "native" => parser1(parseString, PMNative), 163 | "macroCall" => parser0(PMC_MacroCall), 164 | "c.cast" => parser1(parseString, PMC_Cast), 165 | "c.macroCall" => parser0(PMC_MacroCall), 166 | "c.prereturn" => parser1(parseString, PMC_Prereturn), 167 | "c.return" => parser1(parseString, PMC_Return), 168 | // "cpp.constructor", 169 | // "cpp.member", 170 | "ret.derive" => parser2(parseExpr, parseComplexType, PMRet_Derive), 171 | ]; 172 | 173 | /** 174 | Metadata allowed for a method of a library. 175 | **/ 176 | public static final LIBRARY_METHOD = COMMON_METHOD; 177 | 178 | /** 179 | Metadata allowed for a method of a struct. 180 | **/ 181 | public static final STRUCT_METHOD = COMMON_METHOD; 182 | 183 | /** 184 | Metadata allowed for a variable of a struct. 185 | **/ 186 | public static final STRUCT_VAR = [ 187 | "haxe" => parser0(PMHaxe), 188 | "native" => parser1(parseString, PMNative), 189 | // TODO: get/set/ref 190 | ]; 191 | 192 | public static final COMMON_FIELD = [ 193 | "haxe" => parser0(PMHaxe), 194 | "native" => parser1(parseString, PMNative), 195 | ]; 196 | 197 | public static final ENUM_FIELD = [ 198 | "native" => parser1(parseString, PMNative), 199 | ]; 200 | 201 | // /** 202 | // Metadata allowed for a variable of a library. 203 | // **/ 204 | // public static final LIBRARY_VARIABLE = [ 205 | // "native", 206 | // ]; 207 | 208 | /** 209 | Metadata allowed for the class defining a struct type. 210 | **/ 211 | public static final STRUCT_CLASS = [ 212 | "alloc" => parser0(PMAlloc), 213 | "nativePrefix" => parser1(parseString, PMNativePrefix), 214 | "sub" => parser1(parseComplexType, PMSub), 215 | // "struct", // TODO: deprecation warning? 216 | "gen.alloc" => parser1(parseString, PMGen_Alloc), 217 | "gen.free" => parser1(parseString, PMGen_Free), 218 | "gen.nullPtr" => parser1(parseString, PMGen_NullPtr), 219 | ]; 220 | 221 | /** 222 | Metadata allowed for the class defining an opaque type. 223 | **/ 224 | public static final OPAQUE_CLASS = [ 225 | "nativePrefix" => parser1(parseString, PMNativePrefix), 226 | //"sub" => parser1(parseComplexType, PMSub), 227 | ]; 228 | 229 | public static final METHOD_ARG = [ 230 | "c.cast" => parser1(parseString, PMC_Cast), 231 | "skip" => parser0(PMSkip), 232 | "derive" => parser1(parseExpr, PMDerive), 233 | ]; 234 | 235 | /** 236 | Iterate through the given `metas`. Any entries that do not start with 237 | `:ammer` will be ignored. 238 | 239 | If `strict` is `true`, all `:ammer.*` metadata must be present in the 240 | `parsers` map to be accepted; an error is thrown if not. 241 | 242 | If `strict` is `false`, `:ammer.*` metadata which are not present in the 243 | `parsers` map are ignored. 244 | **/ 245 | public static function extract( 246 | metas:Metadata, 247 | parsers:Map, 248 | strict:Bool = true 249 | ):Array { 250 | var ret = []; 251 | for (meta in metas) { 252 | if (!meta.name.startsWith(":ammer.")) 253 | continue; 254 | var id = meta.name.substr(":ammer.".length); 255 | Reporting.withPosition(meta.pos, () -> { 256 | var parser = parsers[id]; 257 | if (parser == null) { 258 | if (strict) { 259 | var ids = [ for (k => _ in parsers) k ]; 260 | ids.sort(Reflect.compare); 261 | Reporting.error('unsupported or incorrectly specified ammer metadata ${meta.name} (should be one of ${ids.join(", ")})'); 262 | } 263 | return; 264 | } 265 | try { 266 | ret.push(parser(meta.params)); 267 | } catch (error:String) { 268 | Reporting.error('cannot parse ammer metadata ${meta.name}: $error'); 269 | } 270 | }); 271 | } 272 | return ret; 273 | } 274 | } 275 | 276 | #end 277 | -------------------------------------------------------------------------------- /src/ammer/Lib.macro.hx: -------------------------------------------------------------------------------- 1 | package ammer; 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Context.fatalError as fail; 5 | import haxe.macro.Expr; 6 | import haxe.macro.Type; 7 | import haxe.macro.TypeTools; 8 | import ammer.internal.*; 9 | import ammer.internal.Ammer.mergedInfo as info; 10 | import ammer.internal.Utils.access; 11 | import ammer.internal.Utils.accessTp; 12 | import ammer.internal.Utils.complexTypeExpr; 13 | import ammer.internal.Utils.expectTypePath; 14 | import ammer.internal.Utils.isNull; 15 | import ammer.internal.Utils.triggerTyping; 16 | import ammer.internal.Utils.typeId; 17 | import ammer.internal.Utils.typeId2; 18 | 19 | using Lambda; 20 | 21 | class Lib { 22 | static function withPos(pos:Position, f:()->T):T { 23 | return Reporting.withPosition(pos, f); 24 | } 25 | 26 | static function resolveType(ct:ComplexType, pos:Position):Type { 27 | return Reporting.withPosition(pos, () -> Reporting.resolveType(ct, pos)); 28 | } 29 | 30 | // These methods are inserted into `Lib.macro.hx` when baking a library. 31 | // This avoids code duplication/synchronisation issues. Importantly, the code 32 | // is just string-pasted, so it is important that the `import`s that are 33 | // in `Lib.macro.baked.hx` are sufficient for the code to work. 34 | 35 | // ammer-fragment-begin: lib-baked 36 | public static function allocStruct(cls:Expr, ?initVals:Expr):Expr { 37 | var ct = complexTypeExpr(cls); 38 | var clsType = withPos(cls.pos, () -> triggerTyping(ct)); 39 | clsType != null || throw fail("invalid type in allocStruct call", cls.pos); 40 | var struct = info.structs[typeId(clsType)]; 41 | struct != null || throw fail("not a struct type in allocStruct call", cls.pos); 42 | struct.gen.alloc != null || throw fail("struct type was not marked with @:ammer.alloc", cls.pos); 43 | var alloc = struct.gen.alloc; 44 | if (isNull(initVals)) { 45 | return macro @:privateAccess $p{access(clsType)}.$alloc(); 46 | } 47 | var assigns = (switch (initVals) { 48 | case {expr: EObjectDecl(fields)}: 49 | [ for (field in fields) macro @:pos(field.expr.pos) $p{["_allocated", field.field]} = $e{field.expr} ]; 50 | case _: throw fail("expected initial values (e.g. {a: 1, b: 2, ...}) as second argument of allocStruct call", initVals.pos); 51 | }); 52 | return macro { 53 | var _allocated = @:privateAccess $p{access(clsType)}.$alloc(); 54 | $b{assigns}; 55 | _allocated; 56 | }; 57 | } 58 | 59 | public static function nullPtrStruct(cls:Expr):Expr { 60 | var ct = complexTypeExpr(cls); 61 | var clsType = withPos(cls.pos, () -> triggerTyping(ct)); 62 | clsType != null || throw fail("invalid type in nullPtrStruct call", cls.pos); 63 | var typeId = typeId(clsType); 64 | var struct = info.structs[typeId]; 65 | //var opaque = info.opaques[typeId]; 66 | //(struct != null || opaque != null) || throw fail("not a struct type or opaque type in nullPtrStruct call", cls.pos); 67 | struct != null || throw fail("not a struct type in nullPtrStruct call", cls.pos); 68 | struct.gen.nullPtr != null || throw fail("struct type was not marked with @:ammer.alloc", cls.pos); 69 | var nullPtr = struct.gen.nullPtr; 70 | return macro @:privateAccess $p{access(clsType)}.$nullPtr(); 71 | } 72 | 73 | public static function allocBox(cls:Expr, ?initVal:Expr):Expr { 74 | var elCt = complexTypeExpr(cls); 75 | #if ammer 76 | var elType = resolveType(elCt, cls.pos); 77 | resolveType((macro : ammer.ffi.Box<$elCt>), cls.pos); 78 | var box = info.boxes.byElementTypeId[typeId2(elType)]; 79 | #else 80 | // if baked, ammer.ffi.Box does not exist, only its monomorphisations 81 | var box = info.boxes.byElementTypeId[typeIdCt(elCt)]; 82 | #end 83 | box != null || throw fail("not a known box type in allocBox call", cls.pos); 84 | var tp = expectTypePath(box.boxCt); 85 | if (isNull(initVal)) { 86 | return macro @:privateAccess new $tp($e{box.alloc}); 87 | } 88 | return macro { 89 | var _allocated = @:privateAccess new $tp($e{box.alloc}); 90 | _allocated.set($initVal); 91 | _allocated; 92 | }; 93 | } 94 | 95 | public static function nullPtrBox(cls:Expr):Expr { 96 | var elCt = complexTypeExpr(cls); 97 | #if ammer 98 | var elType = resolveType(elCt, cls.pos); 99 | resolveType((macro : ammer.ffi.Box<$elCt>), cls.pos); 100 | var box = info.boxes.byElementTypeId[typeId2(elType)]; 101 | #else 102 | // if baked, ammer.ffi.Box does not exist, only its monomorphisations 103 | var box = info.boxes.byElementTypeId[typeIdCt(elCt)]; 104 | #end 105 | box != null || throw fail("not a known box type in nullPtrBox call", cls.pos); 106 | var tp = expectTypePath(box.boxCt); 107 | return macro @:privateAccess $p{accessTp(tp)}.nullPtr(); 108 | } 109 | 110 | public static function allocArray(cls:Expr, size:Expr, ?initVal:Expr):Expr { 111 | var elCt = complexTypeExpr(cls); 112 | #if ammer 113 | var elType = resolveType(elCt, cls.pos); 114 | resolveType((macro : ammer.ffi.Array<$elCt>), cls.pos); 115 | var array = info.arrays.byElementTypeId[typeId2(elType)]; 116 | #else 117 | // if baked, ammer.ffi.Array does not exist, only its monomorphisations 118 | var array = info.arrays.byElementTypeId[typeIdCt(elCt)]; 119 | #end 120 | array != null || throw fail("not a known array type in allocArray call", cls.pos); 121 | var tp = expectTypePath(array.arrayCt); 122 | if (isNull(initVal)) { 123 | return macro { 124 | var _size = $size; 125 | @:privateAccess new $tp($e{array.alloc}); 126 | } 127 | } 128 | return macro { 129 | var _size = $size; 130 | var _val = $initVal; 131 | var _allocated = @:privateAccess new $tp($e{array.alloc}); 132 | for (i in 0..._size) { 133 | _allocated.set(i, _val); 134 | } 135 | _allocated; 136 | }; 137 | } 138 | 139 | public static function nullPtrArray(cls:Expr):Expr { 140 | var elCt = complexTypeExpr(cls); 141 | #if ammer 142 | var elType = resolveType(elCt, cls.pos); 143 | resolveType((macro : ammer.ffi.Array<$elCt>), cls.pos); 144 | var array = info.arrays.byElementTypeId[typeId2(elType)]; 145 | #else 146 | // if baked, ammer.ffi.Array does not exist, only its monomorphisations 147 | var array = info.arrays.byElementTypeId[typeIdCt(elCt)]; 148 | #end 149 | array != null || throw fail("not a known array type in allocArray call", cls.pos); 150 | var tp = expectTypePath(array.arrayCt); 151 | return macro @:privateAccess $p{accessTp(tp)}.nullPtr(); 152 | } 153 | 154 | public static function vecToArrayCopy(vec:Expr):Expr { 155 | var typed = withPos(vec.pos, () -> Context.typeExpr(vec)); 156 | var elType = (switch (typed.t) { 157 | case TInst(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; 158 | case TAbstract(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; 159 | case _: throw fail("argument should be a haxe.ds.Vector", vec.pos); 160 | }); 161 | var elCt = TypeTools.toComplexType(elType); 162 | var stored = Context.storeTypedExpr(typed); 163 | #if ammer 164 | // if baked, ammer.ffi.Array does not exist, only its monomorphisations 165 | resolveType((macro : ammer.ffi.Array<$elCt>), vec.pos); 166 | #end 167 | var array = info.arrays.byElementTypeId[typeId2(elType)]; 168 | array != null || throw fail("not a known array type in vecToArrayCopy call", vec.pos); 169 | var tp = expectTypePath(array.arrayCt); 170 | return macro { 171 | var _vec = $vec; 172 | @:privateAccess new $tp($e{array.fromHaxeCopy}); 173 | }; 174 | } 175 | 176 | public static function vecToArrayRef(vec:Expr):Expr { 177 | var typed = withPos(vec.pos, () -> Context.typeExpr(vec)); 178 | var elType = (switch (typed.t) { 179 | case TInst(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; 180 | case TAbstract(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; 181 | case _: throw fail("argument should be a haxe.ds.Vector", vec.pos); 182 | }); 183 | var elCt = TypeTools.toComplexType(elType); 184 | var stored = Context.storeTypedExpr(typed); 185 | #if ammer 186 | // if baked, ammer.ffi.Array does not exist, only its monomorphisations 187 | resolveType((macro : ammer.ffi.Array<$elCt>), vec.pos); 188 | #end 189 | var array = info.arrays.byElementTypeId[typeId2(elType)]; 190 | array != null || throw fail("not a known array type in vecToArrayRef call", vec.pos); 191 | var tp = expectTypePath(array.arrayRefCt); 192 | if (array.fromHaxeRef != null) { 193 | // if references are supported, create an array ref 194 | return macro { 195 | var _vec = $vec; 196 | @:privateAccess new $tp($e{array.fromHaxeRef}, _vec); 197 | }; 198 | } 199 | // if not, create a fake ref with the same API 200 | return macro { 201 | var _vec = $vec; 202 | @:privateAccess new $tp($e{array.fromHaxeCopy}, _vec); 203 | }; 204 | } 205 | 206 | public static function vecToArrayRefForce(vec:Expr):Expr { 207 | var typed = withPos(vec.pos, () -> Context.typeExpr(vec)); 208 | var elType = (switch (typed.t) { 209 | case TInst(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; 210 | case TAbstract(typeId(_.get()) => "haxe.ds.Vector.Vector", [el]): el; 211 | case _: throw fail("argument should be a haxe.ds.Vector", vec.pos); 212 | }); 213 | var elCt = TypeTools.toComplexType(elType); 214 | var stored = Context.storeTypedExpr(typed); 215 | #if ammer 216 | // if baked, ammer.ffi.Array does not exist, only its monomorphisations 217 | resolveType((macro : ammer.ffi.Array<$elCt>), vec.pos); 218 | #end 219 | var array = info.arrays.byElementTypeId[typeId2(elType)]; 220 | array != null || throw fail("not a known array type in vecToArrayRefForce call", vec.pos); 221 | var tp = expectTypePath(array.arrayRefCt); 222 | array.fromHaxeRef != null || throw fail("platform does not support non-copy references to Vector", vec.pos); 223 | return macro { 224 | var _vec = $vec; 225 | @:privateAccess new $tp($e{array.fromHaxeRef}, _vec); 226 | }; 227 | } 228 | 229 | public static function createHaxeRef(cls:Expr, e:Expr):Expr { 230 | var elCt = complexTypeExpr(cls); 231 | var elType = resolveType(elCt, cls.pos); 232 | #if ammer 233 | // if baked, ammer.ffi.Haxe does not exist, only its monomorphisations 234 | resolveType((macro : ammer.ffi.Haxe<$elCt>), cls.pos); 235 | #end 236 | var elId = typeId2(elType); 237 | if (info.haxeRefs.byElementTypeId.exists(elId)) { 238 | var haxeRef = info.haxeRefs.byElementTypeId[elId]; 239 | return macro { 240 | var _hxval = $e; 241 | $e{haxeRef.create}; 242 | }; 243 | } 244 | info.haxeRefs.byElementTypeId.exists(".Any.Any") || throw 0; 245 | return macro { 246 | var _hxval = $e; 247 | new ammer.internal.LibTypes.HaxeAnyRef<$elCt>($e{info.haxeRefs.byElementTypeId[".Any.Any"].create}); 248 | }; 249 | } 250 | // ammer-fragment-end: lib-baked 251 | } 252 | -------------------------------------------------------------------------------- /src/ammer/internal/Types.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type; 8 | import haxe.macro.TypeTools; 9 | 10 | using Lambda; 11 | 12 | typedef ResolvedType = { 13 | marshal:ammer.core.TypeMarshal, 14 | haxeType:ComplexType, 15 | wrap:NullExpr>, 16 | unwrap:NullExpr>, 17 | }; 18 | 19 | typedef CommonType = { 20 | type:Type, 21 | id:String, 22 | match:(Type)->Bool, 23 | }; 24 | 25 | class Types { 26 | static function prepareType(type:Type):CommonType { 27 | var ret = { 28 | type: type, 29 | id: null, 30 | match: null, 31 | }; 32 | switch (type) { 33 | case TInst(_.get() => a, []): 34 | ret.id = '${a.pack.join(".")}.${a.module.split(".").pop()}.${a.name}'; 35 | ret.match = (ctype:Type) -> switch (ctype) { 36 | case TInst(_.get() => b, []): a.name == b.name && a.module == b.module; 37 | case _: false; 38 | }; 39 | case TInst(_.get() => a, [TInst(_.get() => a2, [])]): 40 | ret.id = '${a.pack.join(".")}.${a.module.split(".").pop()}.${a.name}'; 41 | ret.match = (ctype:Type) -> switch (ctype) { 42 | case TInst(_.get() => b, [TInst(_.get() => b2, [])]): 43 | a.name == b.name && a.module == b.module 44 | && a2.name == b2.name && a2.module == b2.module; 45 | case _: false; 46 | }; 47 | case TAbstract(_.get() => a, []): 48 | ret.id = '${a.pack.join(".")}.${a.module.split(".").pop()}.${a.name}'; 49 | ret.match = (ctype:Type) -> switch (ctype) { 50 | case TAbstract(_.get() => b, []): a.name == b.name && a.module == b.module; 51 | case _: false; 52 | }; 53 | case _: throw 'how to match $type ?'; 54 | } 55 | return ret; 56 | } 57 | 58 | public static var TYPES = { 59 | var herePos = (macro null).pos; 60 | function c(ct:ComplexType, enable:Bool = true):CommonType { 61 | if (!enable) return null; 62 | // do not follow type: on some targets, some primitive types are typedefs 63 | return prepareType(Context.resolveType(ct, herePos)); 64 | } 65 | var hasSingle = (switch (Context.definedValue("target.name")) { 66 | case "cpp" | "cs" | "hl" | "java": true; 67 | case _: false; 68 | }); 69 | { 70 | void: c((macro : ammer.ffi.Void)), hxVoid: c((macro : Void)), 71 | bool: c((macro : ammer.ffi.Bool)), hxBool: c((macro : Bool)), 72 | u8: c((macro : ammer.ffi.UInt8)), 73 | u16: c((macro : ammer.ffi.UInt16)), 74 | u32: c((macro : ammer.ffi.UInt32)), hxU32: c((macro : UInt)), 75 | u64: c((macro : ammer.ffi.UInt64)), 76 | i8: c((macro : ammer.ffi.Int8)), 77 | i16: c((macro : ammer.ffi.Int16)), 78 | i32: c((macro : ammer.ffi.Int32)), hxI32: c((macro : Int)), 79 | i64: c((macro : ammer.ffi.Int64)), hxI64: c((macro : haxe.Int64)), 80 | f32: c((macro : ammer.ffi.Float32)), hxF32: c((macro : Single), hasSingle), 81 | f64: c((macro : ammer.ffi.Float64)), hxF64: c((macro : Float)), 82 | string: c((macro : ammer.ffi.String)), hxString: c((macro : String)), 83 | bytes: /*c((macro : ammer.ffi.Bytes)),*/ (null : CommonType), 84 | hxBytes: c((macro : haxe.io.Bytes)), //? 85 | this_: c((macro : ammer.ffi.This)), 86 | derefThis: c((macro : ammer.ffi.Deref)), 87 | }; 88 | }; 89 | static function initTypes():Void { 90 | if (TYPES == null || TYPES.bytes != null) return; 91 | var herePos = (macro null).pos; 92 | function c(ct:ComplexType):CommonType { 93 | return prepareType(Context.resolveType(ct, herePos)); 94 | } 95 | TYPES.bytes = c((macro : ammer.ffi.Bytes)); 96 | } 97 | 98 | public static function resolveComplexType( 99 | ct:ComplexType, 100 | lib:LibContext 101 | ):ResolvedType { 102 | return resolveType(Reporting.resolveType(ct, Reporting.currentPos()), lib); 103 | } 104 | 105 | static function resolveContexts(type:Type):Array { 106 | //var followedType = TypeTools.follow(type); 107 | var ret:Array = []; 108 | function c(target:CommonType):Bool { 109 | if (target == null) return false; 110 | return target.match(type); 111 | } 112 | initTypes(); 113 | // ammer.ffi.* types 114 | c(TYPES.void) 115 | || c(TYPES.bool) 116 | || c(TYPES.u8) 117 | || c(TYPES.u16) 118 | || c(TYPES.u32) 119 | || c(TYPES.u64) 120 | || c(TYPES.i8) 121 | || c(TYPES.i16) 122 | || c(TYPES.i32) 123 | || c(TYPES.i64) 124 | || c(TYPES.f32) 125 | || c(TYPES.f64) 126 | || c(TYPES.string) 127 | || c(TYPES.bytes) 128 | // Haxe shortcuts 129 | || c(TYPES.hxVoid) 130 | || c(TYPES.hxBool) 131 | || c(TYPES.hxU32) 132 | || c(TYPES.hxI32) 133 | || c(TYPES.hxI64) 134 | || c(TYPES.hxF32) 135 | || c(TYPES.hxF64) 136 | || c(TYPES.hxString) 137 | //|| c(TYPES.hxBytes) 138 | || { 139 | ret = (switch (type) { 140 | case TInst(Utils.typeId(_.get()) => id, []): 141 | if (Ammer.mergedInfo.structs.exists(id)) { 142 | Ammer.mergedInfo.structs[id].ctx != null || throw "context for struct not initialised yet"; 143 | [Ammer.mergedInfo.structs[id].ctx]; 144 | } else if (Ammer.mergedInfo.opaques.exists(id)) [Ammer.mergedInfo.opaques[id].ctx]; 145 | else if (Ammer.mergedInfo.sublibraries.exists(id)) [Ammer.mergedInfo.sublibraries[id].ctx]; 146 | else if (Ammer.mergedInfo.arrays.byTypeId.exists(id)) throw 0; // Ammer.arrays.byTypeId[id].ctx; 147 | else if (Ammer.mergedInfo.boxes.byTypeId.exists(id)) throw 0; // Ammer.boxes.byTypeId[id].ctx; 148 | else if (Ammer.mergedInfo.callbacks.byTypeId.exists(id)) [Ammer.mergedInfo.callbacks.byTypeId[id].ctx]; 149 | // TODO: enums ? 150 | else if (Ammer.mergedInfo.haxeRefs.byTypeId.exists(id)) [Ammer.mergedInfo.haxeRefs.byTypeId[id].ctx]; 151 | else if (Ammer.libraries.byTypeId.exists(id)) [Ammer.libraries.byTypeId[id]]; 152 | else []; 153 | case TInst(Utils.typeId(_.get()) => "ammer.ffi.Deref.Deref", [type]): 154 | resolveContexts(type); 155 | case TAbstract(_, []): 156 | var next = TypeTools.followWithAbstracts(type, true); 157 | if (Utils.typeId2(type) != Utils.typeId2(next)) resolveContexts(next); 158 | else []; 159 | case TFun(args, ret): 160 | var ret = []; 161 | var retMap:Map = []; 162 | for (arg in args) { 163 | for (ctx in resolveContexts(arg.t)) { 164 | if (retMap.exists(ctx.name)) continue; 165 | retMap[ctx.name] = true; 166 | ret.push(ctx); 167 | } 168 | } 169 | ret; 170 | case TType(_): return resolveContexts(TypeTools.follow(type, true)); 171 | case _: trace(type); throw 0; 172 | }); 173 | true; 174 | }; 175 | return ret; 176 | } 177 | 178 | public static function resolveContext(type:Type):LibContext { 179 | var ret = resolveContexts(type); 180 | if (ret.length > 1) { 181 | trace("contexts", ret); 182 | throw "multiple contexts ..."; 183 | } else if (ret.length == 0) { 184 | return Ammer.libraries.byTypeId["ammer.internal.LibTypes.LibTypes"]; 185 | } 186 | return ret[0]; 187 | } 188 | 189 | public static function resolveType( 190 | type:Type, 191 | lib:LibContext 192 | ):ResolvedType { 193 | var marshal = lib.marshal; 194 | var ret:ResolvedType = null; 195 | function c( 196 | target:CommonType, ffi:()->ammer.core.TypeMarshal, 197 | ?haxeType:ComplexType, ?wrap:Expr->Expr, ?unwrap:Expr->Expr 198 | ):Bool { 199 | if (target == null) return false; 200 | if (target.match(type)) { 201 | var retMarshal = ffi(); 202 | ret = { 203 | marshal: retMarshal, 204 | haxeType: haxeType != null ? haxeType : retMarshal.haxeType, 205 | wrap: wrap, 206 | unwrap: unwrap, 207 | }; 208 | return true; 209 | } 210 | return false; 211 | } 212 | initTypes(); 213 | // check abstracts first: may unify with primitive types otherwise 214 | (switch (type) { 215 | case TAbstract(_.get() => abs, []): 216 | // make sure build macros for the abstract get triggered 217 | if (abs.impl != null) { 218 | abs.impl.get(); 219 | } 220 | 221 | var id = Utils.typeId(abs); 222 | if (Ammer.mergedInfo.enums.exists(id)) { 223 | ret = { 224 | marshal: Ammer.mergedInfo.enums[id].marshal, 225 | haxeType: Ammer.mergedInfo.enums[id].marshal.haxeType, 226 | wrap: e -> e, 227 | unwrap: e -> e, 228 | }; 229 | true; 230 | } else { 231 | false; 232 | } 233 | case _: false; 234 | }) 235 | // ammer.ffi.* types 236 | || c(TYPES.void, marshal.void ) 237 | || c(TYPES.bool, marshal.bool ) 238 | || c(TYPES.u8, marshal.uint8 ) 239 | || c(TYPES.u16, marshal.uint16 ) 240 | || c(TYPES.u32, marshal.uint32 ) 241 | || c(TYPES.u64, marshal.uint64 ) 242 | || c(TYPES.i8, marshal.int8 ) 243 | || c(TYPES.i16, marshal.int16 ) 244 | || c(TYPES.i32, marshal.int32 ) 245 | || c(TYPES.i64, marshal.int64 ) 246 | || c(TYPES.f32, marshal.float32) 247 | || c(TYPES.f64, marshal.float64) 248 | || c(TYPES.string, marshal.string ) 249 | || c(TYPES.bytes, () -> marshal.bytes().type, 250 | (macro : ammer.ffi.Bytes), 251 | e -> macro @:privateAccess new ammer.ffi.Bytes($e), 252 | e -> macro @:privateAccess $e._ammer_native) 253 | || c(TYPES.this_, () -> throw Reporting.error("ammer.ffi.This type not allowed here")) 254 | // Haxe shortcuts 255 | || c(TYPES.hxVoid, marshal.void ) 256 | || c(TYPES.hxBool, marshal.bool ) 257 | || c(TYPES.hxU32, marshal.uint32 ) 258 | || c(TYPES.hxI32, marshal.int32 ) 259 | || c(TYPES.hxI64, marshal.int64 ) 260 | || c(TYPES.hxF32, marshal.float32) 261 | || c(TYPES.hxF64, marshal.float64) 262 | || c(TYPES.hxString, marshal.string ) 263 | //|| c(TYPES.hxBytes, () -> marshal.bytes().type) 264 | || { 265 | // TODO: better handling of typarams, better errors... 266 | // TODO: cache ResolvedTypes directly in the relevant info structs? 267 | switch (type) { 268 | case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.structs.exists(id)): 269 | Ammer.mergedInfo.structs[id].marshalOpaque != null || throw 0; 270 | var ct = TypeTools.toComplexType(type); 271 | var tp = Utils.expectTypePath(ct); 272 | ret = { 273 | marshal: Ammer.mergedInfo.structs[id].marshalOpaque, 274 | haxeType: ct, 275 | wrap: e -> macro @:privateAccess new $tp($e), 276 | unwrap: e -> macro @:privateAccess $e._ammer_native, 277 | }; 278 | true; 279 | case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.opaques.exists(id)): 280 | Ammer.mergedInfo.opaques[id].marshal != null || throw 0; 281 | var ct = TypeTools.toComplexType(type); 282 | var tp = Utils.expectTypePath(ct); 283 | ret = { 284 | marshal: Ammer.mergedInfo.opaques[id].marshal.type, 285 | haxeType: ct, 286 | wrap: e -> macro @:privateAccess new $tp($e), 287 | unwrap: e -> macro @:privateAccess $e._ammer_native, 288 | }; 289 | true; 290 | case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.arrays.byTypeId.exists(id)): 291 | var array = Ammer.mergedInfo.arrays.byTypeId[id]; 292 | var tp = Utils.expectTypePath(array.arrayCt); 293 | ret = { 294 | marshal: array.arrayMarshal.type, 295 | haxeType: TypeTools.toComplexType(type), 296 | wrap: e -> macro @:privateAccess new $tp($e), 297 | unwrap: e -> macro @:privateAccess $e._ammer_native, 298 | }; 299 | true; 300 | case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.boxes.byTypeId.exists(id)): 301 | var box = Ammer.mergedInfo.boxes.byTypeId[id]; 302 | var marshal = box.boxMarshal; 303 | var tp = Utils.expectTypePath(box.boxCt); 304 | ret = { 305 | marshal: marshal.type, 306 | haxeType: TypeTools.toComplexType(type), 307 | wrap: e -> macro @:privateAccess new $tp($e), 308 | unwrap: e -> macro @:privateAccess $e._ammer_native, 309 | }; 310 | true; 311 | case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.haxeRefs.byTypeId.exists(id)): 312 | var haxeRef = Ammer.mergedInfo.haxeRefs.byTypeId[id]; 313 | ret = { 314 | marshal: haxeRef.marshal.type, 315 | haxeType: haxeRef.marshal.refCt, 316 | wrap: e -> macro $e{haxeRef.marshal.restore(e)}, 317 | unwrap: e -> macro $e{e}.handle, 318 | }; 319 | true; 320 | case TAbstract(Utils.typeId(_.get()) => "ammer.internal.LibTypes.HaxeAnyRef", [el]): 321 | // var elId = Utils.typeId2(el); 322 | var ct = TypeTools.toComplexType(type); 323 | var elCt = TypeTools.toComplexType(el); 324 | var haxeRef = Ammer.mergedInfo.haxeRefs.byElementTypeId[".Any.Any"]; 325 | haxeRef != null || throw 0; 326 | var refCt = haxeRef.marshal.refCt; 327 | ret = { 328 | marshal: haxeRef.marshal.type, 329 | haxeType: ct, // haxeRef.marshal.refCt, 330 | wrap: e -> macro new ammer.internal.LibTypes.HaxeAnyRef<$elCt>($e{haxeRef.marshal.restore(e)}), 331 | unwrap: e -> macro ($e{e}.toNative() : $refCt).handle, 332 | }; 333 | true; 334 | case TInst(Utils.typeId(_.get()) => "ammer.ffi.Deref.Deref", [type = TInst(Utils.typeId(_.get()) => id, [])]) if (Ammer.mergedInfo.structs.exists(id)): 335 | Ammer.mergedInfo.structs[id].marshalOpaque != null || throw 0; 336 | var ct = TypeTools.toComplexType(type); 337 | var tp = Utils.expectTypePath(ct); 338 | ret = { 339 | marshal: Ammer.mergedInfo.structs[id].marshalDeref, 340 | haxeType: ct, 341 | wrap: e -> macro @:privateAccess new $tp($e), 342 | unwrap: e -> macro @:privateAccess $e._ammer_native, 343 | }; 344 | true; 345 | case TAbstract(abs, _): 346 | var next = TypeTools.followWithAbstracts(type, true); 347 | if (Utils.typeId2(type) != Utils.typeId2(next)) return resolveType(next, lib); 348 | false; 349 | case TType(_): return resolveType(TypeTools.follow(type, true), lib); 350 | case TInst(Utils.typeId(_.get()) => id, []): 351 | trace("type id was", id); 352 | false; 353 | case _: false; 354 | } 355 | }; 356 | if (ret == null) { 357 | // TODO: error 358 | trace("type:", type); 359 | throw 0; 360 | } 361 | return ret; 362 | } 363 | } 364 | 365 | #end 366 | -------------------------------------------------------------------------------- /src/ammer/internal/Ammer.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Compiler; 6 | import haxe.macro.Context; 7 | import haxe.macro.Expr; 8 | import haxe.macro.PositionTools; 9 | import haxe.macro.Type; 10 | import haxe.io.Path; 11 | import sys.io.File; 12 | import sys.FileSystem; 13 | 14 | using StringTools; 15 | 16 | typedef AmmerConfig = { 17 | buildPath:String, 18 | outputPath:String, 19 | }; 20 | 21 | typedef QueuedSubtype = { 22 | subId:String, 23 | lib:Type, 24 | pos:Position, 25 | process:(ctx:LibContext)->Any, 26 | result:Null, 27 | done:Bool, 28 | prev:Null, 29 | next:Null, 30 | }; 31 | 32 | class Ammer { 33 | public static var baseConfig(get, never):AmmerConfig; 34 | public static var builder(get, never):ammer.core.Builder; 35 | public static var platform(get, never):ammer.core.Platform; 36 | public static var platformConfig(default, null):ammer.core.plat.BaseConfig; 37 | 38 | public static var mergedInfo = new ammer.internal.v1.LibInfo(); 39 | public static var libraries:{ 40 | byLibraryName:Map, 41 | byTypeId:Map, 42 | active:Array, 43 | } = { 44 | byLibraryName: [], 45 | byTypeId: [], 46 | active: [], 47 | }; 48 | 49 | static var queuedSubtypes:{ 50 | first:QueuedSubtype, 51 | last:QueuedSubtype, 52 | } = { 53 | first: null, 54 | last: null, 55 | }; 56 | static function enqueueSubtype(s:QueuedSubtype):Void { 57 | s.prev == null || throw 0; 58 | s.next == null || throw 0; 59 | if (queuedSubtypes.last == null) { 60 | queuedSubtypes.first == null || throw 0; 61 | queuedSubtypes.first = s; 62 | queuedSubtypes.last = s; 63 | return; 64 | } 65 | queuedSubtypes.first != null || throw 0; 66 | queuedSubtypes.last.next == null || throw 0; 67 | s.prev = queuedSubtypes.last; 68 | queuedSubtypes.last.next = s; 69 | queuedSubtypes.last = s; 70 | } 71 | static function dequeueSubtype(s:QueuedSubtype):Void { 72 | if (s.prev == null) { 73 | queuedSubtypes.first = s.next; 74 | } else { 75 | s.prev.next = s.next; 76 | } 77 | if (s.next == null) { 78 | queuedSubtypes.last = s.prev; 79 | } else { 80 | s.next.prev = s.prev; 81 | } 82 | } 83 | 84 | static var baseConfigL:AmmerConfig; 85 | static var builderL:ammer.core.Builder; 86 | static var platformL:ammer.core.Platform; 87 | 88 | static function get_baseConfig():AmmerConfig { 89 | if (baseConfigL != null) 90 | return baseConfigL; 91 | return baseConfigL = { 92 | buildPath: Config.getPath("ammer.buildPath", null, true), 93 | outputPath: Config.getPath("ammer.outputPath", null, true), 94 | }; 95 | } 96 | 97 | static function get_builder():ammer.core.Builder { 98 | if (builderL != null) 99 | return builderL; 100 | 101 | // TODO: allow selection of toolchain, configuration, etc 102 | return ammer.core.Builder.createCurrentBuilder(({} : ammer.core.build.BaseBuilderConfig)); 103 | } 104 | 105 | static function get_platform():ammer.core.Platform { 106 | if (platformL != null) 107 | return platformL; 108 | 109 | Bakery.init(); 110 | 111 | function getPaths(key:String):Array { 112 | var paths = Config.getStringArray(key, ";"); 113 | if (paths == null) return []; 114 | return paths.filter(v -> v != ""); 115 | } 116 | platformConfig = (switch (Context.definedValue("target.name")) { 117 | case "cpp": ({ 118 | buildPath: baseConfig.buildPath, 119 | outputPath: baseConfig.outputPath, 120 | staticLink: Config.getBool("ammer.cpp.staticLink", false), 121 | } : ammer.core.plat.Cpp.CppConfig); 122 | case "cs": ({ 123 | buildPath: baseConfig.buildPath, 124 | outputPath: baseConfig.outputPath, 125 | } : ammer.core.plat.Cs.CsConfig); 126 | // TODO: eval? 127 | case "hl": ({ 128 | buildPath: baseConfig.buildPath, 129 | outputPath: baseConfig.outputPath, 130 | hlc: Config.getBool("ammer.hl.hlc", !Compiler.getOutput().endsWith(".hl")), 131 | hlIncludePaths: getPaths("ammer.hl.includePaths"), 132 | hlLibraryPaths: getPaths("ammer.hl.libraryPaths"), 133 | } : ammer.core.plat.Hashlink.HashlinkConfig); 134 | case "java": ({ 135 | buildPath: baseConfig.buildPath, 136 | outputPath: baseConfig.outputPath, 137 | javaIncludePaths: getPaths("ammer.java.includePaths"), 138 | javaLibraryPaths: getPaths("ammer.java.libraryPaths"), 139 | } : ammer.core.plat.Java.JavaConfig); 140 | case "lua": ({ 141 | buildPath: baseConfig.buildPath, 142 | outputPath: baseConfig.outputPath, 143 | luaIncludePaths: getPaths("ammer.lua.includePaths"), 144 | luaLibraryPaths: getPaths("ammer.lua.libraryPaths"), 145 | } : ammer.core.plat.Lua.LuaConfig); 146 | case "neko": ({ 147 | buildPath: baseConfig.buildPath, 148 | outputPath: baseConfig.outputPath, 149 | nekoIncludePaths: getPaths("ammer.neko.includePaths"), 150 | nekoLibraryPaths: getPaths("ammer.neko.libraryPaths"), 151 | } : ammer.core.plat.Neko.NekoConfig); 152 | case "js": ({ 153 | // TODO: (once implemented,) choose JS build system 154 | buildPath: baseConfig.buildPath, 155 | outputPath: baseConfig.outputPath, 156 | nodeGypBinary: Config.getString("ammer.js.nodeGypBinary", "node-gyp"), 157 | } : ammer.core.plat.Nodejs.NodejsConfig); 158 | case "python": ({ 159 | buildPath: baseConfig.buildPath, 160 | outputPath: baseConfig.outputPath, 161 | pythonVersionMinor: Config.getInt("ammer.python.version", 8), 162 | pythonIncludePaths: getPaths("ammer.python.includePaths"), 163 | pythonLibraryPaths: getPaths("ammer.python.libraryPaths"), 164 | } : ammer.core.plat.Python.PythonConfig); 165 | 166 | case _: ({ 167 | buildPath: baseConfig.buildPath, 168 | outputPath: baseConfig.outputPath, 169 | } : ammer.core.plat.None.NoneConfig); 170 | }); 171 | 172 | platformL = ammer.core.Platform.createCurrentPlatform(platformConfig); 173 | Context.onAfterTyping(_ -> { 174 | var program = platformL.finalise(); 175 | builder.build(program); 176 | }); 177 | return platformL; 178 | } 179 | 180 | public static function registerBakedLibraryV1(info:ammer.internal.v1.LibInfo):Void { 181 | // TODO: also detect duplicate library names (add to libraries.byLibraryName ?) 182 | Reporting.withCurrentPos(() -> { 183 | final fail = Reporting.error; 184 | // ammer-fragment-begin: register-v1 185 | // detect local path 186 | var path = PositionTools.getInfos(info.herePos).file; 187 | if (!Path.isAbsolute(path)) { 188 | path = Path.join([Sys.getCwd(), path]); 189 | } 190 | path = Path.normalize(Path.directory(path)); 191 | var binaryPath = Path.normalize(path + "/" + info.setupToBin); 192 | 193 | if (true) { //if (Context.defined('${info.name}_copy_binary')) { 194 | var outputPath = Context.definedValue("ammer.outputPath"); 195 | outputPath != null || throw fail("missing required define: ammer.outputPath"); 196 | FileSystem.createDirectory(outputPath); 197 | for (file in info.files) { 198 | var dst = file.dst; 199 | var dstFull = Path.normalize(Path.join([outputPath, dst])); 200 | 201 | var dstExists = FileSystem.exists(dstFull); 202 | if (dstExists) { 203 | !FileSystem.isDirectory(dstFull) || throw fail('$dst exists but is a directory'); 204 | if (true) continue; 205 | /* 206 | // TODO: skip if configured (when running ammer proper?) 207 | var digest = haxe.crypto.Sha256.make(File.getBytes(dstFull)).toHex(); 208 | if (digest == file.digest) { 209 | continue; 210 | } else { 211 | Sys.println('[ammer] $dst found with different contents (possibly an older version)'); 212 | }*/ 213 | } 214 | 215 | // at this point, either the required file does not exist 216 | // or its digest does not match what we expect (currently disabled) 217 | // -> look for a source 218 | 219 | var sourcesSkipped = []; 220 | var sourcesAvailableLocally = []; 221 | var sourcesAvailableOnline = []; 222 | 223 | for (source in file.sources) { 224 | // skip sources which are not compatible with the current OS 225 | var compatible = true; 226 | if (source.os != null) { 227 | var osInfo = ammer.internal.v1.OsInfo.info; 228 | if (source.os != osInfo.os) compatible = false; 229 | if (compatible && osInfo.architecture != null) { 230 | if (source.architectures != null 231 | && !source.architectures.contains(osInfo.architecture)) compatible = false; 232 | } 233 | if (compatible && osInfo.version != null) { 234 | if (source.minVersion != null 235 | && osInfo.versionCompare(osInfo.version, source.minVersion) < 0) compatible = false; 236 | if (source.maxVersion != null 237 | && osInfo.versionCompare(osInfo.version, source.maxVersion) > 0) compatible = false; 238 | } 239 | } 240 | if (!compatible) { 241 | sourcesSkipped.push(source); 242 | continue; 243 | } 244 | 245 | var srcFull = Path.normalize(Path.join([binaryPath, source.name])); 246 | if (FileSystem.exists(srcFull)) { 247 | sourcesAvailableLocally.push(source); 248 | continue; 249 | } 250 | 251 | if (source.downloadFrom != null) { 252 | sourcesAvailableOnline.push(source); 253 | continue; 254 | } 255 | 256 | sourcesSkipped.push(source); 257 | } 258 | 259 | // exactly one locally available, compatible source: copy it 260 | if (!dstExists && sourcesAvailableLocally.length == 1) { 261 | var source = sourcesAvailableLocally[0]; 262 | var srcFull = Path.normalize(Path.join([binaryPath, source.name])); 263 | Sys.println('[ammer] copying $srcFull -> $dstFull'); 264 | File.copy(srcFull, dstFull); 265 | continue; 266 | } 267 | 268 | // at this point, we will prompt the user to choose an action, either 269 | // because there are multiple locally available, compatible sources, 270 | // or because a source will need to be downloaded 271 | 272 | Sys.println('[ammer] required file $dst not found for library ${info.name}'); 273 | Sys.println(" options (press a key):"); 274 | 275 | var options = new Map(); 276 | function option(char:Int, text:String, f:()->Void):Void { 277 | Sys.println(' [${String.fromCharCode(char)}] $text'); 278 | options[char] = f; 279 | } 280 | var yeses = "y0123456789".split(""); 281 | function optionYes(text:String, f:()->Void):Void { 282 | if (yeses.length == 0) { 283 | Sys.println(' [ ] (too many options...) $text'); 284 | } else { 285 | var cc = yeses.shift().charCodeAt(0); 286 | option(cc, text, f); 287 | } 288 | } 289 | 290 | function describe(source:ammer.internal.v1.LibInfo.LibInfoFileSource, online:Bool):String { 291 | var infos = []; 292 | if (online && source.downloadFrom != null) infos.push('URL: ${source.downloadFrom}'); 293 | if (source.os != null) infos.push('OS: ${source.os}'); 294 | if (source.minVersion != null) infos.push('OS version >= ${source.minVersion}'); 295 | if (source.maxVersion != null) infos.push('OS version <= ${source.maxVersion}'); 296 | if (source.architectures != null) infos.push('arch: ${source.architectures.join("/")}'); 297 | if (infos.length == 0) return source.description; 298 | return '${source.description} (${infos.join(", ")})'; 299 | } 300 | 301 | var choiceDone = false; 302 | var doDownload = null; 303 | var doCopy = null; 304 | if (dstExists) { 305 | for (source in sourcesAvailableLocally) { 306 | optionYes('override file using: ${describe(source, false)}', () -> { 307 | doCopy = source; 308 | }); 309 | } 310 | for (source in sourcesAvailableOnline) { 311 | optionYes('download file now and override using: ${describe(source, true)}', () -> { 312 | doDownload = source; 313 | doCopy = source; 314 | }); 315 | } 316 | option("s".code, "keep existing file", () -> {}); 317 | } else { 318 | for (source in sourcesAvailableLocally) { 319 | optionYes('use: ${describe(source, false)}', () -> { 320 | doCopy = source; 321 | }); 322 | } 323 | for (source in sourcesAvailableOnline) { 324 | optionYes('download file now: ${describe(source, true)}', () -> { 325 | doDownload = source; 326 | doCopy = source; 327 | }); 328 | } 329 | if (sourcesAvailableLocally.length == 0 && sourcesAvailableOnline.length == 0) { 330 | Sys.println(" This file is not available online: you may need to compile it locally."); 331 | // TODO: add link to manual page (once it exists) 332 | } 333 | option("s".code, "ignore (program may not function correctly)", () -> {}); 334 | } 335 | if (sourcesSkipped.length > 0) { 336 | option("i".code, 'show details of ${sourcesSkipped.length} skipped sources', () -> { 337 | for (source in sourcesSkipped) { 338 | Sys.println(' ${describe(source, true)}'); 339 | } 340 | choiceDone = false; 341 | }); 342 | } 343 | option("q".code, "abort compilation", () -> throw fail("aborting")); 344 | 345 | while (!choiceDone) { 346 | var choice = Sys.getChar(false); 347 | if (!options.exists(choice)) continue; 348 | choiceDone = true; 349 | options[choice](); 350 | } 351 | 352 | if (doDownload != null) { 353 | Sys.println(" downloading file ..."); 354 | var url = doDownload.downloadFrom; 355 | var srcFull = Path.normalize(Path.join([binaryPath, doDownload.name])); 356 | var followed = 0; 357 | var downloaded = false; 358 | while (!downloaded) { 359 | var http = new haxe.Http(url); 360 | var done = false; 361 | var success = false; 362 | try { 363 | var status = "999"; 364 | http.onBytes = (data) -> { 365 | if (status.charAt(0) == "2") { 366 | Sys.println(" file downloaded"); 367 | /* 368 | var digest = haxe.crypto.Sha256.make(data).toHex(); 369 | if (digest != file.digest) { 370 | Sys.println(" warning: file digest has changed (possibly a newer version)"); 371 | } 372 | */ 373 | File.saveBytes(srcFull, data); 374 | downloaded = true; 375 | done = true; 376 | success = true; 377 | } else if (status.charAt(0) == "3") { 378 | http.responseHeaders.exists("Location") || throw fail("redirect without Location header"); 379 | Sys.println(" following redirect ..."); 380 | url = http.responseHeaders["Location"]; 381 | done = true; 382 | success = true; 383 | } 384 | }; 385 | http.onError = (msg) -> { 386 | Sys.println(' download error: $msg'); 387 | done = true; 388 | success = false; 389 | }; 390 | http.onStatus = (s:Int) -> { 391 | status = '$s'; 392 | Sys.println(' status: $status'); 393 | if (status.charAt(0) != "2" && status.charAt(0) != "3") { 394 | done = true; 395 | success = false; 396 | } 397 | } 398 | http.request(false); 399 | } catch (ex:Dynamic) { 400 | Sys.println(' download error: $ex'); 401 | done = true; 402 | success = false; 403 | } 404 | // TODO: timeout? 405 | while (!done) Sys.sleep(.25); 406 | if (!success) fail("download error"); 407 | followed++; 408 | if (followed >= 5) fail("too many redirects"); 409 | } 410 | } 411 | if (doCopy != null) { 412 | var srcFull = Path.normalize(Path.join([binaryPath, doCopy.name])); 413 | Sys.println('[ammer] copying $srcFull -> $dstFull'); 414 | File.copy(srcFull, dstFull); 415 | } 416 | } 417 | } 418 | 419 | function extend(target:Map, source:Map):Void { 420 | for (k => v in source) { 421 | if (target.exists(k)) throw fail("library replaces an existing type"); 422 | target[k] = v; 423 | } 424 | } 425 | extend(mergedInfo.arrays.byTypeId, info.arrays.byTypeId); 426 | extend(mergedInfo.arrays.byElementTypeId, info.arrays.byElementTypeId); 427 | extend(mergedInfo.boxes.byTypeId, info.boxes.byTypeId); 428 | extend(mergedInfo.boxes.byElementTypeId, info.boxes.byElementTypeId); 429 | extend(mergedInfo.callbacks.byTypeId, info.callbacks.byTypeId); 430 | extend(mergedInfo.callbacks.byElementTypeId, info.callbacks.byElementTypeId); 431 | extend(mergedInfo.enums, info.enums); 432 | extend(mergedInfo.haxeRefs.byTypeId, info.haxeRefs.byTypeId); 433 | extend(mergedInfo.haxeRefs.byElementTypeId, info.haxeRefs.byElementTypeId); 434 | extend(mergedInfo.opaques, info.opaques); 435 | extend(mergedInfo.structs, info.structs); 436 | extend(mergedInfo.sublibraries, info.sublibraries); 437 | // ammer-fragment-end: register-v1 438 | }); 439 | } 440 | 441 | public static function initLibrary(lib:ClassType, name:String, options:LibContext.LibContextOptions):LibContext { 442 | if (libraries.byLibraryName.exists(name)) 443 | throw Reporting.error('duplicate definition of library "$name"'); 444 | 445 | var libId = Utils.typeId(lib); 446 | var ctx = new LibContext(name, options); 447 | ctx.isLibTypes = (libId == "ammer.internal.LibTypes.LibTypes"); 448 | libraries.byLibraryName[name] = ctx; 449 | libraries.byTypeId[libId] = ctx; 450 | libraries.active.push(ctx); 451 | 452 | contextReady(lib, ctx); 453 | 454 | return ctx; 455 | } 456 | 457 | public static function contextReady(lib:ClassType, ctx:LibContext):Void { 458 | var libId = Utils.typeId(lib); 459 | var curr = queuedSubtypes.first; 460 | while (curr != null) { 461 | var currId = Utils.typeId2(curr.lib); 462 | if (Utils.typeId2(curr.lib) == libId) { 463 | dequeueSubtype(curr); 464 | curr.result = Reporting.withPosition(curr.pos, () -> curr.process(ctx)); 465 | curr.done = true; 466 | } 467 | curr = curr.next; 468 | } 469 | } 470 | 471 | // TODO: support multiple libraries for a subtype 472 | public static function initSubtype( 473 | subId:String, 474 | lib:Type, 475 | process:(ctx:LibContext)->T, 476 | ?processLibTypes:(ctx:LibContext)->T 477 | ):T { 478 | // enqueue deferred subtype processing 479 | var queued:QueuedSubtype = { 480 | subId: subId, 481 | lib: lib, 482 | pos: Reporting.currentPos(), 483 | process: process, 484 | result: null, 485 | done: false, 486 | prev: null, 487 | next: null, 488 | }; 489 | enqueueSubtype(queued); 490 | 491 | // trigger typing of the library 492 | var ctx = Types.resolveContext(lib); 493 | ctx != null || { 494 | trace("could not resolve context", subId, lib); 495 | Context.fatalError("here", Context.currentPos()); 496 | //throw 0; 497 | }; 498 | 499 | if (queued.done) { 500 | return queued.result; 501 | } 502 | 503 | // if it was not processed, then either: 504 | // - the library is currently being processed, or 505 | // - the library has already finished processing 506 | 507 | if (!ctx.done) { 508 | // currently being processed 509 | dequeueSubtype(queued); 510 | return process(ctx); 511 | } else { 512 | if (ctx.isLibTypes && processLibTypes != null) { 513 | return processLibTypes(ctx); 514 | } 515 | 516 | // already processed, report missing subtype link 517 | // TODO: format types better 518 | Reporting.error( 519 | '$subId is declared as a subtype of library ${Utils.typeId2(lib)}, ' 520 | + 'but the library was already fully processed. Please add a subtype ' 521 | + 'link @:ammer.sub((_ : $subId)) to the library definition.'); 522 | return null; 523 | } 524 | } 525 | } 526 | 527 | #end 528 | -------------------------------------------------------------------------------- /src/ammer/internal/Fields.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type; 8 | import haxe.macro.TypeTools; 9 | 10 | using Lambda; 11 | using StringTools; 12 | 13 | enum FieldContext { 14 | FCLibrary(options:ammer.internal.v1.LibInfo.LibInfoLibrary); 15 | FCOpaque(options:ammer.internal.v1.LibInfo.LibInfoOpaque); 16 | FCStruct(options:ammer.internal.v1.LibInfo.LibInfoStruct); 17 | FCSublibrary(options:ammer.internal.v1.LibInfo.LibInfoSublibrary); 18 | } 19 | 20 | typedef CategorisedMethod = { 21 | nativeName:String, 22 | metas:Array, 23 | field:Field, 24 | fun:Function, 25 | }; 26 | typedef CategorisedVar = { 27 | nativeName:String, 28 | metas:Array, 29 | field:Field, 30 | ct:ComplexType, 31 | isFinal:Bool, 32 | }; 33 | 34 | class Fields { 35 | static function categorise( 36 | fields:Array, 37 | fieldCtx:FieldContext 38 | ):{ 39 | staticMethods:Array, 40 | staticVars:Array, 41 | instanceMethods:Array, 42 | instanceVars:Array, 43 | passMethods:Array, 44 | } { 45 | var nativePrefix:String = null; 46 | switch (fieldCtx) { 47 | case FCLibrary(options): 48 | nativePrefix = options.nativePrefix; 49 | case FCOpaque(options): 50 | nativePrefix = options.nativePrefix; 51 | case FCStruct(options): 52 | nativePrefix = options.nativePrefix; 53 | case FCSublibrary(options): 54 | nativePrefix = options.nativePrefix; 55 | } 56 | 57 | var staticMethods = []; 58 | var staticVars = []; 59 | var instanceMethods = []; 60 | var instanceVars = []; 61 | var passMethods = []; 62 | for (field in fields) Reporting.withPosition(field.pos, () -> { 63 | var nativeName = nativePrefix != null 64 | ? nativePrefix + field.name 65 | : field.name; 66 | 67 | // validate field access modifiers 68 | var isInstance = !field.access.contains(AStatic); 69 | if (isInstance && !fieldCtx.match(FCStruct(_) | FCOpaque(_))) 70 | return Reporting.error('non-static field ${field.name} only allowed in struct types and opaque types'); 71 | 72 | var isFinal = field.access.contains(AFinal); 73 | 74 | for (access in field.access) switch (access) { 75 | case AOverride: Reporting.error('"override" not allowed in ammer definitions'); 76 | case ADynamic: Reporting.error('"dynamic" not allowed in ammer definitions'); 77 | case AInline: Reporting.error('"inline" not allowed in ammer definitions'); 78 | case AMacro: Reporting.error('"macro" not allowed in ammer definitions'); 79 | case AExtern: Reporting.error('"extern" not allowed in ammer definitions'); 80 | case AAbstract: Reporting.error('"abstract" not allowed in ammer definitions'); 81 | case AOverload: Reporting.error('"overload" not allowed in ammer definitions'); 82 | case _: 83 | } 84 | 85 | // parse metadata 86 | var allowHaxe = false; 87 | var defaultNativeName = true; 88 | var metas = Meta.extract(field.meta, Meta.COMMON_FIELD, false); 89 | for (meta in metas) switch (meta) { 90 | case PMHaxe: allowHaxe = true; 91 | case PMNative(name): defaultNativeName = false; nativeName = name; 92 | case _: throw 0; 93 | } 94 | if (allowHaxe) { 95 | defaultNativeName || throw Reporting.error("@:ammer.native has no effect on @:ammer.haxe fields"); 96 | } 97 | 98 | // categorise 99 | switch (field.kind) { 100 | case FFun(f): 101 | if (allowHaxe) { 102 | f.expr != null || throw Reporting.error("function body required for @:ammer.haxe functions"); 103 | passMethods.push({ 104 | nativeName: null, 105 | metas: metas, 106 | field: field, 107 | fun: f, 108 | }); 109 | } else { 110 | f.expr == null || throw Reporting.error("function body not allowed (unless @:ammer.haxe is added)"); 111 | f.ret != null || return Reporting.error("type annotation required for return type"); 112 | !isFinal || return Reporting.error('"final" not allowed on methods'); 113 | for (arg in f.args) arg.type != null || return Reporting.error("type annotation required for arguments"); 114 | (isInstance ? instanceMethods : staticMethods).push({ 115 | nativeName: nativeName, 116 | metas: metas, 117 | field: field, 118 | fun: f, 119 | }); 120 | } 121 | case FVar(ct, def): 122 | !allowHaxe || return Reporting.error("@:ammer.haxe not yet supported on variable fields"); 123 | ct != null || return Reporting.error("type annotation required"); 124 | def == null || return Reporting.error("default values are not allowed"); 125 | (isInstance ? instanceVars : staticVars).push({ 126 | nativeName: nativeName, 127 | metas: metas, 128 | field: field, 129 | ct: ct, 130 | isFinal: isFinal, 131 | }); 132 | case _: return Reporting.error("invalid field kind"); 133 | } 134 | }); 135 | return { 136 | staticMethods: staticMethods, 137 | staticVars: staticVars, 138 | instanceMethods: instanceMethods, 139 | instanceVars: instanceVars, 140 | passMethods: passMethods, 141 | }; 142 | } 143 | 144 | public static function process( 145 | fields:Array, 146 | ctx:LibContext, 147 | fieldCtx:FieldContext 148 | ):Array { 149 | var categorised = categorise(fields, fieldCtx); 150 | var processed:Array = []; 151 | 152 | // create type representation 153 | var libraryOptions:ammer.internal.v1.LibInfo.LibInfoLibrary; 154 | var opaqueOptions:ammer.internal.v1.LibInfo.LibInfoOpaque; 155 | var structOptions:ammer.internal.v1.LibInfo.LibInfoStruct; 156 | var sublibraryOptions:ammer.internal.v1.LibInfo.LibInfoSublibrary; 157 | switch (fieldCtx) { 158 | case FCLibrary(options): 159 | libraryOptions = options; 160 | case FCOpaque(options): 161 | // TODO: reduce duplication between opaque and struct cases 162 | opaqueOptions = options; 163 | options.marshal = ctx.marshal.opaque(options.opaqueName); 164 | processed.push({ 165 | name: "_ammer_native", 166 | meta: [], 167 | pos: Reporting.currentPos(), 168 | kind: FVar(options.marshal.type.haxeType, null), 169 | doc: null, 170 | access: [APrivate], 171 | }); 172 | processed.push({ 173 | name: "new", 174 | meta: [], 175 | pos: Reporting.currentPos(), 176 | kind: FFun({ 177 | ret: null, 178 | expr: macro _ammer_native = native, 179 | args: [{ name: "native", type: options.marshal.type.haxeType }], 180 | }), 181 | doc: null, 182 | access: [APrivate], 183 | }); 184 | var implCt = TypeTools.toComplexType(options.implType); 185 | var implTp = (switch (implCt) { 186 | case TPath(tp): tp; 187 | case _: throw 0; 188 | }); 189 | processed.push({ 190 | name: "_ammer_lib_nullPtr", 191 | meta: [], 192 | pos: Reporting.currentPos(), 193 | kind: FFun({ 194 | ret: implCt, 195 | expr: macro return new $implTp($e{options.marshal.nullPtr}), 196 | args: [], 197 | }), 198 | doc: null, 199 | access: [APrivate, AStatic], 200 | }); 201 | case FCStruct(options): 202 | structOptions = options; 203 | 204 | // TODO: this is not great, is there better API design? 205 | var structEmpty = ctx.marshal.structPtr(options.structName, [], false); 206 | options.marshalOpaque = structEmpty.type; 207 | options.marshalDeref = structEmpty.typeDeref; 208 | 209 | // TODO: store the fieldRefs? 210 | var fieldRefs = categorised.instanceVars.map(f -> { 211 | var fieldType = Types.resolveComplexType(f.ct, ctx); 212 | var fref = ctx.marshal.fieldRef(f.nativeName, fieldType.marshal); 213 | fref; 214 | }); 215 | options.marshal = ctx.marshal.structPtr( 216 | options.structName, 217 | fieldRefs, 218 | options.gen.alloc != null 219 | || options.gen.free != null 220 | || options.gen.nullPtr != null 221 | ); 222 | processed.push({ 223 | name: "_ammer_native", 224 | meta: [], 225 | pos: Reporting.currentPos(), 226 | kind: FVar(options.marshal.type.haxeType, null), 227 | doc: null, 228 | access: [APrivate], 229 | }); 230 | processed.push({ 231 | name: "new", 232 | meta: [], 233 | pos: Reporting.currentPos(), 234 | kind: FFun({ 235 | ret: null, 236 | expr: macro _ammer_native = native, 237 | args: [{ name: "native", type: options.marshal.type.haxeType }], 238 | }), 239 | doc: null, 240 | access: [APrivate], 241 | }); 242 | var implCt = TypeTools.toComplexType(options.implType); 243 | var implTp = (switch (implCt) { 244 | case TPath(tp): tp; 245 | case _: throw 0; 246 | }); 247 | 248 | if (options.gen.alloc != null) { 249 | processed.push({ 250 | name: options.gen.alloc, 251 | meta: [], 252 | pos: Reporting.currentPos(), 253 | kind: FFun({ 254 | ret: implCt, 255 | expr: macro return new $implTp($e{options.marshal.alloc}), 256 | args: [], 257 | }), 258 | doc: null, 259 | access: [APublic, AStatic], 260 | }); 261 | } 262 | if (options.gen.free != null) { 263 | processed.push({ 264 | name: options.gen.free, 265 | meta: [], 266 | pos: Reporting.currentPos(), 267 | kind: FFun({ 268 | ret: (macro : Void), 269 | expr: macro $e{options.marshal.free(macro _ammer_native)}, 270 | args: [], 271 | }), 272 | doc: null, 273 | access: [APublic], 274 | }); 275 | } 276 | if (options.gen.nullPtr != null) { 277 | processed.push({ 278 | name: options.gen.nullPtr, 279 | meta: [], 280 | pos: Reporting.currentPos(), 281 | kind: FFun({ 282 | ret: implCt, 283 | expr: macro return new $implTp($e{options.marshal.nullPtr}), 284 | args: [], 285 | }), 286 | doc: null, 287 | access: [APublic, AStatic], 288 | }); 289 | } 290 | case FCSublibrary(options): 291 | sublibraryOptions = options; 292 | } 293 | 294 | // process fields 295 | function processMethod(method:CategorisedMethod, isInstance:Bool):Void { 296 | var argCount = method.fun.args.length; 297 | 298 | // process method metadata 299 | var retCCast = null; 300 | var cPrereturn = ""; 301 | var cReturn = "%CALL%"; 302 | var derivedRet = null; 303 | var derivedRetType = null; 304 | for (meta in Meta.extract(method.field.meta, Meta.COMMON_METHOD)) switch (meta) { 305 | case PMNative(name): // already processed in `categorised`, ignore 306 | case PMC_Cast(to): retCCast = to; 307 | case PMC_MacroCall: // no-op 308 | case PMC_Prereturn(expr): cPrereturn = expr; 309 | case PMC_Return(expr): 310 | // ret.mangled != "v" || throw Reporting.error(":ammer.c.return cannot be used on a method with `Void` return type"); 311 | cReturn = expr; 312 | case PMRet_Derive(expr, ct): 313 | derivedRet = expr; 314 | derivedRetType = ct; 315 | case _: throw 0; 316 | } 317 | 318 | // process argument metadata 319 | var skipArgs = [ for (idx in 0...argCount) false ]; 320 | var derivedHaxe = [ for (idx in 0...argCount) null ]; 321 | var replaceHaxe = [ for (idx in 0...argCount) null ]; // TODO: messy! 322 | var replaceArgType = [ for (idx in 0...argCount) null ]; 323 | var cCast = [ for (idx in 0...argCount) null ]; // TODO: messy! 324 | for (idx in 0...argCount) { 325 | method.fun.args[idx].meta != null || continue; 326 | for (meta in Meta.extract(method.fun.args[idx].meta, Meta.METHOD_ARG)) switch (meta) { 327 | case PMC_Cast(to): cCast[idx] = to; 328 | case PMSkip: skipArgs[idx] = true; 329 | case PMDerive(e): derivedHaxe[idx] = e; 330 | // TODO: c.derive 331 | case _: throw 0; 332 | } 333 | } 334 | var nonSkipCtr = 0; 335 | var derivedC = [ for (idx in 0...argCount) skipArgs[idx] 336 | ? null 337 | : ((cCast[idx] != null ? '(${cCast[idx]})' : "") + '_arg${nonSkipCtr++}') ]; 338 | var preExprs = []; 339 | 340 | // TODO: sanity checks, e.g. disallow skip and derive on the same argument 341 | // TODO: ammer.argN... variant of metadata? 342 | // TODO: ammer.ret... metadata 343 | 344 | // process special types 345 | // TODO: retAlloc is not a very clean solution 346 | var retAlloc = null; 347 | function localResolve(res:Type, idx:Int):Types.ResolvedType { 348 | if (Types.TYPES.this_.match(res)) { 349 | isInstance || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); 350 | (structOptions != null || opaqueOptions != null) || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); 351 | idx != -1 || throw Reporting.error("ammer.ffi.This cannot be used as the return type"); 352 | derivedHaxe[idx] = macro this; 353 | res = structOptions != null ? structOptions.implType : opaqueOptions.implType; 354 | } else if (Types.TYPES.derefThis.match(res)) { 355 | isInstance || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); 356 | (structOptions != null || opaqueOptions != null) || throw Reporting.error("ammer.ffi.This can only be used in instance methods of opaque or struct types"); 357 | idx != -1 || throw Reporting.error("ammer.ffi.This cannot be used as the return type"); 358 | derivedHaxe[idx] = macro this; 359 | var implCt = TypeTools.toComplexType(structOptions != null ? structOptions.implType : opaqueOptions.implType); 360 | res = haxe.macro.ComplexTypeTools.toType((macro : ammer.ffi.Deref<$implCt>)); 361 | } else { 362 | switch (res) { 363 | case TInst(Utils.typeId(_.get()) => id, []) if (Ammer.mergedInfo.callbacks.byTypeId.exists(id)): 364 | var callback = Ammer.mergedInfo.callbacks.byTypeId[id]; 365 | derivedC[idx] = callback.callbackName; 366 | if (callback.isGlobal) { 367 | var ident = 'arg$idx'; 368 | preExprs.push(macro $p{Utils.accessTp(Utils.expectTypePath(callback.callbackCt))}.store($i{ident})); 369 | replaceHaxe[idx] = macro 0; 370 | replaceArgType[idx] = callback.funCt; 371 | } else { 372 | derivedHaxe[idx] = macro 0; 373 | } 374 | res = Types.TYPES.i32.type; 375 | case TInst(Utils.typeId(_.get()) => "ammer.ffi.Alloc.Alloc", params): 376 | idx == -1 || throw Reporting.error("ammer.ffi.Alloc cannot be used as an argument type"); 377 | params.length == 1 || throw Reporting.error("ammer.ffi.Alloc should have one type parameter"); 378 | var type = Utils.classOfParam(params[0]).get(); 379 | var id = Utils.typeId(type); 380 | Ammer.mergedInfo.structs[id].marshal != null || throw Reporting.error("type parameter should be a struct"); 381 | Ammer.mergedInfo.structs[id].alloc || throw Reporting.error("struct is not allocatable"); 382 | var ct = TypeTools.toComplexType(params[0]); 383 | var tp = (switch (ct) { 384 | case TPath(tp): tp; 385 | case _: throw 0; 386 | }); 387 | retAlloc = Ammer.mergedInfo.structs[id].structName; 388 | res = params[0]; 389 | case TInst(Utils.typeId(_.get()) => "ammer.ffi.Unsupported.Unsupported", [val]): 390 | // TODO: allow ammer.ffi.Unsupported<""> for ignored return values 391 | idx != -1 || throw Reporting.error("ammer.ffi.Unsupported cannot be used as the return type"); 392 | var expr = Utils.stringOfParam(val); 393 | expr != null || throw Reporting.error("ammer.def.Unsupported type parameter should be a string"); 394 | derivedHaxe[idx] = (macro 0); 395 | derivedC[idx] = expr; 396 | res = Types.TYPES.i32.type; 397 | case TType(_): return localResolve(TypeTools.follow(res, true), idx); 398 | case _: 399 | } 400 | } 401 | return Types.resolveType(res, ctx); 402 | } 403 | function localResolveCt(ct:ComplexType, idx:Int):Types.ResolvedType { 404 | var res = Reporting.resolveType(ct, Reporting.currentPos()); 405 | return localResolve(res, idx); 406 | } 407 | 408 | // resolve types 409 | var ret = localResolveCt(method.fun.ret, -1); 410 | var args = method.fun.args.mapi((idx, arg) -> skipArgs[idx] ? null : localResolveCt(arg.type, idx)); 411 | 412 | // create native representation 413 | var nativeCall = '${method.nativeName}(${[ for (idx in 0...argCount) if (!skipArgs[idx]) derivedC[idx] ].join(", ")})'; 414 | var native = ctx.library.addFunction( 415 | ret.marshal, 416 | [ for (idx in 0...argCount) if (!skipArgs[idx]) args[idx].marshal ], 417 | cPrereturn + "\n" + (retAlloc != null 418 | // TODO: configure malloc, memcpy 419 | ? '_return = ($retAlloc*)malloc(sizeof($retAlloc)); 420 | $retAlloc retval = ${cReturn.replace("%CALL%", nativeCall)}; 421 | memcpy(_return, &retval, sizeof($retAlloc));' 422 | : '${ret.marshal.mangled != "v" ? "_return = " : ""}${retCCast != null ? '($retCCast)' : ""}${cReturn.replace("%CALL%", nativeCall)};'), 423 | { 424 | comment: 'original field name: ${method.field.name}', 425 | } 426 | ); 427 | 428 | // create Haxe call 429 | var call:Expr = { 430 | expr: ECall( 431 | native, 432 | [ for (idx in 0...argCount) if (!skipArgs[idx]) { 433 | var expr = derivedHaxe[idx] != null 434 | ? derivedHaxe[idx] 435 | : (replaceHaxe[idx] != null 436 | ? replaceHaxe[idx] 437 | : { expr: EConst(CIdent('arg$idx')), pos: method.field.pos }); 438 | Utils.exprMap(expr, args[idx].unwrap); 439 | } ] 440 | ), 441 | pos: method.field.pos, 442 | }; 443 | var finalExprs = derivedRet != null 444 | ? [macro (var ret = $e{Utils.exprMap(call, ret.wrap)}), macro return $derivedRet] 445 | : [macro return $e{Utils.exprMap(call, ret.wrap)}]; 446 | processed.push(Utils.updateField(method.field, FFun({ 447 | ret: derivedRetType != null ? derivedRetType : ret.haxeType, 448 | expr: macro $b{preExprs.concat(finalExprs)}, 449 | args: [ for (idx in 0...argCount) { 450 | derivedHaxe[idx] == null || continue; 451 | ({name: 'arg$idx', type: skipArgs[idx] 452 | ? method.fun.args[idx].type 453 | : (replaceArgType[idx] != null 454 | ? replaceArgType[idx] 455 | : args[idx].haxeType)}:FunctionArg); 456 | } ], 457 | }))); 458 | } 459 | 460 | for (method in categorised.staticMethods) 461 | Reporting.withPosition(method.field.pos, () -> processMethod(method, false)); 462 | for (method in categorised.instanceMethods) 463 | Reporting.withPosition(method.field.pos, () -> processMethod(method, true)); 464 | for (method in categorised.passMethods) 465 | processed.push(method.field); 466 | 467 | // TODO: reduce duplication? 468 | for (v in categorised.staticVars) Reporting.withPosition(v.field.pos, () -> { 469 | var fieldType = Types.resolveComplexType(v.ct, ctx); 470 | var marshal = ctx.marshal.fieldRef(v.nativeName, fieldType.marshal); 471 | var getter = ctx.library.addFunction( 472 | marshal.type, 473 | [], 474 | '_return = ${v.nativeName};' 475 | ); 476 | if (v.isFinal) { 477 | processed.push({ 478 | name: 'get_${v.field.name}', 479 | pos: v.field.pos, 480 | kind: FFun({ 481 | ret: fieldType.haxeType, 482 | expr: macro return $e{Utils.exprMap(macro $getter(), fieldType.wrap)}, 483 | args: [], 484 | }), 485 | access: [APrivate, AStatic], 486 | }); 487 | v.field.access.remove(AFinal); // Haxe#8859 488 | processed.push(Utils.updateField(v.field, FProp("get", "never", fieldType.haxeType, null))); 489 | } else { 490 | processed.push({ 491 | name: 'get_${v.field.name}', 492 | pos: v.field.pos, 493 | kind: FFun({ 494 | ret: fieldType.haxeType, 495 | expr: macro return $e{Utils.exprMap(macro $getter(), fieldType.wrap)}, 496 | args: [], 497 | }), 498 | access: [APrivate, AStatic], 499 | }); 500 | var setter = ctx.library.addFunction( 501 | ctx.marshal.void(), 502 | [marshal.type], 503 | '${v.nativeName} = _arg0;' 504 | ); 505 | processed.push({ 506 | name: 'set_${v.field.name}', 507 | pos: v.field.pos, 508 | kind: FFun({ 509 | ret: fieldType.haxeType, 510 | expr: macro { 511 | $setter($e{Utils.exprMap(macro val, fieldType.unwrap)}); 512 | return val; 513 | }, 514 | args: [{ 515 | name: "val", 516 | type: fieldType.haxeType, 517 | }], 518 | }), 519 | access: [APrivate, AStatic], 520 | }); 521 | processed.push(Utils.updateField(v.field, FProp("get", "set", fieldType.haxeType, null))); 522 | } 523 | }); 524 | 525 | for (v in categorised.instanceVars) Reporting.withPosition(v.field.pos, () -> { 526 | var fieldType = Types.resolveComplexType(v.ct, ctx); 527 | var marshal = ctx.marshal.fieldRef(v.nativeName, fieldType.marshal); 528 | processed.push({ 529 | name: 'get_${v.field.name}', 530 | pos: v.field.pos, 531 | kind: FFun({ 532 | ret: fieldType.haxeType, 533 | expr: macro return $e{Utils.exprMap(structOptions.marshal.fieldGet[v.nativeName](macro _ammer_native), fieldType.wrap)}, 534 | args: [], 535 | }), 536 | access: [APrivate], 537 | }); 538 | processed.push({ 539 | name: 'set_${v.field.name}', 540 | pos: v.field.pos, 541 | kind: FFun({ 542 | ret: fieldType.haxeType, 543 | expr: macro { 544 | $e{structOptions.marshal.fieldSet[v.nativeName](macro _ammer_native, Utils.exprMap(macro val, fieldType.unwrap))}; 545 | return val; 546 | }, 547 | args: [{ 548 | name: "val", 549 | type: fieldType.haxeType, 550 | }], 551 | }), 552 | access: [APrivate], 553 | }); 554 | processed.push(Utils.updateField(v.field, FProp("get", "set", fieldType.haxeType, null))); 555 | }); 556 | 557 | return processed; 558 | } 559 | } 560 | 561 | #end 562 | -------------------------------------------------------------------------------- /src/ammer/internal/Bakery.hx: -------------------------------------------------------------------------------- 1 | package ammer.internal; 2 | 3 | #if macro 4 | 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Printer; 8 | import haxe.macro.Type; 9 | import haxe.macro.TypeTools; 10 | import haxe.io.Path; 11 | import sys.io.File; 12 | import sys.FileSystem; 13 | import ammer.core.utils.LineBuf; 14 | 15 | using Lambda; 16 | using StringTools; 17 | 18 | // TODO: haxelib.json (extraParams for `--macro`) 19 | 20 | class Bakery { 21 | public static final BAKE_PREFIX = "// ammer-bake: "; 22 | static var ammerRootPath:String = { 23 | var path = haxe.macro.PositionTools.getInfos((macro 0).pos).file; 24 | if (!Path.isAbsolute(path)) { 25 | path = Path.join([Sys.getCwd(), path]); 26 | } 27 | Path.normalize(Path.directory(Path.normalize(path)) + "/../"); 28 | }; 29 | 30 | public static var isBaking = false; 31 | public static var mainType:Type; 32 | static var bakeOutput:String; 33 | static var rootToBin:String; 34 | static var fileSources:Array; 35 | 36 | static var downloadURL:Null; 37 | static var description:Null; 38 | static var os:Null; 39 | static var architectures:Null>; 40 | static var minVersion:Null; 41 | static var maxVersion:Null; 42 | 43 | public static function init():Void { 44 | if (isBaking) return; 45 | if (!Config.getBool("ammer.bake", false)) return; 46 | isBaking = true; 47 | 48 | var mainTypeStr = Config.getString("ammer.bake.mainType", null, true); 49 | var mainTypePack = mainTypeStr.split("."); 50 | mainType = Context.resolveType(TPath({ 51 | // TODO: support subtypes? merge somehow with Utils.complexTypeExpr? 52 | name: mainTypePack.pop(), 53 | pack: mainTypePack, 54 | }), Context.currentPos()); 55 | 56 | bakeOutput = Config.getPath("ammer.bake.output", null, true); 57 | rootToBin = Config.getString("ammer.bake.rootToBin", null, true); 58 | var sourceCtr = 0; 59 | fileSources = [ while (true) { 60 | var prefix = 'ammer.bake.fileSource.${sourceCtr++}'; 61 | var anyData = false; 62 | inline function c(x: Null):Null { 63 | if (x != null) anyData = true; 64 | return x; 65 | } 66 | var source:ammer.internal.v1.LibInfo.LibInfoFileSource = { 67 | name: c(Config.getString('$prefix.name', null)), 68 | downloadFrom: c(Config.getString('$prefix.downloadFrom', null)), 69 | description: c(Config.getString('$prefix.description', null)), 70 | os: c(Config.getString('$prefix.os', null)), 71 | architectures: c(Config.getStringArray('$prefix.architectures', ",", null)), 72 | minVersion: c(Config.getString('$prefix.minVersion', null)), 73 | maxVersion: c(Config.getString('$prefix.maxVersion', null)), 74 | }; 75 | if (!anyData) break; 76 | source; 77 | } ]; 78 | 79 | Context.onAfterTyping(writeFiles); 80 | } 81 | 82 | static function writeFiles(_):Void { 83 | FileSystem.createDirectory(bakeOutput); 84 | var printer = new Printer(" "); 85 | var modules = BakeryOutput.withBufs(); 86 | 87 | var extraParams:String; 88 | var targetId:String; 89 | var targetCondition:String; 90 | var targetDescription:String; 91 | switch (Ammer.platform.kind) { 92 | case Cpp: 93 | extraParams = "cpp"; 94 | targetId = "cpp"; 95 | targetCondition = 'Context.definedValue("target.name") == "cpp"'; 96 | targetDescription = "Haxe/C++"; 97 | case Cs: 98 | extraParams = "cs"; 99 | targetId = "cs"; 100 | targetCondition = 'Context.definedValue("target.name") == "cs"'; 101 | targetDescription = "Haxe/C#"; 102 | case Eval: 103 | extraParams = "eval"; 104 | targetId = "eval"; 105 | targetCondition = 'Context.definedValue("target.name") == "eval"'; 106 | targetDescription = "Haxe interpreter"; 107 | case Hashlink: 108 | // var platformConfig = (cast Ammer.platformConfig : ammer.core.plat.Hashlink.HashlinkConfig); 109 | // TODO: HL/C? 110 | extraParams = "hl"; 111 | targetId = "hl"; 112 | targetCondition = 'Context.definedValue("target.name") == "hl"'; 113 | targetDescription = "Haxe/HashLink"; 114 | case Java: 115 | // var platformConfig = (cast Ammer.platformConfig : ammer.core.plat.Java.JavaConfig); 116 | // TODO: JVM? 117 | extraParams = "java"; 118 | targetId = "java"; 119 | targetCondition = 'Context.definedValue("target.name") == "java"'; 120 | targetDescription = "Haxe/Java"; 121 | case Lua: 122 | extraParams = "lua"; 123 | targetId = "lua"; 124 | targetCondition = 'Context.definedValue("target.name") == "lua"'; 125 | targetDescription = "Haxe/Lua"; 126 | case Neko: 127 | extraParams = "neko"; 128 | targetId = "neko"; 129 | targetCondition = 'Context.definedValue("target.name") == "neko"'; 130 | targetDescription = "Haxe/Neko"; 131 | case Nodejs: 132 | extraParams = "js && nodejs"; 133 | targetId = "nodejs"; 134 | targetCondition = 'Context.definedValue("target.name") == "js" && Context.defined("nodejs")'; 135 | targetDescription = "Haxe/Node.js"; 136 | case Python: 137 | extraParams = "python"; 138 | targetId = "python"; 139 | targetCondition = 'Context.definedValue("target.name") == "python"'; 140 | targetDescription = "Haxe/Python"; 141 | case None: Context.fatalError("cannot bake without selecting platform", Context.currentPos()); 142 | } 143 | 144 | // TODO: use as fragment in AmmerBaked? 145 | var osName = (switch (Sys.systemName()) { 146 | case "Windows": "win"; 147 | case "Linux": "linux"; 148 | case "BSD": "bsd"; 149 | case "Mac": "mac"; 150 | case _: "unknown"; // TODO: throw? 151 | }); 152 | var extensionDll = (switch (Sys.systemName()) { 153 | case "Windows": "dll"; 154 | case "Mac": "dylib"; 155 | case _: "so"; 156 | }); 157 | var prefixLib = (switch (Sys.systemName()) { 158 | case "Windows": ""; 159 | case _: "lib"; 160 | }); 161 | 162 | for (t in Utils.modifiedTypes) { 163 | switch (t.t.kind) { 164 | case KAbstractImpl(_.get() => abs): 165 | var typeId = Utils.typeId(abs); 166 | var output = modules.outputSub(abs.pack, abs.module.split(".").pop(), abs.name); 167 | output 168 | .ail("#if !macro"); 169 | var isEnum = false; 170 | for (meta in t.t.meta.get()) { 171 | if (meta.name == ":build" || meta.name == ":autoBuild") 172 | continue; 173 | if (meta.name.startsWith(":ammer.")) 174 | continue; 175 | if (meta.name == ":enum") { 176 | isEnum = true; 177 | continue; 178 | } 179 | output.ail(printer.printMetadata(meta)); 180 | } 181 | output 182 | .ai('${isEnum ? "enum " : ""}abstract ${abs.name}(${printer.printComplexType(TypeTools.toComplexType(abs.type))})') 183 | .map(abs.from, t -> t.field == null ? ' from ${printer.printComplexType(TypeTools.toComplexType(t.t))}' : "") 184 | .map(abs.to, t -> t.field == null ? ' to ${printer.printComplexType(TypeTools.toComplexType(t.t))}' : "") 185 | .al(' {') 186 | .lmap(t.fields, field -> printer.printField(field) + ";") 187 | .ail("}") 188 | .ail("#end /*!macro*/"); 189 | case KNormal: 190 | var typeId = Utils.typeId(t.t); 191 | var isLibrary = Ammer.libraries.byTypeId.exists(typeId); 192 | var output = modules.outputSub(t.t.pack, t.t.module.split(".").pop(), t.t.name); 193 | output 194 | .ail("#if !macro"); 195 | for (meta in t.t.meta.get()) { 196 | if (meta.name == ":build" || meta.name == ":autoBuild") 197 | continue; 198 | if (meta.name.startsWith(":ammer.")) 199 | continue; 200 | output.ail(printer.printMetadata(meta)); 201 | } 202 | output 203 | .ail('${t.t.isExtern ? "extern " : ""}class ${t.t.name} {') 204 | .lmap(t.fields, field -> printer.printField(field) + ";") 205 | .ail("}") 206 | .ail("#end /*!macro*/"); 207 | 208 | if (isLibrary) { 209 | var outputMacro = modules.outputSub(t.t.pack, t.t.module.split(".").pop() + ".macro", t.t.name); 210 | var ctx = Ammer.libraries.byTypeId[typeId]; 211 | var targetScript = ""; 212 | if (Ammer.platform.kind == Cpp) { 213 | var extc = ctx.libraryOptions.language.extension(); 214 | var exth = ctx.libraryOptions.language.extensionHeader(); 215 | targetScript = new LineBuf() 216 | .ail('var outputPath = haxe.macro.Compiler.getOutput();') 217 | .ail('sys.FileSystem.createDirectory(outputPath + "/ammer_build/ammer_${ctx.name}");') 218 | .ail('var herePath = haxe.macro.PositionTools.getInfos(info.herePos).file;') 219 | .ail('if (!haxe.io.Path.isAbsolute(herePath)) herePath = haxe.io.Path.join([Sys.getCwd(), herePath]);') 220 | .ail('herePath = haxe.io.Path.normalize(haxe.io.Path.directory(herePath));') 221 | .ail('sys.io.File.copy(herePath + "/lib.${ctx.name}.cpp_static.$extc", outputPath + "/ammer_build/ammer_${ctx.name}/lib.cpp_static.$extc");') 222 | .ail('sys.io.File.copy(herePath + "/lib.${ctx.name}.cpp_static.$exth", outputPath + "/ammer_build/ammer_${ctx.name}/lib.cpp_static.$exth");') 223 | .done(); 224 | } 225 | 226 | if (typeId != "ammer.internal.LibTypes.LibTypes") { 227 | function sortedKeys(map:Map):Array { 228 | var keys = [ for (k in map.keys()) k ]; 229 | keys.sort(Reflect.compare); 230 | return keys; 231 | } 232 | var sortedArrayNames = sortedKeys(ctx.info.arrays.byElementTypeId); 233 | var sortedBoxNames = sortedKeys(ctx.info.boxes.byElementTypeId); 234 | var sortedCallbackNames = sortedKeys(ctx.info.callbacks.byElementTypeId); 235 | var sortedEnumNames = sortedKeys(ctx.info.enums); 236 | var sortedHaxeRefNames = sortedKeys(ctx.info.haxeRefs.byElementTypeId); 237 | var sortedOpaqueNames = sortedKeys(ctx.info.opaques); 238 | var sortedStructNames = sortedKeys(ctx.info.structs); 239 | var sortedSublibraryNames = sortedKeys(ctx.info.sublibraries); 240 | 241 | // TODO: share this with ammer-core somehow? or else make outputPathRelative private again 242 | //var outputPathRelative = ctx.library.outputPathRelative(); 243 | //var sourcePath = 'prebuilt-$$osName-$targetId.bin'; 244 | var destPath = (switch (Ammer.platform.kind) { 245 | // C++ (static) does not put the glue code into a dynamic lib. 246 | case Cpp: null; 247 | 248 | case Cs: 'ammer_${ctx.name}.dll'; 249 | case Hashlink: 'ammer_${ctx.name}.hdll'; // TODO: HL/C uses the standard DLL naming 250 | case Neko: 'ammer_${ctx.name}.ndll'; 251 | case Nodejs: 'ammer_${ctx.name}.node'; 252 | case Python: 'ammer_${ctx.name}.$${osName == "win" ? "pyd" : "so"}'; 253 | case _: '$${prefixLib}ammer_${ctx.name}.$${extensionDll}'; 254 | }); 255 | //var outputPathRelative = Ammer.baseConfig.outputPath + "/" + destPath; 256 | 257 | function printString(s:String):String { 258 | // not quote safe! 259 | return s == null ? "null" : '"$s"'; 260 | } 261 | function printStringArray(s:Array):String { 262 | return s == null ? "null" : '[${s.map(printString).join(", ")}]'; 263 | } 264 | function printExpr(e:Expr):String { 265 | return e == null ? "null" : '(macro ${printer.printExpr(e)})'; 266 | } 267 | function printCt(ct:ComplexType):String { 268 | return '(macro : ${printer.printComplexType(ct)})'; 269 | } 270 | /*function printType(t:Type):String { 271 | return 'ComplexTypeTools.toType((macro : ${printer.printComplexType(TypeTools.toComplexType(t))}))'; 272 | }*/ 273 | 274 | outputMacro.a( 275 | File.getContent('$ammerRootPath/internal/v1/AmmerSetup.baked.hx') 276 | .replace("/*libname*/", '${t.t.module.split(".").pop()}_${t.t.name}') 277 | .replace("/*libinfo*/", new LineBuf() 278 | .al("// ammer-bake-common") 279 | .ail('/*ammer-bake-common-start*/ if ($targetCondition) {').i() 280 | .ail(targetScript) 281 | .ail('info.name = "${ctx.name}";') 282 | .ail('info.arrays.byElementTypeId = [').i() 283 | .lmap(sortedArrayNames, name -> { 284 | var info = ctx.info.arrays.byElementTypeId[name]; 285 | '"$name" => {\n' + 286 | 'arrayCt: ${printCt(info.arrayCt)},\n' + 287 | 'arrayRefCt: ${printCt(info.arrayRefCt)},\n' + 288 | 'alloc: ${printExpr(info.alloc)},\n' + 289 | 'fromHaxeCopy: ${printExpr(info.fromHaxeCopy)},\n' + 290 | 'fromHaxeRef: ${printExpr(info.fromHaxeRef)},\n' + 291 | '},'; 292 | }) 293 | .d().ail('];') 294 | .ail('info.boxes.byElementTypeId = [').i() 295 | .lmap(sortedBoxNames, name -> { 296 | var info = ctx.info.boxes.byElementTypeId[name]; 297 | '"$name" => {\n' + 298 | 'boxCt: ${printCt(info.boxCt)},\n' + 299 | 'alloc: ${printExpr(info.alloc)},\n' + 300 | '},'; 301 | }) 302 | .d().ail('];') 303 | .ail('info.callbacks.byElementTypeId = [').i() 304 | .lmap(sortedCallbackNames, name -> { 305 | var info = ctx.info.callbacks.byElementTypeId[name]; 306 | '"$name" => {\n' + 307 | 'isGlobal: ${info.isGlobal},\n' + 308 | 'callbackCt: ${printCt(info.callbackCt)},\n' + 309 | 'funCt: ${printCt(info.funCt)},\n' + 310 | 'callbackName: ${printString(info.callbackName)},\n' + 311 | '},'; 312 | }) 313 | .d().ail('];') 314 | .ail('info.enums = [').i() 315 | .lmap(sortedEnumNames, name -> { 316 | var info = ctx.info.enums[name]; 317 | '"$name" => {},\n'; 318 | }) 319 | .d().ail('];') 320 | .ail('info.haxeRefs.byElementTypeId = [').i() 321 | .lmap(sortedHaxeRefNames, name -> { 322 | var info = ctx.info.haxeRefs.byElementTypeId[name]; 323 | '"$name" => {' + 324 | 'create: ${printExpr(info.create)},\n' + 325 | '},\n'; 326 | }) 327 | .d().ail('];') 328 | .ail('info.opaques = [').i() 329 | .lmap(sortedOpaqueNames, name -> { 330 | var info = ctx.info.opaques[name]; 331 | '"$name" => {\n' + 332 | 'opaqueName: "${info.opaqueName}",\n' + 333 | '},'; 334 | }) 335 | .d().ail('];') 336 | .ail('info.structs = [').i() 337 | .lmap(sortedStructNames, name -> { 338 | var info = ctx.info.structs[name]; 339 | '"$name" => {\n' + 340 | 'alloc: ${info.alloc},\n' + 341 | 'gen: {\n' + 342 | 'alloc: ${printString(info.gen.alloc)},\n' + 343 | 'free: ${printString(info.gen.free)},\n' + 344 | 'nullPtr: ${printString(info.gen.nullPtr)},\n' + 345 | '},\n' + 346 | 'structName: "${info.structName}",\n' + 347 | '},'; 348 | }) 349 | .d().ail('];') 350 | .ail('info.sublibraries = [').i() 351 | .lmap(sortedSublibraryNames, name -> { 352 | var info = ctx.info.sublibraries[name]; 353 | '"$name" => {},'; 354 | }) 355 | .d().ail('];') 356 | .ail('info.setupToBin = "${t.t.pack.map(_ -> "..").concat(rootToBin.split("/")).join("/")}";') 357 | .ifi(destPath != null) 358 | .ail("info.files.push({").i() 359 | .ail('dst: \'$destPath\',') 360 | .ail("sources: [").i() 361 | .map(fileSources, source -> new LineBuf().ail("{").i() 362 | .ail('name: ${printString(source.name)},') 363 | //.ail('digest: ${printString(haxe.crypto.Sha256.make(File.getBytes('$bakeOutput/$rootToBin/${extensions(outputPathRelative)}')).toHex())},') 364 | .ail('description: "pre-compiled library for $targetDescription",') 365 | .ail('downloadFrom: ${printString(source.downloadFrom)},') 366 | .ail('os: ${printString(source.os)},') 367 | .ail('architectures: ${printStringArray(source.architectures)},') 368 | .ail('minVersion: ${printString(source.minVersion)},') 369 | .ail('maxVersion: ${printString(source.maxVersion)},') 370 | .d().ail("},").done()) 371 | .d().ail("],") 372 | .d().ail("});") 373 | .ifd() 374 | .d().ail("/*ammer-bake-common-end*/ }") 375 | .done()) 376 | ); 377 | } 378 | outputMacro.al('class ${t.t.name} {}'); 379 | } 380 | case _: throw 0; 381 | } 382 | } 383 | for (t in ammer.core.utils.TypeUtils.definedTypes) { 384 | var extraMeta = []; 385 | t.meta = [ for (meta in t.meta) { 386 | if (meta.name == ":buildXml") switch (meta.params) { 387 | case [{expr: EConst(CString(val))}] if (val.startsWith("")[0]; 389 | var ctx = Ammer.libraries.byLibraryName[libname]; 390 | ctx != null || throw 0; 391 | var includePaths = ctx.originalOptions.includePaths.map(p -> macro $v{p.rel}); 392 | var libraryPaths = ctx.originalOptions.libraryPaths.map(p -> macro $v{p.rel}); 393 | var includePathsArr = macro $a{includePaths}; 394 | var libraryPathsArr = macro $a{libraryPaths}; 395 | extraMeta.push({ 396 | name: ":build", 397 | params: [macro ammer.internal.v1.RelativePathsHelper.build($includePathsArr, $libraryPathsArr)], 398 | pos: meta.pos, 399 | }); 400 | continue; 401 | case _: 402 | } 403 | meta; 404 | } ]; 405 | t.meta = t.meta.concat(extraMeta); 406 | modules.outputSub(t.pack, t.name, t.name) 407 | .ail(printer.printTypeDefinition(t, false)); 408 | } 409 | for (t in Utils.definedTypes) { 410 | modules.outputSub(t.pack, t.name, t.name) 411 | .ail(printer.printTypeDefinition(t, false)); 412 | } 413 | 414 | modules.outputCombined(extraParams, bakeOutput); 415 | 416 | // TODO: paths (handle backslashes etc on Windows) 417 | // This is a little "preprocessor" system to reduce code duplication. 418 | // It isn't great! 419 | FileSystem.createDirectory('$bakeOutput/ammer/internal/v1'); 420 | for (req in [ 421 | ["internal/v1/AmmerBaked.hx", "internal/v1/AmmerBaked.hx"], 422 | ["internal/v1/LibInfo.hx", "internal/v1/LibInfo.hx"], 423 | ["internal/v1/OsInfo.hx", "internal/v1/OsInfo.hx"], 424 | ["internal/v1/RelativePathsHelper.hx", "internal/v1/RelativePathsHelper.hx"], 425 | ["Lib.hx", "Lib.hx"], 426 | ["Lib.macro.baked.hx", "Lib.macro.hx"], 427 | ["internal/FilePtrOutput.hx", "internal/FilePtrOutput.hx"], 428 | ]) { 429 | var content = File.getContent('$ammerRootPath/${req[0]}') 430 | .split("\n") 431 | .map(l -> { 432 | var lt = l.trim(); 433 | if (!lt.startsWith("// ammer-include: ")) return l; 434 | var params = lt.substr("// ammer-include: ".length).split(" "); 435 | var refContent = File.getContent('$ammerRootPath/${params[0]}'); 436 | var spl = refContent.split('// ammer-fragment-begin: ${params[1]}'); 437 | spl.length == 2 || throw 'expected fragment begin ${params[1]} in ${params[0]}'; 438 | spl = spl[1].split('// ammer-fragment-end: ${params[1]}'); 439 | spl.length == 2 || throw 'expected fragment end ${params[1]} in ${params[0]}'; 440 | spl[0]; 441 | }) 442 | .join("\n"); 443 | File.saveContent('$bakeOutput/ammer/${req[1]}', content); 444 | } 445 | } 446 | 447 | public static function multiBake(platforms:Array, output:String):Void { 448 | var modules = new BakeryOutput( 449 | () -> { 450 | platforms: ([] : Map), 451 | common: ([] : Array), 452 | }, 453 | mod -> { 454 | var platNames = [ for (k in mod.platforms.keys()) k ]; 455 | platNames.sort(Reflect.compare); 456 | var merged = new LineBuf(); 457 | for (p in platNames) { 458 | merged 459 | .al('#if (${p})') 460 | .al(mod.platforms[p] 461 | .replace("// ammer-bake-common", mod.common.join("\n"))) 462 | .al('#end /*(${p})*/'); 463 | } 464 | merged.done(); 465 | } 466 | ); 467 | 468 | function walkPath(base:String, ext:String):Void { 469 | var path = '$base$ext'; 470 | if (FileSystem.isDirectory(path)) { 471 | FileSystem.readDirectory(path).iter(f -> { 472 | if (f.startsWith(".")) return; 473 | walkPath(base, '$ext/$f'); 474 | }); 475 | } else { 476 | if (!path.endsWith(".hx")) return; 477 | var content = File.getContent(path); 478 | if (!content.startsWith(BAKE_PREFIX)) 479 | throw Context.fatalError('unexpected file in bake path: $path', Context.currentPos()); 480 | var lines = content.split("\n"); 481 | var bakeParams = lines[0].substr(BAKE_PREFIX.length).split(" "); 482 | 483 | var bakePack = bakeParams[0].split("."); 484 | var bakeModule = bakeParams[1]; 485 | var bakeExtra = bakeParams.slice(2).join(" "); 486 | if (path.endsWith(".macro.hx")) bakeExtra = "true"; // join macro files 487 | 488 | var content = []; 489 | var bakeCommon = []; 490 | var isCommon = false; 491 | for (lnum => line in lines) { 492 | if (line.contains("/*ammer-bake-common-start*/")) isCommon = true; 493 | if (isCommon) bakeCommon.push(line); 494 | else if (lnum >= 2) { 495 | // remove bake prefix, package, and common lines (will be merged later) 496 | content.push(line); 497 | } 498 | if (line.contains("/*ammer-bake-common-end*/")) isCommon = false; 499 | } 500 | 501 | var outputSub = modules.outputSub(bakePack, bakeModule, "true"); 502 | outputSub.platforms[bakeExtra] = content.join("\n"); 503 | outputSub.common = outputSub.common.concat(bakeCommon); 504 | } 505 | } 506 | platforms.iter(p -> walkPath(p, "")); 507 | 508 | modules.outputCombined("combined", output); 509 | } 510 | } 511 | 512 | class BakeryOutput { 513 | public static function withBufs():BakeryOutput { 514 | return new BakeryOutput(() -> new LineBuf(), buf -> buf.done()); 515 | } 516 | 517 | var modules:Map> = []; 518 | var create:()->T; 519 | var finish:T->String; 520 | 521 | public function new(create:()->T, finish:T->String) { 522 | this.create = create; 523 | this.finish = finish; 524 | } 525 | 526 | public function outputSub(pack:Array, module:String, sub:String):T { 527 | var mid = pack.concat([module]).join("."); 528 | if (!modules.exists(mid)) modules[mid] = new Map(); 529 | if (!modules[mid].exists(sub)) modules[mid][sub] = create(); 530 | return modules[mid][sub]; 531 | } 532 | 533 | public function outputCombined(extraParams:String, outputPath:String):Void { 534 | for (module => types in modules) { 535 | var pack = module.split("."); 536 | var module = pack.pop(); 537 | if (module == "macro") { 538 | module = '${pack.pop()}.$module'; 539 | } 540 | var subNames = [ for (k in types.keys()) k ]; 541 | subNames.sort(Reflect.compare); 542 | var moduleOutput = new LineBuf() 543 | .ail('${Bakery.BAKE_PREFIX}${pack.join(".")} $module $extraParams') 544 | .ail('package ${pack.join(".")};'); 545 | for (subName in subNames) { 546 | moduleOutput.al(finish(types[subName])); 547 | } 548 | 549 | var out = outputPath + "/" + pack.join("/"); 550 | FileSystem.createDirectory(out); 551 | File.saveContent('${out}/${module}.hx', moduleOutput.done()); 552 | } 553 | } 554 | } 555 | 556 | #end 557 | --------------------------------------------------------------------------------