@thi.ng/wasm-api 0.4.0 → 0.7.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 +403 -16
- package/api.d.ts +213 -3
- package/api.js +9 -1
- package/bin/wasm-api +12 -0
- package/bridge.d.ts +78 -6
- package/bridge.js +96 -14
- package/cli.d.ts +5 -0
- package/cli.js +142 -0
- package/codegen/typescript.d.ts +26 -0
- package/codegen/typescript.js +153 -0
- package/codegen/utils.d.ts +29 -0
- package/codegen/utils.js +29 -0
- package/codegen/zig.d.ts +22 -0
- package/codegen/zig.js +75 -0
- package/codegen.d.ts +16 -0
- package/codegen.js +116 -0
- package/include/wasmapi.h +60 -0
- package/{zig/core.zig → include/wasmapi.zig} +76 -26
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/package.json +32 -7
- package/dev/custom.zig +0 -12
- package/dev/fieldinfo.zig +0 -135
- package/dev/hello.zig +0 -9
- package/dev/zig-cache/o/0fd683610fe16c12563bf410950c8193/builtin.zig +0 -39
- package/test/custom.zig +0 -12
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 { isPrim } 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 (!(isPrim(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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ICodeGen } from "../api.js";
|
|
2
|
+
export interface TSOpts {
|
|
3
|
+
/**
|
|
4
|
+
* Indentation string
|
|
5
|
+
*
|
|
6
|
+
* @defaultValue "\t"
|
|
7
|
+
*/
|
|
8
|
+
indent: string;
|
|
9
|
+
/**
|
|
10
|
+
* If true (default), forces uppercase enums
|
|
11
|
+
*/
|
|
12
|
+
uppercaseEnums: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* TypeScript code generator. Call with options and then pass to
|
|
16
|
+
* {@link generateTypes} (see its docs for further usage).
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* This codegen generates interface and enum definitions for a {@link TypeColl}
|
|
20
|
+
* given to {@link generateTypes}. For structs it will also generate memory
|
|
21
|
+
* mapped wrappers with fully typed accessors.
|
|
22
|
+
*
|
|
23
|
+
* @param opts
|
|
24
|
+
*/
|
|
25
|
+
export declare const TYPESCRIPT: (opts?: Partial<TSOpts>) => ICodeGen;
|
|
26
|
+
//# sourceMappingURL=typescript.d.ts.map
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray";
|
|
2
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
3
|
+
import { PKG_NAME, USIZE, } from "../api.js";
|
|
4
|
+
import { isBigNumeric, isNumeric, isPrim, prefixLines } from "./utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* TypeScript code generator. Call with options and then pass to
|
|
7
|
+
* {@link generateTypes} (see its docs for further usage).
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* This codegen generates interface and enum definitions for a {@link TypeColl}
|
|
11
|
+
* given to {@link generateTypes}. For structs it will also generate memory
|
|
12
|
+
* mapped wrappers with fully typed accessors.
|
|
13
|
+
*
|
|
14
|
+
* @param opts
|
|
15
|
+
*/
|
|
16
|
+
export const TYPESCRIPT = (opts) => {
|
|
17
|
+
const { indent, uppercaseEnums } = {
|
|
18
|
+
indent: "\t",
|
|
19
|
+
uppercaseEnums: true,
|
|
20
|
+
...opts,
|
|
21
|
+
};
|
|
22
|
+
const I = indent;
|
|
23
|
+
const I2 = I + I;
|
|
24
|
+
const I3 = I2 + I;
|
|
25
|
+
const gen = {
|
|
26
|
+
pre: `import type { WasmTypeBase, WasmTypeConstructor } from "${PKG_NAME}";`,
|
|
27
|
+
doc: (doc, indent, acc) => {
|
|
28
|
+
if (doc.indexOf("\n") !== -1) {
|
|
29
|
+
acc.push(indent + "/**", prefixLines(indent + " * ", doc), indent + " */");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
acc.push(`${indent}/** ${doc} */`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
enum: (type, _, acc) => {
|
|
36
|
+
const e = type;
|
|
37
|
+
acc.push(`export enum ${e.name} {`);
|
|
38
|
+
for (let v of e.values) {
|
|
39
|
+
var line = indent;
|
|
40
|
+
if (!isString(v)) {
|
|
41
|
+
v.doc && gen.doc(v.doc, indent, acc);
|
|
42
|
+
line += uppercaseEnums ? v.name.toUpperCase() : v.name;
|
|
43
|
+
if (v.value != null)
|
|
44
|
+
line += ` = ${v.value}`;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
line += uppercaseEnums ? v.toUpperCase() : v;
|
|
48
|
+
}
|
|
49
|
+
acc.push(line + ",");
|
|
50
|
+
}
|
|
51
|
+
acc.push("}\n");
|
|
52
|
+
return acc;
|
|
53
|
+
},
|
|
54
|
+
struct: (type, types, acc) => {
|
|
55
|
+
const struct = type;
|
|
56
|
+
const returnTypes = {};
|
|
57
|
+
// interface definition
|
|
58
|
+
acc.push(`export interface ${struct.name} extends WasmTypeBase {`);
|
|
59
|
+
for (let f of struct.fields) {
|
|
60
|
+
f.doc && gen.doc(f.doc, indent, acc);
|
|
61
|
+
let line = `${indent}${f.name}: `;
|
|
62
|
+
let rtype = "";
|
|
63
|
+
if (f.tag == "array" || f.tag == "slice" || f.tag === "vec") {
|
|
64
|
+
rtype = isNumeric(f.type)
|
|
65
|
+
? TYPEDARRAY_CTORS[f.type].name
|
|
66
|
+
: isBigNumeric(f.type)
|
|
67
|
+
? BIGINT_ARRAY_CTORS[f.type].name
|
|
68
|
+
: f.type + "[]";
|
|
69
|
+
}
|
|
70
|
+
else if (!f.tag || f.tag === "scalar" || f.tag === "ptr") {
|
|
71
|
+
rtype = isBigNumeric(f.type)
|
|
72
|
+
? "bigint"
|
|
73
|
+
: isNumeric(f.type)
|
|
74
|
+
? "number"
|
|
75
|
+
: f.type;
|
|
76
|
+
}
|
|
77
|
+
returnTypes[f.name] = rtype;
|
|
78
|
+
acc.push(line + rtype + ";");
|
|
79
|
+
}
|
|
80
|
+
acc.push("}\n");
|
|
81
|
+
// type implementation
|
|
82
|
+
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}); },`);
|
|
83
|
+
for (let f of struct.fields) {
|
|
84
|
+
const offset = f.__offset || 0;
|
|
85
|
+
acc.push(`${I2}get ${f.name}(): ${returnTypes[f.name]} {`);
|
|
86
|
+
const prim = isPrim(f.type);
|
|
87
|
+
if (f.tag === "ptr") {
|
|
88
|
+
acc.push(prim
|
|
89
|
+
? `${I3}return mem.${f.type}[${__ptrShift(offset, f.type)}];`
|
|
90
|
+
: `${I3}return $${f.type}.instance(${__ptr(offset)});`);
|
|
91
|
+
}
|
|
92
|
+
else if (f.tag === "slice") {
|
|
93
|
+
acc.push(`${I3}const len = ${__ptr(offset + 4)};`, prim
|
|
94
|
+
? `${I3}const addr = ${__ptrShift(offset, f.type)};
|
|
95
|
+
${I3}return mem.${f.type}.subarray(addr, addr + len);`
|
|
96
|
+
: `${I3}const addr = ${__ptr(offset)};\n${__mapArray(f, I3)}`);
|
|
97
|
+
}
|
|
98
|
+
else if (f.tag === "array" || f.tag === "vec") {
|
|
99
|
+
acc.push(prim
|
|
100
|
+
? `${I3}const addr = ${__addrShift(offset, f.type)};
|
|
101
|
+
${I3}return mem.${f.type}.subarray(addr, addr + ${f.len});`
|
|
102
|
+
: `${I3}const addr = ${__addr(offset)};\n${__mapArray(f, I3, f.len)}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
let setter;
|
|
106
|
+
if (prim) {
|
|
107
|
+
const addr = __mem(f.type, f.__offset);
|
|
108
|
+
acc.push(`${I3}return ${addr};`);
|
|
109
|
+
setter = `${addr} = x`;
|
|
110
|
+
}
|
|
111
|
+
else if (types[f.type].type === "enum") {
|
|
112
|
+
const tag = types[f.type].tag;
|
|
113
|
+
const addr = __mem(tag, f.__offset);
|
|
114
|
+
acc.push(`${I3}return ${addr};`);
|
|
115
|
+
setter = `${addr} = x`;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
acc.push(`${I3}return $${f.type}(mem).instance(${__addr(offset)});`);
|
|
119
|
+
setter = `mem.u8.set(x.__bytes, ${__addr(offset)})`;
|
|
120
|
+
}
|
|
121
|
+
// close getter
|
|
122
|
+
acc.push(`${I2}},`);
|
|
123
|
+
// setter
|
|
124
|
+
acc.push(`${I2}set ${f.name}(x: ${returnTypes[f.name]}) {`, `${I3}${setter};`);
|
|
125
|
+
}
|
|
126
|
+
// close field accessor
|
|
127
|
+
acc.push(`${I2}},`);
|
|
128
|
+
}
|
|
129
|
+
acc.push(`${I}})\n});\n`);
|
|
130
|
+
return acc;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
return gen;
|
|
134
|
+
};
|
|
135
|
+
/** @internal */
|
|
136
|
+
const __shift = (type) => BIT_SHIFTS[type];
|
|
137
|
+
/** @internal */
|
|
138
|
+
const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
|
|
139
|
+
/** @internal */
|
|
140
|
+
const __addrShift = (offset, shift) => {
|
|
141
|
+
const bits = __shift(shift);
|
|
142
|
+
return __addr(offset) + (bits ? " >>> " + bits : "");
|
|
143
|
+
};
|
|
144
|
+
/** @internal */
|
|
145
|
+
const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
|
|
146
|
+
/** @internal */
|
|
147
|
+
const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
|
|
148
|
+
const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
|
|
149
|
+
/** @internal */
|
|
150
|
+
const __mapArray = (f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
|
|
151
|
+
const slice: ${f.type}[] = [];
|
|
152
|
+
for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));
|
|
153
|
+
return slice;`);
|
|
@@ -0,0 +1,29 @@
|
|
|
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 isPrim: (x: string) => x is WasmPrim;
|
|
21
|
+
/**
|
|
22
|
+
* Splits given string into lines, prefixes each with given `prefix` and then
|
|
23
|
+
* returns rejoined result.
|
|
24
|
+
*
|
|
25
|
+
* @param prefix
|
|
26
|
+
* @param str
|
|
27
|
+
*/
|
|
28
|
+
export declare const prefixLines: (prefix: string, str: string) => string;
|
|
29
|
+
//# sourceMappingURL=utils.d.ts.map
|
package/codegen/utils.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true iff `x` is a {@link WasmPrim32}.
|
|
3
|
+
*
|
|
4
|
+
* @param x
|
|
5
|
+
*/
|
|
6
|
+
export const isNumeric = (x) => /^(([iu](8|16|32))|(f(32|64)))$/.test(x);
|
|
7
|
+
/**
|
|
8
|
+
* Returns true iff `x` is a `i64` or `u64`.
|
|
9
|
+
*
|
|
10
|
+
* @param x
|
|
11
|
+
*/
|
|
12
|
+
export const isBigNumeric = (x) => /^[iu]64$/.test(x);
|
|
13
|
+
/**
|
|
14
|
+
* Returns true iff `x` is a {@link WasmPrim}.
|
|
15
|
+
*
|
|
16
|
+
* @param x
|
|
17
|
+
*/
|
|
18
|
+
export const isPrim = (x) => isNumeric(x) || isBigNumeric(x);
|
|
19
|
+
/**
|
|
20
|
+
* Splits given string into lines, prefixes each with given `prefix` and then
|
|
21
|
+
* returns rejoined result.
|
|
22
|
+
*
|
|
23
|
+
* @param prefix
|
|
24
|
+
* @param str
|
|
25
|
+
*/
|
|
26
|
+
export const prefixLines = (prefix, str) => str
|
|
27
|
+
.split("\n")
|
|
28
|
+
.map((line) => prefix + line)
|
|
29
|
+
.join("\n");
|
package/codegen/zig.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ICodeGen } from "../api.js";
|
|
2
|
+
export interface ZigOpts {
|
|
3
|
+
/**
|
|
4
|
+
* If true, generates various struct & struct field analysis functions
|
|
5
|
+
* (sizes, alignment, offsets etc.).
|
|
6
|
+
*
|
|
7
|
+
* @defaultValue false
|
|
8
|
+
*/
|
|
9
|
+
debug: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Zig code generator. Call with options and then pass to {@link generateTypes}
|
|
13
|
+
* (see its docs for further usage).
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This codegen generates struct and enum definitions for a {@link TypeColl}
|
|
17
|
+
* given to {@link generateTypes}.
|
|
18
|
+
*
|
|
19
|
+
* @param opts
|
|
20
|
+
*/
|
|
21
|
+
export declare const ZIG: (opts?: Partial<ZigOpts>) => ICodeGen;
|
|
22
|
+
//# sourceMappingURL=zig.d.ts.map
|
package/codegen/zig.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
2
|
+
import { prefixLines } from "./utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Zig code generator. Call with options and then pass to {@link generateTypes}
|
|
5
|
+
* (see its docs for further usage).
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* This codegen generates struct and enum definitions for a {@link TypeColl}
|
|
9
|
+
* given to {@link generateTypes}.
|
|
10
|
+
*
|
|
11
|
+
* @param opts
|
|
12
|
+
*/
|
|
13
|
+
export const ZIG = (opts) => {
|
|
14
|
+
const { debug } = { debug: false, ...opts };
|
|
15
|
+
const gen = {
|
|
16
|
+
doc: (doc, indent, acc, topLevel = false) => {
|
|
17
|
+
acc.push(prefixLines(topLevel ? "//! " : indent + "/// ", doc));
|
|
18
|
+
},
|
|
19
|
+
enum: (e, _, acc) => {
|
|
20
|
+
acc.push(`pub const ${e.name} = enum(${e.tag}) {`);
|
|
21
|
+
for (let v of e.values) {
|
|
22
|
+
let line = ` `;
|
|
23
|
+
if (!isString(v)) {
|
|
24
|
+
v.doc && gen.doc(v.doc, " ", acc);
|
|
25
|
+
line += v.name;
|
|
26
|
+
if (v.value != null)
|
|
27
|
+
line += ` = ${v.value}`;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
line += v;
|
|
31
|
+
}
|
|
32
|
+
acc.push(line + ",");
|
|
33
|
+
}
|
|
34
|
+
acc.push("};\n");
|
|
35
|
+
},
|
|
36
|
+
struct: (struct, _, acc) => {
|
|
37
|
+
const name = struct.name;
|
|
38
|
+
acc.push(`pub const ${name} = struct {`);
|
|
39
|
+
const ftypes = {};
|
|
40
|
+
for (let f of struct.fields) {
|
|
41
|
+
f.doc && gen.doc(f.doc, " ", acc);
|
|
42
|
+
var ftype;
|
|
43
|
+
switch (f.tag) {
|
|
44
|
+
case "array":
|
|
45
|
+
ftype = `[${f.len}]${f.type}`;
|
|
46
|
+
break;
|
|
47
|
+
case "slice":
|
|
48
|
+
ftype = `[]${f.type}`;
|
|
49
|
+
break;
|
|
50
|
+
case "vec":
|
|
51
|
+
ftype = `@Vector(${f.len}, ${f.type})`;
|
|
52
|
+
break;
|
|
53
|
+
case "ptr":
|
|
54
|
+
ftype = `*${f.len ? `[${f.len}]` : ""}${f.type}`;
|
|
55
|
+
break;
|
|
56
|
+
case "scalar":
|
|
57
|
+
default:
|
|
58
|
+
ftype = f.type;
|
|
59
|
+
}
|
|
60
|
+
ftypes[f.name] = ftype;
|
|
61
|
+
acc.push(` ${f.name}: ${ftype},`);
|
|
62
|
+
}
|
|
63
|
+
acc.push("};\n");
|
|
64
|
+
if (!debug)
|
|
65
|
+
return;
|
|
66
|
+
const fn = (fname, body) => `export fn ${name}_${fname}() usize { return ${body}; }`;
|
|
67
|
+
acc.push(fn("align", `@alignOf(${name})`), fn("size", `@sizeOf(${name})`));
|
|
68
|
+
for (let f of struct.fields) {
|
|
69
|
+
acc.push(fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`), fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`), fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`));
|
|
70
|
+
}
|
|
71
|
+
acc.push("");
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
return gen;
|
|
75
|
+
};
|
package/codegen.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ICodeGen, TypeColl } from "./api.js";
|
|
2
|
+
export interface CodeGenOpts {
|
|
3
|
+
/**
|
|
4
|
+
* Optional string to be injected before generated type defs (but after
|
|
5
|
+
* codegen's own prelude, if any)
|
|
6
|
+
*/
|
|
7
|
+
pre: string;
|
|
8
|
+
/**
|
|
9
|
+
* Optional string to be injected after generated type defs (but before
|
|
10
|
+
* codegen's own epilogue, if any)
|
|
11
|
+
*/
|
|
12
|
+
post: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const prepareTypes: (types: TypeColl) => void;
|
|
15
|
+
export declare const generateTypes: (types: TypeColl, codegen: ICodeGen, opts?: Partial<CodeGenOpts>) => string;
|
|
16
|
+
//# sourceMappingURL=codegen.d.ts.map
|
package/codegen.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
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 } from "./codegen/utils.js";
|
|
9
|
+
const sizeOf = defmulti((x) => x.type, {}, {
|
|
10
|
+
[DEFAULT]: (field, types) => {
|
|
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
|
+
: sizeOf(types[field.type], types);
|
|
24
|
+
if (field.tag == "array" || field.tag === "vec") {
|
|
25
|
+
size *= field.len;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return (field.__size = align(size, field.__align));
|
|
29
|
+
},
|
|
30
|
+
enum: (type) => {
|
|
31
|
+
if (type.__size)
|
|
32
|
+
return type.__size;
|
|
33
|
+
return (type.__size = SIZEOF[type.tag]);
|
|
34
|
+
},
|
|
35
|
+
struct: (type, types) => {
|
|
36
|
+
if (type.__size)
|
|
37
|
+
return type.__size;
|
|
38
|
+
const struct = type;
|
|
39
|
+
let size = 0;
|
|
40
|
+
for (let f of struct.fields) {
|
|
41
|
+
size = align(size, f.__align);
|
|
42
|
+
f.__offset = size;
|
|
43
|
+
size += sizeOf(f, types);
|
|
44
|
+
}
|
|
45
|
+
return (type.__size = align(size, type.__align));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const alignOf = defmulti((x) => x.type, {}, {
|
|
49
|
+
[DEFAULT]: (field, types) => {
|
|
50
|
+
if (field.__align)
|
|
51
|
+
return field.__align;
|
|
52
|
+
let align = isNumeric(field.type)
|
|
53
|
+
? SIZEOF[field.type]
|
|
54
|
+
: alignOf(types[field.type], types);
|
|
55
|
+
if (field.tag === "vec") {
|
|
56
|
+
align *= ceilPow2(field.len);
|
|
57
|
+
}
|
|
58
|
+
field.__align = align;
|
|
59
|
+
return align;
|
|
60
|
+
},
|
|
61
|
+
enum: (e) => {
|
|
62
|
+
return (e.__align = SIZEOF[e.tag]);
|
|
63
|
+
},
|
|
64
|
+
struct: (type, types) => {
|
|
65
|
+
const struct = type;
|
|
66
|
+
let maxAlign = 0;
|
|
67
|
+
for (let f of struct.fields) {
|
|
68
|
+
maxAlign = Math.max(maxAlign, alignOf(f, types));
|
|
69
|
+
}
|
|
70
|
+
return (type.__align = maxAlign);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const prepareType = defmulti((x) => x.type, {}, {
|
|
74
|
+
[DEFAULT]: (x, types) => {
|
|
75
|
+
if (x.__align && x.__size)
|
|
76
|
+
return;
|
|
77
|
+
alignOf(x, types);
|
|
78
|
+
sizeOf(x, types);
|
|
79
|
+
},
|
|
80
|
+
struct: (x, types) => {
|
|
81
|
+
if (x.__align && x.__size)
|
|
82
|
+
return;
|
|
83
|
+
const struct = x;
|
|
84
|
+
alignOf(struct, types);
|
|
85
|
+
if (struct.auto) {
|
|
86
|
+
struct.fields.sort(compareByKey("__align", compareNumDesc));
|
|
87
|
+
}
|
|
88
|
+
for (let f of struct.fields) {
|
|
89
|
+
if (types[f.type]) {
|
|
90
|
+
prepareType(types[f.type], types);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
sizeOf(struct, types);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
export const prepareTypes = (types) => {
|
|
97
|
+
for (let id in types) {
|
|
98
|
+
prepareType(types[id], types);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
export const generateTypes = (types, codegen, opts = {}) => {
|
|
102
|
+
prepareTypes(types);
|
|
103
|
+
const res = [];
|
|
104
|
+
codegen.doc(`Generated by ${PKG_NAME} at ${new Date().toISOString()} - DO NOT EDIT!`, "", res, true);
|
|
105
|
+
res.push("");
|
|
106
|
+
codegen.pre && res.push(codegen.pre, "");
|
|
107
|
+
opts.pre && res.push(opts.pre, "");
|
|
108
|
+
for (let id in types) {
|
|
109
|
+
const type = types[id];
|
|
110
|
+
type.doc && codegen.doc(type.doc, "", res);
|
|
111
|
+
codegen[type.type](type, types, res);
|
|
112
|
+
}
|
|
113
|
+
opts.post && res.push("", opts.post);
|
|
114
|
+
codegen.post && res.push("", codegen.post);
|
|
115
|
+
return res.join("\n");
|
|
116
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#ifdef __cplusplus
|
|
4
|
+
extern "C" {
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
#include <stddef.h>
|
|
8
|
+
#include <stdint.h>
|
|
9
|
+
|
|
10
|
+
#define WASM_IMPORT(MODULE, TYPE, NAME, PREFIX) \
|
|
11
|
+
extern __attribute__((import_module(MODULE), import_name(#NAME))) \
|
|
12
|
+
TYPE PREFIX##NAME
|
|
13
|
+
#define WASM_KEEP __attribute__((used))
|
|
14
|
+
|
|
15
|
+
// Generate stubs only if explicitly disabled by defining this symbol
|
|
16
|
+
#ifdef WASMAPI_NO_MALLOC
|
|
17
|
+
size_t WASM_KEEP _wasm_allocate(size_t num_bytes) { return 0; }
|
|
18
|
+
void WASM_KEEP _wasm_free(size_t addr) {}
|
|
19
|
+
#else
|
|
20
|
+
#include <stdlib.h>
|
|
21
|
+
size_t WASM_KEEP _wasm_allocate(size_t numBytes) {
|
|
22
|
+
return (size_t)malloc(numBytes);
|
|
23
|
+
}
|
|
24
|
+
void WASM_KEEP _wasm_free(size_t addr) { free((void*)addr); }
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
WASM_IMPORT("wasmapi", void, printI8, wasm_)(int8_t x);
|
|
28
|
+
WASM_IMPORT("wasmapi", void, printU8, wasm_)(uint8_t x);
|
|
29
|
+
WASM_IMPORT("wasmapi", void, printU8Hex, wasm_)(uint8_t x);
|
|
30
|
+
WASM_IMPORT("wasmapi", void, printI16, wasm_)(int16_t x);
|
|
31
|
+
WASM_IMPORT("wasmapi", void, printU16, wasm_)(uint16_t x);
|
|
32
|
+
WASM_IMPORT("wasmapi", void, printU16Hex, wasm_)(uint16_t x);
|
|
33
|
+
WASM_IMPORT("wasmapi", void, printI32, wasm_)(int32_t x);
|
|
34
|
+
WASM_IMPORT("wasmapi", void, printU32, wasm_)(uint32_t x);
|
|
35
|
+
WASM_IMPORT("wasmapi", void, printU32Hex, wasm_)(uint32_t x);
|
|
36
|
+
WASM_IMPORT("wasmapi", void, printI64, wasm_)(int64_t x);
|
|
37
|
+
WASM_IMPORT("wasmapi", void, printU64, wasm_)(uint64_t x);
|
|
38
|
+
WASM_IMPORT("wasmapi", void, printU64Hex, wasm_)(uint64_t x);
|
|
39
|
+
WASM_IMPORT("wasmapi", void, printF32, wasm_)(float x);
|
|
40
|
+
WASM_IMPORT("wasmapi", void, printF64, wasm_)(double x);
|
|
41
|
+
|
|
42
|
+
WASM_IMPORT("wasmapi", void, _printI8Array, wasm)(void* addr, size_t len);
|
|
43
|
+
WASM_IMPORT("wasmapi", void, _printU8Array, wasm)(void* addr, size_t len);
|
|
44
|
+
WASM_IMPORT("wasmapi", void, _printI16Array, wasm)(void* addr, size_t len);
|
|
45
|
+
WASM_IMPORT("wasmapi", void, _printU16Array, wasm)(void* addr, size_t len);
|
|
46
|
+
WASM_IMPORT("wasmapi", void, _printI32Array, wasm)(void* addr, size_t len);
|
|
47
|
+
WASM_IMPORT("wasmapi", void, _printU32Array, wasm)(void* addr, size_t len);
|
|
48
|
+
WASM_IMPORT("wasmapi", void, _printI64Array, wasm)(void* addr, size_t len);
|
|
49
|
+
WASM_IMPORT("wasmapi", void, _printU64Array, wasm)(void* addr, size_t len);
|
|
50
|
+
WASM_IMPORT("wasmapi", void, _printF32Array, wasm)(void* addr, size_t len);
|
|
51
|
+
WASM_IMPORT("wasmapi", void, _printF64Array, wasm)(void* addr, size_t len);
|
|
52
|
+
|
|
53
|
+
WASM_IMPORT("wasmapi", void, _printStr0, wasm)(void* addr);
|
|
54
|
+
WASM_IMPORT("wasmapi", void, _printStr, wasm)(void* addr, size_t len);
|
|
55
|
+
|
|
56
|
+
void wasm_printPtr(void* ptr) { wasm_printU32Hex((size_t)ptr); }
|
|
57
|
+
|
|
58
|
+
#ifdef __cplusplus
|
|
59
|
+
}
|
|
60
|
+
#endif
|