2023-05-08 14:43:50 +00:00
import { writeFileSync } from "fs" ;
import { ApiFunction } from "./api"
import { CodeGenerator , CodeWriter , GenericCodeGenerator } from "./generation"
2023-05-08 21:37:58 +00:00
export type StructLookup = { [ struct : string ] : string }
2023-05-08 14:43:50 +00:00
export class QuickJsHeader {
2023-05-08 21:37:58 +00:00
public readonly structLookup : StructLookup = { }
2023-05-08 14:43:50 +00:00
public readonly module FunctionList : QuickJsGenerator
public readonly structs : QuickJsGenerator
public readonly functions : QuickJsGenerator
public readonly module Init : QuickJsGenerator
public readonly module Entry : QuickJsGenerator
public readonly declarations : QuickJsGenerator
public readonly body : QuickJsGenerator
public readonly includes : QuickJsGenerator
private readonly root : QuickJsGenerator
constructor ( private name : string ) {
const root = this . root = new QuickJsGenerator ( )
const body = this . body = root . header ( "JS_" + this . name + "_GUARD" )
const includes = this . includes = body . child ( )
includes . include ( "stdio.h" )
includes . include ( "stdlib.h" )
includes . include ( "string.h" )
includes . breakLine ( )
includes . include ( "quickjs.h" )
body . breakLine ( )
body . line ( "#ifndef countof" )
body . line ( "#define countof(x) (sizeof(x) / sizeof((x)[0]))" )
body . line ( "#endif" )
body . breakLine ( )
this . declarations = body . child ( )
body . breakLine ( )
this . structs = body . child ( )
this . functions = body . child ( )
this . module FunctionList = body . jsFunctionList ( "js_" + name + "_funcs" )
const module InitFunc = body . function ( "js_" + this . name + "_init" , "int" , [ { type : "JSContext *" , name : "ctx" } , { type : "JSModuleDef *" , name : "m" } ] , true )
const module Init = this . module Init = module InitFunc.child ( )
module Init.statement ( ` JS_SetModuleExportList(ctx, m, ${ this . module FunctionList.getTag ( "_name" ) } ,countof( ${ this . module FunctionList.getTag ( "_name" ) } )) ` )
module InitFunc.returnExp ( "0" )
const module EntryFunc = body . function ( "js_init_module_" + this . name , "JSModuleDef *" , [ { type : "JSContext *" , name : "ctx" } , { type : "const char *" , name : "module_name" } ] , false )
const module Entry = this . module Entry = module EntryFunc.child ( )
module Entry.statement ( "JSModuleDef *m" )
module Entry.statement ( ` m = JS_NewCModule(ctx, module_name, ${ module InitFunc.getTag ( "_name" ) } ) ` )
module Entry.statement ( "if(!m) return NULL" )
module Entry.statement ( ` JS_AddModuleExportList(ctx, m, ${ this . module FunctionList.getTag ( "_name" ) } , countof( ${ this . module FunctionList.getTag ( "_name" ) } )) ` )
module EntryFunc.statement ( "return m" )
}
2023-05-08 21:37:58 +00:00
registerStruct ( struct : string , classId : string ) {
this . structLookup [ struct ] = classId ;
}
2023-05-08 14:43:50 +00:00
public writeTo ( filename : string ) {
const writer = new CodeWriter ( )
writer . writeGenerator ( this . root )
writeFileSync ( filename , writer . toString ( ) )
}
}
2023-05-08 16:05:03 +00:00
export abstract class GenericQuickJsGenerator < T extends QuickJsGenerator > extends GenericCodeGenerator < T > {
2023-05-08 14:43:50 +00:00
jsBindingFunction ( jsName : string ) {
const args = [
{ type : "JSContext *" , name : "ctx" } ,
{ type : "JSValueConst" , name : "this_val" } ,
{ type : "int" , name : "argc" } ,
{ type : "JSValueConst *" , name : "argv" } ,
]
const sub = this . function ( "js_" + jsName , "JSValue" , args , true )
return sub
}
2023-05-08 16:05:03 +00:00
jsToC ( type : string , name : string , src : string ) {
2023-05-08 14:43:50 +00:00
switch ( type ) {
case "const char *" :
2023-05-08 21:37:58 +00:00
this . statement ( ` ${ type } ${ name } = JS_ToCString(ctx, ${ src } ) ` )
2023-05-08 14:43:50 +00:00
this . statement ( ` if( ${ name } == NULL) return JS_EXCEPTION ` )
break ;
case "int" :
2023-05-08 21:37:58 +00:00
this . statement ( ` ${ type } ${ name } ` )
2023-05-08 16:05:03 +00:00
this . statement ( ` JS_ToInt32(ctx, & ${ name } , ${ src } ) ` )
break ;
2023-05-08 21:37:58 +00:00
case "unsigned char" :
this . statement ( ` int _tmp ` )
this . statement ( ` JS_ToInt32(ctx, &_tmp, ${ src } ) ` )
this . statement ( ` ${ type } ${ name } = ( ${ type } )_tmp ` )
break ;
2023-05-08 16:05:03 +00:00
default :
throw new Error ( "Cannot handle parameter type: " + type )
}
}
2023-05-08 21:37:58 +00:00
jsToJs ( type : string , name : string , src : string , classIds : StructLookup = { } ) {
2023-05-08 16:05:03 +00:00
switch ( type ) {
case "int" :
2023-05-08 21:37:58 +00:00
case "unsigned char" :
this . declare ( name , 'JSValue' , false , ` JS_NewInt32(ctx, ${ src } ) ` )
2023-05-08 14:43:50 +00:00
break ;
default :
2023-05-08 21:37:58 +00:00
const classId = classIds [ type ]
if ( ! classId ) throw new Error ( "Cannot handle parameter type: " + type )
this . jsStructToOpq ( type , name , src , classId )
2023-05-08 14:43:50 +00:00
}
}
2023-05-08 21:37:58 +00:00
jsStructToOpq ( structType : string , jsVar : string , srcVar : string , classId : string ) {
this . declare ( "ptr" , structType + "*" , false , ` ( ${ structType } *)js_malloc(ctx, sizeof( ${ structType } )) ` )
this . statement ( "*ptr = " + srcVar )
this . declare ( jsVar , "JSValue" , false , ` JS_NewObjectClass(ctx, ${ classId } ) ` )
this . call ( "JS_SetOpaque" , [ jsVar , "ptr" ] )
}
2023-05-08 14:43:50 +00:00
jsCleanUpParameter ( type : string , name : string ) {
switch ( type ) {
case "const char *" :
this . statement ( ` JS_FreeCString(ctx, ${ name } ) ` )
break ;
default :
break ;
}
}
jsFunctionList ( name : string ) {
this . line ( "static const JSCFunctionListEntry " + name + "[] = {" )
this . indent ( )
const sub = this . createGenerator ( )
sub . setTag ( "_type" , "js-function-list" )
sub . setTag ( "_name" , name )
this . child ( sub )
this . unindent ( )
this . statement ( "}" )
this . breakLine ( )
return sub
}
jsFuncDef ( jsName : string , numArgs : number , cName : string ) {
this . line ( ` JS_CFUNC_DEF(" ${ jsName } ", ${ numArgs } , ${ cName } ), ` )
}
jsClassId ( id : string ) {
this . declare ( id , "JSClassID" , true )
return id
}
jsPropStringDef ( key : string , value : string ) {
this . line ( ` JS_PROP_STRING_DEF(" ${ key } "," ${ value } ", JS_PROP_CONFIGURABLE), ` )
}
2023-05-08 16:05:03 +00:00
jsGetSetDef ( key : string , getFunc? : string , setFunc? : string ) {
this . line ( ` JS_CGETSET_DEF(" ${ key } ", ${ getFunc || "NULL" } , ${ setFunc || "NULL" } ), ` )
}
2023-05-08 14:43:50 +00:00
jsStructFinalizer ( classId : string , structName : string , onFinalize ? : ( gen : T , ptrName : string ) = > void ) {
const args = [ { type : "JSRuntime *" , name : "rt" } , { type : "JSValue" , name : "val" } ]
const body = this . function ( ` js_ ${ structName } _finalizer ` , "void" , args , true )
body . statement ( ` ${ structName } * ptr = JS_GetOpaque(val, ${ classId } ) ` )
body . if ( "ptr" , cond = > {
2023-05-08 21:37:58 +00:00
cond . call ( "TraceLog" , [ "LOG_INFO" , ` "Finalize ${ structName } " ` ] )
2023-05-08 14:43:50 +00:00
if ( onFinalize ) onFinalize ( < T > cond , "ptr" )
cond . call ( "js_free_rt" , [ "rt" , "ptr" ] )
} )
return body
}
jsClassDeclaration ( structName : string , classId : string , finalizerName : string , funcListName : string ) {
const body = this . function ( "js_declare_" + structName , "int" , [ { type : "JSContext *" , name : "ctx" } , { type : "JSModuleDef *" , name : "m" } ] , true )
body . call ( "JS_NewClassID" , [ "&" + classId ] )
const classDefName = ` js_ ${ structName } _def `
body . declare ( classDefName , "JSClassDef" , false , ` { .class_name = " ${ structName } ", .finalizer = ${ finalizerName } } ` )
body . call ( "JS_NewClass" , [ "JS_GetRuntime(ctx)" , classId , "&" + classDefName ] )
body . declare ( "proto" , "JSValue" , false , "JS_NewObject(ctx)" )
body . call ( "JS_SetPropertyFunctionList" , [ "ctx" , "proto" , funcListName , ` countof( ${ funcListName } ) ` ] )
body . call ( "JS_SetClassProto" , [ "ctx" , classId , "proto" ] )
body . statement ( "return 0" )
return body
}
jsStructGetter ( structName : string , classId : string , field : string , type : string ) {
const args = [ { type : "JSContext*" , name : "ctx" } , { type : "JSValueConst" , name : "this_val" } ]
const fun = this . function ( ` js_ ${ structName } _get_ ${ field } ` , "JSValue" , args , true )
fun . declare ( "ptr" , structName + "*" , false , ` JS_GetOpaque2(ctx, this_val, ${ classId } ) ` )
fun . if ( "!ptr" , cond = > {
cond . returnExp ( "JS_EXCEPTION" )
} )
fun . declare ( field , type , false , "ptr->" + field )
2023-05-08 16:05:03 +00:00
fun . jsToJs ( type , "ret" , field )
2023-05-08 14:43:50 +00:00
fun . returnExp ( "ret" )
2023-05-08 16:05:03 +00:00
return fun
2023-05-08 14:43:50 +00:00
}
2023-05-08 21:37:58 +00:00
jsStructSetter ( structName : string , classId : string , field : string , type : string ) {
const args = [ { type : "JSContext*" , name : "ctx" } , { type : "JSValueConst" , name : "this_val" } , { type : "JSValueConst" , name : "v" } ]
const fun = this . function ( ` js_ ${ structName } _set_ ${ field } ` , "JSValue" , args , true )
fun . declare ( "ptr" , structName + "*" , false , ` JS_GetOpaque2(ctx, this_val, ${ classId } ) ` )
fun . if ( "!ptr" , cond = > {
cond . returnExp ( "JS_EXCEPTION" )
} )
fun . jsToC ( type , "value" , "v" ) ;
fun . statement ( "ptr->" + field + " = value" )
fun . returnExp ( "JS_UNDEFINED" )
return fun
}
2023-05-08 14:43:50 +00:00
}
export class QuickJsGenerator extends GenericQuickJsGenerator < QuickJsGenerator > {
createGenerator ( ) : QuickJsGenerator {
return new QuickJsGenerator ( )
}
}