@thi.ng/wasm-api-bindgen
Version:
Polyglot bindings code generators (TS/JS, Zig, C11) for hybrid WebAssembly projects
219 lines (218 loc) • 6.26 kB
JavaScript
import { SIZEOF } from "@thi.ng/api/typedarray";
import { compareByKey } from "@thi.ng/compare/keys";
import { compareNumDesc } from "@thi.ng/compare/numeric";
import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
import {
PKG_NAME,
WASM32
} from "./api.js";
import { selectAlignment } from "./internal/align.js";
import {
ensureStringArray,
isBigNumeric,
isNumeric,
isOpaque,
isPointer,
isPointerLike,
isSizeT,
isSlice,
isStringSlice,
isWasmString
} from "./internal/utils.js";
const DEFAULT_CODEGEN_OPTS = {
debug: false,
header: true,
lineWidth: 80,
stringType: "ptr",
target: WASM32,
uppercaseEnums: true
};
const __sizeOf = defmulti(
(x) => x.type,
{},
{
[DEFAULT]: (field, coll, align, opts) => {
if (field.__size) return field.__size;
if (field.pad != null) {
field.pad < 1 && illegalArgs(`pad size must be > 0`);
return field.__size = field.pad;
}
let size = 0;
if (isPointer(field.tag)) {
size = opts.target.sizeBytes;
} else if (isSlice(field.tag)) {
size = opts.target.sizeBytes * 2;
} else {
size = isNumeric(field.type) || isBigNumeric(field.type) ? SIZEOF[field.type] : isWasmString(field.type) ? opts.target.sizeBytes * (isStringSlice(opts.stringType) ? 2 : 1) : isOpaque(field.type) ? opts.target.sizeBytes : __sizeOf(coll[field.type], coll, align, opts);
if (field.tag == "array" || field.tag === "vec") {
size *= field.len;
if (field.sentinel !== void 0 && field.tag === "array") {
size += SIZEOF[field.type];
}
}
}
return field.__size = align.size(size, field.__align);
},
ext: (type) => type.__size = type.size,
enum: (type) => {
if (type.__size) return type.__size;
return type.__size = SIZEOF[type.tag];
},
struct: (type, coll, align, opts) => {
if (type.__size) return type.__size;
let offset = 0;
for (let f of type.fields) {
offset = align.offset(offset, f.__align);
f.__offset = offset;
offset += __sizeOf(f, coll, align, opts);
}
return type.__size = align.size(offset, type.__align);
},
union: (type, coll, align, opts) => {
if (type.__size) return type.__size;
let maxSize = 0;
for (let f of type.fields) {
f.__offset = 0;
maxSize = Math.max(maxSize, __sizeOf(f, coll, align, opts));
}
return type.__size = align.size(maxSize, type.__align);
},
funcptr: (type, _, __, opts) => {
return type.__size || (type.__size = opts.target.sizeBytes);
}
}
);
const __alignOf = defmulti(
(x) => x.type,
{},
{
[DEFAULT]: (field, coll, align, opts) => {
if (field.__align) return field.__align;
if (field.pad) return field.__align = 1;
if (isSizeT(field.type)) {
field.type = opts.target[field.type];
}
return field.__align = isPointerLike(field, coll) ? align.align({ type: opts.target.usize }) : isNumeric(field.type) || isBigNumeric(field.type) ? align.align(field) : __alignOf(
coll[field.type],
coll,
selectAlignment(coll[field.type]),
opts
);
},
ext: (type) => {
const e = type;
return e.__align = e.align;
},
enum: (type, _, align) => {
const e = type;
if (!e.tag) e.tag = "i32";
return e.__align = align.align({
type: e.tag
});
},
struct: (type, coll, align, opts) => {
let maxAlign = 1;
for (let f of type.fields) {
maxAlign = Math.max(maxAlign, __alignOf(f, coll, align, opts));
}
return type.__align = maxAlign;
},
union: (type, coll, align, opts) => {
let maxAlign = 1;
for (let f of type.fields) {
maxAlign = Math.max(maxAlign, __alignOf(f, coll, align, opts));
}
return type.__align = maxAlign;
},
funcptr: (type, coll, align, opts) => {
if (type.__align) return type.__align;
const ptr = type;
if (ptr.rtype !== "void") {
__sizeOf(ptr.rtype, coll, align, opts);
}
for (let a of ptr.args) {
__alignOf(a, coll, align, opts);
}
return type.__align = align.align({
type: opts.target.usize
});
}
}
);
const __prepareType = defmulti(
(x) => x.type,
{},
{
[DEFAULT]: (x, coll, alignImpl, opts) => {
if (x.__align) return;
__alignOf(x, coll, alignImpl, opts);
__sizeOf(x, coll, alignImpl, opts);
},
struct: (x, coll, align, opts) => {
if (x.__align) return;
const struct = x;
__alignOf(struct, coll, align, opts);
if (struct.auto) {
struct.fields.sort(
compareByKey("__align", compareNumDesc)
);
}
for (let f of struct.fields) {
const type = coll[f.type];
if (type) {
__prepareType(type, coll, selectAlignment(type), opts);
}
}
__sizeOf(struct, coll, align, opts);
}
}
);
const prepareTypes = (coll, opts) => {
for (let id in coll) {
__prepareType(coll[id], coll, selectAlignment(coll[id]), opts);
}
return coll;
};
const generateTypes = (coll, codegen, opts = {}) => {
const $opts = {
...DEFAULT_CODEGEN_OPTS,
...opts
};
prepareTypes(coll, $opts);
const res = [];
if ($opts.header) {
codegen.doc(
[
`Generated by ${PKG_NAME} at ${(/* @__PURE__ */ new Date()).toISOString()}`,
"DO NOT EDIT!"
],
res,
$opts,
true
);
res.push("");
}
if (codegen.pre) {
const pre = codegen.pre(coll, $opts);
pre && res.push(pre, "");
}
$opts.pre && res.push(...ensureStringArray($opts.pre), "");
for (let id in coll) {
const type = coll[id];
if (type.skip?.includes(codegen.id)) continue;
type.doc && codegen.doc(type.doc, res, $opts);
codegen[type.type](type, coll, res, $opts);
}
$opts.post && res.push("", ...ensureStringArray($opts.post));
if (codegen.post) {
const post = codegen.post(coll, $opts);
post && res.push("", post);
}
return res.join("\n");
};
export {
DEFAULT_CODEGEN_OPTS,
generateTypes,
prepareTypes
};