Skip to content

TypeScript / WASM

Hyperlark ships as a WebAssembly module with a typed TypeScript interface. It runs on Node ≥ 22 (the default entry), and in the browser, bundlers, Deno, Bun and edge runtimes (the @hyperlark/hyperlark/web entry).

You write a grammar in Lark’s .lark language and get a parse tree, or a value computed by a transformer.

Terminal window
npm install @hyperlark/hyperlark@beta

Hyperlark is in beta, hence the @beta tag.

import { Lark, pretty } from "@hyperlark/hyperlark";
const parser = new Lark(String.raw`
start: "hello" NAME
NAME: /\w+/
%ignore " "
`);
const tree = parser.parse("hello world");
console.log(pretty(tree));
start world

start is a rule, NAME a terminal. Terminals define the alphabet of your language, while rules define its structure; terminals are written in UPPER-CASE, rules in lower-case. %ignore " " tells the parser to ignore spaces. The literal "hello" is matched, but filtered out of the tree as punctuation.

String.raw keeps the backslash in \w; a plain template literal drops it.

parse returns Node, which is Tree | Token | null; narrow it with isTree before reading children (Working with trees).

The sample uses the default, Earley, which accepts any context-free grammar, even ambiguous ones. LALR is much faster, with some grammar restrictions; we recommend starting with it: pass { parser: "lalr" } as the second argument. Only LALR can compute a value during the parse, recover from errors, parse interactively, or stream subtrees with parseStream. How it works compares the two.

A failed parse throws a subclass of UnexpectedInput. Errors lists them and shows how to catch them.

Everywhere but Node, import the /web entry and await init() once, before the first parse:

import init, { pretty } from "@hyperlark/hyperlark/web";
const { Lark } = await init();
const parser = new Lark(String.raw`
start: "hello" NAME
NAME: /\w+/
%ignore " "
`);
console.log(pretty(parser.parse("hello world")));

init() returns Lark, BasicLexer, LarkError and its subclasses, and the other WASM classes, under the Node entry’s names. Helpers that need no WASM, such as pretty and Transformer, stay named imports.

With no argument, init() fetches hyperlark_wasm_bg.wasm relative to the module’s URL. To load it from elsewhere, pass a URL, a Response, the bytes or a compiled WebAssembly.Module. The file is 2 MB, 740 KB gzipped.

%import common.NUMBER and the rest of the built-in library need no setup. To import your own .lark files, pass a loader in the import_loader option: mapLoader(sources) for text in memory, fsLoader(rootDir) on Node (Directives).

A compiled LALR grammar can be saved as JSON and loaded later, instead of compiling again. The npm package loads one but cannot save it: save from Rust or Python, then call Lark.fromJSON(json). The Node-only @hyperlark/hyperlark/slim entry has no grammar compiler and a smaller WASM module, so it loads saved grammars only. See Saving a compiled grammar.