UNPKG

@thi.ng/wasm-api-bindgen

Version:

Polyglot bindings code generators (TS/JS, Zig, C11) for hybrid WebAssembly projects

226 lines (225 loc) 6.47 kB
import { isNumber } from "@thi.ng/checks"; import { isString } from "@thi.ng/checks/is-string"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import { capitalize } from "@thi.ng/strings/case"; import { classifyField } from "./internal/classify.js"; import { defaultValue, ensureStringArray, injectBody, isOpaque, isPadding, isStringSlice, isUnion, isWasmString, prefixLines, sliceTypes, withIndentation } from "./internal/utils.js"; const ZIG = (opts = {}) => { const INDENT = " "; const SCOPES = [/\{$/, /(?<!\{.*)\}\)?[;,]?$/]; const gen = { id: "zig", pre: (coll) => { const res = [ `const std = @import("std");`, `const bindgen = @import("wasm-api-bindgen");` ]; for (let type of sliceTypes(coll)) { if (type !== "string" && type !== "opaque") { const name = capitalize(type); res.push( ` pub const ${name}Slice = bindgen.Slice([]${type}, [*]${type});`, `pub const Const${name}Slice = bindgen.Slice([]const ${type}, [*]const ${type});` ); } } if (opts.pre) res.push("", ...ensureStringArray(opts.pre)); return res.join("\n"); }, post: () => opts.post ? isString(opts.post) ? opts.post : opts.post.join("\n") : "", doc: (doc, acc, opts2, topLevel = false) => { acc.push( ...prefixLines(topLevel ? "//! " : "/// ", doc, opts2.lineWidth) ); }, ext: (e, _, acc) => { acc.push( `// external type: ${e.name} (size: ${e.size}, align: ${e.align}) ` ); }, enum: (e, _, acc, opts2) => { const lines = []; lines.push(`pub const ${e.name} = enum(${e.tag}) {`); for (let v of e.values) { let line; if (!isString(v)) { v.doc && gen.doc(v.doc, lines, opts2); line = v.name; if (v.value != null) line += ` = ${v.value}`; } else { line = v; } lines.push(line + ","); } injectBody(lines, e.body?.zig); lines.push("};", ""); acc.push(...withIndentation(lines, INDENT, ...SCOPES)); }, struct: (struct, coll, acc, opts2) => __structOrUnion(gen, struct, coll, acc, opts2, INDENT, SCOPES), union: (union, coll, acc, opts2) => __structOrUnion(gen, union, coll, acc, opts2, INDENT, SCOPES), funcptr: (ptr, coll, acc, opts2) => { const args = ptr.args.map((a) => `${a.name}: ${fieldType(a, ptr, coll, opts2).type}`).join(", "); const rtype = ptr.rtype === "void" ? ptr.rtype : fieldType( { name: "return", ...ptr.rtype }, ptr, coll, opts2 ).type; acc.push( `pub const ${ptr.name} = *const fn (${args}) callconv(.c) ${rtype};`, "" ); } }; return gen; }; const __structOrUnion = (gen, spec, coll, acc, opts, indent, scopes) => acc.push( ...withIndentation( [ `pub const ${spec.name} = extern ${spec.type} {`, ...__generateFields(gen, spec, coll, opts) ], indent, ...scopes ) ); const __generateFields = (gen, parent, coll, opts) => { const res = []; const ftypes = {}; const name = parent.name; let padID = 0; for (let f of parent.fields) { if (isPadding(f)) { res.push(`__pad${padID}: [${f.pad}]u8,`); padID++; continue; } f.doc && gen.doc(f.doc, res, opts); const { type, defaultVal } = fieldType(f, parent, coll, opts); ftypes[f.name] = type; res.push(`${f.name}: ${type}${defaultVal},`); } injectBody(res, parent.body?.zig); res.push("};"); if (opts.debug) { const fn = (fname, body) => res.push( "", `export fn ${name}_${fname}() usize {`, `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]})`); !isUnion(parent) && fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`); fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`); } } res.push(""); return res; }; const fieldType = (f, parent, coll, opts) => { let type = f.type; let defaultVal = defaultValue(f, "zig"); const { classifier, isConst } = classifyField(f, coll); const $isConst = isConst ? "Const" : ""; if (isWasmString(f.type)) { type = isStringSlice(opts.stringType) ? `bindgen.${$isConst}String` : `bindgen.${$isConst}StringPtr`; switch (classifier) { case "strPtr": type = `*${type}`; break; case "strPtrFixed": type = `*[${f.len}]${type}`; break; case "strPtrMulti": type = `[*]${type}`; break; case "strSlice": type += "Slice"; break; case "strArray": type = `[${f.len}]${type}`; break; } } else if (isOpaque(f.type)) { type = `bindgen.${$isConst}OpaquePtr`; switch (classifier) { case "opaquePtr": type = `*${type}`; break; case "opaquePtrFixed": type = `*[${f.len}]${type}`; break; case "opaquePtrMulti": type = `[*]${type}`; break; case "opaqueSlice": type += "Slice"; break; case "opaqueArray": type = `[${f.len}]${type}`; break; } } else { const $const = isConst ? "const " : ""; const sentinel = f.sentinel != null ? `:${f.sentinel}` : ""; switch (classifier) { case "ptr": case "enumPtr": type = `*${$const}${type}`; break; case "ptrFixed": case "enumPtrFixed": type = `*${$const}[${f.len}${sentinel}]${type}`; break; case "ptrMulti": case "enumPtrMulti": type = `[*${sentinel}]${$const}${type}`; break; case "slice": case "enumSlice": type = `${$isConst}${capitalize(f.type)}Slice`; break; case "array": case "enumArray": type = `[${f.len}${sentinel}]${type}`; break; case "vec": type = `@Vector(${f.len}, ${type})`; break; } } if (f.optional) type = "?" + type; if (defaultVal != void 0) { if (!(isString(defaultVal) || isNumber(defaultVal))) { illegalArgs( `wrong default value for ${parent.name}.${f.name} (${defaultVal})` ); } } return { type, defaultVal: defaultVal != void 0 ? ` = ${defaultVal}` : "" }; }; export { ZIG, fieldType };