@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2022-08-15T15:40:55Z
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
- (8/16/32/64 bit (u)ints, 32/64 bit floats). In the future we aim to also supply
41
- support modules for DOM manipulation, WebGL, WebGPU, WebAudio etc.
42
- 3. Extensible shared datatype code generators for (currently)
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
- 4. Include files for C11 and Zig, defining imports for the JS core API defined
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
- #### Data type definitions
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
- TODO
234
+ ```text
235
+ {
236
+ global: { ... },
237
+ ts: { ... },
238
+ zig: { ... },
239
+ }
240
+ ```
187
241
 
188
- ##### Struct
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
- ##### Enum
248
+ #### Data type definitions
191
249
 
192
- #### Code generation
250
+ Currently, the code generator supports structs and enums. See API docs for
251
+ further details:
193
252
 
194
- TODO
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
- TODO
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: 3.98 KB
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
- * Zig & TS)
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
@@ -0,0 +1,5 @@
1
+ export declare const INSTALL_DIR: string;
2
+ export declare const PKG: any;
3
+ export declare const APP_NAME: any;
4
+ export declare const HEADER: string;
5
+ //# sourceMappingURL=cli.d.ts.map
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
+ }
@@ -6,6 +6,10 @@ export interface TSOpts {
6
6
  * @defaultValue "\t"
7
7
  */
8
8
  indent: string;
9
+ /**
10
+ * If true (default), forces uppercase enums
11
+ */
12
+ uppercaseEnums: boolean;
9
13
  }
10
14
  /**
11
15
  * TypeScript code generator. Call with options and then pass to
@@ -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 } = { indent: "\t", ...opts };
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(struct, f, I3)}`);
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(struct, f, I3, f.len)}`);
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) => __addr(offset) + " >>> " + __shift(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 = (struct, f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
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 * ${struct.__size}));
152
+ for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));
146
153
  return slice;`);
@@ -1,5 +1,29 @@
1
- export declare const isNumeric: (x: string) => boolean;
2
- export declare const isBigNumeric: (x: string) => boolean;
3
- export declare const isPrim: (x: string) => boolean;
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
- export const isNumeric = (x) => /^([iu](8|16|32))|(f(32|64))$/.test(x);
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.6.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
- "codegen",
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": "295e76c6f68ef34ba2117ff77612848e09f5c587\n"
120
+ "gitHead": "78193cdd838d9d09114352b117b6c5e92631e3cd\n"
117
121
  }