@thi.ng/wasm-api-bindgen
Version:
Polyglot bindings code generators (TS/JS, Zig, C11) for hybrid WebAssembly projects
325 lines (324 loc) • 8.73 kB
JavaScript
import { topoSort } from "@thi.ng/arrays/topo-sort";
import { isString } from "@thi.ng/checks/is-string";
import { unsupported } from "@thi.ng/errors/unsupported";
import { capitalize } from "@thi.ng/strings/case";
import { classifyField } from "./internal/classify.js";
import {
ensureStringArray,
enumName,
isOpaque,
isPadding,
isStringSlice,
isStruct,
isUnion,
isWasmString,
prefixLines,
sliceTypes,
withIndentation
} from "./internal/utils.js";
const PRIM_ALIASES = {
i8: "int8_t",
u8: "uint8_t",
i16: "int16_t",
u16: "uint16_t",
i32: "int32_t",
u32: "uint32_t",
i64: "int64_t",
u64: "uint64_t",
f32: "float",
f64: "double"
};
const C11 = (opts = {}) => {
const { typePrefix = "" } = opts;
const INDENT = " ";
const SCOPES = [/\{$/, /^\}[ A-Za-z0-9_]*[;,]?$/];
const gen = {
id: "c",
pre: (coll, opts2) => {
const res = [
"#pragma once",
"",
"#ifdef __cplusplus",
`extern "C" {`,
"#endif",
opts2.debug ? "\n#include <stdalign.h>" : "",
`#include "wasmapi.h"`,
""
];
const slices = sliceTypes(coll);
for (let id of slices) {
const prim = PRIM_ALIASES[id];
if (!prim) continue;
res.push(
...__sliceDef(prim, typePrefix, capitalize(id), coll)
);
}
for (let id of __declOrder(coll)) {
const type = coll[id];
const name = __prefixedName(typePrefix, type.name, coll);
if (type.type == "funcptr") {
res.push(
__funcptr(type, coll, opts2, typePrefix)
);
} else if (type.type !== "ext") {
res.push(`
typedef ${type.type} ${name} ${name};`);
}
if (slices.has(id)) {
const [ptr, name2] = id === "opaque" ? ["void*", "Opaque"] : [
__prefixedName(typePrefix, id, coll),
capitalize(id)
];
res.push(...__sliceDef(ptr, typePrefix, name2, coll));
}
}
if (opts2.pre) res.push("", ...ensureStringArray(opts2.pre));
return res.join("\n");
},
post: () => {
const res = [];
if (opts.post) res.push(...ensureStringArray(opts.post), "");
res.push("#ifdef __cplusplus", "}", "#endif", "");
return res.join("\n");
},
doc: (doc, acc, opts2) => {
acc.push(...prefixLines("// ", doc, opts2.lineWidth));
},
ext: (e, _, acc) => {
acc.push(
`// external type: ${e.name} (size: ${e.size}, align: ${e.align})
`
);
},
enum: (e, _, acc, opts2) => {
if (!(e.tag === "i32" || e.tag === "u32")) {
unsupported(
`enum ${e.name} must be a i32/u32 in C, but got '${e.tag}'`
);
}
const name = typePrefix + e.name;
const lines = [];
lines.push(`enum ${name} {`);
for (let v of e.values) {
let line;
if (!isString(v)) {
v.doc && gen.doc(v.doc, lines, opts2);
line = enumName(opts2, v.name);
if (v.value != null) line += ` = ${v.value}`;
} else {
line = enumName(opts2, v);
}
lines.push(line + ",");
}
lines.push(`};`, "");
acc.push(...withIndentation(lines, INDENT, ...SCOPES));
},
struct: (struct, coll, acc, opts2) => __structOrUnion(
gen,
struct,
coll,
acc,
opts2,
typePrefix,
INDENT,
SCOPES
),
union: (union, coll, acc, opts2) => __structOrUnion(
gen,
union,
coll,
acc,
opts2,
typePrefix,
INDENT,
SCOPES
),
// funcpointers are emitted in `pre` phase above
funcptr: () => {
}
};
return gen;
};
const __structOrUnion = (gen, spec, coll, acc, opts, prefix, indent, scopes) => acc.push(
...withIndentation(
[
`${spec.type} ${prefix + spec.name} {`,
...__generateFields(gen, spec, coll, opts, prefix)
],
indent,
...scopes
)
);
const __generateFields = (gen, parent, coll, opts, typePrefix) => {
const res = [];
const ftypes = {};
const isUnion2 = parent.type === "union";
const name = typePrefix + parent.name;
let padID = 0;
for (let f of parent.fields) {
if (isPadding(f)) {
res.push(`uint8_t __pad${padID++}[${f.pad}];`);
continue;
}
f.doc && gen.doc(f.doc, res, opts);
const { type, decl, sentinel } = __fieldType(f, coll, opts, typePrefix);
ftypes[f.name] = type;
res.push(decl + ";");
if (sentinel) {
res.push(
`// Hidden sentinel. Must be manually initialized to ${f.sentinel || 0}`,
`${sentinel} __${f.name}Sentinel;`
);
}
}
res.push("};");
if (opts.debug) {
const fn = (fname, body) => res.push(
"",
`size_t __attribute__((used)) ${name}_${fname}() {`,
`return ${body};`,
`}`
);
fn("align", `alignof(${name})`);
fn("size", `sizeof(${name})`);
for (let f of parent.fields) {
if (isPadding(f)) continue;
fn(f.name + "_align", `alignof(${ftypes[f.name]})`);
!isUnion2 && fn(f.name + "_offset", `offsetof(${name}, ${f.name})`);
fn(f.name + "_size", `sizeof(${ftypes[f.name]})`);
}
}
res.push("");
return res;
};
const __fieldType = (f, coll, opts, prefix) => {
let type = f.type;
let decl;
let sentinel;
const { classifier, isConst } = classifyField(f, coll);
const $isConst = isConst ? "Const" : "";
const __ptr = () => {
decl = `${type}* ${f.name}`;
type = `${type}*`;
};
const __array = () => {
decl = `${type} ${f.name}[${f.len}]`;
type = `${type}[${f.len}]`;
};
const __slice = () => {
type += "Slice";
decl = `${type} ${f.name}`;
};
if (isWasmString(f.type)) {
const useStrSlice = isStringSlice(opts.stringType);
type = prefix + (useStrSlice ? `${$isConst}String` : `${$isConst}StringPtr`);
switch (classifier) {
case "strPtr":
case "strPtrFixed":
case "strPtrMulti":
__ptr();
break;
case "strSlice":
__slice();
break;
case "strArray":
__array();
break;
default:
decl = `${type} ${f.name}`;
}
} else if (isOpaque(f.type)) {
type = `${prefix}${$isConst}OpaquePtr`;
switch (classifier) {
case "opaquePtr":
case "opaquePtrFixed":
case "opaquePtrMulti":
__ptr();
break;
case "opaqueSlice":
__slice();
break;
case "opaqueArray":
__array();
break;
default:
decl = `${type} ${f.name}`;
}
} else {
const $const = isConst ? "const " : "";
type = PRIM_ALIASES[type] || __prefixedName(prefix, type, coll);
switch (classifier) {
case "ptr":
case "ptrFixed":
case "ptrMulti":
case "enumPtr":
case "enumPtrFixed":
case "enumPtrMulti":
type = `${$const}${type}*`;
decl = `${type} ${f.name}`;
break;
case "slice":
case "enumSlice":
type = __sliceName(prefix, f.type, coll, isConst);
decl = `${type} ${f.name}`;
break;
case "array":
case "enumArray":
__array();
if (f.sentinel != null) {
sentinel = PRIM_ALIASES[f.type];
}
break;
case "vec":
unsupported("C doesn't support vector");
default:
decl = `${type} ${f.name}`;
}
}
return {
type,
decl,
sentinel
};
};
const __declOrder = (coll) => topoSort(coll, (type) => {
const fields = isStruct(type) || isUnion(type) ? type.fields : type.type === "funcptr" ? type.args : void 0;
return fields ? fields.map((x) => x.type).filter((x) => !!coll[x]) : void 0;
});
const __funcptr = (ptr, coll, opts, typePrefix) => {
const name = __prefixedName(typePrefix, ptr.name, coll);
const args = ptr.args.map((a) => __fieldType(a, coll, opts, typePrefix).decl).join(", ");
const rtype = ptr.rtype === "void" ? ptr.rtype : __fieldType(
{ name: "return", ...ptr.rtype },
coll,
opts,
typePrefix
).type;
return `typedef ${rtype} (*${name})(${args});`;
};
const __sliceDef = (ptr, prefix, name, coll) => [
"",
`typedef struct { ${ptr}* ptr; size_t len; } ${__sliceName(
prefix,
name,
coll,
false
)};`,
`typedef struct { const ${ptr}* ptr; size_t len; } ${__sliceName(
prefix,
name,
coll,
true
)};`
];
const __sliceName = (prefix, name, coll, isConst) => `${__prefixedName(
prefix,
capitalize(name),
coll,
isConst ? "Const" : ""
)}Slice`;
const __prefixedName = (prefix, name, coll, qualifier = "") => __prefix(prefix, name, coll) + qualifier + name;
const __prefix = (prefix, name, coll) => coll[name]?.type !== "ext" ? prefix : "";
export {
C11
};