125 lines
4.7 KiB
JavaScript
125 lines
4.7 KiB
JavaScript
/**
|
|
* Low-level op-stream writer shared by the MDAST/HAST declarative compilers.
|
|
*
|
|
* Emits the compact OPEN/CLOSE/field/REF/KEEP_CHILDREN/PROP stream that Rust
|
|
* replays straight into the arena (`replay_opstream` in js_commands.rs; byte
|
|
* values in generated/wire-constants.ts) — the only structural encoding for
|
|
* plugin-built content. Strings ride ByteWriter's zero-alloc path (inline
|
|
* char codes when short ASCII, `encodeInto` otherwise).
|
|
*/
|
|
import { ByteWriter } from "./byte-writer.js";
|
|
import { OP_OPEN, OP_CLOSE, OP_REF, OP_KEEP_CHILDREN, OP_STR, OP_U8, OP_U32, OP_BOOL, OP_DATA, OP_PROP, OP_ALIGN, MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "./generated/wire-constants.js";
|
|
// Re-exported so visitors/readers keep importing wire constants from here.
|
|
export { OF_VALUE, OF_URL, OF_TITLE, OF_ALT, OF_LANG, OF_META, OF_IDENTIFIER, OF_LABEL, OF_NAME, OF_REFERENCE_TYPE, OF_DEPTH, OF_CHECKED, OF_START, OF_ORDERED, OF_SPREAD, OF_TAGNAME, OF_EXPLICIT, PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_COMMA_SEP, PROP_INT, PROP_NULL, MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "./generated/wire-constants.js";
|
|
export class OpWriter extends ByteWriter {
|
|
#encoding = false;
|
|
constructor() {
|
|
super(512);
|
|
}
|
|
/**
|
|
* Start a compile on this (shared, module-level) writer. Encoding evaluates
|
|
* plugin-supplied getters and `toJSON`; if one of those calls a context
|
|
* mutation, the nested compile would reset the stream under the outer one
|
|
* and corrupt it silently — throw instead. Pair with `end` in a `finally`.
|
|
*/
|
|
begin() {
|
|
if (this.#encoding) {
|
|
throw new Error("reentrant op-stream compile: a node getter or toJSON invoked a context mutation while its node was being encoded");
|
|
}
|
|
this.#encoding = true;
|
|
this.reset();
|
|
}
|
|
end() {
|
|
this.#encoding = false;
|
|
}
|
|
open(type) {
|
|
this.ensure(2);
|
|
this.buf[this.n++] = OP_OPEN;
|
|
this.buf[this.n++] = type;
|
|
}
|
|
close() {
|
|
this.ensure(1);
|
|
this.buf[this.n++] = OP_CLOSE;
|
|
}
|
|
str(field, s) {
|
|
this.ensure(2);
|
|
this.buf[this.n++] = OP_STR;
|
|
this.buf[this.n++] = field;
|
|
this.utf8WithU32Len(s);
|
|
}
|
|
u8(field, v) {
|
|
this.ensure(3);
|
|
this.buf[this.n++] = OP_U8;
|
|
this.buf[this.n++] = field;
|
|
this.buf[this.n++] = v & 255;
|
|
}
|
|
u32(field, v) {
|
|
this.ensure(6);
|
|
this.buf[this.n++] = OP_U32;
|
|
this.buf[this.n++] = field;
|
|
this.writeU32(v);
|
|
}
|
|
bool(field, v) {
|
|
this.ensure(3);
|
|
this.buf[this.n++] = OP_BOOL;
|
|
this.buf[this.n++] = field;
|
|
this.buf[this.n++] = v ? 1 : 0;
|
|
}
|
|
data(value) {
|
|
const json = JSON.stringify(value);
|
|
// `JSON.stringify` yields undefined for a function or a `toJSON`
|
|
// returning undefined; a clear error beats a TypeError deep in the
|
|
// string encoder.
|
|
if (typeof json !== "string") {
|
|
throw new Error("node `data` is not JSON-serializable");
|
|
}
|
|
this.ensure(1);
|
|
this.buf[this.n++] = OP_DATA;
|
|
this.utf8WithU32Len(json);
|
|
}
|
|
prop(name, kind, value) {
|
|
this.ensure(1);
|
|
this.buf[this.n++] = OP_PROP;
|
|
this.utf8WithU32Len(name);
|
|
this.ensure(1);
|
|
this.buf[this.n++] = kind;
|
|
this.utf8WithU32Len(value);
|
|
}
|
|
ref(id) {
|
|
this.ensure(5);
|
|
this.buf[this.n++] = OP_REF;
|
|
this.writeU32(id);
|
|
}
|
|
/** Table column-alignment codes (0=none, 1=left, 2=right, 3=center). */
|
|
align(codes) {
|
|
this.ensure(5 + codes.length);
|
|
this.buf[this.n++] = OP_ALIGN;
|
|
this.writeU32(codes.length);
|
|
for (let i = 0; i < codes.length; i++)
|
|
this.buf[this.n++] = codes[i] & 255;
|
|
}
|
|
keepChildren() {
|
|
this.ensure(1);
|
|
this.buf[this.n++] = OP_KEEP_CHILDREN;
|
|
}
|
|
}
|
|
/** Emit one MDX JSX attribute as an OP_PROP, mirroring `encode_js_jsx_attrs`:
|
|
* null→boolean, string→literal, `{ value }` object→expression, else→boolean. */
|
|
export function emitMdxAttr(w, a) {
|
|
if (a.type === "mdxJsxExpressionAttribute") {
|
|
w.prop("", MDX_ATTR_SPREAD, typeof a.value === "string" ? a.value : "");
|
|
return;
|
|
}
|
|
const name = typeof a.name === "string" ? a.name : "";
|
|
const val = a.value;
|
|
if (typeof val === "string") {
|
|
w.prop(name, MDX_ATTR_LITERAL_PROP, val);
|
|
}
|
|
else if (val !== null && typeof val === "object" && !Array.isArray(val)) {
|
|
const expr = val.value;
|
|
w.prop(name, MDX_ATTR_EXPRESSION_PROP, typeof expr === "string" ? expr : "");
|
|
}
|
|
else {
|
|
w.prop(name, MDX_ATTR_BOOLEAN_PROP, "");
|
|
}
|
|
}
|