@thi.ng/wasm-api 0.6.0 → 0.9.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,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, WasmMemViews } 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> implements WasmMemViews {
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;
@@ -193,7 +193,8 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
193
193
  /**
194
194
  * Encodes given string as UTF-8 and writes it to WASM memory starting at
195
195
  * `addr`. By default the string will be zero-terminated and only `maxBytes`
196
- * will be written. Returns the number of bytes written.
196
+ * will be written. Returns the number of bytes written (excluding final
197
+ * sentinel, if any).
197
198
  *
198
199
  * @remarks
199
200
  * An error will be thrown if the encoded string doesn't fully fit into the
package/bridge.js CHANGED
@@ -55,6 +55,9 @@ export class WasmBridge {
55
55
  _printF64Array: logA(this.getF64Array.bind(this)),
56
56
  _printStr0: (addr) => this.logger.debug(this.getString(addr, 0)),
57
57
  _printStr: (addr, len) => this.logger.debug(this.getString(addr, len)),
58
+ debug: () => {
59
+ debugger;
60
+ },
58
61
  };
59
62
  }
60
63
  /**
@@ -370,7 +373,8 @@ export class WasmBridge {
370
373
  /**
371
374
  * Encodes given string as UTF-8 and writes it to WASM memory starting at
372
375
  * `addr`. By default the string will be zero-terminated and only `maxBytes`
373
- * will be written. Returns the number of bytes written.
376
+ * will be written. Returns the number of bytes written (excluding final
377
+ * sentinel, if any).
374
378
  *
375
379
  * @remarks
376
380
  * An error will be thrown if the encoded string doesn't fully fit into the
@@ -386,11 +390,10 @@ export class WasmBridge {
386
390
  maxBytes = Math.min(maxBytes, this.u8.length - addr);
387
391
  const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
388
392
  if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
389
- illegalArgs(`error writing string to 0x${U32(addr)}`);
393
+ illegalArgs(`error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`);
390
394
  }
391
395
  if (terminate) {
392
396
  this.u8[addr + len] = 0;
393
- return len + 1;
394
397
  }
395
398
  return len;
396
399
  }
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 { 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
+ }
@@ -1,4 +1,7 @@
1
1
  import { ICodeGen } from "../api.js";
2
+ /**
3
+ * TypeScript code generator options.
4
+ */
2
5
  export interface TSOpts {
3
6
  /**
4
7
  * Indentation string
@@ -6,6 +9,16 @@ export interface TSOpts {
6
9
  * @defaultValue "\t"
7
10
  */
8
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";
9
22
  }
10
23
  /**
11
24
  * TypeScript code generator. Call with options and then pass to
@@ -1,7 +1,7 @@
1
1
  import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray";
2
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";
3
+ import { PKG_NAME, USIZE, USIZE_SIZE, } from "../api.js";
4
+ import { isBigNumeric, isNumeric, isWasmPrim, isWasmString, prefixLines, } from "./utils.js";
5
5
  /**
6
6
  * TypeScript code generator. Call with options and then pass to
7
7
  * {@link generateTypes} (see its docs for further usage).
@@ -14,7 +14,12 @@ 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, stringType, uppercaseEnums } = {
18
+ indent: "\t",
19
+ stringType: "slice",
20
+ uppercaseEnums: true,
21
+ ...opts,
22
+ };
18
23
  const I = indent;
19
24
  const I2 = I + I;
20
25
  const I3 = I2 + I;
@@ -32,15 +37,15 @@ export const TYPESCRIPT = (opts) => {
32
37
  const e = type;
33
38
  acc.push(`export enum ${e.name} {`);
34
39
  for (let v of e.values) {
35
- var line = indent;
40
+ let line = indent;
36
41
  if (!isString(v)) {
37
42
  v.doc && gen.doc(v.doc, indent, acc);
38
- line += v.name;
43
+ line += uppercaseEnums ? v.name.toUpperCase() : v.name;
39
44
  if (v.value != null)
40
45
  line += ` = ${v.value}`;
41
46
  }
42
47
  else {
43
- line += v;
48
+ line += uppercaseEnums ? v.toUpperCase() : v;
44
49
  }
45
50
  acc.push(line + ",");
46
51
  }
@@ -79,31 +84,60 @@ export const TYPESCRIPT = (opts) => {
79
84
  for (let f of struct.fields) {
80
85
  const offset = f.__offset || 0;
81
86
  acc.push(`${I2}get ${f.name}(): ${returnTypes[f.name]} {`);
82
- const prim = isPrim(f.type);
87
+ const isPrim = isWasmPrim(f.type);
88
+ const isStr = isWasmString(f.type);
83
89
  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)});`);
90
+ if (isPrim) {
91
+ acc.push(`${I3}return mem.${f.type}[${__ptrShift(offset, f.type)}];`);
92
+ }
93
+ else if (isStr) {
94
+ acc.push(
95
+ // double deref
96
+ stringType === "slice"
97
+ ? `${I3}return mem.getString(mem.${USIZE}[${__ptr(offset)} >>> ${USIZE_SIZE}])`
98
+ : `${I3}return mem.getString(${__ptr(offset)})`);
99
+ }
100
+ else {
101
+ acc.push(`${I3}return $${f.type}.instance(${__ptr(offset)});`);
102
+ }
87
103
  }
88
104
  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)}`);
105
+ acc.push(`${I3}const len = ${__ptr(offset + 4)};`);
106
+ if (isPrim) {
107
+ acc.push(`${I3}const addr = ${__ptrShift(offset, f.type)};`, `${I3}return mem.${f.type}.subarray(addr, addr + len);`);
108
+ }
109
+ else if (isStr) {
110
+ acc.push(`${I3}const addr = ${__ptr(offset)};`, __mapStringArray(I3, stringType));
111
+ }
112
+ else {
113
+ acc.push(`${I3}const addr = ${__ptr(offset)};`, __mapArray(f, I3));
114
+ }
93
115
  }
94
116
  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)}`);
117
+ if (isPrim) {
118
+ acc.push(`${I3}const addr = ${__addrShift(offset, f.type)};`, `${I3}return mem.${f.type}.subarray(addr, addr + ${f.len});`);
119
+ }
120
+ else if (isStr) {
121
+ acc.push(`${I3}const addr = ${__addr(offset)};`, __mapStringArray(I3, stringType, f.len));
122
+ }
123
+ else {
124
+ acc.push(`${I3}const addr = ${__addr(offset)};`, __mapArray(f, I3, f.len));
125
+ }
99
126
  }
100
127
  else {
101
128
  let setter;
102
- if (prim) {
129
+ if (isPrim) {
103
130
  const addr = __mem(f.type, f.__offset);
104
131
  acc.push(`${I3}return ${addr};`);
105
132
  setter = `${addr} = x`;
106
133
  }
134
+ else if (isStr) {
135
+ acc.push(`${I3}return mem.getString(${__ptr(offset)})`);
136
+ setter =
137
+ stringType === "slice"
138
+ ? `mem.setString(x, ${__ptr(offset)}, ${__ptr(offset + 4)} + 1, true);`
139
+ : `throw new Error("unsupported for raw string pointers")`;
140
+ }
107
141
  else if (types[f.type].type === "enum") {
108
142
  const tag = types[f.type].tag;
109
143
  const addr = __mem(tag, f.__offset);
@@ -133,14 +167,23 @@ const __shift = (type) => BIT_SHIFTS[type];
133
167
  /** @internal */
134
168
  const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
135
169
  /** @internal */
136
- const __addrShift = (offset, shift) => __addr(offset) + " >>> " + __shift(shift);
170
+ const __addrShift = (offset, shift) => {
171
+ const bits = __shift(shift);
172
+ return __addr(offset) + (bits ? " >>> " + bits : "");
173
+ };
137
174
  /** @internal */
138
175
  const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
139
176
  /** @internal */
140
177
  const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
141
178
  const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
142
179
  /** @internal */
143
- const __mapArray = (struct, f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
180
+ const __mapArray = (f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
144
181
  const slice: ${f.type}[] = [];
145
- for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${struct.__size}));
182
+ for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));
146
183
  return slice;`);
184
+ /** @internal */
185
+ const __mapStringArray = (indent, type, len = "len") => prefixLines(indent, [
186
+ "const slice: string[] = [];",
187
+ `for(let i = 0; i < ${len}; i++) slice.push(mem.getString(mem.${USIZE}[(addr + i * ${USIZE_SIZE * (type === "slice" ? 2 : 1)}) >>> ${__shift(USIZE)}]));`,
188
+ "return slice;",
189
+ ]);
@@ -1,5 +1,30 @@
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;
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 isWasmPrim: (x: string) => x is WasmPrim;
21
+ export declare const isWasmString: (x: string) => x is "string";
22
+ /**
23
+ * Takes an array of strings or splits given string into lines, prefixes each
24
+ * line with given `prefix` and then returns rejoined result.
25
+ *
26
+ * @param prefix
27
+ * @param str
28
+ */
29
+ export declare const prefixLines: (prefix: string, str: string | string[]) => string;
5
30
  //# sourceMappingURL=utils.d.ts.map
package/codegen/utils.js CHANGED
@@ -1,7 +1,30 @@
1
- export const isNumeric = (x) => /^([iu](8|16|32))|(f(32|64))$/.test(x);
1
+ import { isString } from "@thi.ng/checks/is-string";
2
+ /**
3
+ * Returns true iff `x` is a {@link WasmPrim32}.
4
+ *
5
+ * @param x
6
+ */
7
+ export const isNumeric = (x) => /^(([iu](8|16|32))|(f(32|64)))$/.test(x);
8
+ /**
9
+ * Returns true iff `x` is a `i64` or `u64`.
10
+ *
11
+ * @param x
12
+ */
2
13
  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")
14
+ /**
15
+ * Returns true iff `x` is a {@link WasmPrim}.
16
+ *
17
+ * @param x
18
+ */
19
+ export const isWasmPrim = (x) => isNumeric(x) || isBigNumeric(x);
20
+ export const isWasmString = (x) => x === "string";
21
+ /**
22
+ * Takes an array of strings or splits given string into lines, prefixes each
23
+ * line with given `prefix` and then returns rejoined result.
24
+ *
25
+ * @param prefix
26
+ * @param str
27
+ */
28
+ export const prefixLines = (prefix, str) => (isString(str) ? str.split("\n") : str)
6
29
  .map((line) => prefix + line)
7
30
  .join("\n");
package/codegen/zig.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { ICodeGen } from "../api.js";
2
+ /**
3
+ * Zig code generator options.
4
+ */
2
5
  export interface ZigOpts {
3
6
  /**
4
7
  * If true, generates various struct & struct field analysis functions
package/codegen/zig.js CHANGED
@@ -39,23 +39,22 @@ export const ZIG = (opts) => {
39
39
  const ftypes = {};
40
40
  for (let f of struct.fields) {
41
41
  f.doc && gen.doc(f.doc, " ", acc);
42
- var ftype;
42
+ let ftype = f.type === "string" ? "[]const u8" : f.type;
43
43
  switch (f.tag) {
44
44
  case "array":
45
- ftype = `[${f.len}]${f.type}`;
45
+ ftype = `[${f.len}]${ftype}`;
46
46
  break;
47
47
  case "slice":
48
- ftype = `[]${f.type}`;
48
+ ftype = `[]${ftype}`;
49
49
  break;
50
50
  case "vec":
51
- ftype = `@Vector(${f.len}, ${f.type})`;
51
+ ftype = `@Vector(${f.len}, ${ftype})`;
52
52
  break;
53
53
  case "ptr":
54
- ftype = `*${f.len ? `[${f.len}]` : ""}${f.type}`;
54
+ ftype = `*${f.len ? `[${f.len}]` : ""}${ftype}`;
55
55
  break;
56
56
  case "scalar":
57
57
  default:
58
- ftype = f.type;
59
58
  }
60
59
  ftypes[f.name] = ftype;
61
60
  acc.push(` ${f.name}: ${ftype},`);
package/codegen.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { ICodeGen, TypeColl } from "./api.js";
2
+ /**
3
+ * Global/shared code generator options.
4
+ */
2
5
  export interface CodeGenOpts {
3
6
  /**
4
7
  * Optional string to be injected before generated type defs (but after
@@ -10,7 +13,41 @@ export interface CodeGenOpts {
10
13
  * codegen's own epilogue, if any)
11
14
  */
12
15
  post: string;
16
+ /**
17
+ * Identifier how strings are stored on WASM side, e.g. in Zig string
18
+ * literals are slices (8 bytes), in C just plain pointers (4 bytes).
19
+ *
20
+ * @defaultValue "slice"
21
+ */
22
+ stringType: "slice" | "ptr";
13
23
  }
14
- export declare const prepareTypes: (types: TypeColl) => void;
24
+ /**
25
+ * Takes a type collection and analyzes each analyzed to compute individual
26
+ * alignments and sizes.
27
+ *
28
+ * @remarks
29
+ * This function is idempotent and called automatically by
30
+ * {@link generateTypes}. Only exported for dev/debug purposes.
31
+ *
32
+ * @param types
33
+ *
34
+ * @internal
35
+ */
36
+ export declare const prepareTypes: (types: TypeColl, opts: CodeGenOpts) => void;
37
+ /**
38
+ * Code generator main entry point. Takes an object of {@link TopLevelType}
39
+ * definitions, an actual code generator implementation for a single target
40
+ * language and (optional) global codegen options. Returns generated source code
41
+ * for all given types as a single string.
42
+ *
43
+ * @remarks
44
+ * Before actual code generation the types are first analyzed to compute their
45
+ * alignments and sizes. This is only ever done once (idempotent), even if
46
+ * `generateTypes()` is called multiple times for different target langs.
47
+ *
48
+ * @param types
49
+ * @param codegen
50
+ * @param opts
51
+ */
15
52
  export declare const generateTypes: (types: TypeColl, codegen: ICodeGen, opts?: Partial<CodeGenOpts>) => string;
16
53
  //# sourceMappingURL=codegen.d.ts.map
package/codegen.js CHANGED
@@ -5,9 +5,9 @@ import { compareByKey } from "@thi.ng/compare/keys";
5
5
  import { compareNumDesc } from "@thi.ng/compare/numeric";
6
6
  import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
7
7
  import { PKG_NAME, USIZE_SIZE, } from "./api.js";
8
- import { isNumeric } from "./codegen/utils.js";
8
+ import { isNumeric, isWasmString } from "./codegen/utils.js";
9
9
  const sizeOf = defmulti((x) => x.type, {}, {
10
- [DEFAULT]: (field, types) => {
10
+ [DEFAULT]: (field, types, opts) => {
11
11
  if (field.__size)
12
12
  return field.__size;
13
13
  let size = 0;
@@ -20,7 +20,9 @@ const sizeOf = defmulti((x) => x.type, {}, {
20
20
  else {
21
21
  size = isNumeric(field.type)
22
22
  ? SIZEOF[field.type]
23
- : sizeOf(types[field.type], types);
23
+ : isWasmString(field.type)
24
+ ? USIZE_SIZE * (opts.stringType === "slice" ? 2 : 1)
25
+ : sizeOf(types[field.type], types, opts);
24
26
  if (field.tag == "array" || field.tag === "vec") {
25
27
  size *= field.len;
26
28
  }
@@ -32,7 +34,7 @@ const sizeOf = defmulti((x) => x.type, {}, {
32
34
  return type.__size;
33
35
  return (type.__size = SIZEOF[type.tag]);
34
36
  },
35
- struct: (type, types) => {
37
+ struct: (type, types, opts) => {
36
38
  if (type.__size)
37
39
  return type.__size;
38
40
  const struct = type;
@@ -40,7 +42,7 @@ const sizeOf = defmulti((x) => x.type, {}, {
40
42
  for (let f of struct.fields) {
41
43
  size = align(size, f.__align);
42
44
  f.__offset = size;
43
- size += sizeOf(f, types);
45
+ size += sizeOf(f, types, opts);
44
46
  }
45
47
  return (type.__size = align(size, type.__align));
46
48
  },
@@ -51,7 +53,9 @@ const alignOf = defmulti((x) => x.type, {}, {
51
53
  return field.__align;
52
54
  let align = isNumeric(field.type)
53
55
  ? SIZEOF[field.type]
54
- : alignOf(types[field.type], types);
56
+ : isWasmString(field.type)
57
+ ? USIZE_SIZE
58
+ : alignOf(types[field.type], types);
55
59
  if (field.tag === "vec") {
56
60
  align *= ceilPow2(field.len);
57
61
  }
@@ -71,13 +75,13 @@ const alignOf = defmulti((x) => x.type, {}, {
71
75
  },
72
76
  });
73
77
  const prepareType = defmulti((x) => x.type, {}, {
74
- [DEFAULT]: (x, types) => {
78
+ [DEFAULT]: (x, types, opts) => {
75
79
  if (x.__align && x.__size)
76
80
  return;
77
81
  alignOf(x, types);
78
- sizeOf(x, types);
82
+ sizeOf(x, types, opts);
79
83
  },
80
- struct: (x, types) => {
84
+ struct: (x, types, opts) => {
81
85
  if (x.__align && x.__size)
82
86
  return;
83
87
  const struct = x;
@@ -87,30 +91,58 @@ const prepareType = defmulti((x) => x.type, {}, {
87
91
  }
88
92
  for (let f of struct.fields) {
89
93
  if (types[f.type]) {
90
- prepareType(types[f.type], types);
94
+ prepareType(types[f.type], types, opts);
91
95
  }
92
96
  }
93
- sizeOf(struct, types);
97
+ sizeOf(struct, types, opts);
94
98
  },
95
99
  });
96
- export const prepareTypes = (types) => {
100
+ /**
101
+ * Takes a type collection and analyzes each analyzed to compute individual
102
+ * alignments and sizes.
103
+ *
104
+ * @remarks
105
+ * This function is idempotent and called automatically by
106
+ * {@link generateTypes}. Only exported for dev/debug purposes.
107
+ *
108
+ * @param types
109
+ *
110
+ * @internal
111
+ */
112
+ export const prepareTypes = (types, opts) => {
97
113
  for (let id in types) {
98
- prepareType(types[id], types);
114
+ prepareType(types[id], types, opts);
99
115
  }
100
116
  };
117
+ /**
118
+ * Code generator main entry point. Takes an object of {@link TopLevelType}
119
+ * definitions, an actual code generator implementation for a single target
120
+ * language and (optional) global codegen options. Returns generated source code
121
+ * for all given types as a single string.
122
+ *
123
+ * @remarks
124
+ * Before actual code generation the types are first analyzed to compute their
125
+ * alignments and sizes. This is only ever done once (idempotent), even if
126
+ * `generateTypes()` is called multiple times for different target langs.
127
+ *
128
+ * @param types
129
+ * @param codegen
130
+ * @param opts
131
+ */
101
132
  export const generateTypes = (types, codegen, opts = {}) => {
102
- prepareTypes(types);
133
+ const $opts = { stringType: "slice", ...opts };
134
+ prepareTypes(types, $opts);
103
135
  const res = [];
104
136
  codegen.doc(`Generated by ${PKG_NAME} at ${new Date().toISOString()} - DO NOT EDIT!`, "", res, true);
105
137
  res.push("");
106
138
  codegen.pre && res.push(codegen.pre, "");
107
- opts.pre && res.push(opts.pre, "");
139
+ $opts.pre && res.push($opts.pre, "");
108
140
  for (let id in types) {
109
141
  const type = types[id];
110
142
  type.doc && codegen.doc(type.doc, "", res);
111
143
  codegen[type.type](type, types, res);
112
144
  }
113
- opts.post && res.push("", opts.post);
145
+ $opts.post && res.push("", $opts.post);
114
146
  codegen.post && res.push("", codegen.post);
115
147
  return res.join("\n");
116
148
  };