2023-05-08 14:43:50 +00:00
import { writeFileSync } from "fs" ;
import { ApiFunction } from "./api"
2023-05-09 21:25:28 +00:00
import { CodeGenerator , CodeWriter , FunctionArgument , GenericCodeGenerator } from "./generation"
2023-05-08 14:43:50 +00:00
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
2023-05-14 20:19:47 +00:00
public readonly definitions : QuickJsGenerator
2023-05-08 14:43:50 +00:00
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 ( )
2023-05-14 20:19:47 +00:00
this . definitions = body . child ( )
2023-05-08 14:43:50 +00:00
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-10 21:26:53 +00:00
jsToC ( type : string , name : string , src : string , classIds : StructLookup = { } ) {
2023-05-08 14:43:50 +00:00
switch ( type ) {
case "const char *" :
2023-05-13 12:49:05 +00:00
case "char *" :
2023-05-13 14:31:12 +00:00
this . statement ( ` ${ type } ${ name } = ( ${ type } )JS_ToCString(ctx, ${ src } ) ` )
2023-05-08 14:43:50 +00:00
break ;
2023-05-14 20:19:47 +00:00
case "double" :
this . statement ( ` ${ type } ${ name } ` )
this . statement ( ` JS_ToFloat64(ctx, & ${ name } , ${ src } ) ` )
break ;
2023-05-10 21:26:53 +00:00
case "float" :
this . statement ( "double _double_" + name )
this . statement ( ` JS_ToFloat64(ctx, &_double_ ${ name } , ${ src } ) ` )
this . statement ( ` ${ type } ${ name } = ( ${ type } )_double_ ${ name } ` )
break ;
2023-05-08 14:43:50 +00:00
case "int" :
2023-05-14 20:19:47 +00:00
this . statement ( ` ${ type } ${ name } ` )
this . statement ( ` JS_ToInt32(ctx, & ${ name } , ${ src } ) ` )
break ;
2023-05-13 12:49:05 +00:00
case "unsigned int" :
2023-05-08 21:37:58 +00:00
this . statement ( ` ${ type } ${ name } ` )
2023-05-14 20:19:47 +00:00
this . statement ( ` JS_ToUint32(ctx, & ${ name } , ${ src } ) ` )
2023-05-08 16:05:03 +00:00
break ;
2023-05-08 21:37:58 +00:00
case "unsigned char" :
2023-05-14 20:19:47 +00:00
this . statement ( "unsigned int _int_" + name )
this . statement ( ` JS_ToUint32(ctx, &_int_ ${ name } , ${ src } ) ` )
2023-05-09 16:24:54 +00:00
this . statement ( ` ${ type } ${ name } = ( ${ type } )_int_ ${ name } ` )
2023-05-08 21:37:58 +00:00
break ;
2023-05-14 20:19:47 +00:00
case "bool" :
this . statement ( ` ${ type } ${ name } = JS_ToBool(ctx, ${ src } ) ` )
break ;
2023-05-08 16:05:03 +00:00
default :
2023-05-14 20:19:47 +00:00
const isConst = type . startsWith ( 'const' )
2023-05-15 21:02:41 +00:00
const isPointer = type . endsWith ( ' *' )
const classId = classIds [ type . replace ( "const " , "" ) . replace ( " *" , "" ) ]
2023-05-10 21:26:53 +00:00
if ( ! classId ) throw new Error ( "Cannot convert into parameter type: " + type )
2023-05-15 21:02:41 +00:00
const suffix = isPointer ? "" : "_ptr"
this . jsOpqToStructPtr ( type . replace ( " *" , "" ) , name + suffix , src , classId )
this . statement ( ` if( ${ name + suffix } == NULL) return JS_EXCEPTION ` )
if ( ! isPointer ) this . declare ( name , type , false , ` * ${ name } _ptr ` )
2023-05-08 16:05:03 +00:00
}
}
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-13 12:49:05 +00:00
case "long" :
2023-05-08 21:37:58 +00:00
this . declare ( name , 'JSValue' , false , ` JS_NewInt32(ctx, ${ src } ) ` )
2023-05-08 14:43:50 +00:00
break ;
2023-05-14 20:19:47 +00:00
case "long" :
this . declare ( name , 'JSValue' , false , ` JS_NewInt64(ctx, ${ src } ) ` )
break ;
case "unsigned int" :
case "unsigned char" :
this . declare ( name , 'JSValue' , false , ` JS_NewUint32(ctx, ${ src } ) ` )
break ;
2023-05-10 21:26:53 +00:00
case "bool" :
this . declare ( name , 'JSValue' , false , ` JS_NewBool(ctx, ${ src } ) ` )
break ;
case "float" :
2023-05-13 12:49:05 +00:00
case "double" :
2023-05-10 21:26:53 +00:00
this . declare ( name , 'JSValue' , false , ` JS_NewFloat64(ctx, ${ src } ) ` )
break ;
2023-05-13 12:49:05 +00:00
case "const char *" :
case "char *" :
this . declare ( name , 'JSValue' , false , ` JS_NewString(ctx, ${ src } ) ` )
break ;
2023-05-08 14:43:50 +00:00
default :
2023-05-08 21:37:58 +00:00
const classId = classIds [ type ]
2023-05-10 21:26:53 +00:00
if ( ! classId ) throw new Error ( "Cannot convert parameter type to Javascript: " + type )
2023-05-08 21:37:58 +00:00
this . jsStructToOpq ( type , name , src , classId )
2023-05-08 14:43:50 +00:00
}
}
2023-05-09 16:24:54 +00:00
2023-05-08 21:37:58 +00:00
jsStructToOpq ( structType : string , jsVar : string , srcVar : string , classId : string ) {
2023-05-10 21:26:53 +00:00
this . declare ( jsVar + "_ptr" , structType + "*" , false , ` ( ${ structType } *)js_malloc(ctx, sizeof( ${ structType } )) ` )
this . statement ( "*" + jsVar + "_ptr = " + srcVar )
2023-05-08 21:37:58 +00:00
this . declare ( jsVar , "JSValue" , false , ` JS_NewObjectClass(ctx, ${ classId } ) ` )
2023-05-10 21:26:53 +00:00
this . call ( "JS_SetOpaque" , [ jsVar , jsVar + "_ptr" ] )
2023-05-08 21:37:58 +00:00
}
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-11 20:10:40 +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
}
2023-05-13 12:49:05 +00:00
jsStructGetter ( structName : string , classId : string , field : string , type : string , classIds : StructLookup ) {
2023-05-08 14:43:50 +00:00
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-13 12:49:05 +00:00
fun . jsToJs ( type , "ret" , field , classIds )
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
2023-05-13 12:49:05 +00:00
jsStructSetter ( structName : string , classId : string , field : string , type : string , classIds : StructLookup ) {
2023-05-08 21:37:58 +00:00
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" )
} )
2023-05-13 12:49:05 +00:00
fun . jsToC ( type , "value" , "v" , classIds ) ;
2023-05-08 21:37:58 +00:00
fun . statement ( "ptr->" + field + " = value" )
fun . returnExp ( "JS_UNDEFINED" )
return fun
}
2023-05-09 21:25:28 +00:00
2023-05-10 21:26:53 +00:00
jsOpqToStructPtr ( structType : string , structVar : string , srcVar : string , classId : string ) {
this . declare ( structVar , structType + "*" , false , ` ( ${ structType } *)JS_GetOpaque2(ctx, ${ srcVar } , ${ classId } ) ` )
}
2023-05-13 12:49:05 +00:00
jsStructConstructor ( structName : string , fields : FunctionArgument [ ] , classId : string , classIds : StructLookup ) {
2023-05-09 21:25:28 +00:00
const body = this . jsBindingFunction ( structName + "_constructor" )
for ( let i = 0 ; i < fields . length ; i ++ ) {
const para = fields [ i ]
2023-05-13 12:49:05 +00:00
body . jsToC ( para . type , para . name , "argv[" + i + "]" , classIds )
2023-05-09 21:25:28 +00:00
}
2023-05-10 21:26:53 +00:00
body . declareStruct ( structName , "_struct" , fields . map ( x = > x . name ) )
2023-05-09 21:25:28 +00:00
body . jsStructToOpq ( structName , "_return" , "_struct" , classId )
body . returnExp ( "_return" )
return body
}
2023-05-08 14:43:50 +00:00
}
export class QuickJsGenerator extends GenericQuickJsGenerator < QuickJsGenerator > {
createGenerator ( ) : QuickJsGenerator {
return new QuickJsGenerator ( )
}
}