@thi.ng/wasm-api 0.10.0 → 0.12.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 +77 -1
- package/README.md +248 -170
- package/api.d.ts +98 -14
- package/bin/wasm-api.mjs_ +17 -0
- package/bridge.d.ts +11 -1
- package/bridge.js +15 -10
- package/cli.js +51 -9
- package/codegen/c.d.ts +33 -0
- package/codegen/c.js +100 -0
- package/codegen/c11.d.ts +22 -0
- package/codegen/c11.js +125 -0
- package/codegen/typescript.d.ts +2 -12
- package/codegen/typescript.js +119 -88
- package/codegen/utils.d.ts +53 -4
- package/codegen/utils.js +63 -5
- package/codegen/zig.d.ts +2 -9
- package/codegen/zig.js +74 -29
- package/codegen.d.ts +1 -23
- package/codegen.js +35 -8
- package/include/wasmapi.h +34 -20
- package/include/wasmapi.zig +53 -46
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/package.json +32 -19
- package/pointer.d.ts +23 -0
- package/pointer.js +27 -0
- package/string.d.ts +96 -0
- package/string.js +145 -0
package/codegen/typescript.d.ts
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import { ICodeGen } from "../api.js";
|
|
1
|
+
import { CodeGenOptsBase, ICodeGen } from "../api.js";
|
|
2
2
|
/**
|
|
3
3
|
* TypeScript code generator options.
|
|
4
4
|
*/
|
|
5
|
-
export interface TSOpts {
|
|
5
|
+
export interface TSOpts extends CodeGenOptsBase {
|
|
6
6
|
/**
|
|
7
7
|
* Indentation string
|
|
8
8
|
*
|
|
9
9
|
* @defaultValue "\t"
|
|
10
10
|
*/
|
|
11
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";
|
|
22
12
|
}
|
|
23
13
|
/**
|
|
24
14
|
* 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
3
|
import { PKG_NAME, USIZE, USIZE_SIZE, } from "../api.js";
|
|
4
|
-
import { isBigNumeric, isNumeric, isWasmPrim, isWasmString, prefixLines, } from "./utils.js";
|
|
4
|
+
import { enumName, isBigNumeric, isNumeric, isPadding, isStringSlice, isWasmPrim, isWasmString, pointerFields, prefixLines, stringFields, withIndentation, } 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).
|
|
@@ -13,156 +13,185 @@ import { isBigNumeric, isNumeric, isWasmPrim, isWasmString, prefixLines, } from
|
|
|
13
13
|
*
|
|
14
14
|
* @param opts
|
|
15
15
|
*/
|
|
16
|
-
export const TYPESCRIPT = (opts) => {
|
|
17
|
-
const { indent
|
|
16
|
+
export const TYPESCRIPT = (opts = {}) => {
|
|
17
|
+
const { indent } = {
|
|
18
18
|
indent: "\t",
|
|
19
19
|
stringType: "slice",
|
|
20
|
-
uppercaseEnums: true,
|
|
21
20
|
...opts,
|
|
22
21
|
};
|
|
23
|
-
const
|
|
24
|
-
const I2 = I + I;
|
|
25
|
-
const I3 = I2 + I;
|
|
22
|
+
const SCOPES = [/\{$/, /\}\)?[;,]?$/];
|
|
26
23
|
const gen = {
|
|
27
|
-
pre:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
else {
|
|
33
|
-
acc.push(`${indent}/** ${doc} */`);
|
|
34
|
-
}
|
|
24
|
+
pre: (opts) => `// @ts-ignore possibly includes unused imports
|
|
25
|
+
import { Pointer, ${__stringImpl(opts)}, WasmTypeBase, WasmTypeConstructor } from "${PKG_NAME}";${opts.pre ? `\n${opts.pre}` : ""}`,
|
|
26
|
+
post: () => opts.post || "",
|
|
27
|
+
doc: (doc, acc, opts) => {
|
|
28
|
+
acc.push("/**", ...prefixLines(" * ", doc, opts.lineWidth), " */");
|
|
35
29
|
},
|
|
36
|
-
enum: (
|
|
37
|
-
const
|
|
38
|
-
|
|
30
|
+
enum: (e, _, acc, opts) => {
|
|
31
|
+
const res = [];
|
|
32
|
+
res.push(`export enum ${e.name} {`);
|
|
39
33
|
for (let v of e.values) {
|
|
40
|
-
let line
|
|
34
|
+
let line;
|
|
41
35
|
if (!isString(v)) {
|
|
42
|
-
v.doc && gen.doc(v.doc,
|
|
43
|
-
line
|
|
36
|
+
v.doc && gen.doc(v.doc, res, opts);
|
|
37
|
+
line = enumName(opts, v.name);
|
|
44
38
|
if (v.value != null)
|
|
45
39
|
line += ` = ${v.value}`;
|
|
46
40
|
}
|
|
47
41
|
else {
|
|
48
|
-
line
|
|
42
|
+
line = enumName(opts, v);
|
|
49
43
|
}
|
|
50
|
-
|
|
44
|
+
res.push(line + ",");
|
|
51
45
|
}
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
res.push("}", "");
|
|
47
|
+
acc.push(...withIndentation(res, indent, ...SCOPES));
|
|
54
48
|
},
|
|
55
|
-
struct: (
|
|
56
|
-
const
|
|
57
|
-
const
|
|
49
|
+
struct: (struct, types, acc, opts) => {
|
|
50
|
+
const fieldTypes = {};
|
|
51
|
+
const $stringImpl = __stringImpl(opts);
|
|
52
|
+
const lines = [];
|
|
58
53
|
// interface definition
|
|
59
|
-
|
|
54
|
+
lines.push(`export interface ${struct.name} extends WasmTypeBase {`);
|
|
60
55
|
for (let f of struct.fields) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 + ";");
|
|
56
|
+
if (isPadding(f))
|
|
57
|
+
continue;
|
|
58
|
+
f.doc && gen.doc(f.doc, lines, opts);
|
|
59
|
+
const ftype = __fieldType(f, opts);
|
|
60
|
+
fieldTypes[f.name] = ftype;
|
|
61
|
+
lines.push(`${f.name}: ${ftype};`);
|
|
80
62
|
}
|
|
81
|
-
|
|
63
|
+
lines.push("}", "");
|
|
64
|
+
const pointerDecls = pointerFields(struct.fields).map((x) => {
|
|
65
|
+
return `let $${x.name}: ${fieldTypes[x.name]} | null = null;`;
|
|
66
|
+
});
|
|
67
|
+
const stringDecls = stringFields(struct.fields).map((x) => {
|
|
68
|
+
const suffix = x.tag === "array" || x.tag === "slice" ? "[]" : "";
|
|
69
|
+
return `let $${x.name}: ${$stringImpl}${suffix} | null = null;`;
|
|
70
|
+
});
|
|
82
71
|
// type implementation
|
|
83
|
-
|
|
72
|
+
lines.push(`export const $${struct.name}: WasmTypeConstructor<${struct.name}> = (mem) => ({`, `get align() {`, `return ${struct.__align};`, `},`, `get size() {`, `return ${struct.__size};`, `},`, `instance: (base) => {`, ...pointerDecls, ...stringDecls, `return {`, `get __base() {`, `return base;`, `},`, `get __bytes() {`, `return mem.u8.subarray(base, base + ${struct.__size});`, `},`);
|
|
84
73
|
for (let f of struct.fields) {
|
|
74
|
+
// skip explicit padding fields
|
|
75
|
+
if (isPadding(f))
|
|
76
|
+
continue;
|
|
77
|
+
const ftype = fieldTypes[f.name];
|
|
85
78
|
const offset = f.__offset || 0;
|
|
86
|
-
|
|
79
|
+
lines.push(`get ${f.name}(): ${ftype} {`);
|
|
87
80
|
const isPrim = isWasmPrim(f.type);
|
|
88
81
|
const isStr = isWasmString(f.type);
|
|
82
|
+
const isConst = isStr ? f.const !== false : !!f.const;
|
|
89
83
|
if (f.tag === "ptr") {
|
|
90
84
|
if (isPrim) {
|
|
91
|
-
|
|
85
|
+
const fn = f.len
|
|
86
|
+
? `(base) => mem.${f.type}.subarray(${__addrShift(0, f.type)}, (${__addrShift(0, f.type)}) + ${f.len})`
|
|
87
|
+
: `(base) => mem.${f.type}[${__addrShift(0, f.type)}]`;
|
|
88
|
+
lines.push(`return $${f.name} || ($${f.name} = new ${ftype}(mem, ${__addr(offset)}, ${fn}));`);
|
|
92
89
|
}
|
|
93
90
|
else if (isStr) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
const fn = f.len
|
|
92
|
+
? [
|
|
93
|
+
`(addr) => {`,
|
|
94
|
+
...__mapStringArray("buf", $stringImpl, f.len, isConst, true),
|
|
95
|
+
`}`,
|
|
96
|
+
]
|
|
97
|
+
: [
|
|
98
|
+
`(addr) => new ${$stringImpl}(mem, addr, ${isConst})`,
|
|
99
|
+
];
|
|
100
|
+
lines.push(`return $${f.name} || ($${f.name} = new ${ftype}(mem, ${__addr(offset)},`, ...fn, `));`);
|
|
99
101
|
}
|
|
100
102
|
else {
|
|
101
|
-
|
|
103
|
+
const fn = f.len
|
|
104
|
+
? [`(addr) => {`, ...__mapArray(f, f.len), `}`]
|
|
105
|
+
: [`(addr) => new $${f.type}.instance(addr)`];
|
|
106
|
+
lines.push(`return $${f.name} || ($${f.name} = new ${ftype}(mem, ${__addr(offset)},`, ...fn, `));`);
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
else if (f.tag === "slice") {
|
|
105
|
-
|
|
110
|
+
lines.push(`const len = ${__ptr(offset + 4)};`);
|
|
106
111
|
if (isPrim) {
|
|
107
|
-
|
|
112
|
+
lines.push(`const addr = ${__ptrShift(offset, f.type)};`, `return mem.${f.type}.subarray(addr, addr + len);`);
|
|
108
113
|
}
|
|
109
114
|
else if (isStr) {
|
|
110
|
-
|
|
115
|
+
lines.push(`const addr = ${__ptr(offset)};`, ...__mapStringArray("buf", $stringImpl, "len", isConst));
|
|
111
116
|
}
|
|
112
117
|
else {
|
|
113
|
-
|
|
118
|
+
lines.push(`const addr = ${__ptr(offset)};`, ...__mapArray(f));
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
else if (f.tag === "array" || f.tag === "vec") {
|
|
117
122
|
if (isPrim) {
|
|
118
|
-
|
|
123
|
+
lines.push(`const addr = ${__addrShift(offset, f.type)};`, `return mem.${f.type}.subarray(addr, addr + ${f.len});`);
|
|
119
124
|
}
|
|
120
125
|
else if (isStr) {
|
|
121
|
-
|
|
126
|
+
lines.push(`if ($${f.name}) return $${f.name};`, `const addr = ${__addr(offset)};`, ...__mapStringArray(f.name, $stringImpl, f.len, isConst));
|
|
122
127
|
}
|
|
123
128
|
else {
|
|
124
|
-
|
|
129
|
+
lines.push(`const addr = ${__addr(offset)};`, ...__mapArray(f, f.len));
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
132
|
else {
|
|
128
133
|
let setter;
|
|
129
134
|
if (isPrim) {
|
|
130
135
|
const addr = __mem(f.type, f.__offset);
|
|
131
|
-
|
|
136
|
+
lines.push(`return ${addr};`);
|
|
132
137
|
setter = `${addr} = x`;
|
|
133
138
|
}
|
|
134
139
|
else if (isStr) {
|
|
135
|
-
|
|
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
|
+
lines.push(`return $${f.name} || ($${f.name} = new ${$stringImpl}(mem, ${__addr(offset)}, ${isConst}));`);
|
|
140
141
|
}
|
|
141
142
|
else if (types[f.type].type === "enum") {
|
|
142
143
|
const tag = types[f.type].tag;
|
|
143
144
|
const addr = __mem(tag, f.__offset);
|
|
144
|
-
|
|
145
|
+
lines.push(`return ${addr};`);
|
|
145
146
|
setter = `${addr} = x`;
|
|
146
147
|
}
|
|
147
148
|
else {
|
|
148
|
-
|
|
149
|
+
lines.push(`return $${f.type}(mem).instance(${__addr(offset)});`);
|
|
149
150
|
setter = `mem.u8.set(x.__bytes, ${__addr(offset)})`;
|
|
150
151
|
}
|
|
151
|
-
// close getter
|
|
152
|
-
acc.push(`${I2}},`);
|
|
153
152
|
// setter
|
|
154
|
-
|
|
153
|
+
if (setter) {
|
|
154
|
+
lines.push(
|
|
155
|
+
// close getter
|
|
156
|
+
`},`, `set ${f.name}(x: ${fieldTypes[f.name]}) {`, `${setter};`);
|
|
157
|
+
}
|
|
155
158
|
}
|
|
156
159
|
// close field accessor
|
|
157
|
-
|
|
160
|
+
lines.push(`},`);
|
|
158
161
|
}
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
lines.push("};", "}", "});", "");
|
|
163
|
+
acc.push(...withIndentation(lines, indent, ...SCOPES));
|
|
161
164
|
},
|
|
162
165
|
};
|
|
163
166
|
return gen;
|
|
164
167
|
};
|
|
165
168
|
/** @internal */
|
|
169
|
+
const __stringImpl = (opts) => isStringSlice(opts.stringType) ? "WasmStringSlice" : "WasmStringPtr";
|
|
170
|
+
/** @internal */
|
|
171
|
+
const __fieldType = (f, opts) => {
|
|
172
|
+
const ftype = f.tag == "array" ||
|
|
173
|
+
f.tag == "slice" ||
|
|
174
|
+
f.tag === "vec" ||
|
|
175
|
+
(f.tag === "ptr" && f.len)
|
|
176
|
+
? isNumeric(f.type)
|
|
177
|
+
? TYPEDARRAY_CTORS[f.type].name
|
|
178
|
+
: isBigNumeric(f.type)
|
|
179
|
+
? BIGINT_ARRAY_CTORS[f.type].name
|
|
180
|
+
: isWasmString(f.type)
|
|
181
|
+
? __stringImpl(opts) + "[]"
|
|
182
|
+
: f.type + "[]"
|
|
183
|
+
: !f.tag || f.tag === "scalar" || f.tag === "ptr"
|
|
184
|
+
? isBigNumeric(f.type)
|
|
185
|
+
? "bigint"
|
|
186
|
+
: isNumeric(f.type)
|
|
187
|
+
? "number"
|
|
188
|
+
: isWasmString(f.type)
|
|
189
|
+
? __stringImpl(opts)
|
|
190
|
+
: f.type
|
|
191
|
+
: f.type;
|
|
192
|
+
return f.tag === "ptr" ? `Pointer<${ftype}>` : ftype;
|
|
193
|
+
};
|
|
194
|
+
/** @internal */
|
|
166
195
|
const __shift = (type) => BIT_SHIFTS[type];
|
|
167
196
|
/** @internal */
|
|
168
197
|
const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
|
|
@@ -177,13 +206,15 @@ const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
|
|
|
177
206
|
const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
|
|
178
207
|
const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
|
|
179
208
|
/** @internal */
|
|
180
|
-
const __mapArray = (f,
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
209
|
+
const __mapArray = (f, len = "len") => [
|
|
210
|
+
`const inst = $${f.type}(mem);`,
|
|
211
|
+
`const slice: ${f.type}[] = [];`,
|
|
212
|
+
`for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));`,
|
|
213
|
+
`return slice;`,
|
|
214
|
+
];
|
|
184
215
|
/** @internal */
|
|
185
|
-
const __mapStringArray = (
|
|
186
|
-
|
|
187
|
-
`for(let i = 0; i < ${len}; i++)
|
|
188
|
-
|
|
189
|
-
]
|
|
216
|
+
const __mapStringArray = (name, type, len, isConst, isLocal = false) => [
|
|
217
|
+
isLocal ? `const $${name}: ${type}[] = [];` : `$${name} = [];`,
|
|
218
|
+
`for(let i = 0; i < ${len}; i++) $${name}.push(new ${type}(mem, addr + i * ${USIZE_SIZE * (type === "WasmStringSlice" ? 2 : 1)}, ${isConst}));`,
|
|
219
|
+
`return $${name};`,
|
|
220
|
+
];
|
package/codegen/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BigType } from "@thi.ng/api";
|
|
2
|
-
import type { WasmPrim, WasmPrim32 } from "../api.js";
|
|
2
|
+
import type { CodeGenOpts, StructField, WasmPrim, WasmPrim32 } from "../api.js";
|
|
3
3
|
/**
|
|
4
4
|
* Returns true iff `x` is a {@link WasmPrim32}.
|
|
5
5
|
*
|
|
@@ -19,12 +19,61 @@ export declare const isBigNumeric: (x: string) => x is BigType;
|
|
|
19
19
|
*/
|
|
20
20
|
export declare const isWasmPrim: (x: string) => x is WasmPrim;
|
|
21
21
|
export declare const isWasmString: (x: string) => x is "string";
|
|
22
|
+
export declare const isPadding: (f: StructField) => boolean;
|
|
22
23
|
/**
|
|
23
|
-
* Takes an array of strings or splits given string into lines,
|
|
24
|
-
* line with given `
|
|
24
|
+
* Takes an array of strings or splits given string into lines, word wraps and
|
|
25
|
+
* then prefixes each line with given `width` and `prefix`. Returns array of new
|
|
26
|
+
* lines.
|
|
25
27
|
*
|
|
26
28
|
* @param prefix
|
|
27
29
|
* @param str
|
|
30
|
+
* @param width
|
|
28
31
|
*/
|
|
29
|
-
export declare const prefixLines: (prefix: string, str: string | string[]) => string;
|
|
32
|
+
export declare const prefixLines: (prefix: string, str: string | string[], width: number) => string[];
|
|
33
|
+
/**
|
|
34
|
+
* Returns true if `type` is "slice".
|
|
35
|
+
*
|
|
36
|
+
* @param type
|
|
37
|
+
*/
|
|
38
|
+
export declare const isStringSlice: (type: CodeGenOpts["stringType"]) => type is "slice";
|
|
39
|
+
/**
|
|
40
|
+
* Returns filtered array of struct fields of with "ptr" tag.
|
|
41
|
+
*
|
|
42
|
+
* @param fields
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export declare const pointerFields: (fields: StructField[]) => StructField[];
|
|
47
|
+
/**
|
|
48
|
+
* Returns filtered array of struct fields of only "string" fields.
|
|
49
|
+
*
|
|
50
|
+
* @param fields
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export declare const stringFields: (fields: StructField[]) => StructField[];
|
|
55
|
+
/**
|
|
56
|
+
* Returns enum identifier formatted according to given opts.
|
|
57
|
+
*
|
|
58
|
+
* @param opts
|
|
59
|
+
* @param name
|
|
60
|
+
*
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export declare const enumName: (opts: CodeGenOpts, name: string) => string;
|
|
64
|
+
/**
|
|
65
|
+
* Yields iterator of given lines, each with applied indentation based on given
|
|
66
|
+
* scope regexp's which are applied to each line to increase or decrease
|
|
67
|
+
* indentation level (the initial indentation level can be specified via
|
|
68
|
+
* optional `level` arg, default 0). If `scopeStart` succeeds, the indent is
|
|
69
|
+
* increased for the _next_ line. If `scopeEnd` succeeds the level is decreased
|
|
70
|
+
* for the _current_ line. ...
|
|
71
|
+
*
|
|
72
|
+
* @param lines
|
|
73
|
+
* @param indent
|
|
74
|
+
* @param scopeStart
|
|
75
|
+
* @param scopeEnd
|
|
76
|
+
* @param level
|
|
77
|
+
*/
|
|
78
|
+
export declare function withIndentation(lines: string[], indent: string, scopeStart: RegExp, scopeEnd: RegExp, level?: number): Generator<string, void, unknown>;
|
|
30
79
|
//# sourceMappingURL=utils.d.ts.map
|
package/codegen/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isString } from "@thi.ng/checks/is-string";
|
|
2
|
+
import { wordWrapLine, wordWrapLines } from "@thi.ng/strings/word-wrap";
|
|
2
3
|
/**
|
|
3
4
|
* Returns true iff `x` is a {@link WasmPrim32}.
|
|
4
5
|
*
|
|
@@ -18,13 +19,70 @@ export const isBigNumeric = (x) => /^[iu]64$/.test(x);
|
|
|
18
19
|
*/
|
|
19
20
|
export const isWasmPrim = (x) => isNumeric(x) || isBigNumeric(x);
|
|
20
21
|
export const isWasmString = (x) => x === "string";
|
|
22
|
+
export const isPadding = (f) => f.pad != null && f.pad > 0;
|
|
21
23
|
/**
|
|
22
|
-
* Takes an array of strings or splits given string into lines,
|
|
23
|
-
* line with given `
|
|
24
|
+
* Takes an array of strings or splits given string into lines, word wraps and
|
|
25
|
+
* then prefixes each line with given `width` and `prefix`. Returns array of new
|
|
26
|
+
* lines.
|
|
24
27
|
*
|
|
25
28
|
* @param prefix
|
|
26
29
|
* @param str
|
|
30
|
+
* @param width
|
|
27
31
|
*/
|
|
28
|
-
export const prefixLines = (prefix, str) => (isString(str)
|
|
29
|
-
|
|
30
|
-
.
|
|
32
|
+
export const prefixLines = (prefix, str, width) => (isString(str)
|
|
33
|
+
? wordWrapLines(str, { width: width - prefix.length })
|
|
34
|
+
: str.flatMap((x) => wordWrapLine(x, { width: width - prefix.length }))).map((line) => prefix + line);
|
|
35
|
+
/**
|
|
36
|
+
* Returns true if `type` is "slice".
|
|
37
|
+
*
|
|
38
|
+
* @param type
|
|
39
|
+
*/
|
|
40
|
+
export const isStringSlice = (type) => type === "slice";
|
|
41
|
+
/**
|
|
42
|
+
* Returns filtered array of struct fields of with "ptr" tag.
|
|
43
|
+
*
|
|
44
|
+
* @param fields
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export const pointerFields = (fields) => fields.filter((f) => f.tag === "ptr");
|
|
49
|
+
/**
|
|
50
|
+
* Returns filtered array of struct fields of only "string" fields.
|
|
51
|
+
*
|
|
52
|
+
* @param fields
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export const stringFields = (fields) => fields.filter((f) => isWasmString(f.type) && f.tag !== "ptr");
|
|
57
|
+
/**
|
|
58
|
+
* Returns enum identifier formatted according to given opts.
|
|
59
|
+
*
|
|
60
|
+
* @param opts
|
|
61
|
+
* @param name
|
|
62
|
+
*
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export const enumName = (opts, name) => opts.uppercaseEnums ? name.toUpperCase() : name;
|
|
66
|
+
/**
|
|
67
|
+
* Yields iterator of given lines, each with applied indentation based on given
|
|
68
|
+
* scope regexp's which are applied to each line to increase or decrease
|
|
69
|
+
* indentation level (the initial indentation level can be specified via
|
|
70
|
+
* optional `level` arg, default 0). If `scopeStart` succeeds, the indent is
|
|
71
|
+
* increased for the _next_ line. If `scopeEnd` succeeds the level is decreased
|
|
72
|
+
* for the _current_ line. ...
|
|
73
|
+
*
|
|
74
|
+
* @param lines
|
|
75
|
+
* @param indent
|
|
76
|
+
* @param scopeStart
|
|
77
|
+
* @param scopeEnd
|
|
78
|
+
* @param level
|
|
79
|
+
*/
|
|
80
|
+
export function* withIndentation(lines, indent, scopeStart, scopeEnd, level = 0) {
|
|
81
|
+
const stack = new Array(level).fill(indent);
|
|
82
|
+
for (let l of lines) {
|
|
83
|
+
scopeEnd.test(l) && stack.pop();
|
|
84
|
+
const curr = stack.length ? stack[stack.length - 1] : "";
|
|
85
|
+
yield curr + l;
|
|
86
|
+
scopeStart.test(l) && stack.push(curr + indent);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/codegen/zig.d.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import type { ICodeGen } from "../api.js";
|
|
1
|
+
import type { CodeGenOptsBase, ICodeGen } from "../api.js";
|
|
2
2
|
/**
|
|
3
3
|
* Zig code generator options.
|
|
4
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;
|
|
5
|
+
export interface ZigOpts extends CodeGenOptsBase {
|
|
13
6
|
}
|
|
14
7
|
/**
|
|
15
8
|
* Zig code generator. Call with options and then pass to {@link generateTypes}
|
package/codegen/zig.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { isNumber } from "@thi.ng/checks/is-number";
|
|
1
2
|
import { isString } from "@thi.ng/checks/is-string";
|
|
2
|
-
import {
|
|
3
|
+
import { unsupported } from "@thi.ng/errors/unsupported";
|
|
4
|
+
import { split } from "@thi.ng/strings/split";
|
|
5
|
+
import { enumName, isPadding, isStringSlice, prefixLines, withIndentation, } from "./utils.js";
|
|
3
6
|
/**
|
|
4
7
|
* Zig code generator. Call with options and then pass to {@link generateTypes}
|
|
5
8
|
* (see its docs for further usage).
|
|
@@ -10,64 +13,106 @@ import { prefixLines } from "./utils.js";
|
|
|
10
13
|
*
|
|
11
14
|
* @param opts
|
|
12
15
|
*/
|
|
13
|
-
export const ZIG = (opts) => {
|
|
14
|
-
const
|
|
16
|
+
export const ZIG = (opts = {}) => {
|
|
17
|
+
const INDENT = " ";
|
|
18
|
+
const SCOPES = [/\{$/, /\}\)?[;,]?$/];
|
|
15
19
|
const gen = {
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
pre: () => `const std = @import("std");${opts.pre ? `\n${opts.pre}` : ""}`,
|
|
21
|
+
post: () => opts.post || "",
|
|
22
|
+
doc: (doc, acc, opts, topLevel = false) => {
|
|
23
|
+
acc.push(...prefixLines(topLevel ? "//! " : "/// ", doc, opts.lineWidth));
|
|
18
24
|
},
|
|
19
|
-
enum: (e, _, acc) => {
|
|
20
|
-
|
|
25
|
+
enum: (e, _, acc, opts) => {
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(`pub const ${e.name} = enum(${e.tag}) {`);
|
|
21
28
|
for (let v of e.values) {
|
|
22
|
-
let line
|
|
29
|
+
let line;
|
|
23
30
|
if (!isString(v)) {
|
|
24
|
-
v.doc && gen.doc(v.doc,
|
|
25
|
-
line
|
|
31
|
+
v.doc && gen.doc(v.doc, lines, opts);
|
|
32
|
+
line = enumName(opts, v.name);
|
|
26
33
|
if (v.value != null)
|
|
27
34
|
line += ` = ${v.value}`;
|
|
28
35
|
}
|
|
29
36
|
else {
|
|
30
|
-
line
|
|
37
|
+
line = enumName(opts, v);
|
|
31
38
|
}
|
|
32
|
-
|
|
39
|
+
lines.push(line + ",");
|
|
33
40
|
}
|
|
34
|
-
|
|
41
|
+
if (e.body?.zig) {
|
|
42
|
+
lines.push("", ...split(e.body.zig), "");
|
|
43
|
+
}
|
|
44
|
+
lines.push("};", "");
|
|
45
|
+
acc.push(...withIndentation(lines, INDENT, ...SCOPES));
|
|
35
46
|
},
|
|
36
|
-
struct: (struct, _, acc) => {
|
|
47
|
+
struct: (struct, _, acc, opts) => {
|
|
37
48
|
const name = struct.name;
|
|
38
|
-
|
|
49
|
+
const res = [];
|
|
50
|
+
res.push(`pub const ${name} = ${struct.tag ? struct.tag + " " : ""}struct {`);
|
|
39
51
|
const ftypes = {};
|
|
52
|
+
let padID = 0;
|
|
40
53
|
for (let f of struct.fields) {
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
// autolabel explicit padding fields
|
|
55
|
+
if (isPadding(f)) {
|
|
56
|
+
res.push(`__pad${padID++}: [${f.pad}]u8,`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
f.doc && gen.doc(f.doc, res, opts);
|
|
60
|
+
let ftype = f.type === "string"
|
|
61
|
+
? isStringSlice(opts.stringType)
|
|
62
|
+
? f.const !== false
|
|
63
|
+
? "[]const u8"
|
|
64
|
+
: "[]u8"
|
|
65
|
+
: f.const !== false
|
|
66
|
+
? "[*:0]const u8"
|
|
67
|
+
: "[*:0]u8"
|
|
68
|
+
: f.type;
|
|
69
|
+
let defaultVal = "";
|
|
43
70
|
switch (f.tag) {
|
|
44
71
|
case "array":
|
|
45
|
-
ftype =
|
|
72
|
+
ftype =
|
|
73
|
+
f.sentinel !== undefined
|
|
74
|
+
? `[${f.len}:${f.sentinel}]${ftype}`
|
|
75
|
+
: `[${f.len}]${ftype}`;
|
|
46
76
|
break;
|
|
47
77
|
case "slice":
|
|
48
|
-
ftype = `[]${ftype}`;
|
|
78
|
+
ftype = `[${f.sentinel !== undefined ? ":" + f.sentinel : ""}]${f.const ? "const " : ""}${ftype}`;
|
|
49
79
|
break;
|
|
50
80
|
case "vec":
|
|
51
81
|
ftype = `@Vector(${f.len}, ${ftype})`;
|
|
52
82
|
break;
|
|
53
83
|
case "ptr":
|
|
54
|
-
ftype = `*${f.len ? `[${f.len}]` : ""}${ftype}`;
|
|
84
|
+
ftype = `*${f.const ? "const " : ""}${f.len ? `[${f.len}]` : ""}${ftype}`;
|
|
55
85
|
break;
|
|
56
86
|
case "scalar":
|
|
57
87
|
default:
|
|
88
|
+
if (f.default != undefined) {
|
|
89
|
+
if (!(isString(f.default) || isNumber(f.default))) {
|
|
90
|
+
unsupported(`wrong default value for ${name}.${f.name} (${f.default})`);
|
|
91
|
+
}
|
|
92
|
+
defaultVal = ` = ${JSON.stringify(f.default)}`;
|
|
93
|
+
}
|
|
58
94
|
}
|
|
59
95
|
ftypes[f.name] = ftype;
|
|
60
|
-
|
|
96
|
+
res.push(`${f.name}: ${ftype}${defaultVal},`);
|
|
61
97
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
if (struct.body?.zig) {
|
|
99
|
+
res.push("", ...split(struct.body.zig), "");
|
|
100
|
+
}
|
|
101
|
+
res.push("};");
|
|
102
|
+
if (opts.debug) {
|
|
103
|
+
const fn = (fname, body) => res.push("", `export fn ${name}_${fname}() usize {`, `return ${body};`, `}`);
|
|
104
|
+
fn("align", `@alignOf(${name})`);
|
|
105
|
+
fn("size", `@sizeOf(${name})`);
|
|
106
|
+
for (let f of struct.fields) {
|
|
107
|
+
if (isPadding(f))
|
|
108
|
+
continue;
|
|
109
|
+
fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`);
|
|
110
|
+
fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`);
|
|
111
|
+
fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`);
|
|
112
|
+
}
|
|
69
113
|
}
|
|
70
|
-
|
|
114
|
+
res.push("");
|
|
115
|
+
acc.push(...withIndentation(res, INDENT, ...SCOPES));
|
|
71
116
|
},
|
|
72
117
|
};
|
|
73
118
|
return gen;
|