Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@thi.ng/wasm-api-bindgen

Package Overview
Dependencies
Maintainers
1
Versions
123
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@thi.ng/wasm-api-bindgen - npm Package Compare versions

Comparing version 0.4.32 to 0.4.33

29

api.js

@@ -1,17 +0,16 @@

export const PKG_NAME = "@thi.ng/wasm-api-bindgen";
/**
* WASM32 target spec
*/
export const WASM32 = {
isize: "i32",
usize: "u32",
sizeBytes: 4,
const PKG_NAME = "@thi.ng/wasm-api-bindgen";
const WASM32 = {
isize: "i32",
usize: "u32",
sizeBytes: 4
};
/**
* WASM64 target spec
*/
export const WASM64 = {
isize: "i64",
usize: "u64",
sizeBytes: 8,
const WASM64 = {
isize: "i64",
usize: "u64",
sizeBytes: 8
};
export {
PKG_NAME,
WASM32,
WASM64
};

@@ -6,313 +6,300 @@ import { topoSort } from "@thi.ng/arrays/topo-sort";

import { classifyField } from "./internal/classify.js";
import { ensureStringArray, enumName, isOpaque, isPadding, isStringSlice, isStruct, isUnion, isWasmString, prefixLines, sliceTypes, withIndentation, } from "./internal/utils.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",
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"
};
/**
* Zig code generator. Call with options and then pass to {@link generateTypes}
* (see its docs for further usage).
*
* @remarks
* This codegen generates struct and enum definitions for a {@link TypeColl}
* given to {@link generateTypes}.
*
* @param opts
*/
export const C11 = (opts = {}) => {
const { typePrefix } = {
typePrefix: "",
...opts,
};
const INDENT = " ";
const SCOPES = [/\{$/, /^\}[ A-Za-z0-9_]*[;,]?$/];
const gen = {
id: "c",
pre: (coll, opts) => {
const res = [
"#pragma once",
"",
"#ifdef __cplusplus",
`extern "C" {`,
"#endif",
opts.debug ? "\n#include <stdalign.h>" : "",
`#include "wasmapi.h"`,
"",
];
// pre-declare any primitive slice types used (e.g. `U8Slice`)
const slices = sliceTypes(coll);
for (let id of slices) {
const prim = PRIM_ALIASES[id];
if (!prim)
continue;
res.push(__sliceDef(prim, typePrefix, capitalize(id)));
}
// ...then pre-declare any custom types in dependency order
for (let id of __declOrder(coll)) {
const type = coll[id];
if (type.type == "funcptr") {
res.push(__funcptr(type, coll, opts, typePrefix));
}
else {
res.push(`\ntypedef ${type.type} ${typePrefix}${type.name} ${typePrefix}${type.name};`);
}
if (slices.has(id)) {
const [ptr, name] = id === "opaque"
? ["void*", "Opaque"]
: [typePrefix + id, capitalize(id)];
res.push(__sliceDef(ptr, typePrefix, name));
}
}
if (opts.pre)
res.push("", ...ensureStringArray(opts.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, opts) => {
acc.push(...prefixLines("// ", doc, opts.lineWidth));
},
enum: (e, _, acc, opts) => {
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, opts);
line = enumName(opts, v.name);
if (v.value != null)
line += ` = ${v.value}`;
}
else {
line = enumName(opts, v);
}
lines.push(line + ",");
}
lines.push(`};`, "");
acc.push(...withIndentation(lines, INDENT, ...SCOPES));
},
struct: (struct, coll, acc, opts) => {
const name = typePrefix + struct.name;
acc.push(...withIndentation([
`struct ${name} {`,
...__generateFields(gen, struct, coll, opts, typePrefix),
], INDENT, ...SCOPES));
},
union: (union, coll, acc, opts) => {
const name = typePrefix + union.name;
acc.push(...withIndentation([
`union ${name} {`,
...__generateFields(gen, union, coll, opts, typePrefix),
], INDENT, ...SCOPES));
},
// funcpointers are emitted in `pre` phase above
funcptr: () => { },
};
return gen;
};
/**
* Generates source code for given {@link Struct} or {@link Union}.
*
* @internal
*/
const __generateFields = (gen, parent, coll, opts, typePrefix) => {
const res = [];
const ftypes = {};
const isUnion = parent.type === "union";
const name = typePrefix + parent.name;
let padID = 0;
for (let f of parent.fields) {
// autolabel explicit padding fields
if (isPadding(f)) {
res.push(`uint8_t __pad${padID++}[${f.pad}];`);
continue;
const C11 = (opts = {}) => {
const { typePrefix } = {
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)));
}
for (let id of __declOrder(coll)) {
const type = coll[id];
if (type.type == "funcptr") {
res.push(
__funcptr(type, coll, opts2, typePrefix)
);
} else {
res.push(
`
typedef ${type.type} ${typePrefix}${type.name} ${typePrefix}${type.name};`
);
}
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;`);
if (slices.has(id)) {
const [ptr, name] = id === "opaque" ? ["void*", "Opaque"] : [typePrefix + id, capitalize(id)];
res.push(__sliceDef(ptr, typePrefix, name));
}
}
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]})`);
!isUnion && fn(f.name + "_offset", `offsetof(${name}, ${f.name})`);
fn(f.name + "_size", `sizeof(${ftypes[f.name]})`);
}
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));
},
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) => {
const name = typePrefix + struct.name;
acc.push(
...withIndentation(
[
`struct ${name} {`,
...__generateFields(
gen,
struct,
coll,
opts2,
typePrefix
)
],
INDENT,
...SCOPES
)
);
},
union: (union, coll, acc, opts2) => {
const name = typePrefix + union.name;
acc.push(
...withIndentation(
[
`union ${name} {`,
...__generateFields(gen, union, coll, opts2, typePrefix)
],
INDENT,
...SCOPES
)
);
},
// funcpointers are emitted in `pre` phase above
funcptr: () => {
}
res.push("");
return res;
};
return gen;
};
/**
* Returns the C type name and declaration (incl. field name) for a given
* {@link Field}.
*
* @internal
*/
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";
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}`;
};
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 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] || prefix + type;
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 = `${prefix}${$isConst}${capitalize(f.type)}Slice`;
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}`;
} else {
const $const = isConst ? "const " : "";
type = PRIM_ALIASES[type] || prefix + type;
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 = `${prefix}${$isConst}${capitalize(f.type)}Slice`;
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: type,
decl: decl,
sentinel: sentinel,
};
}
return {
type,
decl,
sentinel
};
};
/**
* Takes a {@link TypeColl} and returns user type names in dependency order.
*
* @param coll
*
* @internal
*/
const __declOrder = (coll) => topoSort(coll, (type) => {
const fields = isStruct(type) || isUnion(type)
? type.fields
: type.type === "funcptr"
? type.args
: undefined;
return fields
? fields.map((x) => x.type).filter((x) => !!coll[x])
: undefined;
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;
});
/**
* Generates C `typedef` for given {@link FuncPointer} spec.
*
* @param ptr
* @param coll
* @param opts
* @param typePrefix
*
* @internal
*/
const __funcptr = (ptr, coll, opts, typePrefix) => {
const name = typePrefix + ptr.name;
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 name = typePrefix + ptr.name;
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});`;
};
/**
* Generates `typedef`s for a slice emulation struct of given pointer type.
*
* @param ptr
* @param prefix
* @param name
*
* @internal
*/
const __sliceDef = (ptr, prefix, name) => `\ntypedef struct { ${ptr}* ptr; size_t len; } ${prefix}${name}Slice;` +
`\ntypedef struct { const ${ptr}* ptr; size_t len; } ${prefix}Const${name}Slice;`;
const __sliceDef = (ptr, prefix, name) => `
typedef struct { ${ptr}* ptr; size_t len; } ${prefix}${name}Slice;
typedef struct { const ${ptr}* ptr; size_t len; } ${prefix}Const${name}Slice;`;
export {
C11
};
# Change Log
- **Last updated**: 2023-12-09T19:12:04Z
- **Last updated**: 2023-12-11T10:07:09Z
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)

@@ -5,0 +5,0 @@

@@ -1,2 +0,11 @@

import { ParseError, flag, oneOf, oneOfMulti, parse, string, strings, usage, } from "@thi.ng/args";
import {
ParseError,
flag,
oneOf,
oneOfMulti,
parse,
string,
strings,
usage
} from "@thi.ng/args";
import { isArray, isPlainObject, isString } from "@thi.ng/checks";

@@ -10,3 +19,9 @@ import { illegalArgs } from "@thi.ng/errors";

import { generateTypes } from "./codegen.js";
import { isOpaque, isPadding, isSizeT, isWasmPrim, isWasmString, } from "./internal/utils.js";
import {
isOpaque,
isPadding,
isSizeT,
isWasmPrim,
isWasmString
} from "./internal/utils.js";
import { TYPESCRIPT } from "./typescript.js";

@@ -16,48 +31,48 @@ import { ZIG } from "./zig.js";

const argOpts = {
analytics: string({
alias: "a",
hint: "FILE",
desc: "output file path for raw codegen analytics",
}),
config: string({
alias: "c",
hint: "FILE",
desc: "JSON config file with codegen options",
}),
debug: flag({
alias: "d",
default: false,
desc: "enable debug output & functions",
}),
dryRun: flag({
default: false,
desc: "enable dry run (don't overwrite files)",
}),
lang: oneOfMulti(Object.keys(GENERATORS), {
alias: "l",
desc: "target language",
default: ["ts", "zig"],
delim: ",",
}),
out: strings({ alias: "o", hint: "FILE", desc: "output file path" }),
string: oneOf(["slice", "ptr"], {
alias: "s",
hint: "TYPE",
desc: "Force string type implementation",
}),
analytics: string({
alias: "a",
hint: "FILE",
desc: "output file path for raw codegen analytics"
}),
config: string({
alias: "c",
hint: "FILE",
desc: "JSON config file with codegen options"
}),
debug: flag({
alias: "d",
default: false,
desc: "enable debug output & functions"
}),
dryRun: flag({
default: false,
desc: "enable dry run (don't overwrite files)"
}),
lang: oneOfMulti(Object.keys(GENERATORS), {
alias: "l",
desc: "target language",
default: ["ts", "zig"],
delim: ","
}),
out: strings({ alias: "o", hint: "FILE", desc: "output file path" }),
string: oneOf(["slice", "ptr"], {
alias: "s",
hint: "TYPE",
desc: "Force string type implementation"
})
};
export const INSTALL_DIR = resolve(`${process.argv[2]}/..`);
export const PKG = readJSON(`${INSTALL_DIR}/package.json`);
export const APP_NAME = PKG.name.split("/")[1];
export const HEADER = `
█ █ █ │
██ █ │
█ █ █ █ █ █ █ █ │ ${PKG.name} ${PKG.version}
█ █ █ █ █ █ █ █ █ │ Multi-language data bindings code generator
█ │
█ █ │
const INSTALL_DIR = resolve(`${process.argv[2]}/..`);
const PKG = readJSON(`${INSTALL_DIR}/package.json`);
const APP_NAME = PKG.name.split("/")[1];
const HEADER = `
\u2588 \u2588 \u2588 \u2502
\u2588\u2588 \u2588 \u2502
\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2502 ${PKG.name} ${PKG.version}
\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2502 Multi-language data bindings code generator
\u2588 \u2502
\u2588 \u2588 \u2502
`;
const usageOpts = {
lineWidth: process.stdout.columns,
prefix: `${HEADER}
lineWidth: process.stdout.columns,
prefix: `${HEADER}
usage: ${APP_NAME} [OPTS] JSON-INPUT-FILE(S) ...

@@ -67,130 +82,146 @@ ${APP_NAME} --help

`,
showGroupNames: true,
paramWidth: 32,
showGroupNames: true,
paramWidth: 32
};
const showUsage = () => {
process.stderr.write(usage(argOpts, usageOpts));
process.exit(1);
process.stderr.write(usage(argOpts, usageOpts));
process.exit(1);
};
const invalidSpec = (path, msg) => {
throw new Error(`invalid typedef: ${path}${msg ? ` (${msg})` : ""}`);
throw new Error(`invalid typedef: ${path}${msg ? ` (${msg})` : ""}`);
};
const addTypeSpec = (ctx, path, coll, spec) => {
if (!(spec.name && spec.type))
invalidSpec(path);
if (!["enum", "funcptr", "struct", "union"].includes(spec.type))
invalidSpec(path, `${spec.name} type: ${spec.type}`);
if (coll[spec.name])
invalidSpec(path, `duplicate name: ${spec.name}`);
if (spec.body) {
if (!isPlainObject(spec.body))
invalidSpec(path, `${spec.name}.body must be an object`);
for (let lang in spec.body) {
const src = spec.body[lang];
if (isString(src) && src[0] === "@") {
spec.body[lang] = readText(src.substring(1), ctx.logger);
}
}
if (!(spec.name && spec.type))
invalidSpec(path);
if (!["enum", "funcptr", "struct", "union"].includes(spec.type))
invalidSpec(path, `${spec.name} type: ${spec.type}`);
if (coll[spec.name])
invalidSpec(path, `duplicate name: ${spec.name}`);
if (spec.body) {
if (!isPlainObject(spec.body))
invalidSpec(path, `${spec.name}.body must be an object`);
for (let lang in spec.body) {
const src = spec.body[lang];
if (isString(src) && src[0] === "@") {
spec.body[lang] = readText(src.substring(1), ctx.logger);
}
}
ctx.logger.debug(`registering ${spec.type}: ${spec.name}`);
coll[spec.name] = spec;
spec.__path = path;
}
ctx.logger.debug(`registering ${spec.type}: ${spec.name}`);
coll[spec.name] = spec;
spec.__path = path;
};
const validateTypeRefs = (coll) => {
for (let spec of Object.values(coll)) {
if (!["funcptr", "struct", "union"].includes(spec.type))
continue;
for (let f of spec.fields || spec.args) {
if (!(isPadding(f) ||
isWasmPrim(f.type) ||
isSizeT(f.type) ||
isOpaque(f.type) ||
isWasmString(f.type) ||
coll[f.type])) {
invalidSpec(spec.__path, `${spec.name}.${f.name} has unknown type: ${f.type}`);
}
}
for (let spec of Object.values(coll)) {
if (!["funcptr", "struct", "union"].includes(spec.type))
continue;
for (let f of spec.fields || spec.args) {
if (!(isPadding(f) || isWasmPrim(f.type) || isSizeT(f.type) || isOpaque(f.type) || isWasmString(f.type) || coll[f.type])) {
invalidSpec(
spec.__path,
`${spec.name}.${f.name} has unknown type: ${f.type}`
);
}
}
}
};
const parseTypeSpecs = (ctx, inputs) => {
const coll = {};
for (let path of inputs) {
try {
const spec = readJSON(resolve(path), ctx.logger);
if (isArray(spec)) {
for (let s of spec)
addTypeSpec(ctx, path, coll, s);
}
else if (isPlainObject(spec)) {
addTypeSpec(ctx, path, coll, spec);
}
else {
invalidSpec(path);
}
}
catch (e) {
process.stderr.write(e.message);
process.exit(1);
}
const coll = {};
for (let path of inputs) {
try {
const spec = readJSON(resolve(path), ctx.logger);
if (isArray(spec)) {
for (let s of spec)
addTypeSpec(ctx, path, coll, s);
} else if (isPlainObject(spec)) {
addTypeSpec(ctx, path, coll, spec);
} else {
invalidSpec(path);
}
} catch (e) {
process.stderr.write(e.message);
process.exit(1);
}
validateTypeRefs(coll);
return coll;
}
validateTypeRefs(coll);
return coll;
};
const generateOutputs = ({ config, logger, opts }, coll) => {
for (let i = 0; i < opts.lang.length; i++) {
const lang = opts.lang[i];
logger.debug(`generating ${lang.toUpperCase()} output...`);
const src = generateTypes(coll, GENERATORS[lang](config[lang]), config.global);
if (opts.out) {
writeText(resolve(opts.out[i]), src, logger, opts.dryRun);
}
else {
process.stdout.write(src + "\n");
}
for (let i = 0; i < opts.lang.length; i++) {
const lang = opts.lang[i];
logger.debug(`generating ${lang.toUpperCase()} output...`);
const src = generateTypes(
coll,
GENERATORS[lang](config[lang]),
config.global
);
if (opts.out) {
writeText(resolve(opts.out[i]), src, logger, opts.dryRun);
} else {
process.stdout.write(src + "\n");
}
}
};
const resolveUserCode = (ctx, conf, key) => {
if (conf[key]) {
if (isString(conf[key]) && conf[key][0] === "@") {
conf[key] = readText(resolve(dirname(ctx.opts.config), conf[key].substring(1)), ctx.logger);
}
if (conf[key]) {
if (isString(conf[key]) && conf[key][0] === "@") {
conf[key] = readText(
resolve(
dirname(ctx.opts.config),
conf[key].substring(1)
),
ctx.logger
);
}
}
};
try {
const result = parse(argOpts, process.argv, { start: 3, usageOpts });
if (!result)
process.exit(1);
const { result: opts, rest } = result;
if (!rest.length)
showUsage();
if (opts.out && opts.lang.length != opts.out.length) {
illegalArgs(`expected ${opts.lang.length} outputs, but got ${opts.out.length}`);
const result = parse(argOpts, process.argv, { start: 3, usageOpts });
if (!result)
process.exit(1);
const { result: opts, rest } = result;
if (!rest.length)
showUsage();
if (opts.out && opts.lang.length != opts.out.length) {
illegalArgs(
`expected ${opts.lang.length} outputs, but got ${opts.out.length}`
);
}
const ctx = {
logger: new ConsoleLogger("wasm-api", opts.debug ? "DEBUG" : "INFO"),
config: { global: {} },
opts
};
if (opts.config) {
opts.config = resolve(opts.config);
ctx.config = readJSON(opts.config, ctx.logger);
for (let id in ctx.config) {
const conf = ctx.config[id];
resolveUserCode(ctx, conf, "pre");
resolveUserCode(ctx, conf, "post");
}
const ctx = {
logger: new ConsoleLogger("wasm-api", opts.debug ? "DEBUG" : "INFO"),
config: { global: {} },
opts,
};
if (opts.config) {
opts.config = resolve(opts.config);
ctx.config = readJSON(opts.config, ctx.logger);
for (let id in ctx.config) {
const conf = ctx.config[id];
resolveUserCode(ctx, conf, "pre");
resolveUserCode(ctx, conf, "post");
}
}
opts.debug && mutIn(ctx, ["config", "global", "debug"], true);
opts.string && mutIn(ctx, ["config", "global", "stringType"], opts.string);
const types = parseTypeSpecs(ctx, rest);
generateOutputs(ctx, types);
if (ctx.opts.analytics) {
// always write analytics, even if dry run
writeJSON(resolve(ctx.opts.analytics), types, undefined, "\t", ctx.logger);
}
}
opts.debug && mutIn(ctx, ["config", "global", "debug"], true);
opts.string && mutIn(ctx, ["config", "global", "stringType"], opts.string);
const types = parseTypeSpecs(ctx, rest);
generateOutputs(ctx, types);
if (ctx.opts.analytics) {
writeJSON(
resolve(ctx.opts.analytics),
types,
void 0,
" ",
ctx.logger
);
}
} catch (e) {
if (!(e instanceof ParseError))
process.stderr.write(e.message);
process.exit(1);
}
catch (e) {
if (!(e instanceof ParseError))
process.stderr.write(e.message);
process.exit(1);
}
export {
APP_NAME,
HEADER,
INSTALL_DIR,
PKG
};

@@ -6,218 +6,220 @@ import { SIZEOF } from "@thi.ng/api/typedarray";

import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
import { PKG_NAME, WASM32, } from "./api.js";
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";
export const DEFAULT_CODEGEN_OPTS = {
debug: false,
header: true,
lineWidth: 80,
stringType: "ptr",
target: WASM32,
uppercaseEnums: true,
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, {}, {
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);
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];
}
}
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 !== undefined && field.tag === "array") {
size += SIZEOF[field.type];
}
}
}
return (field.__size = align.size(size, field.__align));
}
return field.__size = align.size(size, field.__align);
},
enum: (type) => {
if (type.__size)
return type.__size;
return (type.__size = SIZEOF[type.tag]);
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));
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));
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, {}, {
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));
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
);
},
enum: (type, _, align) => {
const e = type;
if (!e.tag)
e.tag = "i32";
return (e.__align = align.align({
type: e.tag,
}));
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);
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);
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, {}, {
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);
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));
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);
}
for (let f of struct.fields) {
const type = coll[f.type];
if (type) {
prepareType(type, coll, selectAlignment(type), opts);
}
}
sizeOf(struct, coll, align, opts);
},
});
/**
* Takes a type collection and analyzes each analyzed to compute individual
* alignments and sizes.
*
* @remarks
* This function is idempotent and called automatically by
* {@link generateTypes}. Only exported for dev/debug purposes.
*
* @param coll
*
* @internal
*/
export const prepareTypes = (coll, opts) => {
for (let id in coll) {
prepareType(coll[id], coll, selectAlignment(coll[id]), opts);
}
sizeOf(struct, coll, align, opts);
}
return coll;
}
);
const prepareTypes = (coll, opts) => {
for (let id in coll) {
prepareType(coll[id], coll, selectAlignment(coll[id]), opts);
}
return coll;
};
/**
* Code generator main entry point. Takes an object of {@link TopLevelType}
* definitions, an actual code generator implementation for a single target
* language and (optional) global codegen options. Returns generated source code
* for all given types as a single string.
*
* @remarks
* Before actual code generation the types are first analyzed to compute their
* alignments and sizes. This is only ever done once (idempotent), even if
* `generateTypes()` is called multiple times for different target langs.
*
* @param coll
* @param codegen
* @param opts
*/
export 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 ${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");
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
};

@@ -5,27 +5,23 @@ import { SIZEOF } from "@thi.ng/api/typedarray";

import { isStruct, isUnion } from "./utils.js";
/**
* C ABI compatible alignment
*/
export const ALIGN_C = {
align: (field) => {
let align = SIZEOF[field.type];
if (field.tag === "vec") {
align *= ceilPow2(field.len);
}
return align;
},
size: (size, align) => $align(size, align),
offset: (offset, align) => $align(offset, align),
const ALIGN_C = {
align: (field) => {
let align = SIZEOF[field.type];
if (field.tag === "vec") {
align *= ceilPow2(field.len);
}
return align;
},
size: (size, align) => $align(size, align),
offset: (offset, align) => $align(offset, align)
};
export const ALIGN_PACKED = {
align: () => 1,
size: (size) => size,
offset: (offset) => offset,
const ALIGN_PACKED = {
align: () => 1,
size: (size) => size,
offset: (offset) => offset
};
/**
* Returns a suitable alignment strategy for given type, i.e. either the user
* supplied impl defined for the type or the default ({@link ALIGN_C}).
*
* @param type
*/
export const selectAlignment = (type) => isStruct(type) || isUnion(type) ? type.align || ALIGN_C : ALIGN_C;
const selectAlignment = (type) => isStruct(type) || isUnion(type) ? type.align || ALIGN_C : ALIGN_C;
export {
ALIGN_C,
ALIGN_PACKED,
selectAlignment
};
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
import { isEnum, isOpaque, isPadding, isPointer, isSlice, isWasmPrim, isWasmString, } from "./utils.js";
/**
* Analyses and classifies given field and returns a classifier constant used by
* each codegen to emit the relevant syntax.
*
* @param field
* @param coll
*
* @internal
*/
export const classifyField = (field, coll) => {
if (isPadding(field))
return { classifier: "pad", isConst: false };
const $isPrim = isWasmPrim(field.type);
const $isStr = isWasmString(field.type);
const $isSlice = isSlice(field.tag);
const $isPtr = isPointer(field.tag);
const $isEnum = isEnum(field.type, coll);
const $isArray = field.tag === "array";
const $isVec = field.tag === "vec";
if ($isVec && !$isPrim) {
illegalArgs("only vectors of numeric types allowed");
import {
isEnum,
isOpaque,
isPadding,
isPointer,
isSlice,
isWasmPrim,
isWasmString
} from "./utils.js";
const classifyField = (field, coll) => {
if (isPadding(field))
return { classifier: "pad", isConst: false };
const $isPrim = isWasmPrim(field.type);
const $isStr = isWasmString(field.type);
const $isSlice = isSlice(field.tag);
const $isPtr = isPointer(field.tag);
const $isEnum = isEnum(field.type, coll);
const $isArray = field.tag === "array";
const $isVec = field.tag === "vec";
if ($isVec && !$isPrim) {
illegalArgs("only vectors of numeric types allowed");
}
if (field.sentinel != null && !$isPrim) {
illegalArgs("sentinels only supported for numeric types");
}
let id = "single";
let $const = !!field.const;
if ($isStr) {
$const = field.const !== false;
id = __classifyType("str", $isArray, $isSlice, $isPtr, field.len);
} else if (isOpaque(field.type)) {
id = __classifyType("opaque", $isArray, $isSlice, $isPtr, field.len);
} else if ($isEnum) {
id = __classifyType("enum", $isArray, $isSlice, $isPtr, field.len);
} else {
if ($isArray || $isVec) {
__ensureLength(field.len);
id = $isArray ? "array" : "vec";
} else if ($isSlice) {
id = "slice";
} else if ($isPtr) {
id = __selectForLength(field.len, "ptr", "ptrFixed", "ptrMulti");
}
if (field.sentinel != null && !$isPrim) {
illegalArgs("sentinels only supported for numeric types");
}
let id = "single";
let $const = !!field.const;
if ($isStr) {
$const = field.const !== false;
id = __classifyType("str", $isArray, $isSlice, $isPtr, field.len);
}
else if (isOpaque(field.type)) {
id = __classifyType("opaque", $isArray, $isSlice, $isPtr, field.len);
}
else if ($isEnum) {
id = __classifyType("enum", $isArray, $isSlice, $isPtr, field.len);
}
else {
if ($isArray || $isVec) {
__ensureLength(field.len);
id = $isArray ? "array" : "vec";
}
else if ($isSlice) {
id = "slice";
}
else if ($isPtr) {
id = __selectForLength(field.len, "ptr", "ptrFixed", "ptrMulti");
}
}
return { classifier: id, isConst: $const };
}
return { classifier: id, isConst: $const };
};
const __classifyType = (base, $isArray, $isSlice, $isPtr, len) => {
if ($isArray) {
__ensureLength(len);
return (base + "Array");
}
else if ($isSlice) {
return (base + "Slice");
}
else if ($isPtr) {
return __selectForLength(len, (base + "Ptr"), (base + "PtrFixed"), (base + "PtrMulti"));
}
return base;
if ($isArray) {
__ensureLength(len);
return base + "Array";
} else if ($isSlice) {
return base + "Slice";
} else if ($isPtr) {
return __selectForLength(
len,
base + "Ptr",
base + "PtrFixed",
base + "PtrMulti"
);
}
return base;
};
/** @internal */
const __selectForLength = (len, single, fixed, multi) => (len != null ? (len > 0 ? fixed : multi) : single);
/** @internal */
const __selectForLength = (len, single, fixed, multi) => len != null ? len > 0 ? fixed : multi : single;
const __ensureLength = (len) => {
if (len == null || len < 1)
illegalArgs("missing or invalid array/vec length");
if (len == null || len < 1)
illegalArgs("missing or invalid array/vec length");
};
export {
classifyField
};

@@ -6,132 +6,61 @@ import { isArray } from "@thi.ng/checks/is-array";

import { wordWrapLine, wordWrapLines } from "@thi.ng/strings/word-wrap";
/**
* Returns true iff `x` is a {@link WasmPrim32}.
*
* @param x
*/
export const isNumeric = (x) => /^(([iu](8|16|32))|(f(32|64)))$/.test(x);
/**
* Returns true iff `x` is a `i64` or `u64`.
*
* @param x
*/
export const isBigNumeric = (x) => /^[iu]64$/.test(x);
export const isSizeT = (x) => /^[iu]size$/.test(x);
/**
* Returns true iff `x` is a {@link WasmPrim}.
*
* @param x
*/
export const isWasmPrim = (x) => isNumeric(x) || isBigNumeric(x);
export const isWasmString = (x) => x === "string";
export const isPadding = (f) => f.pad != null && f.pad > 0;
export const isPointer = (x) => x === "ptr";
export const isFuncPointer = (type, coll) => coll[type]?.type === "funcptr";
export const isEnum = (type, coll) => coll[type]?.type === "enum";
export const isSlice = (x) => x === "slice";
export const isOpaque = (x) => x === "opaque";
/**
* Returns true iff the struct field is a pointer, slice or "string" type
*
* @param f
*/
export const isPointerLike = (f, coll) => isPointer(f.tag) ||
isSlice(f.tag) ||
isWasmString(f.type) ||
isOpaque(f.type) ||
isFuncPointer(f.type, coll);
/**
* Returns true if `type` is "slice".
*
* @param type
*/
export const isStringSlice = (type) => type === "slice";
export const isStruct = (x) => x.type === "struct";
export const isUnion = (x) => x.type === "union";
/**
* Returns filtered array of struct fields of with "ptr" tag or function
* pointers.
*
* @param fields
*
* @internal
*/
export const pointerFields = (fields) => fields.filter((f) => isPointer(f.tag));
/**
* Returns filtered array of struct fields of only "string" fields.
*
* @param fields
*
* @internal
*/
export const stringFields = (fields) => fields.filter((f) => isWasmString(f.type) && f.tag !== "ptr");
export const sliceTypes = (coll) => new Set(Object.values(coll)
.flatMap((x) => (isStruct(x) || isUnion(x) ? x.fields : []))
.map((x) => (x.tag === "slice" ? x.type : null))
.filter((x) => !!x));
/**
* Returns enum identifier formatted according to given opts.
*
* @param opts
* @param name
*
* @internal
*/
export const enumName = (opts, name) => opts.uppercaseEnums ? name.toUpperCase() : name;
/**
* Returns given field's default value (or undefined). The `lang` ID is required
* to obtain the language specific value if the default is given as object.
*
* @param f
* @param lang
*/
export const defaultValue = (f, lang) => f.default !== undefined
? isPlainObject(f.default)
? f.default[lang]
: f.default
: undefined;
/**
* Takes an array of strings or splits given string into lines, word wraps and
* then prefixes each line with given `width` and `prefix`. Returns array of new
* lines.
*
* @param prefix
* @param str
* @param width
*/
export const prefixLines = (prefix, str, width) => (isString(str)
? wordWrapLines(str, { width: width - prefix.length })
: str.flatMap((x) => wordWrapLine(x, { width: width - prefix.length }))).map((line) => prefix + line);
export const ensureLines = (src, key) => isString(src)
? split(src)
: isArray(src)
? src
: key
? src[key]
? ensureLines(src[key], key)
: []
: [];
export const ensureStringArray = (src) => isString(src) ? [src] : src;
/**
* Yields iterator of given lines, each with applied indentation based on given
* scope regexp's which are applied to each line to increase or decrease
* indentation level (the initial indentation level can be specified via
* optional `level` arg, default 0). If `scopeStart` succeeds, the indent is
* increased for the _next_ line. If `scopeEnd` succeeds the level is decreased
* for the _current_ line. ...
*
* @param lines
* @param indent
* @param scopeStart
* @param scopeEnd
* @param level
*/
export function* withIndentation(lines, indent, scopeStart, scopeEnd, level = 0) {
const stack = new Array(level).fill(indent);
for (let l of lines) {
scopeEnd.test(l) && stack.pop();
const curr = stack.length ? stack[stack.length - 1] : "";
yield curr + l;
scopeStart.test(l) && stack.push(curr + indent);
}
const isNumeric = (x) => /^(([iu](8|16|32))|(f(32|64)))$/.test(x);
const isBigNumeric = (x) => /^[iu]64$/.test(x);
const isSizeT = (x) => /^[iu]size$/.test(x);
const isWasmPrim = (x) => isNumeric(x) || isBigNumeric(x);
const isWasmString = (x) => x === "string";
const isPadding = (f) => f.pad != null && f.pad > 0;
const isPointer = (x) => x === "ptr";
const isFuncPointer = (type, coll) => coll[type]?.type === "funcptr";
const isEnum = (type, coll) => coll[type]?.type === "enum";
const isSlice = (x) => x === "slice";
const isOpaque = (x) => x === "opaque";
const isPointerLike = (f, coll) => isPointer(f.tag) || isSlice(f.tag) || isWasmString(f.type) || isOpaque(f.type) || isFuncPointer(f.type, coll);
const isStringSlice = (type) => type === "slice";
const isStruct = (x) => x.type === "struct";
const isUnion = (x) => x.type === "union";
const pointerFields = (fields) => fields.filter((f) => isPointer(f.tag));
const stringFields = (fields) => fields.filter((f) => isWasmString(f.type) && f.tag !== "ptr");
const sliceTypes = (coll) => new Set(
Object.values(coll).flatMap((x) => isStruct(x) || isUnion(x) ? x.fields : []).map((x) => x.tag === "slice" ? x.type : null).filter((x) => !!x)
);
const enumName = (opts, name) => opts.uppercaseEnums ? name.toUpperCase() : name;
const defaultValue = (f, lang) => f.default !== void 0 ? isPlainObject(f.default) ? f.default[lang] : f.default : void 0;
const prefixLines = (prefix, str, width) => (isString(str) ? wordWrapLines(str, { width: width - prefix.length }) : str.flatMap((x) => wordWrapLine(x, { width: width - prefix.length }))).map((line) => prefix + line);
const ensureLines = (src, key) => isString(src) ? split(src) : isArray(src) ? src : key ? src[key] ? ensureLines(src[key], key) : [] : [];
const ensureStringArray = (src) => isString(src) ? [src] : src;
function* withIndentation(lines, indent, scopeStart, scopeEnd, level = 0) {
const stack = new Array(level).fill(indent);
for (let l of lines) {
scopeEnd.test(l) && stack.pop();
const curr = stack.length ? stack[stack.length - 1] : "";
yield curr + l;
scopeStart.test(l) && stack.push(curr + indent);
}
}
export {
defaultValue,
ensureLines,
ensureStringArray,
enumName,
isBigNumeric,
isEnum,
isFuncPointer,
isNumeric,
isOpaque,
isPadding,
isPointer,
isPointerLike,
isSizeT,
isSlice,
isStringSlice,
isStruct,
isUnion,
isWasmPrim,
isWasmString,
pointerFields,
prefixLines,
sliceTypes,
stringFields,
withIndentation
};
{
"name": "@thi.ng/wasm-api-bindgen",
"version": "0.4.32",
"version": "0.4.33",
"description": "Polyglot bindings code generators for hybrid JS & WebAssembly projects",

@@ -28,3 +28,5 @@ "type": "module",

"scripts": {
"build": "yarn clean && tsc --declaration",
"build": "yarn build:esbuild && yarn build:decl",
"build:decl": "tsc --declaration --emitDeclarationOnly",
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc internal",

@@ -39,18 +41,19 @@ "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",

"dependencies": {
"@thi.ng/api": "^8.9.11",
"@thi.ng/args": "^2.2.45",
"@thi.ng/arrays": "^2.7.7",
"@thi.ng/binary": "^3.3.40",
"@thi.ng/checks": "^3.4.11",
"@thi.ng/compare": "^2.2.7",
"@thi.ng/defmulti": "^3.0.9",
"@thi.ng/errors": "^2.4.5",
"@thi.ng/file-io": "^1.0.4",
"@thi.ng/logger": "^2.0.1",
"@thi.ng/paths": "^5.1.52",
"@thi.ng/strings": "^3.7.2",
"@thi.ng/wasm-api": "^1.4.36"
"@thi.ng/api": "^8.9.12",
"@thi.ng/args": "^2.2.46",
"@thi.ng/arrays": "^2.7.8",
"@thi.ng/binary": "^3.4.0",
"@thi.ng/checks": "^3.4.12",
"@thi.ng/compare": "^2.2.8",
"@thi.ng/defmulti": "^3.0.10",
"@thi.ng/errors": "^2.4.6",
"@thi.ng/file-io": "^1.0.5",
"@thi.ng/logger": "^2.0.2",
"@thi.ng/paths": "^5.1.53",
"@thi.ng/strings": "^3.7.3",
"@thi.ng/wasm-api": "^1.4.37"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.38.3",
"esbuild": "^0.19.8",
"rimraf": "^5.0.5",

@@ -123,3 +126,3 @@ "tools": "^0.0.1",

},
"gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
"gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
}

@@ -1,352 +0,391 @@

import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray";
import {
BIGINT_ARRAY_CTORS,
BIT_SHIFTS,
TYPEDARRAY_CTORS
} from "@thi.ng/api/typedarray";
import { isString } from "@thi.ng/checks/is-string";
import { unsupported } from "@thi.ng/errors/unsupported";
import { classifyField } from "./internal/classify.js";
import { ensureLines, ensureStringArray, enumName, isEnum, isFuncPointer, isNumeric, isOpaque, isPointer, isStringSlice, isWasmPrim, isWasmString, prefixLines, withIndentation, } from "./internal/utils.js";
import {
ensureLines,
ensureStringArray,
enumName,
isEnum,
isFuncPointer,
isNumeric,
isOpaque,
isPointer,
isStringSlice,
isWasmPrim,
isWasmString,
prefixLines,
withIndentation
} from "./internal/utils.js";
import { fieldType as zigFieldType } from "./zig.js";
/**
* TypeScript code generator. Call with options and then pass to
* {@link generateTypes} (see its docs for further usage).
*
* @remarks
* This codegen generates interface and enum definitions for a {@link TypeColl}
* given to {@link generateTypes}. For structs it will also generate memory
* mapped wrappers with fully typed accessors.
*
* @param opts
*/
export const TYPESCRIPT = (opts = {}) => {
const { indent } = {
indent: "\t",
stringType: "slice",
...opts,
};
const SCOPES = [/\{$/, /(?<!\{.*)\}\)?[;,]?$/];
const gen = {
id: "ts",
pre: (_, globalOpts) => {
const res = [
"// @ts-ignore possibly includes unused imports",
`import { Pointer, ${__stringImpl(globalOpts)}, type MemorySlice, type WasmTypeBase, type WasmTypeConstructor } from "@thi.ng/wasm-api";`,
];
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, opts) => {
acc.push("/**", ...prefixLines(" * ", doc, opts.lineWidth), " */");
},
enum: (e, _, acc, opts) => {
const res = [];
res.push(`export enum ${e.name} {`);
for (let v of e.values) {
let line;
if (!isString(v)) {
v.doc && gen.doc(v.doc, res, opts);
line = enumName(opts, v.name);
if (v.value != null)
line += ` = ${v.value}`;
}
else {
line = enumName(opts, v);
}
res.push(line + ",");
}
res.push("}", "");
acc.push(...withIndentation(res, indent, ...SCOPES));
},
struct: (struct, coll, acc, opts) => {
const strType = __stringImpl(opts);
const fields = (struct.fields
.map((f) => generateField(f, coll, opts))
.filter((f) => !!f));
const lines = [];
lines.push(`export interface ${struct.name} extends WasmTypeBase {`);
for (let f of fields) {
const doc = __docType(f.field, struct, coll, opts);
doc && gen.doc(doc, lines, opts);
lines.push(`${f.field.name}: ${f.type};`);
}
if (struct.body?.ts) {
lines.push("", ...ensureLines(struct.body.ts, "decl"));
}
lines.push("}", "");
const pointerDecls = fields
.filter((f) => isPointer(f.field.tag) && f.field.len !== 0)
.map((f) => {
return `let $${f.field.name}: ${f.type} | null = null;`;
});
const stringDecls = fields
.filter((f) => isWasmString(f.field.type) &&
!["array", "ptr", "slice"].includes(f.field.tag))
.map((f) => `let $${f.field.name}: ${strType} | null = null;`);
// type implementation
lines.push(`export const $${struct.name}: WasmTypeConstructor<${struct.name}> = (mem) => ({`, `get align() {`, `return ${struct.__align};`, `},`, `get size() {`, `return ${struct.__size};`, `},`, `instanceArray(base, num) {`, `const items: ${struct.name}[] = [];`, `for (; num --> 0; base += ${struct.__size}) items.push(this.instance(base));`, `return items;`, `},`, `instance: (base) => {`, ...pointerDecls, ...stringDecls, `return {`, `get __base() {`, `return base;`, `},`, `get __bytes() {`, `return mem.u8.subarray(base, base + ${struct.__size});`, `},`);
for (let f of fields) {
if (!f)
continue;
lines.push(`get ${f.field.name}(): ${f.type} {`, ...f.getter, "},");
if (f.setter) {
lines.push(`set ${f.field.name}(x: ${f.type}) {`, ...f.setter, "},");
}
}
if (struct.body?.ts) {
lines.push("", ...ensureLines(struct.body.ts, "impl"), "");
}
lines.push("};", "}", "});", "");
acc.push(...withIndentation(lines, indent, ...SCOPES));
},
union: (type, coll, acc, opts) => {
gen.struct(type, coll, acc, opts);
},
// nothing to emit directly
funcptr: () => { },
};
return gen;
const TYPESCRIPT = (opts = {}) => {
const { indent } = {
indent: " ",
stringType: "slice",
...opts
};
const SCOPES = [/\{$/, /(?<!\{.*)\}\)?[;,]?$/];
const gen = {
id: "ts",
pre: (_, globalOpts) => {
const res = [
"// @ts-ignore possibly includes unused imports",
`import { Pointer, ${__stringImpl(
globalOpts
)}, type MemorySlice, type WasmTypeBase, type WasmTypeConstructor } from "@thi.ng/wasm-api";`
];
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) => {
acc.push("/**", ...prefixLines(" * ", doc, opts2.lineWidth), " */");
},
enum: (e, _, acc, opts2) => {
const res = [];
res.push(`export enum ${e.name} {`);
for (let v of e.values) {
let line;
if (!isString(v)) {
v.doc && gen.doc(v.doc, res, opts2);
line = enumName(opts2, v.name);
if (v.value != null)
line += ` = ${v.value}`;
} else {
line = enumName(opts2, v);
}
res.push(line + ",");
}
res.push("}", "");
acc.push(...withIndentation(res, indent, ...SCOPES));
},
struct: (struct, coll, acc, opts2) => {
const strType = __stringImpl(opts2);
const fields = struct.fields.map((f) => generateField(f, coll, opts2)).filter((f) => !!f);
const lines = [];
lines.push(
`export interface ${struct.name} extends WasmTypeBase {`
);
for (let f of fields) {
const doc = __docType(f.field, struct, coll, opts2);
doc && gen.doc(doc, lines, opts2);
lines.push(`${f.field.name}: ${f.type};`);
}
if (struct.body?.ts) {
lines.push("", ...ensureLines(struct.body.ts, "decl"));
}
lines.push("}", "");
const pointerDecls = fields.filter((f) => isPointer(f.field.tag) && f.field.len !== 0).map((f) => {
return `let $${f.field.name}: ${f.type} | null = null;`;
});
const stringDecls = fields.filter(
(f) => isWasmString(f.field.type) && !["array", "ptr", "slice"].includes(f.field.tag)
).map((f) => `let $${f.field.name}: ${strType} | null = null;`);
lines.push(
`export const $${struct.name}: WasmTypeConstructor<${struct.name}> = (mem) => ({`,
`get align() {`,
`return ${struct.__align};`,
`},`,
`get size() {`,
`return ${struct.__size};`,
`},`,
`instanceArray(base, num) {`,
`const items: ${struct.name}[] = [];`,
`for (; num --> 0; base += ${struct.__size}) items.push(this.instance(base));`,
`return items;`,
`},`,
`instance: (base) => {`,
...pointerDecls,
...stringDecls,
`return {`,
`get __base() {`,
`return base;`,
`},`,
`get __bytes() {`,
`return mem.u8.subarray(base, base + ${struct.__size});`,
`},`
);
for (let f of fields) {
if (!f)
continue;
lines.push(
`get ${f.field.name}(): ${f.type} {`,
...f.getter,
"},"
);
if (f.setter) {
lines.push(
`set ${f.field.name}(x: ${f.type}) {`,
...f.setter,
"},"
);
}
}
if (struct.body?.ts) {
lines.push("", ...ensureLines(struct.body.ts, "impl"), "");
}
lines.push("};", "}", "});", "");
acc.push(...withIndentation(lines, indent, ...SCOPES));
},
union: (type, coll, acc, opts2) => {
gen.struct(type, coll, acc, opts2);
},
// nothing to emit directly
funcptr: () => {
}
};
return gen;
};
/** @internal */
const __stringImpl = (opts) => isStringSlice(opts.stringType) ? "WasmStringSlice" : "WasmStringPtr";
/** @internal */
const __shift = (type) => BIT_SHIFTS[type];
/** @internal */
const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
/** @internal */
const __addr = (offset) => offset > 0 ? `(base + ${offset})` : "base";
const __addrShift = (offset, shift) => {
const bits = __shift(shift);
return __addr(offset) + (bits ? " >>> " + bits : "");
const bits = __shift(shift);
return __addr(offset) + (bits ? " >>> " + bits : "");
};
/** @internal */
const __ptr = (target, offset) => `mem.${target.usize}[${__addrShift(offset, target.usize)}]`;
/** @internal */
const __ptrBody = (type, name, offset, body) => [
`return $${name} || ($${name} = new ${type}(mem, ${__addr(offset)},`,
...body,
`));`,
`return $${name} || ($${name} = new ${type}(mem, ${__addr(offset)},`,
...body,
`));`
];
const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
/** @internal */
const __mapArray = (f, coll, len = "len") => [
`const inst = $${f.type}(mem);`,
`const buf: ${f.type}[] = [];`,
`for(let i = 0; i < ${len}; i++) buf.push(inst.instance(addr + i * ${coll[f.type].__size}));`,
`return buf;`,
`const inst = $${f.type}(mem);`,
`const buf: ${f.type}[] = [];`,
`for(let i = 0; i < ${len}; i++) buf.push(inst.instance(addr + i * ${coll[f.type].__size}));`,
`return buf;`
];
/** @internal */
const __mapStringArray = (target, name, type, len, isConst, isLocal = false) => [
isLocal ? `const $${name}: ${type}[] = [];` : `$${name} = [];`,
`for(let i = 0; i < ${len}; i++) $${name}.push(new ${type}(mem, addr + i * ${target.sizeBytes * (type === "WasmStringSlice" ? 2 : 1)}, ${isConst}));`,
`return $${name};`,
isLocal ? `const $${name}: ${type}[] = [];` : `$${name} = [];`,
`for(let i = 0; i < ${len}; i++) $${name}.push(new ${type}(mem, addr + i * ${target.sizeBytes * (type === "WasmStringSlice" ? 2 : 1)}, ${isConst}));`,
`return $${name};`
];
/** @internal */
const __primSlice = (type, _, __) => [
// `const addr = ${__ptrShift(opts.target, offset, type)};`,
`return mem.${type}.subarray(addr, addr + len);`,
// `const addr = ${__ptrShift(opts.target, offset, type)};`,
`return mem.${type}.subarray(addr, addr + len);`
];
/** @internal */
const __primArray = (type, len, offset) => [
`const addr = ${__addrShift(offset, type)};`,
`return mem.${type}.subarray(addr, addr + ${len});`,
`const addr = ${__addrShift(offset, type)};`,
`return mem.${type}.subarray(addr, addr + ${len});`
];
const __arrayType = (type) => isNumeric(type)
? TYPEDARRAY_CTORS[type].name
: BIGINT_ARRAY_CTORS[type].name;
/** @internal */
const __arrayType = (type) => isNumeric(type) ? TYPEDARRAY_CTORS[type].name : BIGINT_ARRAY_CTORS[type].name;
const __docType = (f, parent, coll, opts) => {
const doc = [...ensureLines(f.doc || [])];
if (isPointer(f.tag) && f.len === 0) {
const typeInfo = `Multi pointer: \`${zigFieldType(f, parent, coll, opts).type}\``;
const remarks = "Only the pointer's target address can be accessed";
if (doc.length) {
doc.push("", "@remarks", typeInfo, remarks);
}
else {
doc.push(typeInfo, "", "@remarks", remarks);
}
const doc = [...ensureLines(f.doc || [])];
if (isPointer(f.tag) && f.len === 0) {
const typeInfo = `Multi pointer: \`${zigFieldType(f, parent, coll, opts).type}\``;
const remarks = "Only the pointer's target address can be accessed";
if (doc.length) {
doc.push("", "@remarks", typeInfo, remarks);
} else {
doc.push(typeInfo, "", "@remarks", remarks);
}
else if (isWasmPrim(f.type)) {
if (doc.length)
doc.push("", "@remarks");
doc.push(`Zig type: \`${zigFieldType(f, parent, coll, opts).type}\``);
}
return doc.length ? doc : undefined;
} else if (isWasmPrim(f.type)) {
if (doc.length)
doc.push("", "@remarks");
doc.push(`Zig type: \`${zigFieldType(f, parent, coll, opts).type}\``);
}
return doc.length ? doc : void 0;
};
/** @internal */
const generateField = (field, coll, opts) => {
if (field.skip)
return;
if (isFuncPointer(field.type, coll) || isOpaque(field.type)) {
field = { ...field, type: opts.target.usize };
if (field.skip)
return;
if (isFuncPointer(field.type, coll) || isOpaque(field.type)) {
field = { ...field, type: opts.target.usize };
}
const { classifier, isConst } = classifyField(field, coll);
const name = field.name;
const offset = field.__offset;
const strType = __stringImpl(opts);
const isPrim = isWasmPrim(field.type);
const $isEnum = isEnum(field.type, coll);
let type = field.type;
let decl;
let getter = [];
let setter;
let ptrType;
let tag;
switch (classifier) {
case "str":
type = strType;
getter = [
`return $${name} || ($${name} = new ${strType}(mem, ${__addr(
offset
)}, ${isConst}));`
];
break;
case "strPtr":
type = `Pointer<${strType}>`;
decl = `let $${name}: ${type} | null = null;`;
getter = __ptrBody(type, name, offset, [
`(addr) => new ${strType}(mem, addr, ${isConst})`
]);
break;
case "strPtrFixed":
type = `Pointer<${strType}[]>`;
getter = __ptrBody(type, name, offset, [
`(addr) => {`,
...__mapStringArray(
opts.target,
"buf",
strType,
field.len,
isConst,
true
),
"}"
]);
break;
case "strArray":
type = `${strType}[]`;
getter = [
`const addr = ${__addr(offset)};`,
...__mapStringArray(
opts.target,
name,
strType,
field.len,
isConst,
true
)
];
break;
case "strSlice":
type = `${strType}[]`;
getter = [
`const addr = ${__ptr(opts.target, offset)};`,
`const len = ${__ptr(
opts.target,
offset + opts.target.sizeBytes
)};`,
...__mapStringArray(
opts.target,
name,
strType,
"len",
isConst,
true
)
];
break;
case "ptr":
if (isPrim) {
ptrType = `Pointer<number>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => mem.${type}[addr >>> ${__shift(type)}]`
]);
} else {
ptrType = `Pointer<${type}>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => $${type}(mem).instance(addr)`
]);
}
type = ptrType;
decl = `let $${name}: ${ptrType} | null = null;`;
break;
case "enumPtr":
tag = coll[type].tag;
type = `Pointer<${type}>`;
getter = __ptrBody(type, name, offset, [
`(addr) => mem.${tag}[addr >>> ${__shift(tag)}]`
]);
break;
case "ptrFixed":
if (isPrim) {
ptrType = `Pointer<${__arrayType(type)}>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => mem.${type}.subarray(addr, addr + ${field.len})`
]);
} else {
ptrType = `Pointer<${type}[]>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => {`,
...__mapArray(field, coll, field.len),
"}"
]);
}
type = ptrType;
break;
case "enumPtrFixed":
tag = coll[type].tag;
type = `Pointer<${__arrayType(tag)}>`;
getter = __ptrBody(type, name, offset, [
`(addr) => mem.${tag}.subarray(addr, addr + ${field.len})`
]);
break;
case "array":
case "vec":
if (isPrim) {
getter = [...__primArray(type, field.len, offset)];
type = __arrayType(type);
} else {
type += "[]";
getter = [
`const addr = ${__addr(offset)};`,
...__mapArray(field, coll, field.len)
];
}
break;
case "enumArray":
tag = coll[type].tag;
type = __arrayType(tag);
getter = [...__primArray(tag, field.len, offset)];
break;
case "slice":
case "enumSlice":
getter = [
`const addr = ${__ptr(opts.target, offset)};`,
`const len = ${__ptr(
opts.target,
offset + opts.target.sizeBytes
)};`
];
if (isPrim) {
getter.push(...__primSlice(type, offset, opts));
type = __arrayType(type);
} else if ($isEnum) {
tag = coll[type].tag;
getter.push(...__primSlice(tag, offset, opts));
type = __arrayType(tag);
} else {
type += "[]";
getter.push(...__mapArray(field, coll));
}
break;
case "single":
if (isPrim) {
getter = [`return ${__mem(type, offset)};`];
setter = [`${__mem(type, offset)} = x;`];
type = isNumeric(type) ? "number" : "bigint";
} else {
getter = [`return $${type}(mem).instance(${__addr(offset)});`];
setter = [`mem.u8.set(x.__bytes, ${__addr(offset)});`];
}
break;
case "enum": {
getter = [`return ${__mem(coll[type].tag, offset)};`];
setter = [`${__mem(coll[type].tag, offset)} = x;`];
break;
}
const { classifier, isConst } = classifyField(field, coll);
const name = field.name;
const offset = field.__offset;
const strType = __stringImpl(opts);
const isPrim = isWasmPrim(field.type);
const $isEnum = isEnum(field.type, coll);
let type = field.type;
let decl;
let getter = [];
let setter;
let ptrType;
let tag;
switch (classifier) {
case "str":
type = strType;
getter = [
`return $${name} || ($${name} = new ${strType}(mem, ${__addr(offset)}, ${isConst}));`,
];
break;
case "strPtr":
type = `Pointer<${strType}>`;
decl = `let $${name}: ${type} | null = null;`;
getter = __ptrBody(type, name, offset, [
`(addr) => new ${strType}(mem, addr, ${isConst})`,
]);
break;
case "strPtrFixed":
type = `Pointer<${strType}[]>`;
getter = __ptrBody(type, name, offset, [
`(addr) => {`,
...__mapStringArray(opts.target, "buf", strType, field.len, isConst, true),
"}",
]);
break;
case "strArray":
type = `${strType}[]`;
getter = [
`const addr = ${__addr(offset)};`,
...__mapStringArray(opts.target, name, strType, field.len, isConst, true),
];
break;
case "strSlice":
type = `${strType}[]`;
getter = [
`const addr = ${__ptr(opts.target, offset)};`,
`const len = ${__ptr(opts.target, offset + opts.target.sizeBytes)};`,
...__mapStringArray(opts.target, name, strType, "len", isConst, true),
];
break;
case "ptr":
if (isPrim) {
ptrType = `Pointer<number>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => mem.${type}[addr >>> ${__shift(type)}]`,
]);
}
else {
ptrType = `Pointer<${type}>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => $${type}(mem).instance(addr)`,
]);
}
type = ptrType;
decl = `let $${name}: ${ptrType} | null = null;`;
break;
case "enumPtr":
tag = coll[type].tag;
type = `Pointer<${type}>`;
getter = __ptrBody(type, name, offset, [
`(addr) => mem.${tag}[addr >>> ${__shift(tag)}]`,
]);
break;
case "ptrFixed":
if (isPrim) {
ptrType = `Pointer<${__arrayType(type)}>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => mem.${type}.subarray(addr, addr + ${field.len})`,
]);
}
else {
ptrType = `Pointer<${type}[]>`;
getter = __ptrBody(ptrType, name, offset, [
`(addr) => {`,
...__mapArray(field, coll, field.len),
"}",
]);
}
type = ptrType;
break;
case "enumPtrFixed":
tag = coll[type].tag;
type = `Pointer<${__arrayType(tag)}>`;
getter = __ptrBody(type, name, offset, [
`(addr) => mem.${tag}.subarray(addr, addr + ${field.len})`,
]);
break;
case "array":
case "vec":
if (isPrim) {
getter = [...__primArray(type, field.len, offset)];
type = __arrayType(type);
}
else {
type += "[]";
getter = [
`const addr = ${__addr(offset)};`,
...__mapArray(field, coll, field.len),
];
}
break;
case "enumArray":
tag = coll[type].tag;
type = __arrayType(tag);
getter = [...__primArray(tag, field.len, offset)];
break;
case "slice":
case "enumSlice":
getter = [
`const addr = ${__ptr(opts.target, offset)};`,
`const len = ${__ptr(opts.target, offset + opts.target.sizeBytes)};`,
];
if (isPrim) {
getter.push(...__primSlice(type, offset, opts));
type = __arrayType(type);
}
else if ($isEnum) {
tag = coll[type].tag;
getter.push(...__primSlice(tag, offset, opts));
type = __arrayType(tag);
}
else {
type += "[]";
getter.push(...__mapArray(field, coll));
}
break;
case "single":
if (isPrim) {
getter = [`return ${__mem(type, offset)};`];
setter = [`${__mem(type, offset)} = x;`];
type = isNumeric(type) ? "number" : "bigint";
}
else {
getter = [`return $${type}(mem).instance(${__addr(offset)});`];
setter = [`mem.u8.set(x.__bytes, ${__addr(offset)});`];
}
break;
case "enum": {
getter = [`return ${__mem(coll[type].tag, offset)};`];
setter = [`${__mem(coll[type].tag, offset)} = x;`];
break;
}
case "ptrMulti":
case "enumPtrMulti":
case "strPtrMulti":
// impossible to map memory of undefined length so only return
// pointer's target addr (same as `opaquePtr`)
type = "number";
getter = [`return ${__ptr(opts.target, offset)};`];
setter = [`${__ptr(opts.target, offset)} = x;`];
break;
case "pad":
// skip codegen for padding fields and multi pointers since
return;
default:
unsupported(`TODO: ${classifier} - please report as issue`);
}
return { field, type, decl, getter, setter };
case "ptrMulti":
case "enumPtrMulti":
case "strPtrMulti":
type = "number";
getter = [`return ${__ptr(opts.target, offset)};`];
setter = [`${__ptr(opts.target, offset)} = x;`];
break;
case "pad":
return;
default:
unsupported(`TODO: ${classifier} - please report as issue`);
}
return { field, type, decl, getter, setter };
};
export {
TYPESCRIPT
};

@@ -6,212 +6,235 @@ import { isNumber } from "@thi.ng/checks";

import { classifyField } from "./internal/classify.js";
import { defaultValue, ensureLines, ensureStringArray, isOpaque, isPadding, isStringSlice, isUnion, isWasmString, prefixLines, sliceTypes, withIndentation, } from "./internal/utils.js";
/**
* Zig code generator. Call with options and then pass to {@link generateTypes}
* (see its docs for further usage).
*
* @remarks
* This codegen generates struct and enum definitions for a {@link TypeColl}
* given to {@link generateTypes}.
*
* @param opts
*/
export 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(`\npub 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, opts, topLevel = false) => {
acc.push(...prefixLines(topLevel ? "//! " : "/// ", doc, opts.lineWidth));
},
enum: (e, _, acc, opts) => {
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, opts);
line = v.name;
if (v.value != null)
line += ` = ${v.value}`;
}
else {
line = v;
}
lines.push(line + ",");
}
if (e.body?.zig) {
lines.push("", ...ensureLines(e.body.zig, "impl"), "");
}
lines.push("};", "");
acc.push(...withIndentation(lines, INDENT, ...SCOPES));
},
struct: (struct, coll, acc, opts) => {
acc.push(...withIndentation([
`pub const ${struct.name} = extern struct {`,
...__generateFields(gen, struct, coll, opts),
], INDENT, ...SCOPES));
},
union: (union, coll, acc, opts) => {
acc.push(...withIndentation([
`pub const ${union.name} = extern union {`,
...__generateFields(gen, union, coll, opts),
], INDENT, ...SCOPES));
},
funcptr: (ptr, coll, acc, opts) => {
const args = ptr.args
.map((a) => `${a.name}: ${fieldType(a, ptr, coll, opts).type}`)
.join(", ");
const rtype = ptr.rtype === "void"
? ptr.rtype
: fieldType({ name: "return", ...ptr.rtype }, ptr, coll, opts).type;
acc.push(`pub const ${ptr.name} = *const fn (${args}) ${rtype};`, "");
},
};
return gen;
import {
defaultValue,
ensureLines,
ensureStringArray,
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)
);
},
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 + ",");
}
if (e.body?.zig) {
lines.push("", ...ensureLines(e.body.zig, "impl"), "");
}
lines.push("};", "");
acc.push(...withIndentation(lines, INDENT, ...SCOPES));
},
struct: (struct, coll, acc, opts2) => {
acc.push(
...withIndentation(
[
`pub const ${struct.name} = extern struct {`,
...__generateFields(gen, struct, coll, opts2)
],
INDENT,
...SCOPES
)
);
},
union: (union, coll, acc, opts2) => {
acc.push(
...withIndentation(
[
`pub const ${union.name} = extern union {`,
...__generateFields(gen, union, coll, 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}) ${rtype};`,
""
);
}
};
return gen;
};
const __generateFields = (gen, parent, coll, opts) => {
const res = [];
const ftypes = {};
const name = parent.name;
let padID = 0;
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},`);
}
if (parent.body?.zig) {
res.push("", ...ensureLines(parent.body.zig, "impl"), "");
}
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) {
// autolabel explicit padding 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},`);
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]})`);
}
if (parent.body?.zig) {
res.push("", ...ensureLines(parent.body.zig, "impl"), "");
}
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;
}
res.push("");
return res;
};
/** @internal */
export 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;
}
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 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;
}
} 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 != undefined) {
if (!(isString(defaultVal) || isNumber(defaultVal))) {
illegalArgs(`wrong default value for ${parent.name}.${f.name} (${defaultVal})`);
}
}
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 != undefined ? ` = ${defaultVal}` : "",
};
}
return {
type,
defaultVal: defaultVal != void 0 ? ` = ${defaultVal}` : ""
};
};
export {
ZIG,
fieldType
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc