@thi.ng/wasm-api 0.5.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -1
- package/README.md +425 -16
- package/api.d.ts +217 -3
- package/api.js +9 -1
- package/bin/wasm-api +12 -0
- package/bridge.d.ts +41 -7
- package/bridge.js +45 -9
- package/cli.d.ts +5 -0
- package/cli.js +142 -0
- package/codegen/typescript.d.ts +35 -0
- package/codegen/typescript.js +189 -0
- package/codegen/utils.d.ts +30 -0
- package/codegen/utils.js +30 -0
- package/codegen/zig.d.ts +25 -0
- package/codegen/zig.js +75 -0
- package/codegen.d.ts +53 -0
- package/codegen.js +148 -0
- package/doc/assets/main.js +52 -0
- package/doc/assets/search.js +1 -0
- package/include/wasmapi.h +60 -0
- package/{zig/core.zig → include/wasmapi.zig} +45 -32
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/package.json +31 -7
- package/dev/custom.zig +0 -12
- package/dev/fieldinfo.zig +0 -135
- package/dev/hello.zig +0 -14
- package/dev/zig-cache/o/0fd683610fe16c12563bf410950c8193/builtin.zig +0 -39
- package/test/custom.zig +0 -16
- package/test/zig-cache/o/117d44467ded6ce3864cde3c180ef73c/builtin.zig +0 -39
package/api.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Fn, Fn2 } from "@thi.ng/api";
|
|
1
|
+
import type { BigType, FloatType, Fn, Fn2 } from "@thi.ng/api";
|
|
2
2
|
import type { WasmBridge } from "./bridge.js";
|
|
3
|
+
export declare const PKG_NAME = "@thi.ng/wasm-api";
|
|
3
4
|
export declare type BigIntArray = bigint[] | BigInt64Array | BigUint64Array;
|
|
4
5
|
/**
|
|
5
6
|
* Common interface for WASM/JS child APIs which will be used in combination
|
|
@@ -60,12 +61,47 @@ export interface WasmExports {
|
|
|
60
61
|
* Implementation specific function to free a previously allocated chunk of
|
|
61
62
|
* of WASM memory (allocated via {@link WasmExports._wasm_allocate}).
|
|
62
63
|
*
|
|
64
|
+
* @param addr
|
|
65
|
+
* @param numBytes
|
|
66
|
+
*/
|
|
67
|
+
_wasm_free(addr: number, numBytes: number): void;
|
|
68
|
+
}
|
|
69
|
+
export interface IWasmMemoryAccess {
|
|
70
|
+
i8: Int8Array;
|
|
71
|
+
u8: Uint8Array;
|
|
72
|
+
i16: Int16Array;
|
|
73
|
+
u16: Uint16Array;
|
|
74
|
+
i32: Int32Array;
|
|
75
|
+
u32: Uint32Array;
|
|
76
|
+
i64: BigInt64Array;
|
|
77
|
+
u64: BigUint64Array;
|
|
78
|
+
f32: Float32Array;
|
|
79
|
+
f64: Float64Array;
|
|
80
|
+
/**
|
|
81
|
+
* Reads UTF-8 encoded string from given address and optional byte length.
|
|
82
|
+
* The default length is 0, which will be interpreted as a zero-terminated
|
|
83
|
+
* string. Returns string.
|
|
84
|
+
*
|
|
85
|
+
* @param addr
|
|
86
|
+
* @param len
|
|
87
|
+
*/
|
|
88
|
+
getString(addr: number, len?: number): string;
|
|
89
|
+
/**
|
|
90
|
+
* Encodes given string as UTF-8 and writes it to WASM memory starting at
|
|
91
|
+
* `addr`. By default the string will be zero-terminated and only `maxBytes`
|
|
92
|
+
* will be written. Returns the number of bytes written.
|
|
93
|
+
*
|
|
63
94
|
* @remarks
|
|
64
|
-
*
|
|
95
|
+
* An error will be thrown if the encoded string doesn't fully fit into the
|
|
96
|
+
* designated memory region (also note that there might need to be space for
|
|
97
|
+
* the additional sentinel/termination byte).
|
|
65
98
|
*
|
|
99
|
+
* @param str
|
|
66
100
|
* @param addr
|
|
101
|
+
* @param maxBytes
|
|
102
|
+
* @param terminate
|
|
67
103
|
*/
|
|
68
|
-
|
|
104
|
+
setString(str: string, addr: number, maxBytes: number, terminate?: boolean): number;
|
|
69
105
|
}
|
|
70
106
|
/**
|
|
71
107
|
* Core API of WASM imports defined by the {@link WasmBridge}. The same
|
|
@@ -100,4 +136,182 @@ export interface CoreAPI extends WebAssembly.ModuleImports {
|
|
|
100
136
|
_printStr0: (addr: number) => void;
|
|
101
137
|
_printStr: (addr: number, len: number) => void;
|
|
102
138
|
}
|
|
139
|
+
export interface WasmTypeBase {
|
|
140
|
+
/**
|
|
141
|
+
* Base address in linear WASM memory.
|
|
142
|
+
*/
|
|
143
|
+
readonly __base: number;
|
|
144
|
+
/**
|
|
145
|
+
* Obtain as byte buffer
|
|
146
|
+
*/
|
|
147
|
+
readonly __bytes: Uint8Array;
|
|
148
|
+
}
|
|
149
|
+
export interface WasmType<T> {
|
|
150
|
+
readonly align: number;
|
|
151
|
+
readonly size: number;
|
|
152
|
+
instance: Fn<number, T>;
|
|
153
|
+
}
|
|
154
|
+
export declare type WasmTypeConstructor<T> = Fn<IWasmMemoryAccess, WasmType<T>>;
|
|
155
|
+
export declare type WasmInt = "i8" | "i16" | "i32" | "i64";
|
|
156
|
+
export declare type WasmUint = "u8" | "u16" | "u32" | "u64";
|
|
157
|
+
export declare type WasmFloat = FloatType;
|
|
158
|
+
export declare type WasmPrim = WasmInt | WasmUint | WasmFloat;
|
|
159
|
+
export declare type WasmPrim32 = Exclude<WasmPrim, BigType>;
|
|
160
|
+
export declare type TypeColl = Record<string, TopLevelType>;
|
|
161
|
+
export interface TypeInfo {
|
|
162
|
+
/**
|
|
163
|
+
* Auto-computed size (in bytes)
|
|
164
|
+
*
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
__size?: number;
|
|
168
|
+
/**
|
|
169
|
+
* Auto-computed offset (in bytes) in parent struct
|
|
170
|
+
*
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
__offset?: number;
|
|
174
|
+
/**
|
|
175
|
+
* Auto-computed alignment (in bytes)
|
|
176
|
+
*
|
|
177
|
+
* @internal
|
|
178
|
+
*/
|
|
179
|
+
__align?: number;
|
|
180
|
+
}
|
|
181
|
+
export interface TopLevelType extends TypeInfo {
|
|
182
|
+
/**
|
|
183
|
+
* Type name
|
|
184
|
+
*/
|
|
185
|
+
name: string;
|
|
186
|
+
/**
|
|
187
|
+
* Optional (multi-line) docstring for this type
|
|
188
|
+
*/
|
|
189
|
+
doc?: string;
|
|
190
|
+
/**
|
|
191
|
+
* Type / kind
|
|
192
|
+
*/
|
|
193
|
+
type: "struct" | "enum";
|
|
194
|
+
}
|
|
195
|
+
export interface Struct extends TopLevelType {
|
|
196
|
+
type: "struct";
|
|
197
|
+
/**
|
|
198
|
+
* List of struct fields (might be re-ordered if {@link Struct.auto} is
|
|
199
|
+
* enabled).
|
|
200
|
+
*/
|
|
201
|
+
fields: StructField[];
|
|
202
|
+
/**
|
|
203
|
+
* If true, struct fields will be re-ordered in descending order based on
|
|
204
|
+
* their {@link TypeInfo.__align} size. This might result in overall smaller
|
|
205
|
+
* structs due to minimizing inter-field padding.
|
|
206
|
+
*
|
|
207
|
+
* @defaultValue false
|
|
208
|
+
*/
|
|
209
|
+
auto?: boolean;
|
|
210
|
+
}
|
|
211
|
+
export interface StructField extends TypeInfo {
|
|
212
|
+
/**
|
|
213
|
+
* Field name (prefix: "__" is reserved)
|
|
214
|
+
*/
|
|
215
|
+
name: string;
|
|
216
|
+
/**
|
|
217
|
+
* Field docstring (can be multiline, will be formatted)
|
|
218
|
+
*/
|
|
219
|
+
doc?: string;
|
|
220
|
+
/**
|
|
221
|
+
* Field type tag/qualifier (note: `slice` & `vec` are only supported by Zig
|
|
222
|
+
* & TS).
|
|
223
|
+
*
|
|
224
|
+
* @remarks
|
|
225
|
+
* - Array & vector fields are statically sized (using
|
|
226
|
+
* {@link StructField.len})
|
|
227
|
+
* - Pointers are emitted as single-value pointers (where this distinction
|
|
228
|
+
* exist), i.e. even if they're pointing to multiple values, there's no
|
|
229
|
+
* explicit length encoded/available
|
|
230
|
+
* - Zig slices are essentially a pointer w/ associated length
|
|
231
|
+
* - Zig vectors will be processed using SIMD (if enabled in WASM target)
|
|
232
|
+
* and therefore will have stricter (larger) alignment requirements.
|
|
233
|
+
*
|
|
234
|
+
* @defaultValue "scalar"
|
|
235
|
+
*/
|
|
236
|
+
tag?: "scalar" | "array" | "ptr" | "slice" | "vec";
|
|
237
|
+
/**
|
|
238
|
+
* Field base type. If not a {@link WasmPrim}, `string` or `opaque`, the
|
|
239
|
+
* value is interpreted as another type name in the {@link TypeColl}.
|
|
240
|
+
*
|
|
241
|
+
* @remarks
|
|
242
|
+
* Please see {@link CodeGenOpts.stringType} and consult package readme for
|
|
243
|
+
* further details re: string handling.
|
|
244
|
+
*
|
|
245
|
+
* TODO `opaque` currently unsupported.
|
|
246
|
+
*/
|
|
247
|
+
type: WasmPrim | "string" | "opaque" | string;
|
|
248
|
+
/**
|
|
249
|
+
* TODO currently unsupported & ignored!
|
|
250
|
+
*/
|
|
251
|
+
sentinel?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Array or vector length (see {@link StructField.tag})
|
|
254
|
+
*/
|
|
255
|
+
len?: number;
|
|
256
|
+
/**
|
|
257
|
+
* TODO currently unsupported & ignored!
|
|
258
|
+
*/
|
|
259
|
+
default?: any;
|
|
260
|
+
}
|
|
261
|
+
export interface Enum extends TopLevelType {
|
|
262
|
+
type: "enum";
|
|
263
|
+
/**
|
|
264
|
+
* No i64/u64 support, due to Typescript not supporting bigint enum values
|
|
265
|
+
*/
|
|
266
|
+
tag: Exclude<WasmPrim32, FloatType>;
|
|
267
|
+
/**
|
|
268
|
+
* List of possible values/IDs. Use {@link EnumValue}s for more detailed
|
|
269
|
+
* config.
|
|
270
|
+
*/
|
|
271
|
+
values: (string | EnumValue)[];
|
|
272
|
+
}
|
|
273
|
+
export interface EnumValue {
|
|
274
|
+
/**
|
|
275
|
+
* Enum value name/ID
|
|
276
|
+
*/
|
|
277
|
+
name: string;
|
|
278
|
+
/**
|
|
279
|
+
* Optional associated numeric value
|
|
280
|
+
*/
|
|
281
|
+
value?: number;
|
|
282
|
+
/**
|
|
283
|
+
* Optional docstring for this value
|
|
284
|
+
*/
|
|
285
|
+
doc?: string;
|
|
286
|
+
}
|
|
287
|
+
export interface ICodeGen {
|
|
288
|
+
/**
|
|
289
|
+
* Optional prelude source, to be prepended before any generated type defs.
|
|
290
|
+
*/
|
|
291
|
+
pre?: string;
|
|
292
|
+
/**
|
|
293
|
+
* Optional source code to be appended after any generated type defs.
|
|
294
|
+
*/
|
|
295
|
+
post?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Docstring codegen
|
|
298
|
+
*/
|
|
299
|
+
doc: (doc: string, indent: string, acc: string[], topLevel?: boolean) => void;
|
|
300
|
+
/**
|
|
301
|
+
* Codegen for enum types.
|
|
302
|
+
*/
|
|
303
|
+
enum: (type: Enum, types: TypeColl, acc: string[]) => void;
|
|
304
|
+
/**
|
|
305
|
+
* Codegen for struct types.
|
|
306
|
+
*/
|
|
307
|
+
struct: (type: Struct, types: TypeColl, acc: string[]) => void;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* WASM usize type. Assuming wasm32 until wasm64 surfaces, then need an option.
|
|
311
|
+
*/
|
|
312
|
+
export declare const USIZE = "u32";
|
|
313
|
+
/**
|
|
314
|
+
* Byte size of {@link USIZE}.
|
|
315
|
+
*/
|
|
316
|
+
export declare const USIZE_SIZE = 4;
|
|
103
317
|
//# sourceMappingURL=api.d.ts.map
|
package/api.js
CHANGED
package/bin/wasm-api
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# https://stackoverflow.com/a/246128/294515
|
|
4
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
5
|
+
while [ -h "$SOURCE" ]; do
|
|
6
|
+
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
7
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
8
|
+
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
9
|
+
done
|
|
10
|
+
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
11
|
+
|
|
12
|
+
/usr/bin/env node "$DIR/../cli.js" "$DIR" "$@"
|
package/bridge.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { NumericArray } from "@thi.ng/api";
|
|
3
3
|
import type { ILogger } from "@thi.ng/logger";
|
|
4
|
-
import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports } from "./api.js";
|
|
4
|
+
import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports, IWasmMemoryAccess } from "./api.js";
|
|
5
5
|
export declare const OutOfMemoryError: {
|
|
6
6
|
new (msg?: string | undefined): {
|
|
7
7
|
name: string;
|
|
@@ -28,7 +28,7 @@ export declare const OutOfMemoryError: {
|
|
|
28
28
|
* 64bit integers are handled via JS `BigInt` and hence require the host env to
|
|
29
29
|
* support it. No polyfill is provided.
|
|
30
30
|
*/
|
|
31
|
-
export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
31
|
+
export declare class WasmBridge<T extends WasmExports = WasmExports> implements IWasmMemoryAccess {
|
|
32
32
|
modules: Record<string, IWasmAPI<T>>;
|
|
33
33
|
logger: ILogger;
|
|
34
34
|
i8: Int8Array;
|
|
@@ -45,7 +45,7 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
45
45
|
utf8Encoder: TextEncoder;
|
|
46
46
|
imports: WebAssembly.Imports;
|
|
47
47
|
exports: T;
|
|
48
|
-
|
|
48
|
+
api: CoreAPI;
|
|
49
49
|
constructor(modules?: Record<string, IWasmAPI<T>>, logger?: ILogger);
|
|
50
50
|
/**
|
|
51
51
|
* Instantiates WASM module from given `src` (and optional provided extra
|
|
@@ -95,7 +95,7 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
95
95
|
* bridge.getImports();
|
|
96
96
|
* {
|
|
97
97
|
* // imports defined by the core API of the bridge itself
|
|
98
|
-
*
|
|
98
|
+
* wasmapi: { ... },
|
|
99
99
|
* // imports defined by the CustomAPI module
|
|
100
100
|
* custom: { ... }
|
|
101
101
|
* }
|
|
@@ -121,15 +121,26 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
121
121
|
* Attempts to allocate `numBytes` using the exported WASM core API function
|
|
122
122
|
* {@link WasmExports._wasm_allocate} (implementation specific) and returns
|
|
123
123
|
* start address of the new memory block. If unsuccessful, throws an
|
|
124
|
-
* {@link OutOfMemoryError}.
|
|
124
|
+
* {@link OutOfMemoryError}. If `clear` is true, the allocated region will
|
|
125
|
+
* be zero-filled.
|
|
125
126
|
*
|
|
126
127
|
* @remarks
|
|
127
128
|
* See {@link WasmExports._wasm_allocate} docs for further details.
|
|
128
129
|
*
|
|
129
130
|
* @param numBytes
|
|
131
|
+
* @param clear
|
|
130
132
|
*/
|
|
131
|
-
allocate(numBytes: number): number;
|
|
132
|
-
|
|
133
|
+
allocate(numBytes: number, clear?: boolean): number;
|
|
134
|
+
/**
|
|
135
|
+
* Frees a previous allocated memory region using the exported WASM core API
|
|
136
|
+
* function {@link WasmExports._wasm_free} (implementation specific). The
|
|
137
|
+
* `numBytes` value must be the same as previously given to
|
|
138
|
+
* {@link WasmBridge.allocate}.
|
|
139
|
+
*
|
|
140
|
+
* @param addr
|
|
141
|
+
* @param numBytes
|
|
142
|
+
*/
|
|
143
|
+
free(addr: number, numBytes: number): void;
|
|
133
144
|
getI8(addr: number): number;
|
|
134
145
|
getU8(addr: number): number;
|
|
135
146
|
getI16(addr: number): number;
|
|
@@ -170,7 +181,30 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
|
|
|
170
181
|
setU64Array(addr: number, buf: BigIntArray): this;
|
|
171
182
|
setF32Array(addr: number, buf: NumericArray): this;
|
|
172
183
|
setF64Array(addr: number, buf: NumericArray): this;
|
|
184
|
+
/**
|
|
185
|
+
* Reads UTF-8 encoded string from given address and optional byte length.
|
|
186
|
+
* The default length is 0, which will be interpreted as a zero-terminated
|
|
187
|
+
* string. Returns string.
|
|
188
|
+
*
|
|
189
|
+
* @param addr
|
|
190
|
+
* @param len
|
|
191
|
+
*/
|
|
173
192
|
getString(addr: number, len?: number): string;
|
|
193
|
+
/**
|
|
194
|
+
* Encodes given string as UTF-8 and writes it to WASM memory starting at
|
|
195
|
+
* `addr`. By default the string will be zero-terminated and only `maxBytes`
|
|
196
|
+
* will be written. Returns the number of bytes written.
|
|
197
|
+
*
|
|
198
|
+
* @remarks
|
|
199
|
+
* An error will be thrown if the encoded string doesn't fully fit into the
|
|
200
|
+
* designated memory region (also note that there might need to be space for
|
|
201
|
+
* the additional sentinel/termination byte).
|
|
202
|
+
*
|
|
203
|
+
* @param str
|
|
204
|
+
* @param addr
|
|
205
|
+
* @param maxBytes
|
|
206
|
+
* @param terminate
|
|
207
|
+
*/
|
|
174
208
|
setString(str: string, addr: number, maxBytes: number, terminate?: boolean): number;
|
|
175
209
|
getElementById(addr: number, len?: number): HTMLElement;
|
|
176
210
|
}
|
package/bridge.js
CHANGED
|
@@ -28,7 +28,7 @@ export class WasmBridge {
|
|
|
28
28
|
this.utf8Encoder = new TextEncoder();
|
|
29
29
|
const logN = (x) => this.logger.debug(x);
|
|
30
30
|
const logA = (method) => (addr, len) => this.logger.debug(method(addr, len).join(", "));
|
|
31
|
-
this.
|
|
31
|
+
this.api = {
|
|
32
32
|
printI8: logN,
|
|
33
33
|
printU8: logN,
|
|
34
34
|
printU8Hex: (x) => this.logger.debug(`0x${U8(x)}`),
|
|
@@ -136,7 +136,7 @@ export class WasmBridge {
|
|
|
136
136
|
* bridge.getImports();
|
|
137
137
|
* {
|
|
138
138
|
* // imports defined by the core API of the bridge itself
|
|
139
|
-
*
|
|
139
|
+
* wasmapi: { ... },
|
|
140
140
|
* // imports defined by the CustomAPI module
|
|
141
141
|
* custom: { ... }
|
|
142
142
|
* }
|
|
@@ -151,7 +151,7 @@ export class WasmBridge {
|
|
|
151
151
|
*/
|
|
152
152
|
getImports() {
|
|
153
153
|
if (!this.imports) {
|
|
154
|
-
this.imports = {
|
|
154
|
+
this.imports = { wasmapi: this.api };
|
|
155
155
|
for (let id in this.modules) {
|
|
156
156
|
if (this.imports[id] !== undefined) {
|
|
157
157
|
illegalArgs(`attempt to redeclare API module ${id}`);
|
|
@@ -176,24 +176,36 @@ export class WasmBridge {
|
|
|
176
176
|
* Attempts to allocate `numBytes` using the exported WASM core API function
|
|
177
177
|
* {@link WasmExports._wasm_allocate} (implementation specific) and returns
|
|
178
178
|
* start address of the new memory block. If unsuccessful, throws an
|
|
179
|
-
* {@link OutOfMemoryError}.
|
|
179
|
+
* {@link OutOfMemoryError}. If `clear` is true, the allocated region will
|
|
180
|
+
* be zero-filled.
|
|
180
181
|
*
|
|
181
182
|
* @remarks
|
|
182
183
|
* See {@link WasmExports._wasm_allocate} docs for further details.
|
|
183
184
|
*
|
|
184
185
|
* @param numBytes
|
|
186
|
+
* @param clear
|
|
185
187
|
*/
|
|
186
|
-
allocate(numBytes) {
|
|
188
|
+
allocate(numBytes, clear = false) {
|
|
187
189
|
const addr = this.exports._wasm_allocate(numBytes);
|
|
188
190
|
if (!addr)
|
|
189
191
|
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
|
|
190
192
|
this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)}`);
|
|
191
193
|
this.ensureMemory();
|
|
194
|
+
clear && this.u8.fill(0, addr, addr + numBytes);
|
|
192
195
|
return addr;
|
|
193
196
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Frees a previous allocated memory region using the exported WASM core API
|
|
199
|
+
* function {@link WasmExports._wasm_free} (implementation specific). The
|
|
200
|
+
* `numBytes` value must be the same as previously given to
|
|
201
|
+
* {@link WasmBridge.allocate}.
|
|
202
|
+
*
|
|
203
|
+
* @param addr
|
|
204
|
+
* @param numBytes
|
|
205
|
+
*/
|
|
206
|
+
free(addr, numBytes) {
|
|
207
|
+
this.logger.debug(`freeing memory @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
|
|
208
|
+
this.exports._wasm_free(addr, numBytes);
|
|
197
209
|
}
|
|
198
210
|
getI8(addr) {
|
|
199
211
|
return this.i8[addr];
|
|
@@ -343,14 +355,38 @@ export class WasmBridge {
|
|
|
343
355
|
this.f64.set(buf, addr >> 3);
|
|
344
356
|
return this;
|
|
345
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Reads UTF-8 encoded string from given address and optional byte length.
|
|
360
|
+
* The default length is 0, which will be interpreted as a zero-terminated
|
|
361
|
+
* string. Returns string.
|
|
362
|
+
*
|
|
363
|
+
* @param addr
|
|
364
|
+
* @param len
|
|
365
|
+
*/
|
|
346
366
|
getString(addr, len = 0) {
|
|
367
|
+
this.ensureMemory();
|
|
347
368
|
return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr)));
|
|
348
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Encodes given string as UTF-8 and writes it to WASM memory starting at
|
|
372
|
+
* `addr`. By default the string will be zero-terminated and only `maxBytes`
|
|
373
|
+
* will be written. Returns the number of bytes written.
|
|
374
|
+
*
|
|
375
|
+
* @remarks
|
|
376
|
+
* An error will be thrown if the encoded string doesn't fully fit into the
|
|
377
|
+
* designated memory region (also note that there might need to be space for
|
|
378
|
+
* the additional sentinel/termination byte).
|
|
379
|
+
*
|
|
380
|
+
* @param str
|
|
381
|
+
* @param addr
|
|
382
|
+
* @param maxBytes
|
|
383
|
+
* @param terminate
|
|
384
|
+
*/
|
|
349
385
|
setString(str, addr, maxBytes, terminate = true) {
|
|
350
386
|
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
351
387
|
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
|
|
352
388
|
if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
|
|
353
|
-
illegalArgs(`error writing string to 0x${U32(addr)}`);
|
|
389
|
+
illegalArgs(`error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`);
|
|
354
390
|
}
|
|
355
391
|
if (terminate) {
|
|
356
392
|
this.u8[addr + len] = 0;
|
package/cli.d.ts
ADDED
package/cli.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { flag, oneOfMulti, parse, ParseError, string, strings, usage, } from "@thi.ng/args";
|
|
2
|
+
import { isArray, isPlainObject } from "@thi.ng/checks";
|
|
3
|
+
import { illegalArgs } from "@thi.ng/errors";
|
|
4
|
+
import { readJSON, writeText } from "@thi.ng/file-io";
|
|
5
|
+
import { ConsoleLogger } from "@thi.ng/logger";
|
|
6
|
+
import { resolve } from "path";
|
|
7
|
+
import { generateTypes } from "./codegen.js";
|
|
8
|
+
import { TYPESCRIPT } from "./codegen/typescript.js";
|
|
9
|
+
import { isWasmPrim, isWasmString } from "./codegen/utils.js";
|
|
10
|
+
import { ZIG } from "./codegen/zig.js";
|
|
11
|
+
const GENERATORS = { ts: TYPESCRIPT, zig: ZIG };
|
|
12
|
+
const argOpts = {
|
|
13
|
+
config: string({
|
|
14
|
+
alias: "c",
|
|
15
|
+
hint: "FILE",
|
|
16
|
+
desc: "JSON config file with codegen options",
|
|
17
|
+
}),
|
|
18
|
+
debug: flag({ alias: "d", default: false, desc: "enable debug output" }),
|
|
19
|
+
dryRun: flag({
|
|
20
|
+
default: false,
|
|
21
|
+
desc: "enable dry run (don't overwrite files)",
|
|
22
|
+
}),
|
|
23
|
+
lang: oneOfMulti(Object.keys(GENERATORS), {
|
|
24
|
+
alias: "l",
|
|
25
|
+
desc: "target language",
|
|
26
|
+
default: ["ts", "zig"],
|
|
27
|
+
delim: ",",
|
|
28
|
+
}),
|
|
29
|
+
out: strings({ alias: "o", hint: "FILE", desc: "output file path" }),
|
|
30
|
+
};
|
|
31
|
+
export const INSTALL_DIR = resolve(`${process.argv[2]}/..`);
|
|
32
|
+
export const PKG = readJSON(`${INSTALL_DIR}/package.json`);
|
|
33
|
+
export const APP_NAME = PKG.name.split("/")[1];
|
|
34
|
+
export const HEADER = `
|
|
35
|
+
█ █ █ │
|
|
36
|
+
██ █ │
|
|
37
|
+
█ █ █ █ █ █ █ █ │ ${PKG.name} ${PKG.version}
|
|
38
|
+
█ █ █ █ █ █ █ █ █ │ Multi-language data bindings code generator
|
|
39
|
+
█ │
|
|
40
|
+
█ █ │
|
|
41
|
+
`;
|
|
42
|
+
const usageOpts = {
|
|
43
|
+
lineWidth: process.stdout.columns,
|
|
44
|
+
prefix: `${HEADER}
|
|
45
|
+
usage: ${APP_NAME} [OPTS] JSON-INPUT-FILE(S) ...
|
|
46
|
+
${APP_NAME} --help
|
|
47
|
+
|
|
48
|
+
`,
|
|
49
|
+
showGroupNames: true,
|
|
50
|
+
paramWidth: 32,
|
|
51
|
+
};
|
|
52
|
+
const showUsage = () => {
|
|
53
|
+
process.stderr.write(usage(argOpts, usageOpts));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
};
|
|
56
|
+
const invalidSpec = (path, msg) => {
|
|
57
|
+
throw new Error(`invalid typedef: ${path}${msg ? ` (${msg})` : ""}`);
|
|
58
|
+
};
|
|
59
|
+
const addTypeSpec = (ctx, path, coll, spec) => {
|
|
60
|
+
if (!(spec.name && spec.type))
|
|
61
|
+
invalidSpec(path);
|
|
62
|
+
if (!(spec.type === "enum" || spec.type === "struct"))
|
|
63
|
+
invalidSpec(path, `${spec.name} type: ${spec.type}`);
|
|
64
|
+
if (coll[spec.name])
|
|
65
|
+
invalidSpec(path, `duplicate name: ${spec.name}`);
|
|
66
|
+
ctx.logger.debug(`registering ${spec.type}: ${spec.name}`);
|
|
67
|
+
coll[spec.name] = spec;
|
|
68
|
+
spec.__path = path;
|
|
69
|
+
};
|
|
70
|
+
const validateTypeRefs = (coll) => {
|
|
71
|
+
for (let spec of Object.values(coll)) {
|
|
72
|
+
if (spec.type !== "struct")
|
|
73
|
+
continue;
|
|
74
|
+
for (let f of spec.fields) {
|
|
75
|
+
if (!(isWasmPrim(f.type) || isWasmString(f.type) || coll[f.type])) {
|
|
76
|
+
invalidSpec(spec.__path, `structfield ${spec.name}.${f.name} of unknown type: ${f.type}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const parseTypeSpecs = (ctx, inputs) => {
|
|
82
|
+
const coll = {};
|
|
83
|
+
for (let path of inputs) {
|
|
84
|
+
try {
|
|
85
|
+
const spec = readJSON(resolve(path), ctx.logger);
|
|
86
|
+
if (isArray(spec)) {
|
|
87
|
+
for (let s of spec)
|
|
88
|
+
addTypeSpec(ctx, path, coll, s);
|
|
89
|
+
}
|
|
90
|
+
else if (isPlainObject(spec)) {
|
|
91
|
+
addTypeSpec(ctx, path, coll, spec);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
invalidSpec(path);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
process.stderr.write(e.message);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
validateTypeRefs(coll);
|
|
103
|
+
return coll;
|
|
104
|
+
};
|
|
105
|
+
const generateOutputs = ({ config, logger, opts }, coll) => {
|
|
106
|
+
for (let i = 0; i < opts.lang.length; i++) {
|
|
107
|
+
const lang = opts.lang[i];
|
|
108
|
+
logger.debug(`generating ${lang.toUpperCase()} output...`);
|
|
109
|
+
const src = generateTypes(coll, GENERATORS[lang](config[lang]), config.global);
|
|
110
|
+
if (opts.out) {
|
|
111
|
+
writeText(resolve(opts.out[i]), src, logger, opts.dryRun);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
process.stdout.write(src + "\n");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
const result = parse(argOpts, process.argv, { start: 3, usageOpts });
|
|
120
|
+
if (!result)
|
|
121
|
+
process.exit(1);
|
|
122
|
+
const { result: opts, rest } = result;
|
|
123
|
+
if (!rest.length)
|
|
124
|
+
showUsage();
|
|
125
|
+
if (opts.out && opts.lang.length != opts.out.length) {
|
|
126
|
+
illegalArgs(`expected ${opts.lang.length} outputs, but got ${opts.out.length}`);
|
|
127
|
+
}
|
|
128
|
+
const ctx = {
|
|
129
|
+
logger: new ConsoleLogger("wasm-api", opts.debug ? "DEBUG" : "INFO"),
|
|
130
|
+
config: {},
|
|
131
|
+
opts,
|
|
132
|
+
};
|
|
133
|
+
if (opts.config) {
|
|
134
|
+
ctx.config = readJSON(resolve(opts.config), ctx.logger);
|
|
135
|
+
}
|
|
136
|
+
generateOutputs(ctx, parseTypeSpecs(ctx, rest));
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
if (!(e instanceof ParseError))
|
|
140
|
+
process.stderr.write(e.message);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ICodeGen } from "../api.js";
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript code generator options.
|
|
4
|
+
*/
|
|
5
|
+
export interface TSOpts {
|
|
6
|
+
/**
|
|
7
|
+
* Indentation string
|
|
8
|
+
*
|
|
9
|
+
* @defaultValue "\t"
|
|
10
|
+
*/
|
|
11
|
+
indent: string;
|
|
12
|
+
/**
|
|
13
|
+
* If true (default), forces uppercase enums
|
|
14
|
+
*
|
|
15
|
+
* @defaultValue true
|
|
16
|
+
*/
|
|
17
|
+
uppercaseEnums: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Same as {@link CodeGenOpts.stringType}.
|
|
20
|
+
*/
|
|
21
|
+
stringType: "slice" | "ptr";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* TypeScript code generator. Call with options and then pass to
|
|
25
|
+
* {@link generateTypes} (see its docs for further usage).
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* This codegen generates interface and enum definitions for a {@link TypeColl}
|
|
29
|
+
* given to {@link generateTypes}. For structs it will also generate memory
|
|
30
|
+
* mapped wrappers with fully typed accessors.
|
|
31
|
+
*
|
|
32
|
+
* @param opts
|
|
33
|
+
*/
|
|
34
|
+
export declare const TYPESCRIPT: (opts?: Partial<TSOpts>) => ICodeGen;
|
|
35
|
+
//# sourceMappingURL=typescript.d.ts.map
|