Skip to content

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 { LexQuery, type LexQueryKindResolver } from '@lanexio/parser-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.

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: '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
const src = decoder.decode(tree.source.slice(node.range.start, node.range.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.

PatternDescription
KindNameMatch nodes of the named kind.
*Match any node.
@fieldNameMatch nodes at the named field slot.
[error]Match only LexError nodes.
A, BMatch either A or B (disjunction).
// All nodes of any kind
const all = LexQuery.compile('*', resolver);
// Only LexError nodes (malformed input)
const errors = LexQuery.compile('[error]', resolver);
// Element or Text nodes
const elemOrText = LexQuery.compile('Element, Text', resolver);

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];

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
}
}
APITypeDescription
LexQuery.compile(pattern: string, resolver: LexQueryKindResolver) => LexQueryCompile a pattern. Throws LexQuerySyntaxError for invalid patterns.
query.matches(tree: LexTree) => IterableIterator<LexNode>Iterate matching nodes in preorder DFS.
query.testNode(node: LexNode) => booleanTest a single node.
query.sourcestringThe original pattern string.