UNPKG

@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
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 };