LexQuery
LexQuery finds nodes in a LexTree by pattern. Patterns describe node kind names, fields, and error status. Queries run in preorder DFS and return an iterator.
Package: @lanexio/parser-query
Layer: 3 (Query). Depends only on @lanexio/parser-core.
Runtime: Universal.
Import
Section titled “Import”import { LexQuery, type LexQueryKindResolver } from '@lanexio/parser-query';Compile a query
Section titled “Compile a query”import { LexQuery } from '@lanexio/parser-query';import { HtmlKind } from '@lanexio/parser-grammar-html';
const resolver: LexQueryKindResolver = (name: string) => { const kind = (HtmlKind as Record<string, number | undefined>)[name]; return kind;};
const query = LexQuery.compile('Element', resolver);LexQuery.compile returns a compiled query object. If the pattern is invalid, it throws LexQuerySyntaxError.
Run a query
Section titled “Run a query”import { parseHtml } from '@lanexio/parser-grammar-html';import { LexQuery } from '@lanexio/parser-query';import { HtmlKind } from '@lanexio/parser-grammar-html';
const encoder = new TextEncoder();const tree = parseHtml(encoder.encode('<div><p>First</p><p>Second</p></div>'));
const resolver = (name: string) => (HtmlKind as Record<string, number | undefined>)[name];const query = LexQuery.compile('Element', resolver);
for (const node of query.matches(tree)) { console.log(node.kind, node.range);}query.matches(tree) returns an IterableIterator<LexNode> in preorder DFS order.
CRITICAL: Kind names are not HTML tag names
Section titled “CRITICAL: Kind names are not HTML tag names”Wrong: filtering by HTML tag name
Section titled “Wrong: filtering by HTML tag name”// WRONG: 'p' is not a kind name. This throws LexQuerySyntaxError (UnknownKind).const query = LexQuery.compile('p', resolver);Correct: filter by kind, then check metadata
Section titled “Correct: filter by kind, then check metadata”import { parseHtml, HtmlKind } from '@lanexio/parser-grammar-html';import { LexQuery } from '@lanexio/parser-query';
const encoder = new TextEncoder();const tree = parseHtml(encoder.encode('<div><p>First</p><h2>Second</h2></div>'));
const resolver = (name: string) => (HtmlKind as Record<string, number | undefined>)[name];const query = LexQuery.compile('Element', resolver);
const decoder = new TextDecoder();for (const node of query.matches(tree)) { // Read the tag name from the source bytes to filter further. // node.range is a tuple: readonly [start, end]. const [start, end] = node.range; const src = decoder.decode(tree.source.subarray(start, end)); if (src.startsWith('<p')) { console.log('found <p> at', node.range); }}Multi-step combinators (":has", ":not", child combinators) are deferred to v1.1. For now, query by kind and filter the results in your loop.
Pattern grammar
Section titled “Pattern grammar”| Pattern | Description |
|---|---|
KindName | Match nodes of the named kind. |
* | Match any node. |
KindName@fieldName | Restrict matches to nodes carrying the named field role. |
KindName[error] | Restrict matches to nodes carrying the parse-error flag. |
A, B | Match either A or B (disjunction). |
Modifiers attach to a simple selector: Element@body, *[error],
String@key. Field names are the grammar’s field roles (see each grammar
guide’s “Navigate by field name” section), resolved through the tree’s own
field metadata at match time.
Examples
Section titled “Examples”// All nodes of any kindconst all = LexQuery.compile('*', resolver);
// Any node carrying the parse-error flag (malformed input)const errors = LexQuery.compile('*[error]', resolver);
// Element or Text nodesconst elemOrText = LexQuery.compile('Element, Text', resolver);Field-modifier examples
Section titled “Field-modifier examples”Field roles let a query target structural positions. With the JSON grammar
(key/value roles on object members):
import { parseJson, JsonKind } from '@lanexio/parser-grammar-json';import { LexQuery } from '@lanexio/parser-query';
const encoder = new TextEncoder();const tree = parseJson(encoder.encode('{"name": "Alice", "id": 42}'));
const resolver = (name: string) => (JsonKind as Record<string, number | undefined>)[name];
// Every member KEY in the document:const keys = LexQuery.compile('String@key', resolver);for (const node of keys.matches(tree)) { console.log('key:', node.text); // "name", "id"}With the CSS grammar (selector/property/value roles):
import { parseCss, CssKind } from '@lanexio/parser-grammar-css';
const cssResolver = (name: string) => (CssKind as Record<string, number | undefined>)[name];
// Every declaration property name in a stylesheet:const props = LexQuery.compile('PropertyName@property', cssResolver);LexQueryKindResolver
Section titled “LexQueryKindResolver”LexQueryKindResolver maps a kind name string to its numeric kind ID, or undefined if unknown.
type LexQueryKindResolver = (name: string) => number | undefined;When the resolver returns undefined for a name in the pattern, LexQuery.compile throws LexQuerySyntaxError with code UnknownKind.
For HTML queries, cast HtmlKind as a record:
import { HtmlKind } from '@lanexio/parser-grammar-html';
const htmlResolver = (name: string): number | undefined => (HtmlKind as Record<string, number | undefined>)[name];For Markdown queries:
import { MdKind } from '@lanexio/parser-grammar-markdown';
const mdResolver = (name: string): number | undefined => (MdKind as Record<string, number | undefined>)[name];LexQuerySyntaxError
Section titled “LexQuerySyntaxError”If LexQuery.compile receives an invalid pattern, it throws LexQuerySyntaxError.
import { LexQuery, LexQuerySyntaxError, LexQuerySyntaxErrorCode } from '@lanexio/parser-query';
try { const query = LexQuery.compile('!!!invalid', resolver);} catch (err) { if (err instanceof LexQuerySyntaxError) { console.error(err.code, 'at offset', err.offset); // LexQuerySyntaxErrorCode values: // EmptyPattern | UnexpectedToken | UnknownKind | UnsupportedAttribute }}API reference
Section titled “API reference”| API | Type | Description |
|---|---|---|
LexQuery.compile | (pattern: string, resolver: LexQueryKindResolver) => LexQuery | Compile a pattern. Throws LexQuerySyntaxError for invalid patterns. |
query.matches | (tree: LexTree) => IterableIterator<LexNode> | Iterate matching nodes in preorder DFS. |
query.testNode | (node: LexNode) => boolean | Test a single node. |
query.source | string | The original pattern string. |