From fd97c55510ce39db305c0d5a06298f1b578d7d51 Mon Sep 17 00:00:00 2001 From: "Alexander Klingenbeck (SHS DI SY R&D DEV4)" Date: Mon, 8 May 2023 18:05:03 +0200 Subject: [PATCH] Add getter generation --- bindings/src/generation.ts | 2 +- bindings/src/index.ts | 2 +- bindings/src/quickjs.ts | 26 ++++++++++++++---- bindings/src/raylib-header.ts | 26 +++++++++++++----- generate-bindings.js | 46 +++++++++++++++++++++++++------- src/bindings/js_raylib_texture.h | 11 ++++++++ 6 files changed, 91 insertions(+), 22 deletions(-) diff --git a/bindings/src/generation.ts b/bindings/src/generation.ts index da2a2f6..253c9e4 100644 --- a/bindings/src/generation.ts +++ b/bindings/src/generation.ts @@ -150,7 +150,7 @@ export abstract class GenericCodeGenerator { this.tokens.push(Token.UNINDENT) } - public function(name: string, returnType: string, args: FunctionArgument[], isStatic: boolean, func?: (gen: T) => void){ + public function(name: string, returnType: string, args: FunctionArgument[], isStatic: boolean, func?: (gen: T) => void): T { const sub = this.createGenerator(); sub.setTag("_type", "function-body") sub.setTag("_name", name) diff --git a/bindings/src/index.ts b/bindings/src/index.ts index ea2cfe7..75ff659 100644 --- a/bindings/src/index.ts +++ b/bindings/src/index.ts @@ -15,7 +15,7 @@ function main(){ core_gen.writeTo("src/bindings/js_raylib_core.h") const texture_gen = new RayLibHeader("raylib_texture", apiDesc) - texture_gen.addApiStructByName("Image", "UnloadImage") + texture_gen.addApiStructByName("Image", "UnloadImage", { properties: { width: { get: true } } }) texture_gen.writeTo("src/bindings/js_raylib_texture.h") } diff --git a/bindings/src/quickjs.ts b/bindings/src/quickjs.ts index b723b1d..a8bd423 100644 --- a/bindings/src/quickjs.ts +++ b/bindings/src/quickjs.ts @@ -55,7 +55,7 @@ export class QuickJsHeader { } } -export abstract class GenericQuickJsGenerator extends GenericCodeGenerator { +export abstract class GenericQuickJsGenerator extends GenericCodeGenerator { jsBindingFunction(jsName: string){ const args = [ @@ -69,16 +69,27 @@ export abstract class GenericQuickJsGenerator extends G return sub } - jsReadParameter(type: string, name: string, index: number){ + jsToC(type: string, name: string, src: string){ this.inline(`${type} ${name}`) switch (type) { case "const char *": - this.statement(` = JS_ToCString(ctx, argv[${index}])`) + this.statement(` = JS_ToCString(ctx, ${src})`) this.statement(`if(${name} == NULL) return JS_EXCEPTION`) break; case "int": this.statement('') - this.statement(`JS_ToInt32(ctx, &${name}, argv[${index}])`) + this.statement(`JS_ToInt32(ctx, &${name}, ${src})`) + break; + default: + throw new Error("Cannot handle parameter type: " + type) + } + } + + jsToJs(type: string, name: string, src: string){ + this.inline(`JSValue ${name}`) + switch (type) { + case "int": + this.statement(` = JS_NewInt32(ctx, ${src})`) break; default: throw new Error("Cannot handle parameter type: " + type) @@ -120,6 +131,10 @@ export abstract class GenericQuickJsGenerator extends G jsPropStringDef(key: string, value: string){ this.line(`JS_PROP_STRING_DEF("${key}","${value}", JS_PROP_CONFIGURABLE),`) } + + jsGetSetDef(key: string, getFunc?: string, setFunc?: string){ + this.line(`JS_CGETSET_DEF("${key}",${getFunc || "NULL"},${setFunc || "NULL"}),`) + } jsStructFinalizer(classId: string, structName: string, onFinalize?: (gen: T, ptrName: string) => void){ const args = [{type: "JSRuntime *", name: "rt"},{type: "JSValue", name: "val"}] @@ -154,8 +169,9 @@ export abstract class GenericQuickJsGenerator extends G cond.returnExp("JS_EXCEPTION") }) fun.declare(field, type, false, "ptr->"+field) - // TODO: to JS + fun.jsToJs(type, "ret", field) fun.returnExp("ret") + return fun } } diff --git a/bindings/src/raylib-header.ts b/bindings/src/raylib-header.ts index e95e64e..2f96fc2 100644 --- a/bindings/src/raylib-header.ts +++ b/bindings/src/raylib-header.ts @@ -1,9 +1,9 @@ import { ApiDescription, ApiFunction, ApiStruct } from "./api" +import { CodeGenerator } from "./generation" import { QuickJsHeader } from "./quickjs" export interface StructBindingOptions { - getters?: string[] - setters?: string[] + properties?: { [key:string]: { get?:boolean, set?:boolean } } } @@ -21,7 +21,7 @@ export class RayLibHeader extends QuickJsHeader { // read parameters for (let i = 0; i < api.params.length; i++) { const para = api.params[i] - fun.jsReadParameter(para.type,para.name,i) + fun.jsToC(para.type,para.name,"argv["+i+"]") } // call c function fun.call(api.name, api.params.map(x => x.name), api.returnType === "void" ? null : {type: api.returnType, name: "returnVal"}) @@ -33,8 +33,8 @@ export class RayLibHeader extends QuickJsHeader { if(api.returnType === "void"){ fun.statement("return JS_UNDEFINED") } else { - // TODO: Convert to JS - fun.statement("return retVal") + fun.jsToJs(api.returnType, "ret", "returnVal") + fun.returnExp("returnVal") } // add binding to function declaration @@ -51,8 +51,22 @@ export class RayLibHeader extends QuickJsHeader { const classId = this.declarations.jsClassId(`js_${struct.name}_class_id`) const finalizer = this.structs.jsStructFinalizer(classId, struct.name, (gen,ptr) => destructor && gen.call(destructor.name, ["*"+ptr])) - //TODO: declareGetterSetter() + + const propDeclarations = this.structs.createGenerator() + if(options && options.properties){ + for (const field of Object.keys(options.properties)) { + const type = struct.fields.find(x => x.name === field)?.type + if(!type) throw new Error(`Struct ${struct.name} does not contain field ${field}`) + const el = options.properties[field] + let _get: CodeGenerator | undefined = undefined + let _set: CodeGenerator | undefined = undefined + if(el.get) _get = this.structs.jsStructGetter(struct.name, classId, field, type) + propDeclarations.jsGetSetDef(field, _get?.getTag("_name"), undefined) + } + } + const classFuncList = this.structs.jsFunctionList(`js_${struct.name}_proto_funcs`) + classFuncList.child(propDeclarations) classFuncList.jsPropStringDef("[Symbol.toStringTag]", "Image") const classDecl = this.structs.jsClassDeclaration(struct.name, classId, finalizer.getTag("_name"), classFuncList.getTag("_name")) diff --git a/generate-bindings.js b/generate-bindings.js index 7664aea..a25ac9f 100644 --- a/generate-bindings.js +++ b/generate-bindings.js @@ -332,16 +332,26 @@ class GenericQuickJsGenerator extends generation_1.GenericCodeGenerator { const sub = this.function("js_" + jsName, "JSValue", args, true); return sub; } - jsReadParameter(type, name, index) { + jsToC(type, name, src) { this.inline(`${type} ${name}`); switch (type) { case "const char *": - this.statement(` = JS_ToCString(ctx, argv[${index}])`); + this.statement(` = JS_ToCString(ctx, ${src})`); this.statement(`if(${name} == NULL) return JS_EXCEPTION`); break; case "int": this.statement(''); - this.statement(`JS_ToInt32(ctx, &${name}, argv[${index}])`); + this.statement(`JS_ToInt32(ctx, &${name}, ${src})`); + break; + default: + throw new Error("Cannot handle parameter type: " + type); + } + } + jsToJs(type, name, src) { + this.inline(`JSValue ${name}`); + switch (type) { + case "int": + this.statement(` = JS_NewInt32(ctx, ${src})`); break; default: throw new Error("Cannot handle parameter type: " + type); @@ -378,6 +388,9 @@ class GenericQuickJsGenerator extends generation_1.GenericCodeGenerator { jsPropStringDef(key, value) { this.line(`JS_PROP_STRING_DEF("${key}","${value}", JS_PROP_CONFIGURABLE),`); } + jsGetSetDef(key, getFunc, setFunc) { + this.line(`JS_CGETSET_DEF("${key}",${getFunc || "NULL"},${setFunc || "NULL"}),`); + } jsStructFinalizer(classId, structName, onFinalize) { const args = [{ type: "JSRuntime *", name: "rt" }, { type: "JSValue", name: "val" }]; const body = this.function(`js_${structName}_finalizer`, "void", args, true); @@ -410,8 +423,9 @@ class GenericQuickJsGenerator extends generation_1.GenericCodeGenerator { cond.returnExp("JS_EXCEPTION"); }); fun.declare(field, type, false, "ptr->" + field); - // TODO: to JS + fun.jsToJs(type, "ret", field); fun.returnExp("ret"); + return fun; } } exports.GenericQuickJsGenerator = GenericQuickJsGenerator; @@ -447,7 +461,7 @@ class RayLibHeader extends quickjs_1.QuickJsHeader { // read parameters for (let i = 0; i < api.params.length; i++) { const para = api.params[i]; - fun.jsReadParameter(para.type, para.name, i); + fun.jsToC(para.type, para.name, "argv[" + i + "]"); } // call c function fun.call(api.name, api.params.map(x => x.name), api.returnType === "void" ? null : { type: api.returnType, name: "returnVal" }); @@ -460,8 +474,8 @@ class RayLibHeader extends quickjs_1.QuickJsHeader { fun.statement("return JS_UNDEFINED"); } else { - // TODO: Convert to JS - fun.statement("return retVal"); + fun.jsToJs(api.returnType, "ret", "returnVal"); + fun.returnExp("returnVal"); } // add binding to function declaration this.moduleFunctionList.jsFuncDef(jName, api.argc, fun.getTag("_name")); @@ -475,8 +489,22 @@ class RayLibHeader extends quickjs_1.QuickJsHeader { addApiStruct(struct, destructor, options) { const classId = this.declarations.jsClassId(`js_${struct.name}_class_id`); const finalizer = this.structs.jsStructFinalizer(classId, struct.name, (gen, ptr) => destructor && gen.call(destructor.name, ["*" + ptr])); - //TODO: declareGetterSetter() + const propDeclarations = this.structs.createGenerator(); + if (options && options.properties) { + for (const field of Object.keys(options.properties)) { + const type = struct.fields.find(x => x.name === field)?.type; + if (!type) + throw new Error(`Struct ${struct.name} does not contain field ${field}`); + const el = options.properties[field]; + let _get = undefined; + let _set = undefined; + if (el.get) + _get = this.structs.jsStructGetter(struct.name, classId, field, type); + propDeclarations.jsGetSetDef(field, _get?.getTag("_name"), undefined); + } + } const classFuncList = this.structs.jsFunctionList(`js_${struct.name}_proto_funcs`); + classFuncList.child(propDeclarations); classFuncList.jsPropStringDef("[Symbol.toStringTag]", "Image"); const classDecl = this.structs.jsClassDeclaration(struct.name, classId, finalizer.getTag("_name"), classFuncList.getTag("_name")); this.moduleInit.call(classDecl.getTag("_name"), ["ctx", "m"]); @@ -559,7 +587,7 @@ function main() { core_gen.addApiFunctionByName("EndDrawing"); core_gen.writeTo("src/bindings/js_raylib_core.h"); const texture_gen = new raylib_header_1.RayLibHeader("raylib_texture", apiDesc); - texture_gen.addApiStructByName("Image", "UnloadImage"); + texture_gen.addApiStructByName("Image", "UnloadImage", { properties: { width: { get: true } } }); texture_gen.writeTo("src/bindings/js_raylib_texture.h"); } main(); diff --git a/src/bindings/js_raylib_texture.h b/src/bindings/js_raylib_texture.h index a7f7602..553b511 100644 --- a/src/bindings/js_raylib_texture.h +++ b/src/bindings/js_raylib_texture.h @@ -23,7 +23,18 @@ static void js_Image_finalizer(JSRuntime * rt, JSValue val) { } } +static JSValue js_Image_get_width(JSContext* ctx, JSValueConst this_val) { + Image* ptr = JS_GetOpaque2(ctx, this_val, js_Image_class_id); + if(!ptr) { + return JS_EXCEPTION; + } + int width = ptr->width; + JSValue ret = JS_NewInt32(ctx, width); + return ret; +} + static const JSCFunctionListEntry js_Image_proto_funcs[] = { + JS_CGETSET_DEF("width",js_Image_get_width,NULL), JS_PROP_STRING_DEF("[Symbol.toStringTag]","Image", JS_PROP_CONFIGURABLE), };