@thi.ng/wasm-api 0.6.0 → 0.7.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 +15 -1
- package/README.md +250 -16
- package/api.d.ts +57 -9
- package/bin/wasm-api +12 -0
- package/cli.d.ts +5 -0
- package/cli.js +142 -0
- package/codegen/typescript.d.ts +4 -0
- package/codegen/typescript.js +15 -8
- package/codegen/utils.d.ts +27 -3
- package/codegen/utils.js +23 -1
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2022-08-
|
|
3
|
+
- **Last updated**: 2022-08-15T23:41:37Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,20 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [0.7.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.7.0) (2022-08-15)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add CLI wrapper for codegens ([683a560](https://github.com/thi-ng/umbrella/commit/683a560))
|
|
17
|
+
- update TSOpts & TS codegen ([4f6bbbf](https://github.com/thi-ng/umbrella/commit/4f6bbbf))
|
|
18
|
+
- add `uppercaseEnum` option to force UC enum IDs
|
|
19
|
+
- update helper predicates ([65b23d4](https://github.com/thi-ng/umbrella/commit/65b23d4))
|
|
20
|
+
|
|
21
|
+
#### 🩹 Bug fixes
|
|
22
|
+
|
|
23
|
+
- allow signed ints for enum tags ([78d0822](https://github.com/thi-ng/umbrella/commit/78d0822))
|
|
24
|
+
- correct TS __mapArray codegen ([289b137](https://github.com/thi-ng/umbrella/commit/289b137))
|
|
25
|
+
|
|
12
26
|
## [0.6.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.6.0) (2022-08-15)
|
|
13
27
|
|
|
14
28
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ This project is part of the
|
|
|
13
13
|
- [Custom API modules](#custom-api-modules)
|
|
14
14
|
- [Object indices & handles](#object-indices--handles)
|
|
15
15
|
- [Data bindings & code generators](#data-bindings--code-generators)
|
|
16
|
+
- [CLI generator](#cli-generator)
|
|
16
17
|
- [Data type definitions](#data-type-definitions)
|
|
17
|
-
- [Code generation](#code-generation)
|
|
18
18
|
- [Example usage](#example-usage)
|
|
19
19
|
- [Status](#status)
|
|
20
20
|
- [Installation](#installation)
|
|
@@ -37,22 +37,26 @@ This package provides a the following:
|
|
|
37
37
|
class as interop basis and much reduced boilerplate for hybrid JS/WebAssembly
|
|
38
38
|
applications.
|
|
39
39
|
2. A minimal core API for debug output, string, pointer, typed array accessors
|
|
40
|
-
|
|
41
|
-
support modules for DOM manipulation, WebGL, WebGPU, WebAudio etc.
|
|
42
|
-
3.
|
|
40
|
+
for 8/16/32/64 bit (u)ints and 32/64 bit floats. In the future we aim to also
|
|
41
|
+
supply support modules for DOM manipulation, WebGL, WebGPU, WebAudio etc.
|
|
42
|
+
3. [Include files for C11/C++ and
|
|
43
|
+
Zig](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api/include),
|
|
44
|
+
defining WASM imports of the JS [core
|
|
45
|
+
API](https://docs.thi.ng/umbrella/wasm-api/interfaces/CoreAPI.html) defined
|
|
46
|
+
by this package
|
|
47
|
+
4. Extensible shared datatype code generators for (currently)
|
|
43
48
|
[Zig](https://ziglang.org) & TypeScript. The latter also generates fully type
|
|
44
49
|
checked memory-mapped accessors of WASM-side data. In general, all languages
|
|
45
50
|
with a WebAssembly target are supported, however currently only bindings for
|
|
46
51
|
these few langs are included.
|
|
47
|
-
|
|
48
|
-
by this package
|
|
52
|
+
5. [CLI frontend/utility](#cli-generator) to invoke the code generator(s)
|
|
49
53
|
|
|
50
54
|
### Custom API modules
|
|
51
55
|
|
|
52
|
-
The WasmBridge is extensible via custom defined API modules. Such API extensions
|
|
56
|
+
The [`WasmBridge`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html) is extensible via custom defined API modules. Such API extensions
|
|
53
57
|
will consist of a collection of JS/TS functions & variables, their related
|
|
54
58
|
counterparts (import definitions) for the WASM target and (optionally) some
|
|
55
|
-
shared data types (bindings for which _can_ be generated by this package too).
|
|
59
|
+
shared data types ([bindings for which _can_ be generated by this package too](#data-bindings--code-generators)).
|
|
56
60
|
|
|
57
61
|
On the JS side, custom API modules can be easily integrated via the [`IWasmAPI`
|
|
58
62
|
interface](https://docs.thi.ng/umbrella/wasm-api/interfaces/IWasmAPI.html). The
|
|
@@ -181,21 +185,249 @@ bilateral design & exchange of data structures shared between the WASM & JS host
|
|
|
181
185
|
env. Currently, code generators for TypeScript & Zig are supplied (more are
|
|
182
186
|
planned). A CLI wrapper is worked on too.
|
|
183
187
|
|
|
184
|
-
####
|
|
188
|
+
#### CLI generator
|
|
189
|
+
|
|
190
|
+
The package includes a [small CLI
|
|
191
|
+
wrapper](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/src/cli.ts)
|
|
192
|
+
to invoke the codegenerator(s) from JSON type definitions and to write the
|
|
193
|
+
generated source code(s) to different files:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
$ npx run @thi.ng/wasm-api
|
|
197
|
+
|
|
198
|
+
█ █ █ │
|
|
199
|
+
██ █ │
|
|
200
|
+
█ █ █ █ █ █ █ █ │ @thi.ng/wasm-api 0.6.0
|
|
201
|
+
█ █ █ █ █ █ █ █ █ │ Multi-language data bindings code generator
|
|
202
|
+
█ │
|
|
203
|
+
█ █ │
|
|
204
|
+
|
|
205
|
+
usage: wasm-api [OPTS] JSON-INPUT-FILE(S) ...
|
|
206
|
+
wasm-api --help
|
|
207
|
+
|
|
208
|
+
Flags:
|
|
209
|
+
|
|
210
|
+
-d, --debug enable debug output
|
|
211
|
+
--dry-run enable dry run (don't overwrite files)
|
|
212
|
+
|
|
213
|
+
Main:
|
|
214
|
+
|
|
215
|
+
-c FILE, --config FILE JSON config file with codegen options
|
|
216
|
+
-l ID[,..], --lang ID[,..] [multiple] target language: "ts", "zig" (default: ["ts","zig"])
|
|
217
|
+
-o FILE, --out FILE [multiple] output file path
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
By default, the CLI generates sources for both TypeScript and Zig (in this
|
|
221
|
+
order!). Order is important, since the output file paths must be given in the
|
|
222
|
+
same order as the target languages. It's recommended to be more explicit. An
|
|
223
|
+
example invocation looks like:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
wasm-api --config codegen-opts.json \
|
|
227
|
+
--lang ts -o src/generated.ts \
|
|
228
|
+
--lang zig -o src.zig/generated.zig \
|
|
229
|
+
typedefs.json
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The structure of the config file is as follows (all optional):
|
|
185
233
|
|
|
186
|
-
|
|
234
|
+
```text
|
|
235
|
+
{
|
|
236
|
+
global: { ... },
|
|
237
|
+
ts: { ... },
|
|
238
|
+
zig: { ... },
|
|
239
|
+
}
|
|
240
|
+
```
|
|
187
241
|
|
|
188
|
-
|
|
242
|
+
More details about possible
|
|
243
|
+
[`global`](https://docs.thi.ng/umbrella/wasm-api/interfaces/CodeGenOpts.html),
|
|
244
|
+
[`ts`](https://docs.thi.ng/umbrella/wasm-api/interfaces/TSOpts.html) and
|
|
245
|
+
[`zig`](https://docs.thi.ng/umbrella/wasm-api/interfaces/ZigOpts.html) config
|
|
246
|
+
options & values.
|
|
189
247
|
|
|
190
|
-
|
|
248
|
+
#### Data type definitions
|
|
191
249
|
|
|
192
|
-
|
|
250
|
+
Currently, the code generator supports structs and enums. See API docs for
|
|
251
|
+
further details:
|
|
193
252
|
|
|
194
|
-
|
|
253
|
+
- [`Enum`](https://docs.thi.ng/umbrella/wasm-api/interfaces/Enum.html)
|
|
254
|
+
- [`EnumValue`](https://docs.thi.ng/umbrella/wasm-api/interfaces/EnumValue.html)
|
|
255
|
+
- [`Struct`](https://docs.thi.ng/umbrella/wasm-api/interfaces/Struct.html)
|
|
256
|
+
- [`StructField`](https://docs.thi.ng/umbrella/wasm-api/interfaces/StructField.html)
|
|
257
|
+
- [`TopLevelType`](https://docs.thi.ng/umbrella/wasm-api/interfaces/TopLevelType.html)
|
|
195
258
|
|
|
196
259
|
#### Example usage
|
|
197
260
|
|
|
198
|
-
|
|
261
|
+
Below is an example file with JSON type definitions and the resulting source
|
|
262
|
+
codes:
|
|
263
|
+
|
|
264
|
+
<details><summary>types.json (Type definitions, click to expand)</summary>
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
[
|
|
268
|
+
{
|
|
269
|
+
"name": "Foo",
|
|
270
|
+
"type": "struct",
|
|
271
|
+
"doc": "Example struct",
|
|
272
|
+
"auto": true,
|
|
273
|
+
"fields": [
|
|
274
|
+
{ "name": "id", "type": "u8", "doc": "Unique ID" },
|
|
275
|
+
{ "name": "bars", "type": "Bar", "tag": "array", "len": 3 },
|
|
276
|
+
{ "name": "color", "type": "f32", "tag": "vec", "len": 4 }
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"name": "Bar",
|
|
281
|
+
"type": "struct",
|
|
282
|
+
"fields": [
|
|
283
|
+
{ "name": "kind", "type": "Kind" },
|
|
284
|
+
{ "name": "flags", "type": "u32" }
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"name": "Kind",
|
|
289
|
+
"type": "enum",
|
|
290
|
+
"tag": "u16",
|
|
291
|
+
"values": [
|
|
292
|
+
"unknown",
|
|
293
|
+
{ "name": "good", "value": 100 },
|
|
294
|
+
{ "name": "best", "value": 1000 }
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
```
|
|
299
|
+
</details>
|
|
300
|
+
|
|
301
|
+
<details><summary>generated.ts (generated TypeScript source, click to expand)</summary>
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
/** Generated by @thi.ng/wasm-api at 2022-08-15T22:32:21.189Z - DO NOT EDIT! */
|
|
305
|
+
|
|
306
|
+
import type { WasmTypeBase, WasmTypeConstructor } from "@thi.ng/wasm-api";
|
|
307
|
+
|
|
308
|
+
/** Example struct */
|
|
309
|
+
export interface Foo extends WasmTypeBase {
|
|
310
|
+
color: Float32Array;
|
|
311
|
+
bars: Bar[];
|
|
312
|
+
/** Unique ID */
|
|
313
|
+
id: number;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export const $Foo: WasmTypeConstructor<Foo> = (mem) => ({
|
|
317
|
+
get align() { return 16; },
|
|
318
|
+
get size() { return 48; },
|
|
319
|
+
instance: (base) => ({
|
|
320
|
+
get __base() { return base; },
|
|
321
|
+
get __bytes() { return mem.u8.subarray(base, base + 48); },
|
|
322
|
+
get color(): Float32Array {
|
|
323
|
+
const addr = base >>> 2;
|
|
324
|
+
return mem.f32.subarray(addr, addr + 4);
|
|
325
|
+
},
|
|
326
|
+
get bars(): Bar[] {
|
|
327
|
+
const addr = (base + 16);
|
|
328
|
+
const inst = $Bar(mem);
|
|
329
|
+
const slice: Bar[] = [];
|
|
330
|
+
for(let i = 0; i < 3; i++) slice.push(inst.instance(addr + i * 24));
|
|
331
|
+
return slice;
|
|
332
|
+
},
|
|
333
|
+
get id(): number {
|
|
334
|
+
return mem.u8[(base + 40)];
|
|
335
|
+
},
|
|
336
|
+
set id(x: number) {
|
|
337
|
+
mem.u8[(base + 40)] = x;
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
export interface Bar extends WasmTypeBase {
|
|
343
|
+
kind: Kind;
|
|
344
|
+
flags: number;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export const $Bar: WasmTypeConstructor<Bar> = (mem) => ({
|
|
348
|
+
get align() { return 4; },
|
|
349
|
+
get size() { return 8; },
|
|
350
|
+
instance: (base) => ({
|
|
351
|
+
get __base() { return base; },
|
|
352
|
+
get __bytes() { return mem.u8.subarray(base, base + 8); },
|
|
353
|
+
get kind(): Kind {
|
|
354
|
+
return mem.u16[base >>> 1];
|
|
355
|
+
},
|
|
356
|
+
set kind(x: Kind) {
|
|
357
|
+
mem.u16[base >>> 1] = x;
|
|
358
|
+
},
|
|
359
|
+
get flags(): number {
|
|
360
|
+
return mem.u32[(base + 4) >>> 2];
|
|
361
|
+
},
|
|
362
|
+
set flags(x: number) {
|
|
363
|
+
mem.u32[(base + 4) >>> 2] = x;
|
|
364
|
+
},
|
|
365
|
+
})
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
export enum Kind {
|
|
369
|
+
UNKNOWN,
|
|
370
|
+
GOOD = 100,
|
|
371
|
+
BEST = 1000,
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
</details>
|
|
375
|
+
|
|
376
|
+
<details><summary>generated.zig (generated Zig source, click to expand)</summary>
|
|
377
|
+
|
|
378
|
+
```zig
|
|
379
|
+
//! Generated by @thi.ng/wasm-api at 2022-08-15T22:32:21.191Z - DO NOT EDIT!
|
|
380
|
+
|
|
381
|
+
/// Example struct
|
|
382
|
+
pub const Foo = struct {
|
|
383
|
+
color: @Vector(4, f32),
|
|
384
|
+
bars: [3]Bar,
|
|
385
|
+
/// Unique ID
|
|
386
|
+
id: u8,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
pub const Bar = struct {
|
|
390
|
+
kind: Kind,
|
|
391
|
+
flags: u32,
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
pub const Kind = enum(u16) {
|
|
395
|
+
unknown,
|
|
396
|
+
good = 100,
|
|
397
|
+
best = 1000,
|
|
398
|
+
};
|
|
399
|
+
```
|
|
400
|
+
</details>
|
|
401
|
+
|
|
402
|
+
On the TypeScript/JS side, the memory-mapped wrappers (e.g. `$Foo` and `$Bar`)
|
|
403
|
+
can be used in combination with the `WasmBridge` to obtain fully typed views
|
|
404
|
+
(according to the generated types) of the underlying WASM memory. Basic usage is
|
|
405
|
+
like:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
import { WasmBridge } from "@thi.ng/wasm-api";
|
|
409
|
+
import { $Foo, Kind } from "./generated.ts";
|
|
410
|
+
|
|
411
|
+
const bridge = new WasmBridge();
|
|
412
|
+
// bridge initialization omitted here (see other examples below)
|
|
413
|
+
// ...
|
|
414
|
+
|
|
415
|
+
// Create an instance using the bridge's memory views
|
|
416
|
+
// and mapping a `Foo` struct from given address
|
|
417
|
+
// (e.g. obtained from an exported WASM function/value)
|
|
418
|
+
const foo = $Foo(bridge).instance(0x10000);
|
|
419
|
+
|
|
420
|
+
// then use like normal JS object
|
|
421
|
+
foo.color
|
|
422
|
+
// Float32Array(4) [0.1, 0.2, 0.3, 0.4]
|
|
423
|
+
|
|
424
|
+
// this even applies to arrays using other types
|
|
425
|
+
// (setters are currently only supported for scalar values, incl. enums)
|
|
426
|
+
foo.bars[2].kind = Kind.BEST;
|
|
427
|
+
|
|
428
|
+
// IMPORTANT: any modifications like this are directly
|
|
429
|
+
// applied to the underlying WASM memory...
|
|
430
|
+
```
|
|
199
431
|
|
|
200
432
|
### Status
|
|
201
433
|
|
|
@@ -226,7 +458,7 @@ node --experimental-repl-await
|
|
|
226
458
|
> const wasmApi = await import("@thi.ng/wasm-api");
|
|
227
459
|
```
|
|
228
460
|
|
|
229
|
-
Package sizes (gzipped, pre-treeshake): ESM:
|
|
461
|
+
Package sizes (gzipped, pre-treeshake): ESM: 4.03 KB
|
|
230
462
|
|
|
231
463
|
**IMPORTANT:** The package includes various code generators and supporting
|
|
232
464
|
functions which are NOT required during runtime. Hence the actual package size
|
|
@@ -235,11 +467,13 @@ in production will be MUCH smaller!
|
|
|
235
467
|
## Dependencies
|
|
236
468
|
|
|
237
469
|
- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
|
|
470
|
+
- [@thi.ng/args](https://github.com/thi-ng/umbrella/tree/develop/packages/args)
|
|
238
471
|
- [@thi.ng/binary](https://github.com/thi-ng/umbrella/tree/develop/packages/binary)
|
|
239
472
|
- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
|
|
240
473
|
- [@thi.ng/compare](https://github.com/thi-ng/umbrella/tree/develop/packages/compare)
|
|
241
474
|
- [@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti)
|
|
242
475
|
- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
|
|
476
|
+
- [@thi.ng/file-io](https://github.com/thi-ng/umbrella/tree/develop/packages/file-io)
|
|
243
477
|
- [@thi.ng/hex](https://github.com/thi-ng/umbrella/tree/develop/packages/hex)
|
|
244
478
|
- [@thi.ng/idgen](https://github.com/thi-ng/umbrella/tree/develop/packages/idgen)
|
|
245
479
|
- [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
|
package/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FloatType, 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
3
|
export declare const PKG_NAME = "@thi.ng/wasm-api";
|
|
4
4
|
export declare type BigIntArray = bigint[] | BigInt64Array | BigUint64Array;
|
|
@@ -131,6 +131,7 @@ export declare type WasmInt = "i8" | "i16" | "i32" | "i64";
|
|
|
131
131
|
export declare type WasmUint = "u8" | "u16" | "u32" | "u64";
|
|
132
132
|
export declare type WasmFloat = FloatType;
|
|
133
133
|
export declare type WasmPrim = WasmInt | WasmUint | WasmFloat;
|
|
134
|
+
export declare type WasmPrim32 = Exclude<WasmPrim, BigType>;
|
|
134
135
|
export declare type TypeColl = Record<string, TopLevelType>;
|
|
135
136
|
export interface TypeInfo {
|
|
136
137
|
/**
|
|
@@ -153,16 +154,30 @@ export interface TypeInfo {
|
|
|
153
154
|
__align?: number;
|
|
154
155
|
}
|
|
155
156
|
export interface TopLevelType extends TypeInfo {
|
|
157
|
+
/**
|
|
158
|
+
* Type name
|
|
159
|
+
*/
|
|
156
160
|
name: string;
|
|
161
|
+
/**
|
|
162
|
+
* Optional (multi-line) docstring for this type
|
|
163
|
+
*/
|
|
157
164
|
doc?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Type / kind
|
|
167
|
+
*/
|
|
158
168
|
type: "struct" | "enum";
|
|
159
169
|
}
|
|
160
170
|
export interface Struct extends TopLevelType {
|
|
161
171
|
type: "struct";
|
|
172
|
+
/**
|
|
173
|
+
* List of struct fields (might be re-ordered if {@link Struct.auto} is
|
|
174
|
+
* enabled).
|
|
175
|
+
*/
|
|
162
176
|
fields: StructField[];
|
|
163
177
|
/**
|
|
164
178
|
* If true, struct fields will be re-ordered in descending order based on
|
|
165
|
-
* their {@link TypeInfo.__align} size.
|
|
179
|
+
* their {@link TypeInfo.__align} size. This might result in overall smaller
|
|
180
|
+
* structs due to minimizing inter-field padding.
|
|
166
181
|
*
|
|
167
182
|
* @defaultValue false
|
|
168
183
|
*/
|
|
@@ -178,8 +193,18 @@ export interface StructField extends TypeInfo {
|
|
|
178
193
|
*/
|
|
179
194
|
doc?: string;
|
|
180
195
|
/**
|
|
181
|
-
* Field type tag/qualifier (note: `slice` & `vec` are only supported by
|
|
182
|
-
*
|
|
196
|
+
* Field type tag/qualifier (note: `slice` & `vec` are only supported by Zig
|
|
197
|
+
* & TS).
|
|
198
|
+
*
|
|
199
|
+
* @remarks
|
|
200
|
+
* - Array & vector fields are statically sized (using
|
|
201
|
+
* {@link StructField.len})
|
|
202
|
+
* - Pointers are emitted as single-value pointers (where this distinction
|
|
203
|
+
* exist), i.e. even if they're pointing to multiple values, there's no
|
|
204
|
+
* explicit length encoded/available
|
|
205
|
+
* - Zig slices are essentially a pointer w/ associated length
|
|
206
|
+
* - Zig vectors will be processed using SIMD (if enabled in WASM target)
|
|
207
|
+
* and therefore will have stricter (larger) alignment requirements.
|
|
183
208
|
*
|
|
184
209
|
* @defaultValue "scalar"
|
|
185
210
|
*/
|
|
@@ -189,32 +214,46 @@ export interface StructField extends TypeInfo {
|
|
|
189
214
|
* interpreted as another type name in the {@link TypeColl}.
|
|
190
215
|
*
|
|
191
216
|
* TODO `opaque` currently unsupported.
|
|
217
|
+
* TODO add string support (see {@link StructField.sentinel})
|
|
192
218
|
*/
|
|
193
219
|
type: WasmPrim | "opaque" | string;
|
|
194
220
|
/**
|
|
195
|
-
* TODO currently unsupported!
|
|
221
|
+
* TODO currently unsupported & ignored!
|
|
196
222
|
*/
|
|
197
223
|
sentinel?: number;
|
|
198
224
|
/**
|
|
199
|
-
* Array or vector length
|
|
225
|
+
* Array or vector length (see {@link StructField.tag})
|
|
200
226
|
*/
|
|
201
227
|
len?: number;
|
|
202
228
|
/**
|
|
203
|
-
* TODO currently unsupported!
|
|
229
|
+
* TODO currently unsupported & ignored!
|
|
204
230
|
*/
|
|
205
231
|
default?: any;
|
|
206
232
|
}
|
|
207
233
|
export interface Enum extends TopLevelType {
|
|
208
234
|
type: "enum";
|
|
209
235
|
/**
|
|
210
|
-
* No u64 support, due to Typescript not supporting bigint enum values
|
|
236
|
+
* No i64/u64 support, due to Typescript not supporting bigint enum values
|
|
237
|
+
*/
|
|
238
|
+
tag: Exclude<WasmPrim32, FloatType>;
|
|
239
|
+
/**
|
|
240
|
+
* List of possible values/IDs. Use {@link EnumValue}s for more detailed
|
|
241
|
+
* config.
|
|
211
242
|
*/
|
|
212
|
-
tag: Exclude<WasmUint, "u64">;
|
|
213
243
|
values: (string | EnumValue)[];
|
|
214
244
|
}
|
|
215
245
|
export interface EnumValue {
|
|
246
|
+
/**
|
|
247
|
+
* Enum value name/ID
|
|
248
|
+
*/
|
|
216
249
|
name: string;
|
|
250
|
+
/**
|
|
251
|
+
* Optional associated numeric value
|
|
252
|
+
*/
|
|
217
253
|
value?: number;
|
|
254
|
+
/**
|
|
255
|
+
* Optional docstring for this value
|
|
256
|
+
*/
|
|
218
257
|
doc?: string;
|
|
219
258
|
}
|
|
220
259
|
export interface ICodeGen {
|
|
@@ -226,8 +265,17 @@ export interface ICodeGen {
|
|
|
226
265
|
* Optional source code to be appended after any generated type defs.
|
|
227
266
|
*/
|
|
228
267
|
post?: string;
|
|
268
|
+
/**
|
|
269
|
+
* Docstring codegen
|
|
270
|
+
*/
|
|
229
271
|
doc: (doc: string, indent: string, acc: string[], topLevel?: boolean) => void;
|
|
272
|
+
/**
|
|
273
|
+
* Codegen for enum types.
|
|
274
|
+
*/
|
|
230
275
|
enum: (type: Enum, types: TypeColl, acc: string[]) => void;
|
|
276
|
+
/**
|
|
277
|
+
* Codegen for struct types.
|
|
278
|
+
*/
|
|
231
279
|
struct: (type: Struct, types: TypeColl, acc: string[]) => void;
|
|
232
280
|
}
|
|
233
281
|
/**
|
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/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 { isPrim } 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 (!(isPrim(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
|
+
}
|
package/codegen/typescript.d.ts
CHANGED
package/codegen/typescript.js
CHANGED
|
@@ -14,7 +14,11 @@ import { isBigNumeric, isNumeric, isPrim, prefixLines } from "./utils.js";
|
|
|
14
14
|
* @param opts
|
|
15
15
|
*/
|
|
16
16
|
export const TYPESCRIPT = (opts) => {
|
|
17
|
-
const { indent } = {
|
|
17
|
+
const { indent, uppercaseEnums } = {
|
|
18
|
+
indent: "\t",
|
|
19
|
+
uppercaseEnums: true,
|
|
20
|
+
...opts,
|
|
21
|
+
};
|
|
18
22
|
const I = indent;
|
|
19
23
|
const I2 = I + I;
|
|
20
24
|
const I3 = I2 + I;
|
|
@@ -35,12 +39,12 @@ export const TYPESCRIPT = (opts) => {
|
|
|
35
39
|
var line = indent;
|
|
36
40
|
if (!isString(v)) {
|
|
37
41
|
v.doc && gen.doc(v.doc, indent, acc);
|
|
38
|
-
line += v.name;
|
|
42
|
+
line += uppercaseEnums ? v.name.toUpperCase() : v.name;
|
|
39
43
|
if (v.value != null)
|
|
40
44
|
line += ` = ${v.value}`;
|
|
41
45
|
}
|
|
42
46
|
else {
|
|
43
|
-
line += v;
|
|
47
|
+
line += uppercaseEnums ? v.toUpperCase() : v;
|
|
44
48
|
}
|
|
45
49
|
acc.push(line + ",");
|
|
46
50
|
}
|
|
@@ -89,13 +93,13 @@ export const TYPESCRIPT = (opts) => {
|
|
|
89
93
|
acc.push(`${I3}const len = ${__ptr(offset + 4)};`, prim
|
|
90
94
|
? `${I3}const addr = ${__ptrShift(offset, f.type)};
|
|
91
95
|
${I3}return mem.${f.type}.subarray(addr, addr + len);`
|
|
92
|
-
: `${I3}const addr = ${__ptr(offset)};\n${__mapArray(
|
|
96
|
+
: `${I3}const addr = ${__ptr(offset)};\n${__mapArray(f, I3)}`);
|
|
93
97
|
}
|
|
94
98
|
else if (f.tag === "array" || f.tag === "vec") {
|
|
95
99
|
acc.push(prim
|
|
96
100
|
? `${I3}const addr = ${__addrShift(offset, f.type)};
|
|
97
101
|
${I3}return mem.${f.type}.subarray(addr, addr + ${f.len});`
|
|
98
|
-
: `${I3}const addr = ${__addr(offset)};\n${__mapArray(
|
|
102
|
+
: `${I3}const addr = ${__addr(offset)};\n${__mapArray(f, I3, f.len)}`);
|
|
99
103
|
}
|
|
100
104
|
else {
|
|
101
105
|
let setter;
|
|
@@ -133,14 +137,17 @@ const __shift = (type) => BIT_SHIFTS[type];
|
|
|
133
137
|
/** @internal */
|
|
134
138
|
const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
|
|
135
139
|
/** @internal */
|
|
136
|
-
const __addrShift = (offset, shift) =>
|
|
140
|
+
const __addrShift = (offset, shift) => {
|
|
141
|
+
const bits = __shift(shift);
|
|
142
|
+
return __addr(offset) + (bits ? " >>> " + bits : "");
|
|
143
|
+
};
|
|
137
144
|
/** @internal */
|
|
138
145
|
const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
|
|
139
146
|
/** @internal */
|
|
140
147
|
const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
|
|
141
148
|
const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
|
|
142
149
|
/** @internal */
|
|
143
|
-
const __mapArray = (
|
|
150
|
+
const __mapArray = (f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
|
|
144
151
|
const slice: ${f.type}[] = [];
|
|
145
|
-
for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${
|
|
152
|
+
for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));
|
|
146
153
|
return slice;`);
|
package/codegen/utils.d.ts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { BigType } from "@thi.ng/api";
|
|
2
|
+
import type { WasmPrim, WasmPrim32 } from "../api.js";
|
|
3
|
+
/**
|
|
4
|
+
* Returns true iff `x` is a {@link WasmPrim32}.
|
|
5
|
+
*
|
|
6
|
+
* @param x
|
|
7
|
+
*/
|
|
8
|
+
export declare const isNumeric: (x: string) => x is WasmPrim32;
|
|
9
|
+
/**
|
|
10
|
+
* Returns true iff `x` is a `i64` or `u64`.
|
|
11
|
+
*
|
|
12
|
+
* @param x
|
|
13
|
+
*/
|
|
14
|
+
export declare const isBigNumeric: (x: string) => x is BigType;
|
|
15
|
+
/**
|
|
16
|
+
* Returns true iff `x` is a {@link WasmPrim}.
|
|
17
|
+
*
|
|
18
|
+
* @param x
|
|
19
|
+
*/
|
|
20
|
+
export declare const isPrim: (x: string) => x is WasmPrim;
|
|
21
|
+
/**
|
|
22
|
+
* Splits given string into lines, prefixes each with given `prefix` and then
|
|
23
|
+
* returns rejoined result.
|
|
24
|
+
*
|
|
25
|
+
* @param prefix
|
|
26
|
+
* @param str
|
|
27
|
+
*/
|
|
4
28
|
export declare const prefixLines: (prefix: string, str: string) => string;
|
|
5
29
|
//# sourceMappingURL=utils.d.ts.map
|
package/codegen/utils.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Returns true iff `x` is a {@link WasmPrim32}.
|
|
3
|
+
*
|
|
4
|
+
* @param x
|
|
5
|
+
*/
|
|
6
|
+
export const isNumeric = (x) => /^(([iu](8|16|32))|(f(32|64)))$/.test(x);
|
|
7
|
+
/**
|
|
8
|
+
* Returns true iff `x` is a `i64` or `u64`.
|
|
9
|
+
*
|
|
10
|
+
* @param x
|
|
11
|
+
*/
|
|
2
12
|
export const isBigNumeric = (x) => /^[iu]64$/.test(x);
|
|
13
|
+
/**
|
|
14
|
+
* Returns true iff `x` is a {@link WasmPrim}.
|
|
15
|
+
*
|
|
16
|
+
* @param x
|
|
17
|
+
*/
|
|
3
18
|
export const isPrim = (x) => isNumeric(x) || isBigNumeric(x);
|
|
19
|
+
/**
|
|
20
|
+
* Splits given string into lines, prefixes each with given `prefix` and then
|
|
21
|
+
* returns rejoined result.
|
|
22
|
+
*
|
|
23
|
+
* @param prefix
|
|
24
|
+
* @param str
|
|
25
|
+
*/
|
|
4
26
|
export const prefixLines = (prefix, str) => str
|
|
5
27
|
.split("\n")
|
|
6
28
|
.map((line) => prefix + line)
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/wasm-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Generic, modular, extensible API bridge, glue code and bindings code generator for hybrid JS & WebAssembly projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
7
7
|
"typings": "./index.d.ts",
|
|
8
|
+
"bin": "bin/wasm-api",
|
|
8
9
|
"sideEffects": false,
|
|
9
10
|
"repository": {
|
|
10
11
|
"type": "git",
|
|
@@ -36,11 +37,13 @@
|
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@thi.ng/api": "^8.4.0",
|
|
40
|
+
"@thi.ng/args": "^2.2.0",
|
|
39
41
|
"@thi.ng/binary": "^3.3.3",
|
|
40
42
|
"@thi.ng/checks": "^3.2.4",
|
|
41
43
|
"@thi.ng/compare": "^2.1.10",
|
|
42
44
|
"@thi.ng/defmulti": "^2.1.12",
|
|
43
45
|
"@thi.ng/errors": "^2.1.10",
|
|
46
|
+
"@thi.ng/file-io": "^0.3.9",
|
|
44
47
|
"@thi.ng/hex": "^2.1.9",
|
|
45
48
|
"@thi.ng/idgen": "^2.1.11",
|
|
46
49
|
"@thi.ng/logger": "^1.2.0"
|
|
@@ -80,8 +83,9 @@
|
|
|
80
83
|
"files": [
|
|
81
84
|
"*.js",
|
|
82
85
|
"*.d.ts",
|
|
83
|
-
"
|
|
84
|
-
"include"
|
|
86
|
+
"bin",
|
|
87
|
+
"include",
|
|
88
|
+
"codegen"
|
|
85
89
|
],
|
|
86
90
|
"exports": {
|
|
87
91
|
".": {
|
|
@@ -113,5 +117,5 @@
|
|
|
113
117
|
"status": "alpha",
|
|
114
118
|
"year": 2022
|
|
115
119
|
},
|
|
116
|
-
"gitHead": "
|
|
120
|
+
"gitHead": "78193cdd838d9d09114352b117b6c5e92631e3cd\n"
|
|
117
121
|
}
|