@thi.ng/wasm-api 0.5.0 → 0.8.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 +60 -1
- package/README.md +425 -16
- package/api.d.ts +217 -3
- package/api.js +9 -1
- package/bin/wasm-api +12 -0
- package/bridge.d.ts +41 -7
- package/bridge.js +45 -9
- package/cli.d.ts +5 -0
- package/cli.js +142 -0
- package/codegen/typescript.d.ts +35 -0
- package/codegen/typescript.js +189 -0
- package/codegen/utils.d.ts +30 -0
- package/codegen/utils.js +30 -0
- package/codegen/zig.d.ts +25 -0
- package/codegen/zig.js +75 -0
- package/codegen.d.ts +53 -0
- package/codegen.js +148 -0
- package/doc/assets/main.js +52 -0
- package/doc/assets/search.js +1 -0
- package/include/wasmapi.h +60 -0
- package/{zig/core.zig → include/wasmapi.zig} +45 -32
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/package.json +31 -7
- package/dev/custom.zig +0 -12
- package/dev/fieldinfo.zig +0 -135
- package/dev/hello.zig +0 -14
- package/dev/zig-cache/o/0fd683610fe16c12563bf410950c8193/builtin.zig +0 -39
- package/test/custom.zig +0 -16
- package/test/zig-cache/o/117d44467ded6ce3864cde3c180ef73c/builtin.zig +0 -39
|
@@ -0,0 +1,189 @@
|
|
|
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, USIZE_SIZE, } from "../api.js";
|
|
4
|
+
import { isBigNumeric, isNumeric, isWasmPrim, isWasmString, 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, stringType, uppercaseEnums } = {
|
|
18
|
+
indent: "\t",
|
|
19
|
+
stringType: "slice",
|
|
20
|
+
uppercaseEnums: true,
|
|
21
|
+
...opts,
|
|
22
|
+
};
|
|
23
|
+
const I = indent;
|
|
24
|
+
const I2 = I + I;
|
|
25
|
+
const I3 = I2 + I;
|
|
26
|
+
const gen = {
|
|
27
|
+
pre: `import type { WasmTypeBase, WasmTypeConstructor } from "${PKG_NAME}";`,
|
|
28
|
+
doc: (doc, indent, acc) => {
|
|
29
|
+
if (doc.indexOf("\n") !== -1) {
|
|
30
|
+
acc.push(indent + "/**", prefixLines(indent + " * ", doc), indent + " */");
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
acc.push(`${indent}/** ${doc} */`);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
enum: (type, _, acc) => {
|
|
37
|
+
const e = type;
|
|
38
|
+
acc.push(`export enum ${e.name} {`);
|
|
39
|
+
for (let v of e.values) {
|
|
40
|
+
let line = indent;
|
|
41
|
+
if (!isString(v)) {
|
|
42
|
+
v.doc && gen.doc(v.doc, indent, acc);
|
|
43
|
+
line += uppercaseEnums ? v.name.toUpperCase() : v.name;
|
|
44
|
+
if (v.value != null)
|
|
45
|
+
line += ` = ${v.value}`;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
line += uppercaseEnums ? v.toUpperCase() : v;
|
|
49
|
+
}
|
|
50
|
+
acc.push(line + ",");
|
|
51
|
+
}
|
|
52
|
+
acc.push("}\n");
|
|
53
|
+
return acc;
|
|
54
|
+
},
|
|
55
|
+
struct: (type, types, acc) => {
|
|
56
|
+
const struct = type;
|
|
57
|
+
const returnTypes = {};
|
|
58
|
+
// interface definition
|
|
59
|
+
acc.push(`export interface ${struct.name} extends WasmTypeBase {`);
|
|
60
|
+
for (let f of struct.fields) {
|
|
61
|
+
f.doc && gen.doc(f.doc, indent, acc);
|
|
62
|
+
let line = `${indent}${f.name}: `;
|
|
63
|
+
let rtype = "";
|
|
64
|
+
if (f.tag == "array" || f.tag == "slice" || f.tag === "vec") {
|
|
65
|
+
rtype = isNumeric(f.type)
|
|
66
|
+
? TYPEDARRAY_CTORS[f.type].name
|
|
67
|
+
: isBigNumeric(f.type)
|
|
68
|
+
? BIGINT_ARRAY_CTORS[f.type].name
|
|
69
|
+
: f.type + "[]";
|
|
70
|
+
}
|
|
71
|
+
else if (!f.tag || f.tag === "scalar" || f.tag === "ptr") {
|
|
72
|
+
rtype = isBigNumeric(f.type)
|
|
73
|
+
? "bigint"
|
|
74
|
+
: isNumeric(f.type)
|
|
75
|
+
? "number"
|
|
76
|
+
: f.type;
|
|
77
|
+
}
|
|
78
|
+
returnTypes[f.name] = rtype;
|
|
79
|
+
acc.push(line + rtype + ";");
|
|
80
|
+
}
|
|
81
|
+
acc.push("}\n");
|
|
82
|
+
// type implementation
|
|
83
|
+
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}); },`);
|
|
84
|
+
for (let f of struct.fields) {
|
|
85
|
+
const offset = f.__offset || 0;
|
|
86
|
+
acc.push(`${I2}get ${f.name}(): ${returnTypes[f.name]} {`);
|
|
87
|
+
const isPrim = isWasmPrim(f.type);
|
|
88
|
+
const isStr = isWasmString(f.type);
|
|
89
|
+
if (f.tag === "ptr") {
|
|
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
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (f.tag === "slice") {
|
|
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
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (f.tag === "array" || f.tag === "vec") {
|
|
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
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
let setter;
|
|
129
|
+
if (isPrim) {
|
|
130
|
+
const addr = __mem(f.type, f.__offset);
|
|
131
|
+
acc.push(`${I3}return ${addr};`);
|
|
132
|
+
setter = `${addr} = x`;
|
|
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
|
+
}
|
|
141
|
+
else if (types[f.type].type === "enum") {
|
|
142
|
+
const tag = types[f.type].tag;
|
|
143
|
+
const addr = __mem(tag, f.__offset);
|
|
144
|
+
acc.push(`${I3}return ${addr};`);
|
|
145
|
+
setter = `${addr} = x`;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
acc.push(`${I3}return $${f.type}(mem).instance(${__addr(offset)});`);
|
|
149
|
+
setter = `mem.u8.set(x.__bytes, ${__addr(offset)})`;
|
|
150
|
+
}
|
|
151
|
+
// close getter
|
|
152
|
+
acc.push(`${I2}},`);
|
|
153
|
+
// setter
|
|
154
|
+
acc.push(`${I2}set ${f.name}(x: ${returnTypes[f.name]}) {`, `${I3}${setter};`);
|
|
155
|
+
}
|
|
156
|
+
// close field accessor
|
|
157
|
+
acc.push(`${I2}},`);
|
|
158
|
+
}
|
|
159
|
+
acc.push(`${I}})\n});\n`);
|
|
160
|
+
return acc;
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
return gen;
|
|
164
|
+
};
|
|
165
|
+
/** @internal */
|
|
166
|
+
const __shift = (type) => BIT_SHIFTS[type];
|
|
167
|
+
/** @internal */
|
|
168
|
+
const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
|
|
169
|
+
/** @internal */
|
|
170
|
+
const __addrShift = (offset, shift) => {
|
|
171
|
+
const bits = __shift(shift);
|
|
172
|
+
return __addr(offset) + (bits ? " >>> " + bits : "");
|
|
173
|
+
};
|
|
174
|
+
/** @internal */
|
|
175
|
+
const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
|
|
176
|
+
/** @internal */
|
|
177
|
+
const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
|
|
178
|
+
const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
|
|
179
|
+
/** @internal */
|
|
180
|
+
const __mapArray = (f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
|
|
181
|
+
const slice: ${f.type}[] = [];
|
|
182
|
+
for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));
|
|
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
|
+
]);
|
|
@@ -0,0 +1,30 @@
|
|
|
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;
|
|
30
|
+
//# sourceMappingURL=utils.d.ts.map
|
package/codegen/utils.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
*/
|
|
13
|
+
export const isBigNumeric = (x) => /^[iu]64$/.test(x);
|
|
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)
|
|
29
|
+
.map((line) => prefix + line)
|
|
30
|
+
.join("\n");
|
package/codegen/zig.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ICodeGen } from "../api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Zig code generator options.
|
|
4
|
+
*/
|
|
5
|
+
export interface ZigOpts {
|
|
6
|
+
/**
|
|
7
|
+
* If true, generates various struct & struct field analysis functions
|
|
8
|
+
* (sizes, alignment, offsets etc.).
|
|
9
|
+
*
|
|
10
|
+
* @defaultValue false
|
|
11
|
+
*/
|
|
12
|
+
debug: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Zig code generator. Call with options and then pass to {@link generateTypes}
|
|
16
|
+
* (see its docs for further usage).
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* This codegen generates struct and enum definitions for a {@link TypeColl}
|
|
20
|
+
* given to {@link generateTypes}.
|
|
21
|
+
*
|
|
22
|
+
* @param opts
|
|
23
|
+
*/
|
|
24
|
+
export declare const ZIG: (opts?: Partial<ZigOpts>) => ICodeGen;
|
|
25
|
+
//# 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
|
+
let ftype = f.type === "string" ? "[]const u8" : f.type;
|
|
43
|
+
switch (f.tag) {
|
|
44
|
+
case "array":
|
|
45
|
+
ftype = `[${f.len}]${ftype}`;
|
|
46
|
+
break;
|
|
47
|
+
case "slice":
|
|
48
|
+
// ftype = `[]${f.const ? "const" : ""}${ftype}`; // TODO
|
|
49
|
+
ftype = `[]${ftype}`;
|
|
50
|
+
break;
|
|
51
|
+
case "vec":
|
|
52
|
+
ftype = `@Vector(${f.len}, ${ftype})`;
|
|
53
|
+
break;
|
|
54
|
+
case "ptr":
|
|
55
|
+
ftype = `*${f.len ? `[${f.len}]` : ""}${ftype}`;
|
|
56
|
+
break;
|
|
57
|
+
case "scalar":
|
|
58
|
+
default:
|
|
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,53 @@
|
|
|
1
|
+
import { ICodeGen, TypeColl } from "./api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Global/shared code generator options.
|
|
4
|
+
*/
|
|
5
|
+
export interface CodeGenOpts {
|
|
6
|
+
/**
|
|
7
|
+
* Optional string to be injected before generated type defs (but after
|
|
8
|
+
* codegen's own prelude, if any)
|
|
9
|
+
*/
|
|
10
|
+
pre: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional string to be injected after generated type defs (but before
|
|
13
|
+
* codegen's own epilogue, if any)
|
|
14
|
+
*/
|
|
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";
|
|
23
|
+
}
|
|
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
|
+
*/
|
|
52
|
+
export declare const generateTypes: (types: TypeColl, codegen: ICodeGen, opts?: Partial<CodeGenOpts>) => string;
|
|
53
|
+
//# sourceMappingURL=codegen.d.ts.map
|
package/codegen.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { SIZEOF } from "@thi.ng/api/typedarray";
|
|
2
|
+
import { align } from "@thi.ng/binary/align";
|
|
3
|
+
import { ceilPow2 } from "@thi.ng/binary/pow";
|
|
4
|
+
import { compareByKey } from "@thi.ng/compare/keys";
|
|
5
|
+
import { compareNumDesc } from "@thi.ng/compare/numeric";
|
|
6
|
+
import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
|
|
7
|
+
import { PKG_NAME, USIZE_SIZE, } from "./api.js";
|
|
8
|
+
import { isNumeric, isWasmString } from "./codegen/utils.js";
|
|
9
|
+
const sizeOf = defmulti((x) => x.type, {}, {
|
|
10
|
+
[DEFAULT]: (field, types, opts) => {
|
|
11
|
+
if (field.__size)
|
|
12
|
+
return field.__size;
|
|
13
|
+
let size = 0;
|
|
14
|
+
if (field.tag === "ptr") {
|
|
15
|
+
size = USIZE_SIZE;
|
|
16
|
+
}
|
|
17
|
+
else if (field.tag === "slice") {
|
|
18
|
+
size = USIZE_SIZE * 2;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
size = isNumeric(field.type)
|
|
22
|
+
? SIZEOF[field.type]
|
|
23
|
+
: isWasmString(field.type)
|
|
24
|
+
? USIZE_SIZE * (opts.stringType === "slice" ? 2 : 1)
|
|
25
|
+
: sizeOf(types[field.type], types, opts);
|
|
26
|
+
if (field.tag == "array" || field.tag === "vec") {
|
|
27
|
+
size *= field.len;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return (field.__size = align(size, field.__align));
|
|
31
|
+
},
|
|
32
|
+
enum: (type) => {
|
|
33
|
+
if (type.__size)
|
|
34
|
+
return type.__size;
|
|
35
|
+
return (type.__size = SIZEOF[type.tag]);
|
|
36
|
+
},
|
|
37
|
+
struct: (type, types, opts) => {
|
|
38
|
+
if (type.__size)
|
|
39
|
+
return type.__size;
|
|
40
|
+
const struct = type;
|
|
41
|
+
let size = 0;
|
|
42
|
+
for (let f of struct.fields) {
|
|
43
|
+
size = align(size, f.__align);
|
|
44
|
+
f.__offset = size;
|
|
45
|
+
size += sizeOf(f, types, opts);
|
|
46
|
+
}
|
|
47
|
+
return (type.__size = align(size, type.__align));
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const alignOf = defmulti((x) => x.type, {}, {
|
|
51
|
+
[DEFAULT]: (field, types) => {
|
|
52
|
+
if (field.__align)
|
|
53
|
+
return field.__align;
|
|
54
|
+
let align = isNumeric(field.type)
|
|
55
|
+
? SIZEOF[field.type]
|
|
56
|
+
: isWasmString(field.type)
|
|
57
|
+
? USIZE_SIZE
|
|
58
|
+
: alignOf(types[field.type], types);
|
|
59
|
+
if (field.tag === "vec") {
|
|
60
|
+
align *= ceilPow2(field.len);
|
|
61
|
+
}
|
|
62
|
+
field.__align = align;
|
|
63
|
+
return align;
|
|
64
|
+
},
|
|
65
|
+
enum: (e) => {
|
|
66
|
+
return (e.__align = SIZEOF[e.tag]);
|
|
67
|
+
},
|
|
68
|
+
struct: (type, types) => {
|
|
69
|
+
const struct = type;
|
|
70
|
+
let maxAlign = 0;
|
|
71
|
+
for (let f of struct.fields) {
|
|
72
|
+
maxAlign = Math.max(maxAlign, alignOf(f, types));
|
|
73
|
+
}
|
|
74
|
+
return (type.__align = maxAlign);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const prepareType = defmulti((x) => x.type, {}, {
|
|
78
|
+
[DEFAULT]: (x, types, opts) => {
|
|
79
|
+
if (x.__align && x.__size)
|
|
80
|
+
return;
|
|
81
|
+
alignOf(x, types);
|
|
82
|
+
sizeOf(x, types, opts);
|
|
83
|
+
},
|
|
84
|
+
struct: (x, types, opts) => {
|
|
85
|
+
if (x.__align && x.__size)
|
|
86
|
+
return;
|
|
87
|
+
const struct = x;
|
|
88
|
+
alignOf(struct, types);
|
|
89
|
+
if (struct.auto) {
|
|
90
|
+
struct.fields.sort(compareByKey("__align", compareNumDesc));
|
|
91
|
+
}
|
|
92
|
+
for (let f of struct.fields) {
|
|
93
|
+
if (types[f.type]) {
|
|
94
|
+
prepareType(types[f.type], types, opts);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
sizeOf(struct, types, opts);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
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) => {
|
|
113
|
+
for (let id in types) {
|
|
114
|
+
prepareType(types[id], types, opts);
|
|
115
|
+
}
|
|
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
|
+
*/
|
|
132
|
+
export const generateTypes = (types, codegen, opts = {}) => {
|
|
133
|
+
const $opts = { stringType: "slice", ...opts };
|
|
134
|
+
prepareTypes(types, $opts);
|
|
135
|
+
const res = [];
|
|
136
|
+
codegen.doc(`Generated by ${PKG_NAME} at ${new Date().toISOString()} - DO NOT EDIT!`, "", res, true);
|
|
137
|
+
res.push("");
|
|
138
|
+
codegen.pre && res.push(codegen.pre, "");
|
|
139
|
+
$opts.pre && res.push($opts.pre, "");
|
|
140
|
+
for (let id in types) {
|
|
141
|
+
const type = types[id];
|
|
142
|
+
type.doc && codegen.doc(type.doc, "", res);
|
|
143
|
+
codegen[type.type](type, types, res);
|
|
144
|
+
}
|
|
145
|
+
$opts.post && res.push("", $opts.post);
|
|
146
|
+
codegen.post && res.push("", codegen.post);
|
|
147
|
+
return res.join("\n");
|
|
148
|
+
};
|