Skip to content

parser-core API

@lanexio/parser-core is the foundation every grammar builds on: the zero-copy LexTree and its views, the writers grammar packs use to emit trees, the streaming and incremental helpers, and the frozen shared-buffer protocol constants.

Layer: 1 (Core). No dependencies. Runtime: Universal.

An immutable, zero-copy parse tree over one ArrayBuffer. Constructing one from an untrusted buffer runs full validation (header + every node record) and throws LexTreeValidationError with a stable code on any violation; trees produced by the writers below skip the per-node pass safely.

import { LexTree } from '@lanexio/parser-core';
const tree = new LexTree(buffer, sourceBytes, { metadata: { fieldNamesById } });
MemberTypeDescription
bufferArrayBufferThe raw flat-AST buffer (header + node records).
sourceUint8ArrayThe original parsed bytes. Never copied.
nodeCountnumberNode records in the tree. Cached; O(1).
rootLexNodeRoot node view.
cursor()LexCursorNew cursor positioned at the root.
fieldNameForId(id)string | nullGrammar field-role name for a field id (null for 0/unknown).
fieldIdForName(name)number | nullReverse lookup; resolve once for hot loops.
readNodeUint8/16/32(index, fieldOffset)numberRaw little-endian record reads. fieldOffset must be a protocol layout constant; only index is bounds-checked.
parentIndex(index)number | nullParent’s node index (null at root). Builds a parent side-table lazily on first use, then O(1).
nextSiblingIndex(index) / previousSiblingIndex(index)number | nullSibling index math over subtreeSize.
getCachedSerialization(key) / setCachedSerialization(key, value)string | undefined / voidPer-tree serialization memo used by grammar serializers (keys like "html:outer"). Trees are immutable, so cached output is always valid.

Thrown only by the constructor on malformed buffers. err.code is one of:

buffer_too_small · invalid_magic · invalid_version · invalid_record_size · empty_tree · invalid_root_index · node_section_out_of_bounds · invalid_node_index · invalid_subtree_size · invalid_node_range

(invalid_node_index is also thrown by the raw readNode* methods for an out-of-range index.)

An immutable 16-byte-record view. Cheap to create; hold or discard freely.

MemberTypeNotes
indexnumberPreorder index in the flat AST.
kind / flags / fieldIdnumberRaw record fields.
hasErrorboolean(flags & LEX_NODE_HAS_ERROR) !== 0.
rangeLexRange = readonly [start, end]Half-open byte range. A tuple — destructure it.
textstringUTF-8 decode of the range. Allocates; avoid in hot loops.
subtreeSizenumberNodes in this subtree including itself.
fieldName()string | nullField-role name via tree metadata.
children()IterableIterator<LexNode>Direct children; O(k) total.
childCount()numberO(k).
child(i)LexNode | nullO(i) per call — prefer children() over indexed loops.
childByField(name)LexNode | nullFirst direct child with the named field role.
firstChildForRange([s, e])LexNode | nullFirst direct child whose range contains [s, e).
descendantForRange([s, e])LexNode | nullDeepest containing descendant.
nextSibling() / previousSibling()LexNode | nullSibling navigation.
followingSiblings()IterableIterator<LexNode>Later siblings.
parent()LexNode | nullVia the lazy parent table.

Traversal is hang-proof by construction: child/sibling stride arithmetic carries forward-progress guards, so even a corrupt-but-header-valid buffer cannot loop traversal forever.

Stateful, allocation-light traversal. All moves return boolean (did the cursor move?).

MethodDescription
currentLexNode at the position.
gotoFirstChild() / gotoNextSibling() / gotoPreviousSibling() / gotoParent()Structural moves.
gotoFirstChildForRange([s, e]) / gotoDescendantForRange([s, e])Byte-range descent for editor tooling.

See Tree Traversal for the canonical DFS pattern.

Grammar packs emit trees through one of two writers. Both attach optional field-name metadata and return a ready LexTree.

import { createTree, createPendingNode } from '@lanexio/parser-core';
const nodes = [
createPendingNode(kind, flags, fieldId, rangeStart, rangeEnd), // subtree_size defaults to 1
// …preorder DFS; patch node.subtree_size as containers close…
];
const tree = createTree(sourceBytes, nodes, { fieldNamesById: MY_FIELD_NAMES });

PendingNode is the mutable record shape (kind, flags, field_id, range_start, range_end, subtree_size).

Typed-array form — createTreeFromUint32Array

Section titled “Typed-array form — createTreeFromUint32Array”

The allocation-light path used by the JSON/CSS/toy grammars: nodes live in a Uint32Array with 6 slots per node.

import {
NODE_STRIDE, // 6
SLOT_KIND, SLOT_FLAGS, SLOT_FIELD, // 0, 1, 2
SLOT_START, SLOT_END, SLOT_SIZE, // 3, 4, 5
growNodeData,
createTreeFromUint32Array,
} from '@lanexio/parser-core';
let nd = new Uint32Array(64 * NODE_STRIDE);
let nc = 0;
nd = growNodeData(nd, nc + 1); // doubling growth; returns (possibly new) buffer
const o = nc * NODE_STRIDE;
nd[o + SLOT_KIND] = kind;
nd[o + SLOT_FLAGS] = 0;
nd[o + SLOT_FIELD] = 0;
nd[o + SLOT_START] = start;
nd[o + SLOT_END] = end;
nd[o + SLOT_SIZE] = 1;
nc += 1;
const tree = createTreeFromUint32Array(nd, nc, sourceBytes, { fieldNamesById });
createParseStream(grammarName: string, parseFn: (bytes: Uint8Array) => LexTree): LexParseStream

Push-based sessions with per-push trees and a never-rejecting async iterator. Full contract in the Streaming guide.

applyEdit(source: Uint8Array, edit: LexEdit): Uint8Array // pure; throws LexEditError on bad ranges
reparse(previousTree: LexTree, edit: LexEdit): LexTree // never throws; toy-grammar demo binding

LexEditErrorCode: range_out_of_bounds · range_inverted. Note that core’s standalone reparse is bound to the toy demo grammar (it exists for the never-throw harness); real applications use parser.reparse from a createParser handle, which binds the loaded grammar. Semantics in the Incremental guide.

ConstantValueMeaning
PROTOCOL_VERSION1Shared TypeScript/WASM buffer-contract version.
LEX_TREE_MAGIC0x4c584943Header magic for tree buffers.
LEX_TREE_VERSION1Tree buffer format version.
TREE_HEADER_SIZE16Bytes before the node section.
NODE_RECORD_SIZE16Bytes per node record.
LEX_NODE_HAS_ERROR1Error flag bit.

Header/record byte offsets are documented in Flat AST.

parse (the toy expression parser), LexToyKind, LexToyField, and LEX_TOY_FIELD_NAMES_BY_ID implement the (ident + ident) demo grammar used by the test harnesses and the @lanexio/parser quickstart (re-exported there as toyParse). They are not general-purpose parsers.