From b4c167b56228b86eb459f0dd049d4d5c537f5724 Mon Sep 17 00:00:00 2001 From: Joao Matos <joao@tritao.eu> Date: Sat, 20 Feb 2021 23:24:30 +0000 Subject: [PATCH] Rework core implementation of QuickJS generator. This adds more accurate overloading, default arguments handling and events. --- src/AST/Event.cs | 2 + .../Generators/C/CppDefaultValuePrinter.cs | 58 ++ .../Generators/QuickJS/QuickJSHeaders.cs | 5 + .../Generators/QuickJS/QuickJSSources.cs | 705 +++++++++++++++++- .../Generators/QuickJS/QuickJSTypeCheckGen.cs | 129 ++++ 5 files changed, 872 insertions(+), 27 deletions(-) create mode 100644 src/Generator/Generators/C/CppDefaultValuePrinter.cs create mode 100644 src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs diff --git a/src/AST/Event.cs b/src/AST/Event.cs index 03e32ea3..28b63832 100644 --- a/src/AST/Event.cs +++ b/src/AST/Event.cs @@ -15,5 +15,7 @@ namespace CppSharp.AST public List<Parameter> Parameters { get; } = new List<Parameter>(); public Declaration OriginalDeclaration { get; set; } + + public int GlobalId { get; set; } } } diff --git a/src/Generator/Generators/C/CppDefaultValuePrinter.cs b/src/Generator/Generators/C/CppDefaultValuePrinter.cs new file mode 100644 index 00000000..6987d434 --- /dev/null +++ b/src/Generator/Generators/C/CppDefaultValuePrinter.cs @@ -0,0 +1,58 @@ + +using System; +using CppSharp.AST; + +namespace CppSharp.Generators.C +{ + public class CppDefaultValuePrinter : CppTypePrinter + { + public CppDefaultValuePrinter(BindingContext context) : base(context) + { + } + + public override TypePrinterResult VisitBuiltinType(BuiltinType builtin, + TypeQualifiers quals) + { + return VisitPrimitiveType(builtin.Type); + } + + public override TypePrinterResult VisitPrimitiveType(PrimitiveType type) + { + switch (type) + { + case PrimitiveType.Bool: + return PrintFlavorKind == CppTypePrintFlavorKind.Cpp ? "false" : "0"; + case PrimitiveType.WideChar: + case PrimitiveType.Char: + case PrimitiveType.SChar: + case PrimitiveType.UChar: + case PrimitiveType.Char16: + case PrimitiveType.Char32: + case PrimitiveType.Short: + case PrimitiveType.UShort: + case PrimitiveType.Int: + case PrimitiveType.UInt: + case PrimitiveType.Long: + case PrimitiveType.ULong: + case PrimitiveType.LongLong: + case PrimitiveType.ULongLong: + case PrimitiveType.Int128: + case PrimitiveType.UInt128: + case PrimitiveType.Half: + case PrimitiveType.Float: + case PrimitiveType.Double: + case PrimitiveType.LongDouble: + case PrimitiveType.Float128: + case PrimitiveType.Decimal: + return "0"; + case PrimitiveType.Null: + case PrimitiveType.IntPtr: + case PrimitiveType.UIntPtr: + case PrimitiveType.String: + return PrintFlavorKind == CppTypePrintFlavorKind.Cpp ? "nullptr" : "0"; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + } +} diff --git a/src/Generator/Generators/QuickJS/QuickJSHeaders.cs b/src/Generator/Generators/QuickJS/QuickJSHeaders.cs index e5038ab7..aab93000 100644 --- a/src/Generator/Generators/QuickJS/QuickJSHeaders.cs +++ b/src/Generator/Generators/QuickJS/QuickJSHeaders.cs @@ -52,5 +52,10 @@ namespace CppSharp.Generators.Cpp { return true; } + + public override bool VisitFieldDecl(Field field) + { + return true; + } } } diff --git a/src/Generator/Generators/QuickJS/QuickJSSources.cs b/src/Generator/Generators/QuickJS/QuickJSSources.cs index 2b184795..9c10cf03 100644 --- a/src/Generator/Generators/QuickJS/QuickJSSources.cs +++ b/src/Generator/Generators/QuickJS/QuickJSSources.cs @@ -1,15 +1,33 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using CppSharp.AST; using CppSharp.AST.Extensions; +using CppSharp.Generators.AST; using CppSharp.Generators.C; using static CppSharp.Generators.Cpp.NAPISources; -using static CppSharp.Generators.Cpp.NAPIInvokes; +using Type = CppSharp.AST.Type; namespace CppSharp.Generators.Cpp { + public class QuickJSCodeGenerator : NAPICodeGenerator + { + public QuickJSCodeGenerator(BindingContext context, IEnumerable<TranslationUnit> units) + : base(context, units) + { + } + + public override MarshalPrinter<MarshalContext> GetMarshalManagedToNativePrinter(MarshalContext ctx) + { + return new QuickJSMarshalManagedToNativePrinter(ctx); + } + + public override MarshalPrinter<MarshalContext> GetMarshalNativeToManagedPrinter(MarshalContext ctx) + { + return new QuickJSMarshalNativeToManagedPrinter(ctx); + } + } + /// <summary> /// Generates QuickJS C/C++ source files. /// QuickJS documentation: https://bellard.org/quickjs/ @@ -42,6 +60,8 @@ namespace CppSharp.Generators.Cpp WriteLine("extern \"C\" {"); NewLine(); + GenerateExternClassIds(); + var registerGen = new QuickJSRegister(Context, TranslationUnits); registerGen.Process(); WriteLine(registerGen.Generate()); @@ -49,16 +69,88 @@ namespace CppSharp.Generators.Cpp PushBlock(); { var name = GetTranslationUnitName(TranslationUnit); - WriteLine($"void register_{name}(JSContext *ctx, JSModuleDef *m, bool set)"); + WriteLine($"void register_{name}(JSContext *ctx, JSModuleDef *m, bool set, int phase)"); WriteOpenBraceAndIndent(); - TranslationUnit.Visit(this); + Block registerBlock; + PushBlock(); + { + WriteLine("if (phase == 0)"); + + WriteOpenBraceAndIndent(); + { + PushBlock(); + VisitOptions.ClearFlags(VisitFlags.NamespaceClasses); + TranslationUnit.Visit(this); + registerBlock = PopBlock(); + + } + UnindentAndWriteCloseBrace(); + } + var phase0Block = PopBlock(NewLineKind.IfNotEmpty); + phase0Block.CheckGenerate = () => !registerBlock.IsEmpty; + + PushBlock(); + { + VisitOptions.ResetFlags(VisitFlags.Default); + VisitOptions.SetFlags(VisitFlags.NamespaceClasses); + TranslationUnit.Visit(this); + } + PopBlock(NewLineKind.IfNotEmpty); UnindentAndWriteCloseBrace(); } PopBlock(NewLineKind.BeforeNextBlock); + NewLine(); + + WriteLine("} // extern \"C\""); + } + + public static string SignalClassId = "classId__Signal"; + + private void GenerateExternClassIds() + { + PushBlock(); + { + var classCollector = new RecordCollector(TranslationUnit); + TranslationUnit.Visit(classCollector); + + var externClasses = new HashSet<Class>(); + foreach (var record in classCollector.Declarations) + { + var @event = record.Value as Event; + Class @class; + if (@event != null) + { + foreach (var param in @event.Parameters) + { + if (!param.Type.GetFinalPointee().TryGetClass(out @class)) + continue; + + externClasses.Add(@class); + } + continue; + } + + @class = record.Value as Class; + if (@class == null || !@class.IsGenerated) + continue; + + externClasses.Add(@class); + } + + foreach (var @class in externClasses) + { + WriteLine($"extern JSClassID classId_{GetCIdentifier(Context, @class)};"); + } + } - WriteLine("}"); + // If any of the classes have events, then also generate an external for Signal. + // TODO: Only generate if necessary and fix possible id conflict. + WriteLine($"extern JSClassID {SignalClassId};"); + + + PopBlock(NewLineKind.BeforeNextBlock); } public override bool VisitClassDecl(Class @class) @@ -67,7 +159,7 @@ namespace CppSharp.Generators.Cpp return true; PushBlock(); - WriteLine($"register_class_{GetCIdentifier(Context, @class)}(ctx, m, set);"); + WriteLine($"register_class_{GetCIdentifier(Context, @class)}(ctx, m, set, phase);"); PopBlock(NewLineKind.BeforeNextBlock); return true; } @@ -93,6 +185,59 @@ namespace CppSharp.Generators.Cpp PopBlock(NewLineKind.BeforeNextBlock); return true; } + + public override bool VisitTypedefDecl(TypedefDecl typedef) + { + return true; + } + } + + public class QuickJSClassFuncDef : NAPICodeGenerator + { + public QuickJSClassFuncDef(BindingContext context) : base(context, null) + { + } + + public override void VisitClassConstructors(Class @class) + { + } + + public override bool VisitEnumDecl(Enumeration @enum) + { + return true; + } + + public override void GenerateMethodGroup(List<Method> @group) + { + GenerateFunctionGroup(@group.OfType<Function>().ToList()); + } + + public override void GenerateFunctionGroup(List<Function> @group) + { + var function = @group.FirstOrDefault(); + var maxArgs = @group.Max(f => f.Parameters.Count); + var type = function is Method ? "method" : "function"; + var callbackId = $"callback_{type}_{GetCIdentifier(Context, function)}"; + WriteLine($"JS_CFUNC_DEF(\"{function.Name}\", {maxArgs}, {callbackId}),"); + } + + public override bool VisitEvent(Event @event) + { + var getterId = $"callback_event_getter_{GetCIdentifier(Context, @event)}"; + WriteLine($"JS_CGETSET_DEF(\"{@event.Name}\", {getterId}, NULL),"); + return true; + } + + public override bool VisitFunctionTemplateDecl(FunctionTemplate template) + { + return true; + } + + public void GenerateToString(Class @class) + { + var callbackId = $"callback_class_{GetCIdentifier(Context, @class)}_toString"; + WriteLine($"JS_CFUNC_DEF(\"toString\", 0, {callbackId}),"); + } } public class QuickJSRegister : NAPIRegister @@ -102,33 +247,110 @@ namespace CppSharp.Generators.Cpp { } + public override MarshalPrinter<MarshalContext> GetMarshalManagedToNativePrinter(MarshalContext ctx) + { + return new QuickJSMarshalManagedToNativePrinter(ctx); + } + + public override MarshalPrinter<MarshalContext> GetMarshalNativeToManagedPrinter(MarshalContext ctx) + { + return new QuickJSMarshalNativeToManagedPrinter(ctx); + } + public override bool VisitClassDecl(Class @class) { if (@class.IsIncomplete) return true; -/* + if (!VisitDeclContext(@class)) + return true; + PushBlock(BlockKind.InternalsClass, @class); { - var callbacks = new QuickJSInvokes(Context); - @class.Visit(callbacks); - Write(callbacks.Generate()); + WriteLine($"JSClassID classId_{GetCIdentifier(Context, @class)};"); + NewLine(); + + GenerateClassExtraData(@class); + + PushBlock(); + { + var callbacks = new QuickJSInvokes(Context); + @class.Visit(callbacks); + callbacks.GenerateToString(@class); + Write(callbacks.Generate()); + } + PopBlock(NewLineKind.BeforeNextBlock); + + var finalizerId = $"finalizer_{GetCIdentifier(Context, @class)}"; + PushBlock(); + { + WriteLine($"void {finalizerId}(JSRuntime *rt, JSValue val)"); + WriteOpenBraceAndIndent(); + + //WriteLine($"printf(\"Calling finalizer for {@class.QualifiedOriginalName}\\n\");"); + + if(ClassNeedsExtraData(@class)) + { + // Remove the event connection from the delegate. + // var invokeId = $"event_invoke_{@event.OriginalName}"; + // WriteLine($"data->instance->{@event.OriginalName}.bind(data, &{classDataId}::{invokeId});"); + // NewLine(); + + var instanceKind = "JS_INTEROP_INSTANCE_SIGNAL_CONTEXT"; + WriteLine($"JS_Interop_CleanupObject(val, {instanceKind});"); + } + else + { + Write($"{@class.QualifiedOriginalName}* instance = "); + WriteLine($"({@class.QualifiedOriginalName}*) JS_GetOpaque(val, 0);"); + } + + UnindentAndWriteCloseBrace(); + } + PopBlock(NewLineKind.BeforeNextBlock); + + PushBlock(); + { + WriteLine($"static JSClassDef classDef_{GetCIdentifier(Context, @class)}"); + WriteOpenBraceAndIndent(); + + WriteLine($"\"{@class.Name}\","); + WriteLine($".finalizer = {finalizerId}"); + + Unindent(); + WriteLine("};"); + } + PopBlock(NewLineKind.BeforeNextBlock); + + PushBlock(); + { + WriteLine($"static JSCFunctionListEntry funcDef_{GetCIdentifier(Context, @class)}[]"); + WriteOpenBraceAndIndent(); + + var funcGen = new QuickJSClassFuncDef(Context); + funcGen.Indent(CurrentIndentation); + funcGen.VisitClassDeclContext(@class); + funcGen.GenerateToString(@class); + + Write(funcGen.Generate()); + + Unindent(); + WriteLine("};"); + } + PopBlock(NewLineKind.BeforeNextBlock); } PopBlock(NewLineKind.BeforeNextBlock); -*/ PushBlock(BlockKind.Class, @class); { Write($"static void register_class_{GetCIdentifier(Context, @class)}"); - WriteLine("(JSContext *ctx, JSModuleDef *m, bool set)"); + WriteLine("(JSContext *ctx, JSModuleDef *m, bool set, int phase)"); WriteOpenBraceAndIndent(); -/* - var sources = new NAPIRegisterImpl(Context); + var sources = new QuickJSRegisterImpl(Context); sources.Indent(CurrentIndentation); @class.Visit(sources); Write(sources.Generate()); -*/ UnindentAndWriteCloseBrace(); } @@ -137,6 +359,128 @@ namespace CppSharp.Generators.Cpp return false; } + public void GenerateClassExtraData(Class @class) + { + if (!ClassNeedsExtraData(@class)) + return; + + PushBlock(); + { + var classDataId = $"data_{GetCIdentifier(Context, @class)}"; + WriteLine($"struct {classDataId} : public JS_Interop_ClassData"); + WriteOpenBraceAndIndent(); + + //WriteLine($"{@class.QualifiedOriginalName}* instance;"); + + foreach (var @event in @class.Events) + { + GenerateEventInvoke(@event); + } + + Unindent(); + WriteLine("};"); + } + PopBlock(NewLineKind.BeforeNextBlock); + } + + private void GenerateEventInvoke(Event @event) + { + var invokeId = $"event_invoke_{@event.OriginalName}"; + PushBlock(); + { + CTypePrinter.PushContext(TypePrinterContextKind.Native); + var functionType = @event.Type as FunctionType; + var retType = functionType.ReturnType.Visit(CTypePrinter); + CTypePrinter.PopContext(); + + Write($"{retType} {invokeId}("); + + var @params = new List<string>(); + foreach (var param in @event.Parameters) + { + CTypePrinter.PushContext(TypePrinterContextKind.Native); + var paramType = param.Type.Visit(CTypePrinter); + CTypePrinter.PopContext(); + + @params.Add($"{paramType} {param.Name}"); + } + + Write(string.Join(", ", @params)); + WriteLine(")"); + WriteOpenBraceAndIndent(); + + WriteLine($"JSValue event = JS_Interop_FindEvent(&events, {@event.GlobalId});"); + WriteLine($"if (JS_IsUndefined(event))"); + + var isVoidReturn = functionType.ReturnType.Type.IsPrimitiveType(PrimitiveType.Void); + if (isVoidReturn) + { + WriteLineIndent($"return;"); + } + else + { + var defaultValuePrinter = new CppDefaultValuePrinter(Context); + var defaultValue = functionType.ReturnType.Visit(defaultValuePrinter); + WriteLineIndent($"return {defaultValue};"); + } + NewLine(); + + // Marshal the arguments. + var marshalers = new List<MarshalPrinter<MarshalContext>>(); + foreach (var param in @event.Parameters) + { + var ctx = new MarshalContext(Context, CurrentIndentation) + { + ArgName = param.Name, + ReturnVarName = param.Name, + ReturnType = param.QualifiedType, + Parameter = param + }; + + var marshal = GetMarshalNativeToManagedPrinter(ctx); + marshalers.Add(marshal); + + param.Visit(marshal); + + if (!string.IsNullOrWhiteSpace(marshal.Context.Before)) + { + Write(marshal.Context.Before); + } + } + + var args = marshalers.Select(m => m.Context.Return.ToString()); + WriteLine($"JSValueConst argv[] = {{ { string.Join(", ", args)} }};"); + WriteLine($"auto data = (JS_SignalContext*) JS_GetOpaque(event, 0);"); + WriteLine($"JSValue ret = JS_Call(ctx, data->function, JS_UNDEFINED, {@event.Parameters.Count}, argv);"); + WriteLine($"JS_FreeValue(ctx, ret);"); + + //WriteLine($"{@class.QualifiedOriginalName}* instance = data->instance;"); + +/* + + if (!isVoidReturn) + { + CTypePrinter.PushContext(TypePrinterContextKind.Native); + var returnType = function.ReturnType.Visit(CTypePrinter); + CTypePrinter.PopContext(); + + Write($"{returnType} {Helpers.ReturnIdentifier} = "); + } + + var @class = function.Namespace as Class; +*/ + + UnindentAndWriteCloseBrace(); + } + PopBlock(NewLineKind.BeforeNextBlock); + } + + public static bool ClassNeedsExtraData(Class @class) + { + return @class.Events.Any() || + @class.Bases.Any(b => b.IsClass && ClassNeedsExtraData(b.Class)); + } + public override void GenerateFunctionGroup(List<Function> group) { var function = group.First(); @@ -213,6 +557,11 @@ namespace CppSharp.Generators.Cpp return true; } + + public override bool VisitFunctionTemplateDecl(FunctionTemplate template) + { + return true; + } } public class QuickJSRegisterImpl : QuickJSInvokes @@ -224,6 +573,70 @@ namespace CppSharp.Generators.Cpp public override bool VisitClassDecl(Class @class) { + var ctors = @class.Constructors.Where(c => c.IsGenerated).ToArray(); + var ctor = ctors.FirstOrDefault(); + if (ctor == null) + return true; + + var funcDef = $"funcDef_{GetCIdentifier(Context, @class)}"; + var classId = $"classId_{GetCIdentifier(Context, @class)}"; + var classDef = $"classDef_{GetCIdentifier(Context, @class)}"; + + WriteLine("if (!set)"); + WriteOpenBraceAndIndent(); + { + WriteLine($"JS_AddModuleExport(ctx, m, \"{ctor.Name}\");"); + WriteLine("return;"); + } + UnindentAndWriteCloseBrace(); + NewLine(); + + WriteLine("if (phase == 0)"); + WriteOpenBraceAndIndent(); + { + WriteLine($"JS_NewClassID(&{classId});"); + NewLine(); + + WriteLine($"JS_NewClass(JS_GetRuntime(ctx), {classId}, &{classDef});"); + NewLine(); + + WriteLine("JSValue proto = JS_NewObject(ctx);"); + WriteLine( + $"JS_SetPropertyFunctionList(ctx, proto, {funcDef}, sizeof({funcDef}) / sizeof({funcDef}[0]));"); + WriteLine($"JS_SetClassProto(ctx, {classId}, proto);"); + NewLine(); + + if (ctor.IsGenerated && !ctor.IsCopyConstructor) + { + var maxArgs = ctors.Max(m => m.Parameters.Count); + var callbackId = $"callback_method_{GetCIdentifier(Context, ctor)}"; + + Write($"JSValue ctor = JS_NewCFunction2(ctx,"); + WriteLine($" {callbackId}, \"{ctor.Name}\", {maxArgs}, JS_CFUNC_constructor, 0);"); + WriteLine($"JS_SetConstructor(ctx, ctor, proto);"); + NewLine(); + + WriteLine($"JS_SetModuleExport(ctx, m, \"{ctor.Name}\", ctor);"); + } + } + UnindentAndWriteCloseBrace(); + + if (@class.HasBaseClass && @class.BaseClass.IsGenerated) + { + WriteLine("else if (phase == 1)"); + WriteOpenBraceAndIndent(); + { + var baseClassId = $"classId_{GetCIdentifier(Context, @class.BaseClass)}"; + + WriteLine($"JSValue proto = JS_GetClassProto(ctx, {classId});"); + WriteLine($"JSValue baseProto = JS_GetClassProto(ctx, {baseClassId});"); + WriteLine("int err = JS_SetPrototype(ctx, proto, baseProto);"); + WriteLine("assert(err != -1);"); + WriteLine("JS_FreeValue(ctx, baseProto);"); + WriteLine("JS_FreeValue(ctx, proto);"); + } + UnindentAndWriteCloseBrace(); + } return true; } @@ -263,9 +676,10 @@ namespace CppSharp.Generators.Cpp WriteLine("// " + item.Name); WriteOpenBraceAndIndent(); { + var @enum = item.Namespace as Enumeration; var ctx = new MarshalContext(Context, CurrentIndentation) { - ArgName = item.Value.ToString(), + ArgName = @enum.GetItemValueAsString(item), ReturnVarName = "item" }; @@ -287,6 +701,12 @@ namespace CppSharp.Generators.Cpp { return true; } + + public override bool VisitEvent(Event @event) + { + Console.WriteLine(@event.QualifiedName); + return base.VisitEvent(@event); + } } public class QuickJSInvokes : NAPIInvokes @@ -313,34 +733,265 @@ namespace CppSharp.Generators.Cpp public override void GenerateFunctionGroup(List<Function> @group) { - GenerateFunctionCallback(@group); + PushBlock(BlockKind.Function, @group); + { + GenerateFunctionCallback(@group); + } + PopBlock(NewLineKind.BeforeNextBlock); } public override void GenerateMethodGroup(List<Method> @group) { - GenerateFunctionCallback(@group.OfType<Function>().ToList()); + PushBlock(BlockKind.Method, @group); + { + GenerateFunctionCallback(@group.OfType<Function>().ToList()); + } + PopBlock(NewLineKind.BeforeNextBlock); } - public override void GenerateFunctionCallback(List<Function> @group) - { - var function = @group.First(); + private void GenerateFunctionSignature(Function function) + { var type = function is Method ? "method" : "function"; var callbackId = $"callback_{type}_{GetCIdentifier(Context, function)}"; - PushBlock(); - WriteLine($"static JSValue {callbackId}(JSContext* ctx, JSValueConst this_val,"); + GenerateFunctionSignature(callbackId); + } + + private void GenerateFunctionSignature(string id) + { + WriteLine($"static JSValue {id}(JSContext* ctx, JSValueConst this_val,"); WriteLineIndent("int argc, JSValueConst* argv)"); + } + + public override void GenerateFunctionCallback(List<Function> @group) + { + var function = @group.First(); + WriteLine($"// {function.QualifiedName}"); + + GenerateFunctionSignature(function); WriteOpenBraceAndIndent(); - GenerateFunctionCall(function); + // Check if the arguments are in the expected range. + CheckArgumentsRange(@group); + NewLine(); + + var method = function as Method; + if (method != null && !method.IsStatic) + { + var @class = method.Namespace as Class; + if (method.IsConstructor) + { + WriteLine($"{@class.QualifiedOriginalName}* instance;"); + } + else if(QuickJSRegister.ClassNeedsExtraData(@class)) + { + var classDataId = $"data_{GetCIdentifier(Context, @class)}"; + WriteLine($"auto data = ({classDataId}*) JS_GetOpaque(this_val, 0);"); + WriteLine($"{@class.QualifiedOriginalName}* instance = ({@class.QualifiedOriginalName}*) data->instance;"); + } + else + { + Write($"{@class.QualifiedOriginalName}* instance = "); + WriteLine($"({@class.QualifiedOriginalName}*) JS_GetOpaque(this_val, 0);"); + } + + NewLine(); + } + + // Handle the zero arguments case right away if one exists. + CheckZeroArguments(@group); + NewLineIfNeeded(); - var needsReturn = !function.ReturnType.Type.IsPrimitiveType(PrimitiveType.Void); - if (!needsReturn) + var needsArguments = @group.Any(f => f.Parameters.Any(p => p.IsGenerated)); + if (needsArguments) { - WriteLine("return JS_UNDEFINED;"); + var stateMachine = CalculateOverloadStates(@group); + CheckArgumentsOverload(@group, stateMachine); + + // Error state. + Unindent(); + WriteLine($"error:"); + Indent(); + + WriteLine("return JS_ThrowTypeError(ctx, \"Unsupported argument type\");"); + NewLine(); + + GenerateOverloadCalls(@group, stateMachine); + } + else + { + GenerateFunctionCall(function); + } + + if (method != null && method.IsConstructor) + { + NewLine(); + + Unindent(); + { + WriteLine("wrap:"); + } + Indent(); + + var @class = method.Namespace as Class; + var classId = $"classId_{GetCIdentifier(Context, @class)}"; + + GenerateJSClassInstance(classId); + NewLine(); + + var instanceKind = QuickJSRegister.ClassNeedsExtraData(@class) + ? "JS_INTEROP_INSTANCE_SIGNAL_CONTEXT" : "JS_INTEROP_INSTANCE_RAW_POINTER"; + WriteLine($"JS_Interop_InitObject(ctx, __obj, {instanceKind}, instance);"); + + var hasExternalInstanceField = @class.Fields.Any(f => f.OriginalName == "__ExternalInstance"); + @class.FindHierarchy(c => + { + hasExternalInstanceField |= c.Fields.Any(f => f.OriginalName == "__ExternalInstance"); + return hasExternalInstanceField; + }); + + if (hasExternalInstanceField) + { + WriteLine("JSObject* __js_obj = JS_VALUE_GET_OBJ(__obj);"); + WriteLine($"instance->__ExternalInstance = (void*) __js_obj;"); + NewLine(); + } + + NewLine(); + WriteLine($"return __obj;"); } UnindentAndWriteCloseBrace(); + } + + public void GenerateJSClassInstance(string classId, bool lookupProtoFromInstance = true) + { + if (lookupProtoFromInstance) + { + WriteLine("JSValue proto;"); + WriteLine("if (JS_IsUndefined(this_val))"); + WriteLineIndent($"proto = JS_GetClassProto(ctx, {classId});"); + WriteLine("else"); + WriteLineIndent($"proto = JS_GetProperty(ctx, this_val, JS_ATOM_prototype);"); + } + else + { + WriteLine($"JSValue proto = JS_GetClassProto(ctx, {classId});"); + } + + NewLine(); + + WriteLine("if (JS_IsException(proto))"); + WriteLineIndent("return proto;"); + NewLine(); + + WriteLine($"JSValue __obj = JS_NewObjectProtoClass(ctx, proto, {classId});"); + WriteLine("JS_FreeValue(ctx, proto);"); + } + + public override void CheckArgumentsRange(IEnumerable<Function> @group) + { + var functions = @group as List<Function> ?? @group.ToList(); + + var (minArgs, maxArgs) = (functions.Min(m => m.Parameters.Count), + functions.Max(m => m.Parameters.Count)); + + var requiredArgs = functions.Min(f => f.Parameters.FindIndex(p => p.HasDefaultValue)); + if (requiredArgs != -1) + minArgs = requiredArgs; + + string rangeCheck; + if (minArgs > 0) + rangeCheck = minArgs == maxArgs ? $"argc != {minArgs}" : $"argc < {minArgs} || argc > {maxArgs}"; + else + rangeCheck = $"argc > {maxArgs}"; + + WriteLine($"if ({rangeCheck})"); + WriteLineIndent("return JS_ThrowRangeError(ctx, \"Unsupported number of arguments\");"); + } + + public override string GenerateTypeCheckForParameter(int paramIndex, Type type) + { + var typeChecker = new QuickJSTypeCheckGen(paramIndex); + type.Visit(typeChecker); + + var condition = typeChecker.Generate(); + if (string.IsNullOrWhiteSpace(condition)) + throw new NotSupportedException(); + + return condition; + } + + public override bool VisitEvent(Event @event) + { + var getterId = $"callback_event_getter_{GetCIdentifier(Context, @event)}"; + PushBlock(); + { + WriteLine($"JSValue {getterId}(JSContext *ctx, JSValueConst this_val)"); + WriteOpenBraceAndIndent(); + + var @class = @event.Namespace as Class; + var classId = $"classId_{GetCIdentifier(Context, @class)}"; + var classDataId = $"data_{GetCIdentifier(Context, @class)}"; + WriteLine($"auto data = ({classDataId}*) JS_GetOpaque(this_val, 0);"); + + WriteLine($"if (data == nullptr)"); + WriteLineIndent("return JS_ThrowTypeError(ctx, \"Could not find object instance\");"); + NewLine(); + + WriteLine($"JSValue event = JS_Interop_FindEvent(&data->events, {@event.GlobalId});"); + WriteLine($"if (!JS_IsUndefined(event))"); + WriteLineIndent($"return JS_DupValue(ctx, event);"); + NewLine(); + + //GenerateJSClassInstance(QuickJSSources.SignalClassId, lookupProtoFromInstance: false); + + // Lookup Signal object constructor + WriteLine($"JSValue signalProto = JS_GetClassProto(ctx, {QuickJSSources.SignalClassId});"); + WriteLine($"JSValue signalCtor = JS_GetProperty(ctx, signalProto, JS_ATOM_constructor);"); + + //WriteLine("JSValue argv[] = { JS_DupValue(ctx, this_val) };"); + //WriteLine("JS_FreeValue(ctx, JS_CallConstructor2(ctx, signalCtor, __obj, 1, argv));"); + WriteLine("JSValue argv[] = { this_val };"); + WriteLine("JSValue __obj = JS_CallConstructor(ctx, signalCtor, 1, argv);"); + + WriteLine("JS_FreeValue(ctx, signalCtor);"); + WriteLine("JS_FreeValue(ctx, signalProto);"); + NewLine(); + + WriteLine($"JS_Interop_InsertEvent(&data->events, {@event.GlobalId}, JS_DupValue(ctx, __obj));"); + //WriteLine($"data->events[{eventIndex}] = JS_DupValue(ctx, __obj);"); + NewLine(); + + var invokeId = $"event_invoke_{@event.OriginalName}"; + WriteLine($"(({@class.QualifiedOriginalName}*)data->instance)->{@event.OriginalName}.bind(data, &{classDataId}::{invokeId});"); + NewLine(); + + WriteLine("return __obj;"); + UnindentAndWriteCloseBrace(); + } + PopBlock(NewLineKind.BeforeNextBlock); + + return true; + } + + public override bool VisitFunctionTemplateDecl(FunctionTemplate template) + { + return true; + } + + public void GenerateToString(Class @class) + { + PushBlock(); + { + var callbackId = $"callback_class_{GetCIdentifier(Context, @class)}_toString"; + GenerateFunctionSignature(callbackId); + WriteOpenBraceAndIndent(); + + WriteLine($"return JS_NewString(ctx, \"{@class.Name}\");"); + + UnindentAndWriteCloseBrace(); + } PopBlock(NewLineKind.BeforeNextBlock); } } diff --git a/src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs b/src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs new file mode 100644 index 00000000..c166c93e --- /dev/null +++ b/src/Generator/Generators/QuickJS/QuickJSTypeCheckGen.cs @@ -0,0 +1,129 @@ +using System; +using CppSharp.AST; +using CppSharp.AST.Extensions; +using CppSharp.Extensions; + +namespace CppSharp.Generators.Cpp +{ + public class QuickJSTypeCheckGen : CodeGenerator + { + private int ParameterIndex; + + public override string FileExtension { get; } + + public QuickJSTypeCheckGen(int parameterIndex) : base(null) + { + ParameterIndex = parameterIndex; + } + + public override void Process() + { + throw new System.NotImplementedException(); + } + + public override bool VisitPrimitiveType(PrimitiveType primitive, TypeQualifiers quals) + { + // TODO: Use TargetInfo to check the actual width of types for the target. + + var condition = string.Empty; + var arg = $"argv[{ParameterIndex}]"; + switch (primitive) + { + case PrimitiveType.Bool: + condition = $"JS_IsBool({arg})"; + break; + case PrimitiveType.Char: + case PrimitiveType.SChar: + condition = $"JS_IsInt8({arg})"; + break; + case PrimitiveType.UChar: + condition = $"JS_IsUInt8({arg})"; + break; + case PrimitiveType.WideChar: + condition = $"JS_IsUInt16({arg})"; + break; + case PrimitiveType.Short: + condition = $"JS_IsInt16({arg})"; + break; + case PrimitiveType.UShort: + condition = $"JS_IsUInt16({arg})"; + break; + case PrimitiveType.Int: + case PrimitiveType.Long: + condition = $"JS_IsInt32({arg})"; + break; + case PrimitiveType.ULong: + case PrimitiveType.UInt: + condition = $"JS_IsUInt32({arg})"; + break; + case PrimitiveType.LongLong: + case PrimitiveType.ULongLong: + case PrimitiveType.Int128: + case PrimitiveType.UInt128: + condition = $"JS_IsBigInt(ctx, {arg})"; + break; + case PrimitiveType.Half: + case PrimitiveType.Float: + case PrimitiveType.Double: + condition = $"JS_IsFloat({arg})"; + break; + case PrimitiveType.LongDouble: + case PrimitiveType.Float128: + condition = $"JS_IsBigFloat({arg})"; + break; + case PrimitiveType.String: + condition = $"JS_IsString({arg}) || JS_IsNull({arg})"; + break; + case PrimitiveType.Decimal: + condition = $"JS_IsBigDecimal({arg})"; + break; + case PrimitiveType.Null: + condition = $"JS_IsNull({arg})"; + break; + case PrimitiveType.Void: + case PrimitiveType.Char16: + case PrimitiveType.Char32: + case PrimitiveType.IntPtr: + case PrimitiveType.UIntPtr: + default: + throw new NotImplementedException(); + } + + Write(condition); + return true; + } + + public override bool VisitPointerType(PointerType pointer, TypeQualifiers quals) + { + if (pointer.IsConstCharString()) + return VisitPrimitiveType(PrimitiveType.String, quals); + + return base.VisitPointerType(pointer, quals); + } + + public override bool VisitFunctionType(FunctionType function, TypeQualifiers quals) + { + Write($"JS_IsFunction(ctx, argv[{ParameterIndex}])"); + return true; + } + + public override bool VisitTagType(TagType tag, TypeQualifiers quals) + { + if (tag.Declaration is Enumeration) + { + VisitPrimitiveType(PrimitiveType.Int, quals); + return true; + } + + var arg = $"argv[{ParameterIndex}]"; + Write($"JS_IsObject({arg}) || JS_IsNull({arg})"); + return true; + } + + public override bool VisitArrayType(ArrayType array, TypeQualifiers quals) + { + Write($"JS_IsArray(ctx, argv[{ParameterIndex}])"); + return true; + } + } +} \ No newline at end of file