@@ -8,7 +8,7 @@ const FFI_TYPES_PATH = process.env.FFI_TYPES_PATH || './ts/ffi-types.ts'
8
8
const DEBUG = process . env . DEBUG === 'true'
9
9
const ASYNCIFY = process . env . ASYNCIFY === 'true'
10
10
11
- // const ASSERT_SYNC_FN = 'assertSync'
11
+ const ASSERT_SYNC_FN = 'assertSync'
12
12
13
13
const INCLUDE_RE = / ^ # i n c l u d e .* $ / gm
14
14
const TYPEDEF_RE = / ^ \s * t y p e d e f \s + ( .+ ) $ / gm
@@ -72,7 +72,14 @@ function main() {
72
72
73
73
const MaybeAsync = 'MaybeAsync('
74
74
75
- function cTypeToTypescriptType ( ctype : string ) {
75
+ interface ParsedType {
76
+ typescript : string
77
+ ffi : string | null
78
+ ctype : string
79
+ async : boolean
80
+ }
81
+
82
+ function cTypeToTypescriptType ( ctype : string ) : ParsedType {
76
83
// simplify
77
84
let type = ctype
78
85
// remove const: ignored in JS
@@ -85,11 +92,10 @@ function cTypeToTypescriptType(ctype: string) {
85
92
async = true
86
93
type = type . slice ( MaybeAsync . length , - 1 )
87
94
}
88
- const maybeAsync = ( type : string ) => ( async && ASYNCIFY ? `${ type } | Promise<${ type } >` : type )
89
95
90
96
// mapping
91
97
if ( type . includes ( 'char*' ) ) {
92
- return { ffi : 'string' , typescript : maybeAsync ( 'string' ) , ctype, async }
98
+ return { ffi : 'string' , typescript : 'string' , ctype, async }
93
99
}
94
100
95
101
let typescript = type . replace ( / \* / g, 'Pointer' )
@@ -110,7 +116,69 @@ function cTypeToTypescriptType(ctype: string) {
110
116
ffi = 'number'
111
117
}
112
118
113
- return { typescript : maybeAsync ( typescript ) , ffi, ctype, async }
119
+ return { typescript : typescript , ffi, ctype, async }
120
+ }
121
+
122
+ function renderFunction ( args : {
123
+ functionName : string
124
+ returnType : ParsedType
125
+ params : Array < { name : string ; type : ParsedType } >
126
+ async : boolean
127
+ } ) {
128
+ const { functionName, returnType, params, async } = args
129
+ const typescriptParams = params
130
+ . map ( param => {
131
+ // Allow JSValue wherever JSValueConst is accepted.
132
+ const tsType =
133
+ param . type . typescript === 'JSValueConstPointer'
134
+ ? 'JSValuePointer | JSValueConstPointer'
135
+ : param . type . typescript
136
+ return `${ param . name } : ${ tsType } `
137
+ } )
138
+ . join ( ', ' )
139
+
140
+ const forceSync = ASYNCIFY && ! async && returnType . async
141
+ const markAsync = async && returnType . async
142
+
143
+ let typescriptFunctionName = functionName
144
+ if ( forceSync ) {
145
+ typescriptFunctionName += '_AssertSync'
146
+ } else if ( markAsync ) {
147
+ typescriptFunctionName += '_MaybeAsync'
148
+ }
149
+
150
+ const typescriptReturnType =
151
+ async && returnType . async
152
+ ? `${ returnType . typescript } | Promise<${ returnType . typescript } >`
153
+ : returnType . typescript
154
+ const typescriptFunctionType = `(${ typescriptParams } ) => ${ typescriptReturnType } `
155
+
156
+ const ffiParams = JSON . stringify ( params . map ( param => param . type . ffi ) )
157
+ const cwrapArgs = [ JSON . stringify ( functionName ) , JSON . stringify ( returnType . ffi ) , ffiParams ]
158
+ if ( DEBUG && async ) {
159
+ // https://emscripten.org/docs/porting/asyncify.html#usage-with-ccall
160
+ // Passing {async:true} to cwrap/ccall will wrap all return values in
161
+ // Promise.resolve(...), even if the c code doesn't suspend and returns a
162
+ // primitive value.
163
+ //
164
+ // When compiled with -s ASSERTIONS=1, Emscripten will throw if the
165
+ // function suspends and {async: true} wasn't passed.
166
+ //
167
+ // However, we'd like to avoid Promise/async overhead if the call can
168
+ // return a primitive value directly. So, we compile in {async:true}
169
+ // only in DEBUG mode, where assertions are enabled.
170
+ //
171
+ // Then we rely on our type system to ensure our code supports both
172
+ // primitive and promise-wrapped return values in production mode.
173
+ cwrapArgs . push ( '{ async: true }' )
174
+ }
175
+
176
+ let cwrap = `this.module.cwrap(${ cwrapArgs . join ( ', ' ) } )`
177
+ if ( forceSync ) {
178
+ cwrap = `${ ASSERT_SYNC_FN } (${ cwrap } )`
179
+ }
180
+
181
+ return ` ${ typescriptFunctionName } : ${ typescriptFunctionType } =\n ${ cwrap } `
114
182
}
115
183
116
184
function buildFFI ( matches : RegExpExecArray [ ] ) {
@@ -119,54 +187,19 @@ function buildFFI(matches: RegExpExecArray[]) {
119
187
const params = parseParams ( rawParams )
120
188
return { functionName, returnType : cTypeToTypescriptType ( returnType . trim ( ) ) , params }
121
189
} )
122
- const decls = parsed . map ( fn => {
123
- const typescriptParams = fn . params
124
- . map ( param => {
125
- // Allow JSValue wherever JSValueConst is accepted.
126
- const tsType =
127
- param . type . typescript === 'JSValueConstPointer'
128
- ? 'JSValuePointer | JSValueConstPointer'
129
- : param . type . typescript
130
-
131
- return `${ param . name } : ${ tsType } `
132
- } )
133
- . join ( ', ' )
134
- const typescriptFnType = `(${ typescriptParams } ) => ${ fn . returnType . typescript } `
135
- const ffiParams = JSON . stringify ( fn . params . map ( param => param . type . ffi ) )
136
- const cwrapArgs = [
137
- JSON . stringify ( fn . functionName ) ,
138
- JSON . stringify ( fn . returnType . ffi ) ,
139
- ffiParams ,
140
- ]
141
- if ( ASYNCIFY && DEBUG && fn . returnType . async ) {
142
- // https://emscripten.org/docs/porting/asyncify.html#usage-with-ccall
143
- // Passing {async:true} to cwrap/ccall will wrap all return values in
144
- // Promise.resolve(...), even if the c code doesn't suspend and returns a
145
- // primitive value.
146
- //
147
- // When compiled with -s ASSERTIONS=1, Emscripten will throw if the
148
- // function suspends and {async: true} wasn't passed.
149
- //
150
- // However, we'd like to avoid Promise/async overhead if the call can
151
- // return a primitive value directly. So, we compile in {async:true}
152
- // only in DEBUG mode, where assertions are enabled.
153
- //
154
- // Then we rely on our type system to ensure our code supports both
155
- // primitive and promise-wrapped return values in production mode.
156
- cwrapArgs . push ( '{ async: true }' )
190
+ const decls : string [ ] = [ ]
191
+ parsed . forEach ( fn => {
192
+ decls . push ( renderFunction ( { ...fn , async : false } ) )
193
+ if ( fn . returnType . async && ASYNCIFY ) {
194
+ decls . push ( renderFunction ( { ...fn , async : true } ) )
157
195
}
158
- let cwrap = `this.module.cwrap(${ cwrapArgs . join ( ', ' ) } )`
159
- // if (DEBUG && ASYNCIFY && !fn.returnType.async) {
160
- // cwrap = `${ASSERT_SYNC_FN}(${cwrap})`
161
- // }
162
- return ` ${ fn . functionName } : ${ typescriptFnType } =\n ${ cwrap } `
163
196
} )
164
197
165
198
const ffiTypes = fs . readFileSync ( FFI_TYPES_PATH , 'utf-8' )
166
199
const importFromFfiTypes = matchAll ( TS_EXPORT_TYPE_RE , ffiTypes ) . map ( match => match [ 1 ] )
167
- // if (DEBUG && ASYNCIFY) {
168
- // importFromFfiTypes.push(ASSERT_SYNC_FN)
169
- // }
200
+ if ( ASYNCIFY ) {
201
+ importFromFfiTypes . push ( ASSERT_SYNC_FN )
202
+ }
170
203
171
204
const ffiClassName = ASYNCIFY ? 'QuickJSAsyncFFI' : 'QuickJSFFI'
172
205
const moduleTypeName = ASYNCIFY ? 'QuickJSAsyncEmscriptenModule' : 'QuickJSEmscriptenModule'
0 commit comments