@dikolab/compiler
v0.3.1
Published
Build a compiler from a PARSEQ grammar by attaching TypeScript semantic actions to its productions — a yacc-style, syntax-directed translator over @dikolab/parser's RPN AST, for Node.js, Deno, and the browser.
Maintainers
Readme
@dikolab/compiler
Build a compiler from a PARSEQ grammar by attaching TypeScript semantic actions to its
productions. It reuses @dikolab/parser to
parse, then folds the postorder (RPN) Lexeme stream into a single augmented lexeme —
dispatching one action per production, keyed by non-terminal name then rule_index, yacc-style
($$ = f($1, …, $n)), one-shot or streaming. Runs the same in Node.js, Deno, and the browser.
Documentation
Full documentation is published on the project docs site:
- Overview — install, the value-stack fold, streaming, and a quick start.
- API Reference —
Compiler,reduce,defineActions, and the augmented-lexeme / action / status types. - Examples — a calculator, typed literals, and chunk-by-chunk streaming.
Install
npm add @dikolab/compiler # pulls in @dikolab/parser
deno add jsr:@dikolab/compiler # Deno (JSR)Quick start
import { init, Compiler } from "@dikolab/compiler";
await init(); // load the parser WASM once (re-exported from @dikolab/parser)
// A JavaScript template literal, so every grammar backslash is DOUBLED.
const GRAMMAR = `%skip WS
%%lexer
NUMBER
PLUS
WS
%%%%
[0-9]+ := NUMBER
\\+ := PLUS
[ ]+ := WS
%%grammar
expr : expr PLUS NUMBER | NUMBER ;
`;
// Actions are keyed by symbol name: a terminal name (NUMBER) → a leaf handler; a
// non-terminal name (expr) → one action per alternative, numbered 1-based per
// non-terminal (`expr : expr PLUS NUMBER` is alternative 1, `expr : NUMBER` is 2).
// Each action returns { type, data }; children arrive as augmented lexemes carrying
// their computed `data` plus their full source lexeme.
const calc = new Compiler(GRAMMAR, {
NUMBER: (lx) => ({ type: "number", data: parseFloat(lx.data as string) }),
expr: {
1: (l, plus, r) => ({
type: "number",
data: (l.data as number) + (r.data as number),
}), // expr PLUS NUMBER
2: (only) => only, // NUMBER — pass the child's { type, data } through
},
});
const root = calc.compile("1 + 2 + 3");
console.log(root.data); // 6 (also root.type "number", root.name "expr", root.from_index 0)How it works
Every parse returns a postorder (RPN) Lexeme[], which is the reduce trace: each
non-terminal carries child_count (how many children to pop) and rule_index (which
alternative of its non-terminal reduced). compile() folds that stream with a value stack of
augmented lexemes — each raw lexeme re-tagged with its computed value (data) and value kind
(type: string / number / bigint / date), keeping name, rule_index, and source span
for auditing. On each reduction it pops the children, runs the production's action, and stamps the
returned { type, data } onto the reduction node. The accepted root is returned as one augmented
lexeme: a syntax-directed translation with no intermediate tree required.
Streaming
Drive the same fold chunk-by-chunk with walk(chunk, lastChunk) — built to be wrapped as a Node or
Web stream. It returns a WalkStatus ({ done, pending, root, error }) and never throws (errors
ride in status.error); the augmented root arrives once EOF is reached. Observe progress via the
getters (completed / pending / error, and the fromLine / toCharacterIndex … Range), reuse
the instance with reset(), and free the WASM parser with destroy() (or using).
// reusing the GRAMMAR + actions from the quick start
const calc = new Compiler(GRAMMAR, {
NUMBER: (lx) => ({ type: "number", data: parseFloat(lx.data as string) }),
expr: {
1: (l, plus, r) => ({
type: "number",
data: (l.data as number) + (r.data as number),
}),
2: (only) => only,
},
});
calc.walk("1 + ", false); // { done: false, pending: true, root: null, error: null }
calc.walk("2 + ", false); // …still pending — feed more
const { done, root } = calc.walk("3", true); // { done: true, root: <augmented "expr"> }
console.log(done, root!.data); // true 6
calc.destroy(); // free the WASM parser (or: `using calc = new Compiler(...)`)Support
If @dikolab/compiler is useful to you, you can support its development:
License
MIT © 2026 Diko Consunji
