@thi.ng/wasm-api 0.3.1 → 0.6.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/bridge.d.ts CHANGED
@@ -1,6 +1,17 @@
1
+ /// <reference types="node" />
1
2
  import type { NumericArray } from "@thi.ng/api";
2
3
  import type { ILogger } from "@thi.ng/logger";
3
- import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports } from "./api.js";
4
+ import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports, WasmMemViews } from "./api.js";
5
+ export declare const OutOfMemoryError: {
6
+ new (msg?: string | undefined): {
7
+ name: string;
8
+ message: string;
9
+ stack?: string | undefined;
10
+ };
11
+ captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
12
+ prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
13
+ stackTraceLimit: number;
14
+ };
4
15
  /**
5
16
  * The main interop API bridge between the JS host environment and a WebAssembly
6
17
  * module. This class provides a small core API with various typed accessors and
@@ -17,7 +28,7 @@ import type { BigIntArray, CoreAPI, IWasmAPI, WasmExports } from "./api.js";
17
28
  * 64bit integers are handled via JS `BigInt` and hence require the host env to
18
29
  * support it. No polyfill is provided.
19
30
  */
20
- export declare class WasmBridge<T extends WasmExports = WasmExports> {
31
+ export declare class WasmBridge<T extends WasmExports = WasmExports> implements WasmMemViews {
21
32
  modules: Record<string, IWasmAPI<T>>;
22
33
  logger: ILogger;
23
34
  i8: Int8Array;
@@ -32,8 +43,9 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
32
43
  f64: Float64Array;
33
44
  utf8Decoder: TextDecoder;
34
45
  utf8Encoder: TextEncoder;
35
- core: CoreAPI;
46
+ imports: WebAssembly.Imports;
36
47
  exports: T;
48
+ api: CoreAPI;
37
49
  constructor(modules?: Record<string, IWasmAPI<T>>, logger?: ILogger);
38
50
  /**
39
51
  * Instantiates WASM module from given `src` (and optional provided extra
@@ -57,17 +69,78 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
57
69
  * @param exports
58
70
  */
59
71
  init(exports: T): Promise<boolean>;
72
+ /**
73
+ * Called automatically. Initializes and/or updates the various typed WASM
74
+ * memory views (e.g. after growing the WASM memory).
75
+ */
76
+ ensureMemory(): void;
60
77
  /**
61
78
  * Required use for WASM module instantiation to provide JS imports to the
62
79
  * module. Returns an object of all WASM imports declared by the bridge core
63
80
  * API and any provided bridge API modules.
64
81
  *
65
82
  * @remarks
66
- * Since all declared imports will be merged into a single flat namespace,
67
- * it's recommended to use per-module naming prefixes to avoid clashes. If
68
- * there're any naming clashes, this function will throw an error.
83
+ * Since v0.4.0 each API module's imports will be in their own WASM import
84
+ * object, named using the same key which was assigned to the module when
85
+ * creating the WASM bridge. The bridge's core API will be named `core` and
86
+ * is reserved.
87
+ *
88
+ * @example
89
+ * The following creates a bridge with a fictional `custom` API module:
90
+ *
91
+ * ```ts
92
+ * const bridge = new WasmBridge({ custom: new CustomAPI() });
93
+ *
94
+ * // get combined imports object
95
+ * bridge.getImports();
96
+ * {
97
+ * // imports defined by the core API of the bridge itself
98
+ * wasmapi: { ... },
99
+ * // imports defined by the CustomAPI module
100
+ * custom: { ... }
101
+ * }
102
+ * ```
103
+ *
104
+ * Any related API bindings on the WASM (Zig) side then also need to refer
105
+ * to these custom import sections (also see `/zig/core.zig`):
106
+ *
107
+ * ```zig
108
+ * pub export "custom" fn foo(x: u32) void;
109
+ * ```
69
110
  */
70
111
  getImports(): WebAssembly.Imports;
112
+ /**
113
+ * Attempts to grow the WASM memory by an additional `numPages` (64KB/page)
114
+ * and if successful updates all typed memory views to use the new
115
+ * underlying buffer.
116
+ *
117
+ * @param numPages
118
+ */
119
+ growMemory(numPages: number): void;
120
+ /**
121
+ * Attempts to allocate `numBytes` using the exported WASM core API function
122
+ * {@link WasmExports._wasm_allocate} (implementation specific) and returns
123
+ * start address of the new memory block. If unsuccessful, throws an
124
+ * {@link OutOfMemoryError}. If `clear` is true, the allocated region will
125
+ * be zero-filled.
126
+ *
127
+ * @remarks
128
+ * See {@link WasmExports._wasm_allocate} docs for further details.
129
+ *
130
+ * @param numBytes
131
+ * @param clear
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;
71
144
  getI8(addr: number): number;
72
145
  getU8(addr: number): number;
73
146
  getI16(addr: number): number;
@@ -108,7 +181,30 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> {
108
181
  setU64Array(addr: number, buf: BigIntArray): this;
109
182
  setF32Array(addr: number, buf: NumericArray): this;
110
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
+ */
111
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
+ */
112
208
  setString(str: string, addr: number, maxBytes: number, terminate?: boolean): number;
113
209
  getElementById(addr: number, len?: number): HTMLElement;
114
210
  }
package/bridge.js CHANGED
@@ -1,7 +1,9 @@
1
+ import { defError } from "@thi.ng/errors/deferror";
1
2
  import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
2
3
  import { U16, U32, U64HL, U8 } from "@thi.ng/hex";
3
4
  import { ConsoleLogger } from "@thi.ng/logger/console";
4
5
  const B32 = BigInt(32);
6
+ export const OutOfMemoryError = defError(() => "Out of memory");
5
7
  /**
6
8
  * The main interop API bridge between the JS host environment and a WebAssembly
7
9
  * module. This class provides a small core API with various typed accessors and
@@ -26,7 +28,7 @@ export class WasmBridge {
26
28
  this.utf8Encoder = new TextEncoder();
27
29
  const logN = (x) => this.logger.debug(x);
28
30
  const logA = (method) => (addr, len) => this.logger.debug(method(addr, len).join(", "));
29
- this.core = {
31
+ this.api = {
30
32
  printI8: logN,
31
33
  printU8: logN,
32
34
  printU8Hex: (x) => this.logger.debug(`0x${U8(x)}`),
@@ -70,14 +72,10 @@ export class WasmBridge {
70
72
  */
71
73
  async instantiate(src, imports) {
72
74
  const $src = await src;
73
- const $imports = { ...imports, ...this.getImports() };
74
- let wasm;
75
- if ($src instanceof Response) {
76
- wasm = await WebAssembly.instantiateStreaming($src, $imports);
77
- }
78
- else {
79
- wasm = await WebAssembly.instantiate($src, $imports);
80
- }
75
+ const $imports = { ...this.getImports(), ...imports };
76
+ const wasm = await ($src instanceof Response
77
+ ? WebAssembly.instantiateStreaming($src, $imports)
78
+ : WebAssembly.instantiate($src, $imports));
81
79
  return this.init(wasm.instance.exports);
82
80
  }
83
81
  /**
@@ -89,7 +87,23 @@ export class WasmBridge {
89
87
  */
90
88
  async init(exports) {
91
89
  this.exports = exports;
92
- const buf = exports.memory.buffer;
90
+ this.ensureMemory();
91
+ for (let id in this.modules) {
92
+ this.logger.debug(`initializing API module: ${id}`);
93
+ const status = await this.modules[id].init(this);
94
+ if (!status)
95
+ return false;
96
+ }
97
+ return true;
98
+ }
99
+ /**
100
+ * Called automatically. Initializes and/or updates the various typed WASM
101
+ * memory views (e.g. after growing the WASM memory).
102
+ */
103
+ ensureMemory() {
104
+ const buf = this.exports.memory.buffer;
105
+ if (this.u8 && this.u8.buffer === buf)
106
+ return;
93
107
  this.i8 = new Int8Array(buf);
94
108
  this.u8 = new Uint8Array(buf);
95
109
  this.i16 = new Int16Array(buf);
@@ -100,13 +114,6 @@ export class WasmBridge {
100
114
  this.u64 = new BigUint64Array(buf);
101
115
  this.f32 = new Float32Array(buf);
102
116
  this.f64 = new Float64Array(buf);
103
- for (let id in this.modules) {
104
- this.logger.debug(`initializing API module: ${id}`);
105
- const status = await this.modules[id].init(this);
106
- if (!status)
107
- return false;
108
- }
109
- return true;
110
117
  }
111
118
  /**
112
119
  * Required use for WASM module instantiation to provide JS imports to the
@@ -114,23 +121,91 @@ export class WasmBridge {
114
121
  * API and any provided bridge API modules.
115
122
  *
116
123
  * @remarks
117
- * Since all declared imports will be merged into a single flat namespace,
118
- * it's recommended to use per-module naming prefixes to avoid clashes. If
119
- * there're any naming clashes, this function will throw an error.
124
+ * Since v0.4.0 each API module's imports will be in their own WASM import
125
+ * object, named using the same key which was assigned to the module when
126
+ * creating the WASM bridge. The bridge's core API will be named `core` and
127
+ * is reserved.
128
+ *
129
+ * @example
130
+ * The following creates a bridge with a fictional `custom` API module:
131
+ *
132
+ * ```ts
133
+ * const bridge = new WasmBridge({ custom: new CustomAPI() });
134
+ *
135
+ * // get combined imports object
136
+ * bridge.getImports();
137
+ * {
138
+ * // imports defined by the core API of the bridge itself
139
+ * wasmapi: { ... },
140
+ * // imports defined by the CustomAPI module
141
+ * custom: { ... }
142
+ * }
143
+ * ```
144
+ *
145
+ * Any related API bindings on the WASM (Zig) side then also need to refer
146
+ * to these custom import sections (also see `/zig/core.zig`):
147
+ *
148
+ * ```zig
149
+ * pub export "custom" fn foo(x: u32) void;
150
+ * ```
120
151
  */
121
152
  getImports() {
122
- const env = { ...this.core };
123
- for (let id in this.modules) {
124
- const imports = this.modules[id].getImports();
125
- // check for naming clashes
126
- for (let k in imports) {
127
- if (env[k] !== undefined) {
128
- illegalArgs(`attempt to redeclare import: ${k} by API module ${id}`);
153
+ if (!this.imports) {
154
+ this.imports = { wasmapi: this.api };
155
+ for (let id in this.modules) {
156
+ if (this.imports[id] !== undefined) {
157
+ illegalArgs(`attempt to redeclare API module ${id}`);
129
158
  }
159
+ this.imports[id] = this.modules[id].getImports();
130
160
  }
131
- Object.assign(env, imports);
132
161
  }
133
- return { env };
162
+ return this.imports;
163
+ }
164
+ /**
165
+ * Attempts to grow the WASM memory by an additional `numPages` (64KB/page)
166
+ * and if successful updates all typed memory views to use the new
167
+ * underlying buffer.
168
+ *
169
+ * @param numPages
170
+ */
171
+ growMemory(numPages) {
172
+ this.exports.memory.grow(numPages);
173
+ this.ensureMemory();
174
+ }
175
+ /**
176
+ * Attempts to allocate `numBytes` using the exported WASM core API function
177
+ * {@link WasmExports._wasm_allocate} (implementation specific) and returns
178
+ * start address of the new memory block. If unsuccessful, throws an
179
+ * {@link OutOfMemoryError}. If `clear` is true, the allocated region will
180
+ * be zero-filled.
181
+ *
182
+ * @remarks
183
+ * See {@link WasmExports._wasm_allocate} docs for further details.
184
+ *
185
+ * @param numBytes
186
+ * @param clear
187
+ */
188
+ allocate(numBytes, clear = false) {
189
+ const addr = this.exports._wasm_allocate(numBytes);
190
+ if (!addr)
191
+ throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
192
+ this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)}`);
193
+ this.ensureMemory();
194
+ clear && this.u8.fill(0, addr, addr + numBytes);
195
+ return addr;
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);
134
209
  }
135
210
  getI8(addr) {
136
211
  return this.i8[addr];
@@ -280,13 +355,37 @@ export class WasmBridge {
280
355
  this.f64.set(buf, addr >> 3);
281
356
  return this;
282
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
+ */
283
366
  getString(addr, len = 0) {
367
+ this.ensureMemory();
284
368
  return this.utf8Decoder.decode(this.u8.subarray(addr, len > 0 ? addr + len : this.u8.indexOf(0, addr)));
285
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
+ */
286
385
  setString(str, addr, maxBytes, terminate = true) {
287
386
  maxBytes = Math.min(maxBytes, this.u8.length - addr);
288
387
  const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
289
- if (len != null && len < maxBytes + (terminate ? 0 : 1)) {
388
+ if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
290
389
  illegalArgs(`error writing string to 0x${U32(addr)}`);
291
390
  }
292
391
  if (terminate) {
@@ -0,0 +1,22 @@
1
+ import { ICodeGen } from "../api.js";
2
+ export interface TSOpts {
3
+ /**
4
+ * Indentation string
5
+ *
6
+ * @defaultValue "\t"
7
+ */
8
+ indent: string;
9
+ }
10
+ /**
11
+ * TypeScript code generator. Call with options and then pass to
12
+ * {@link generateTypes} (see its docs for further usage).
13
+ *
14
+ * @remarks
15
+ * This codegen generates interface and enum definitions for a {@link TypeColl}
16
+ * given to {@link generateTypes}. For structs it will also generate memory
17
+ * mapped wrappers with fully typed accessors.
18
+ *
19
+ * @param opts
20
+ */
21
+ export declare const TYPESCRIPT: (opts?: Partial<TSOpts>) => ICodeGen;
22
+ //# sourceMappingURL=typescript.d.ts.map
@@ -0,0 +1,146 @@
1
+ import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray";
2
+ import { isString } from "@thi.ng/checks/is-string";
3
+ import { PKG_NAME, USIZE, } from "../api.js";
4
+ import { isBigNumeric, isNumeric, isPrim, prefixLines } from "./utils.js";
5
+ /**
6
+ * TypeScript code generator. Call with options and then pass to
7
+ * {@link generateTypes} (see its docs for further usage).
8
+ *
9
+ * @remarks
10
+ * This codegen generates interface and enum definitions for a {@link TypeColl}
11
+ * given to {@link generateTypes}. For structs it will also generate memory
12
+ * mapped wrappers with fully typed accessors.
13
+ *
14
+ * @param opts
15
+ */
16
+ export const TYPESCRIPT = (opts) => {
17
+ const { indent } = { indent: "\t", ...opts };
18
+ const I = indent;
19
+ const I2 = I + I;
20
+ const I3 = I2 + I;
21
+ const gen = {
22
+ pre: `import type { WasmTypeBase, WasmTypeConstructor } from "${PKG_NAME}";`,
23
+ doc: (doc, indent, acc) => {
24
+ if (doc.indexOf("\n") !== -1) {
25
+ acc.push(indent + "/**", prefixLines(indent + " * ", doc), indent + " */");
26
+ }
27
+ else {
28
+ acc.push(`${indent}/** ${doc} */`);
29
+ }
30
+ },
31
+ enum: (type, _, acc) => {
32
+ const e = type;
33
+ acc.push(`export enum ${e.name} {`);
34
+ for (let v of e.values) {
35
+ var line = indent;
36
+ if (!isString(v)) {
37
+ v.doc && gen.doc(v.doc, indent, acc);
38
+ line += v.name;
39
+ if (v.value != null)
40
+ line += ` = ${v.value}`;
41
+ }
42
+ else {
43
+ line += v;
44
+ }
45
+ acc.push(line + ",");
46
+ }
47
+ acc.push("}\n");
48
+ return acc;
49
+ },
50
+ struct: (type, types, acc) => {
51
+ const struct = type;
52
+ const returnTypes = {};
53
+ // interface definition
54
+ acc.push(`export interface ${struct.name} extends WasmTypeBase {`);
55
+ for (let f of struct.fields) {
56
+ f.doc && gen.doc(f.doc, indent, acc);
57
+ let line = `${indent}${f.name}: `;
58
+ let rtype = "";
59
+ if (f.tag == "array" || f.tag == "slice" || f.tag === "vec") {
60
+ rtype = isNumeric(f.type)
61
+ ? TYPEDARRAY_CTORS[f.type].name
62
+ : isBigNumeric(f.type)
63
+ ? BIGINT_ARRAY_CTORS[f.type].name
64
+ : f.type + "[]";
65
+ }
66
+ else if (!f.tag || f.tag === "scalar" || f.tag === "ptr") {
67
+ rtype = isBigNumeric(f.type)
68
+ ? "bigint"
69
+ : isNumeric(f.type)
70
+ ? "number"
71
+ : f.type;
72
+ }
73
+ returnTypes[f.name] = rtype;
74
+ acc.push(line + rtype + ";");
75
+ }
76
+ acc.push("}\n");
77
+ // type implementation
78
+ acc.push(`export const $${struct.name}: WasmTypeConstructor<${struct.name}> = (mem) => ({`, `${I}get align() { return ${struct.__align}; },`, `${I}get size() { return ${struct.__size}; },`, `${I}instance: (base) => ({`, `${I2}get __base() { return base; },`, `${I2}get __bytes() { return mem.u8.subarray(base, base + ${struct.__size}); },`);
79
+ for (let f of struct.fields) {
80
+ const offset = f.__offset || 0;
81
+ acc.push(`${I2}get ${f.name}(): ${returnTypes[f.name]} {`);
82
+ const prim = isPrim(f.type);
83
+ if (f.tag === "ptr") {
84
+ acc.push(prim
85
+ ? `${I3}return mem.${f.type}[${__ptrShift(offset, f.type)}];`
86
+ : `${I3}return $${f.type}.instance(${__ptr(offset)});`);
87
+ }
88
+ else if (f.tag === "slice") {
89
+ acc.push(`${I3}const len = ${__ptr(offset + 4)};`, prim
90
+ ? `${I3}const addr = ${__ptrShift(offset, f.type)};
91
+ ${I3}return mem.${f.type}.subarray(addr, addr + len);`
92
+ : `${I3}const addr = ${__ptr(offset)};\n${__mapArray(struct, f, I3)}`);
93
+ }
94
+ else if (f.tag === "array" || f.tag === "vec") {
95
+ acc.push(prim
96
+ ? `${I3}const addr = ${__addrShift(offset, f.type)};
97
+ ${I3}return mem.${f.type}.subarray(addr, addr + ${f.len});`
98
+ : `${I3}const addr = ${__addr(offset)};\n${__mapArray(struct, f, I3, f.len)}`);
99
+ }
100
+ else {
101
+ let setter;
102
+ if (prim) {
103
+ const addr = __mem(f.type, f.__offset);
104
+ acc.push(`${I3}return ${addr};`);
105
+ setter = `${addr} = x`;
106
+ }
107
+ else if (types[f.type].type === "enum") {
108
+ const tag = types[f.type].tag;
109
+ const addr = __mem(tag, f.__offset);
110
+ acc.push(`${I3}return ${addr};`);
111
+ setter = `${addr} = x`;
112
+ }
113
+ else {
114
+ acc.push(`${I3}return $${f.type}(mem).instance(${__addr(offset)});`);
115
+ setter = `mem.u8.set(x.__bytes, ${__addr(offset)})`;
116
+ }
117
+ // close getter
118
+ acc.push(`${I2}},`);
119
+ // setter
120
+ acc.push(`${I2}set ${f.name}(x: ${returnTypes[f.name]}) {`, `${I3}${setter};`);
121
+ }
122
+ // close field accessor
123
+ acc.push(`${I2}},`);
124
+ }
125
+ acc.push(`${I}})\n});\n`);
126
+ return acc;
127
+ },
128
+ };
129
+ return gen;
130
+ };
131
+ /** @internal */
132
+ const __shift = (type) => BIT_SHIFTS[type];
133
+ /** @internal */
134
+ const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
135
+ /** @internal */
136
+ const __addrShift = (offset, shift) => __addr(offset) + " >>> " + __shift(shift);
137
+ /** @internal */
138
+ const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
139
+ /** @internal */
140
+ const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
141
+ const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
142
+ /** @internal */
143
+ const __mapArray = (struct, f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
144
+ const slice: ${f.type}[] = [];
145
+ for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${struct.__size}));
146
+ return slice;`);
@@ -0,0 +1,5 @@
1
+ export declare const isNumeric: (x: string) => boolean;
2
+ export declare const isBigNumeric: (x: string) => boolean;
3
+ export declare const isPrim: (x: string) => boolean;
4
+ export declare const prefixLines: (prefix: string, str: string) => string;
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1,7 @@
1
+ export const isNumeric = (x) => /^([iu](8|16|32))|(f(32|64))$/.test(x);
2
+ export const isBigNumeric = (x) => /^[iu]64$/.test(x);
3
+ export const isPrim = (x) => isNumeric(x) || isBigNumeric(x);
4
+ export const prefixLines = (prefix, str) => str
5
+ .split("\n")
6
+ .map((line) => prefix + line)
7
+ .join("\n");
@@ -0,0 +1,22 @@
1
+ import type { ICodeGen } from "../api.js";
2
+ export interface ZigOpts {
3
+ /**
4
+ * If true, generates various struct & struct field analysis functions
5
+ * (sizes, alignment, offsets etc.).
6
+ *
7
+ * @defaultValue false
8
+ */
9
+ debug: boolean;
10
+ }
11
+ /**
12
+ * Zig code generator. Call with options and then pass to {@link generateTypes}
13
+ * (see its docs for further usage).
14
+ *
15
+ * @remarks
16
+ * This codegen generates struct and enum definitions for a {@link TypeColl}
17
+ * given to {@link generateTypes}.
18
+ *
19
+ * @param opts
20
+ */
21
+ export declare const ZIG: (opts?: Partial<ZigOpts>) => ICodeGen;
22
+ //# sourceMappingURL=zig.d.ts.map
package/codegen/zig.js ADDED
@@ -0,0 +1,75 @@
1
+ import { isString } from "@thi.ng/checks/is-string";
2
+ import { prefixLines } from "./utils.js";
3
+ /**
4
+ * Zig code generator. Call with options and then pass to {@link generateTypes}
5
+ * (see its docs for further usage).
6
+ *
7
+ * @remarks
8
+ * This codegen generates struct and enum definitions for a {@link TypeColl}
9
+ * given to {@link generateTypes}.
10
+ *
11
+ * @param opts
12
+ */
13
+ export const ZIG = (opts) => {
14
+ const { debug } = { debug: false, ...opts };
15
+ const gen = {
16
+ doc: (doc, indent, acc, topLevel = false) => {
17
+ acc.push(prefixLines(topLevel ? "//! " : indent + "/// ", doc));
18
+ },
19
+ enum: (e, _, acc) => {
20
+ acc.push(`pub const ${e.name} = enum(${e.tag}) {`);
21
+ for (let v of e.values) {
22
+ let line = ` `;
23
+ if (!isString(v)) {
24
+ v.doc && gen.doc(v.doc, " ", acc);
25
+ line += v.name;
26
+ if (v.value != null)
27
+ line += ` = ${v.value}`;
28
+ }
29
+ else {
30
+ line += v;
31
+ }
32
+ acc.push(line + ",");
33
+ }
34
+ acc.push("};\n");
35
+ },
36
+ struct: (struct, _, acc) => {
37
+ const name = struct.name;
38
+ acc.push(`pub const ${name} = struct {`);
39
+ const ftypes = {};
40
+ for (let f of struct.fields) {
41
+ f.doc && gen.doc(f.doc, " ", acc);
42
+ var ftype;
43
+ switch (f.tag) {
44
+ case "array":
45
+ ftype = `[${f.len}]${f.type}`;
46
+ break;
47
+ case "slice":
48
+ ftype = `[]${f.type}`;
49
+ break;
50
+ case "vec":
51
+ ftype = `@Vector(${f.len}, ${f.type})`;
52
+ break;
53
+ case "ptr":
54
+ ftype = `*${f.len ? `[${f.len}]` : ""}${f.type}`;
55
+ break;
56
+ case "scalar":
57
+ default:
58
+ ftype = f.type;
59
+ }
60
+ ftypes[f.name] = ftype;
61
+ acc.push(` ${f.name}: ${ftype},`);
62
+ }
63
+ acc.push("};\n");
64
+ if (!debug)
65
+ return;
66
+ const fn = (fname, body) => `export fn ${name}_${fname}() usize { return ${body}; }`;
67
+ acc.push(fn("align", `@alignOf(${name})`), fn("size", `@sizeOf(${name})`));
68
+ for (let f of struct.fields) {
69
+ acc.push(fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`), fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`), fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`));
70
+ }
71
+ acc.push("");
72
+ },
73
+ };
74
+ return gen;
75
+ };
package/codegen.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { ICodeGen, TypeColl } from "./api.js";
2
+ export interface CodeGenOpts {
3
+ /**
4
+ * Optional string to be injected before generated type defs (but after
5
+ * codegen's own prelude, if any)
6
+ */
7
+ pre: string;
8
+ /**
9
+ * Optional string to be injected after generated type defs (but before
10
+ * codegen's own epilogue, if any)
11
+ */
12
+ post: string;
13
+ }
14
+ export declare const prepareTypes: (types: TypeColl) => void;
15
+ export declare const generateTypes: (types: TypeColl, codegen: ICodeGen, opts?: Partial<CodeGenOpts>) => string;
16
+ //# sourceMappingURL=codegen.d.ts.map