@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/CHANGELOG.md +37 -1
- package/README.md +336 -39
- package/api.d.ts +91 -14
- package/bin/wasm-api +12 -0
- package/bridge.d.ts +4 -3
- package/bridge.js +6 -3
- package/cli.d.ts +5 -0
- package/cli.js +142 -0
- package/codegen/typescript.d.ts +13 -0
- package/codegen/typescript.js +65 -22
- package/codegen/utils.d.ts +29 -4
- package/codegen/utils.js +27 -4
- package/codegen/zig.d.ts +3 -0
- package/codegen/zig.js +5 -6
- package/codegen.d.ts +38 -1
- package/codegen.js +48 -16
- package/include/wasmapi.h +6 -0
- package/include/wasmapi.zig +6 -0
- package/index.d.ts +2 -2
- package/index.js +2 -2
- package/package.json +15 -11
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,
|
|
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
|
|
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
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
|
+
}
|
package/codegen/typescript.d.ts
CHANGED
|
@@ -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
|
package/codegen/typescript.js
CHANGED
|
@@ -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,
|
|
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 } = {
|
|
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
|
-
|
|
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
|
|
87
|
+
const isPrim = isWasmPrim(f.type);
|
|
88
|
+
const isStr = isWasmString(f.type);
|
|
83
89
|
if (f.tag === "ptr") {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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)}
|
|
90
|
-
|
|
91
|
-
${I3}return mem.${f.type}.subarray(addr, addr + len);`
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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 (
|
|
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) =>
|
|
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 = (
|
|
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 * ${
|
|
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
|
+
]);
|
package/codegen/utils.d.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
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
|
-
|
|
42
|
+
let ftype = f.type === "string" ? "[]const u8" : f.type;
|
|
43
43
|
switch (f.tag) {
|
|
44
44
|
case "array":
|
|
45
|
-
ftype = `[${f.len}]${
|
|
45
|
+
ftype = `[${f.len}]${ftype}`;
|
|
46
46
|
break;
|
|
47
47
|
case "slice":
|
|
48
|
-
ftype = `[]${
|
|
48
|
+
ftype = `[]${ftype}`;
|
|
49
49
|
break;
|
|
50
50
|
case "vec":
|
|
51
|
-
ftype = `@Vector(${f.len}, ${
|
|
51
|
+
ftype = `@Vector(${f.len}, ${ftype})`;
|
|
52
52
|
break;
|
|
53
53
|
case "ptr":
|
|
54
|
-
ftype = `*${f.len ? `[${f.len}]` : ""}${
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|