From 93dc4e98ef05c4d0ffc8cf4056aca76e9e6d87d9 Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Sun, 13 Dec 2020 03:17:24 +0000 Subject: [PATCH] Add initial QuickJS primitive type support and test suite. --- .../Generators/C/NAPI/NAPISources.cs | 7 +- .../Generators/C/QuickJS/QuickJSMarshal.cs | 77 ++++++++++++++- .../Generators/C/QuickJS/QuickJSModule.cs | 10 +- .../Generators/C/QuickJS/QuickJSSources.cs | 36 +++++-- tests2/quickjs/.gitignore | 4 + tests2/quickjs/premake5.lua | 21 ++++ tests2/quickjs/test.js | 96 +++++++++++++++++++ tests2/quickjs/test.sh | 26 +++++ 8 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 tests2/quickjs/.gitignore create mode 100644 tests2/quickjs/premake5.lua create mode 100644 tests2/quickjs/test.js create mode 100755 tests2/quickjs/test.sh diff --git a/src/Generator/Generators/C/NAPI/NAPISources.cs b/src/Generator/Generators/C/NAPI/NAPISources.cs index 5bd65417..0a982553 100644 --- a/src/Generator/Generators/C/NAPI/NAPISources.cs +++ b/src/Generator/Generators/C/NAPI/NAPISources.cs @@ -336,6 +336,11 @@ namespace CppSharp.Generators.Cpp { } + public NAPICallbacks(BindingContext context, IEnumerable units) + : base(context, units) + { + } + public override void GenerateFunctionGroup(List @group) { var function = @group.First(); @@ -596,7 +601,7 @@ namespace CppSharp.Generators.Cpp } } - public void GenerateFunctionCall(Function function) + public virtual void GenerateFunctionCall(Function function) { var @params = GenerateFunctionParamsMarshal(function.Parameters, function); diff --git a/src/Generator/Generators/C/QuickJS/QuickJSMarshal.cs b/src/Generator/Generators/C/QuickJS/QuickJSMarshal.cs index 9774ff19..d5f6b066 100644 --- a/src/Generator/Generators/C/QuickJS/QuickJSMarshal.cs +++ b/src/Generator/Generators/C/QuickJS/QuickJSMarshal.cs @@ -145,26 +145,50 @@ namespace CppSharp.Generators.Cpp { case PrimitiveType.Void: return true; + case PrimitiveType.Bool: + Context.Before.WriteLine($"JS_NewBool(ctx, {Context.ArgName});"); + break; + case PrimitiveType.Char: case PrimitiveType.Char16: + case PrimitiveType.Char32: case PrimitiveType.WideChar: case PrimitiveType.SChar: case PrimitiveType.UChar: case PrimitiveType.Short: case PrimitiveType.UShort: case PrimitiveType.Int: - case PrimitiveType.UInt: + case PrimitiveType.Long: Context.Before.WriteLine($"JS_NewInt32(ctx, {Context.ArgName});"); break; - case PrimitiveType.Long: + + case PrimitiveType.UInt: case PrimitiveType.ULong: + Context.Before.WriteLine($"JS_NewUint32(ctx, {Context.ArgName});"); + break; + case PrimitiveType.LongLong: + Context.Before.WriteLine($"JS_NewBigInt64(ctx, {Context.ArgName});"); + break; + case PrimitiveType.ULongLong: + Context.Before.WriteLine($"JS_NewBigUint64(ctx, {Context.ArgName});"); + break; + case PrimitiveType.Float: case PrimitiveType.Double: + Context.Before.WriteLine($"JS_NewFloat64(ctx, {Context.ArgName});"); + break; + case PrimitiveType.LongDouble: + throw new NotImplementedException(); + case PrimitiveType.Null: + Context.Before.WriteLine($"JS_NULL;"); + break; + + default: throw new NotImplementedException(); } @@ -474,23 +498,68 @@ namespace CppSharp.Generators.Cpp { case PrimitiveType.Void: return true; + case PrimitiveType.Bool: - //JS_ToBool + Context.Before.WriteLine($"{Context.ArgName} = JS_ToBool(ctx, argv[{Context.ParameterIndex}]);"); + Context.Before.WriteLine($"if ({Context.ArgName} == -1)"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + return true; + case PrimitiveType.Char: + case PrimitiveType.SChar: case PrimitiveType.UChar: + Context.Before.WriteLine($"int32_t _{Context.ArgName};"); + Context.Before.WriteLine($"if (JS_ToInt32(ctx, &_{Context.ArgName}, argv[{Context.ParameterIndex}]))"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + Context.Before.WriteLine($"{Context.ArgName} = ({type})_{Context.ArgName};"); + return true; + case PrimitiveType.Short: case PrimitiveType.UShort: + Context.Before.WriteLine($"int32_t _{Context.ArgName};"); + Context.Before.WriteLine($"if (JS_ToInt32(ctx, &_{Context.ArgName}, argv[{Context.ParameterIndex}]))"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + Context.Before.WriteLine($"{Context.ArgName} = ({type})_{Context.ArgName};"); + return true; + case PrimitiveType.Int: + case PrimitiveType.Long: Context.Before.WriteLine($"if (JS_ToInt32(ctx, &{Context.ArgName}, argv[{Context.ParameterIndex}]))"); Context.Before.WriteLineIndent("return JS_EXCEPTION;"); return true; + case PrimitiveType.UInt: - case PrimitiveType.Long: case PrimitiveType.ULong: + Context.Before.WriteLine($"if (JS_ToUint32(ctx, &{Context.ArgName}, argv[{Context.ParameterIndex}]))"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + return true; + case PrimitiveType.LongLong: + Context.Before.WriteLine($"int64_t _{Context.ArgName};"); + Context.Before.WriteLine($"if (JS_ToInt64Ext(ctx, &_{Context.ArgName}, argv[{Context.ParameterIndex}]))"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + Context.Before.WriteLine($"{Context.ArgName} = ({type})_{Context.ArgName};"); + return true; + case PrimitiveType.ULongLong: + Context.Before.WriteLine($"int64_t _{Context.ArgName};"); + Context.Before.WriteLine($"if (JS_ToInt64Ext(ctx, &_{Context.ArgName}, argv[{Context.ParameterIndex}]))"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + Context.Before.WriteLine($"{Context.ArgName} = ({type})_{Context.ArgName};"); + return true; + case PrimitiveType.Float: + Context.Before.WriteLine($"double _{Context.ArgName};"); + Context.Before.WriteLine($"if (JS_ToFloat64(ctx, &_{Context.ArgName}, argv[{Context.ParameterIndex}]))"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + Context.Before.WriteLine($"{Context.ArgName} = ({type})_{Context.ArgName};"); + return true; + case PrimitiveType.Double: + Context.Before.WriteLine($"if (JS_ToFloat64(ctx, &{Context.ArgName}, argv[{Context.ParameterIndex}]))"); + Context.Before.WriteLineIndent("return JS_EXCEPTION;"); + return true; + case PrimitiveType.WideChar: default: throw new NotImplementedException(); diff --git a/src/Generator/Generators/C/QuickJS/QuickJSModule.cs b/src/Generator/Generators/C/QuickJS/QuickJSModule.cs index 99f18d51..025f1793 100644 --- a/src/Generator/Generators/C/QuickJS/QuickJSModule.cs +++ b/src/Generator/Generators/C/QuickJS/QuickJSModule.cs @@ -8,15 +8,14 @@ namespace CppSharp.Generators.Cpp /// Generates QuickJS C/C++ module init files. /// QuickJS documentation: https://bellard.org/quickjs/ /// - public class QuickJSModule : CCodeGenerator + public class QuickJSModule : NAPICodeGenerator { - readonly Module module; + private readonly Module module; public QuickJSModule(BindingContext context, Module module) : base(context, module.Units.GetGenerated()) { this.module = module; - CTypePrinter.PushContext(TypePrinterContextKind.Managed); } public override string FileExtension { get; } = "cpp"; @@ -92,7 +91,7 @@ namespace CppSharp.Generators.Cpp WriteLine("JSModuleDef* m;"); WriteLine($"m = JS_NewCModule(ctx, module_name, js_{moduleName}_init);"); WriteLine("if (!m)"); - WriteLineIndent("return NULL;"); + WriteLineIndent("return nullptr;"); NewLine(); WriteLine($"JS_AddModuleExportList(ctx, m, js_{moduleName}_funcs," + @@ -133,6 +132,9 @@ namespace CppSharp.Generators.Cpp public override bool VisitFunctionDecl(Function function) { + if (!function.IsGenerated) + return true; + WriteLine($"JS_CFUNC_DEF(\"{function.Name}\"," + $" {function.Parameters.Count}, js_{function.Name}),"); diff --git a/src/Generator/Generators/C/QuickJS/QuickJSSources.cs b/src/Generator/Generators/C/QuickJS/QuickJSSources.cs index 833a0e4c..aca5d745 100644 --- a/src/Generator/Generators/C/QuickJS/QuickJSSources.cs +++ b/src/Generator/Generators/C/QuickJS/QuickJSSources.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using CppSharp.AST; +using CppSharp.AST.Extensions; using CppSharp.Generators.C; namespace CppSharp.Generators.Cpp @@ -10,12 +12,11 @@ namespace CppSharp.Generators.Cpp /// Generates QuickJS C/C++ source files. /// QuickJS documentation: https://bellard.org/quickjs/ /// - public class QuickJSSources : CppSources + public class QuickJSSources : NAPICallbacks { public QuickJSSources(BindingContext context, IEnumerable units) : base(context, units) { - CTypePrinter.PushContext(TypePrinterContextKind.Managed); } public override void Process() @@ -34,8 +35,8 @@ namespace CppSharp.Generators.Cpp { WriteInclude(new CInclude() { - File = unit.FileName, - Kind = CInclude.IncludeKind.Quoted + File = unit.IncludePath, + Kind = CInclude.IncludeKind.Angled }); } @@ -45,10 +46,10 @@ namespace CppSharp.Generators.Cpp VisitNamespace(TranslationUnit); } - public override ParamMarshal GenerateFunctionParamMarshal(Parameter param, int paramIndex, + public override NAPICallbacks.ParamMarshal GenerateFunctionParamMarshal(Parameter param, int paramIndex, Function function = null) { - var paramMarshal = new ParamMarshal { Name = param.Name, Param = param }; + var paramMarshal = new NAPICallbacks.ParamMarshal { Name = param.Name, Param = param }; var argName = Generator.GeneratedIdentifier(param.Name); @@ -98,8 +99,20 @@ namespace CppSharp.Generators.Cpp WriteLine($"return {marshal.Context.Return};"); } - public override bool VisitFunctionDecl(Function function) + public override void GenerateFunctionGroup(List @group) { + GenerateFunctionCallback(@group); + } + + public override void GenerateMethodGroup(List @group) + { + GenerateFunctionCallback(@group.OfType().ToList()); + } + public override void GenerateFunctionCallback(List @group) + { + var function = @group.First(); + + PushBlock(); Write("extern \"C\" "); WriteLine($"JSValue js_{function.Name}(JSContext* ctx, JSValueConst this_val,"); WriteLineIndent("int argc, JSValueConst* argv)"); @@ -107,9 +120,14 @@ namespace CppSharp.Generators.Cpp GenerateFunctionCall(function); - UnindentAndWriteCloseBrace(); + var needsReturn = !function.ReturnType.Type.IsPrimitiveType(PrimitiveType.Void); + if (!needsReturn) + { + WriteLine("return JS_UNDEFINED;"); + } - return true; + UnindentAndWriteCloseBrace(); + PopBlock(NewLineKind.BeforeNextBlock); } } } diff --git a/tests2/quickjs/.gitignore b/tests2/quickjs/.gitignore new file mode 100644 index 00000000..36316b01 --- /dev/null +++ b/tests2/quickjs/.gitignore @@ -0,0 +1,4 @@ +gen +*.so +*.dylib +*.dll diff --git a/tests2/quickjs/premake5.lua b/tests2/quickjs/premake5.lua new file mode 100644 index 00000000..4166454d --- /dev/null +++ b/tests2/quickjs/premake5.lua @@ -0,0 +1,21 @@ +qjs_inc_dir = path.getabsolute("../../deps/txiki.js/deps/quickjs/include") +qjs_lib_dir = path.getabsolute("../../deps/txiki.js/deps/quickjs/include") + +workspace "qjs" + configurations { "release" } + location "gen" + symbols "On" + optimize "Off" + + project "test" + kind "SharedLib" + language "C++" + files {"gen/**.cpp"} + includedirs { qjs_inc_dir, ".." } + libdirs { qjs_lib_dir } + filter { "kind:StaticLib" } + links { "quickjs" } + filter { "kind:SharedLib" } + defines { "JS_SHARED_LIBRARY" } + filter { "kind:SharedLib", "system:macosx" } + linkoptions { "-undefined dynamic_lookup" } diff --git a/tests2/quickjs/test.js b/tests2/quickjs/test.js new file mode 100644 index 00000000..2053c4c5 --- /dev/null +++ b/tests2/quickjs/test.js @@ -0,0 +1,96 @@ +import * as test from "./libtest.so"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +const eq = assert; +const floateq = (actual, expected) => { assert(Math.abs(actual - expected) < Number.EPSILON) } + +const ascii = v => v.charCodeAt(0) + +function builtins() +{ + eq(test.ReturnsVoid(), undefined) + + eq(test.ReturnsBool(), true) + eq(test.PassAndReturnsBool(false), false) + + eq(test.ReturnsNullptr(), null) + //eq(test.PassAndReturnsNullptr(null), null) + eq(test.ReturnsNullptr(), null) + + eq(test.ReturnsChar (), ascii('a')); + eq(test.ReturnsSChar(), ascii('a')); + eq(test.ReturnsUChar(), ascii('a')); + + eq(test.PassAndReturnsChar (ascii('a')), ascii('a')); + eq(test.PassAndReturnsSChar(ascii('b')), ascii('b')); + eq(test.PassAndReturnsUChar(ascii('c')), ascii('c')); + + // TODO: add wchar_t tests + + eq(test.ReturnsFloat (), 5.0); + eq(test.ReturnsDouble(), -5.0); + //eq(test.ReturnsLongDouble(), -5.0); + + //floateq(test.PassAndReturnsFloat (1.32), 1.32); + floateq(test.PassAndReturnsDouble(1.32), 1.32); + //float(test.PassAndReturnsLongDouble(1.32), 1.32); + + eq(test.ReturnsInt8 (), -5); + eq(test.ReturnsUInt8 (), 5); + eq(test.ReturnsInt16 (), -5); + eq(test.ReturnsUInt16(), 5); + eq(test.ReturnsInt32 (), -5); + eq(test.ReturnsUInt32(), 5); + eq(test.ReturnsInt64 (), -5n); + eq(test.ReturnsUInt64(), 5n); + + const int8 = { min: -(2**7), max: (2**7) - 1 }; + eq(test.PassAndReturnsInt8(int8.min), int8.min); + eq(test.PassAndReturnsInt8(int8.max), int8.max); + + const uint8 = { min: 0, max: (2**8) - 1 }; + eq(test.PassAndReturnsUInt8(uint8.min), uint8.min); + eq(test.PassAndReturnsUInt8(uint8.max), uint8.max); + + const int16 = { min: -(2**15), max: (2**15) - 1 }; + eq(test.PassAndReturnsInt16(int16.min), int16.min); + eq(test.PassAndReturnsInt16(int16.max), int16.max); + + const uint16 = { min: 0, max: (2**16) - 1 }; + eq(test.PassAndReturnsUInt16(uint16.min), uint16.min); + eq(test.PassAndReturnsUInt16(uint16.max), uint16.max); + + const int32 = { min: -(2**31), max: (2**31) - 1 }; + eq(test.PassAndReturnsInt32(int32.min), int32.min); + eq(test.PassAndReturnsInt32(int32.max), int32.max); + + const uint32 = { min: 0, max: (2**32) - 1 }; + eq(test.PassAndReturnsUInt32(uint32.min), uint32.min); + eq(test.PassAndReturnsUInt32(uint32.max), uint32.max); + + const int64 = { min: BigInt(2**63) * -1n, max: BigInt(2**63) - 1n }; + eq(test.PassAndReturnsInt64(int64.min), int64.min); + eq(test.PassAndReturnsInt64(int64.max), int64.max); + + const uint64 = { min: BigInt(0), max: BigInt(2**64) - 1n }; + eq(test.PassAndReturnsUInt64(uint64.min), uint64.min); + eq(test.PassAndReturnsUInt64(uint64.max), uint64.max); +} + +builtins(); diff --git a/tests2/quickjs/test.sh b/tests2/quickjs/test.sh new file mode 100755 index 00000000..bb658cc0 --- /dev/null +++ b/tests2/quickjs/test.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e +dir=$(cd "$(dirname "$0")"; pwd) +rootdir="$dir/../.." +configuration=Release +platform=x64 +jsinterp="$rootdir/deps/quickjs/qjs" + +red=`tput setaf 1` +green=`tput setaf 2` +reset=`tput sgr0` + +echo "${green}Generating bindings${reset}" +#dotnet $rootdir/bin/${configuration}_${platform}/CppSharp.CLI.dll \ +# --gen=qjs -I$dir/.. -o $dir/gen -m tests $dir/../*.h + +echo "${green}Building generated binding files${reset}" +premake=$rootdir/build/premake.sh +$premake --file=$dir/premake5.lua gmake +make -C $dir/gen +echo + +echo "${green}Executing JS tests with QuickJS${reset}" +cp $dir/gen/bin/release/libtest.so $dir +#cp $dir/gen/bin/release/libtest.dylib $dir +$jsinterp $dir/test.js \ No newline at end of file