Skip to content

grammar-kit API

@lanexio/parser-grammar-kit is the authoring toolkit for grammar packs. It validates the metadata sidecar every grammar must expose and generates the TypeScript kind/field modules (HtmlKind-style as const objects) deterministically from that metadata. Nothing in this package throws — every API returns a result object with stable diagnostic codes.

Layer: Authoring/tooling. Depends on @lanexio/parser-core. Runtime: Node.js (library is universal; the CLI writes files).

The sidecar shape every grammar pack declares:

type GrammarMetadata = {
readonly name: string; // npm package name
readonly version: string;
readonly protocolVersion: number; // must equal core's PROTOCOL_VERSION
readonly kinds: ReadonlyArray<{ name: string; id: number; named: boolean }>;
readonly fields?: ReadonlyArray<{ name: string; id: number }>;
};

Rules enforced by validation:

RuleConstraint
Kind namesPascalCase identifiers, unique.
Kind idsIntegers 1–65535 (protocol u16), unique.
Field namescamelCase identifiers, unique.
Field idsIntegers 1–255 (protocol u8; 0 is reserved for “no field”), unique.
protocolVersionMust match the core runtime’s PROTOCOL_VERSION.
import { validateGrammarMetadata } from '@lanexio/parser-grammar-kit';
const result = validateGrammarMetadata(metadata);
if (!result.ok) {
for (const d of result.diagnostics) {
console.error(`${d.code} at ${d.path}: ${d.message}`);
// e.g. "invalid_kind_name at kinds.3.name: Kind names must be PascalCase identifiers."
}
}

Diagnostics are path-addressed (kinds.3.id, fields.0.name) and coded:

invalid_name · invalid_version · invalid_protocol_version · empty_kinds · invalid_kind_name · duplicate_kind_name · invalid_kind_id · duplicate_kind_id · invalid_field_name · duplicate_field_name · invalid_field_id · duplicate_field_id · invalid_export_name

Generates the TypeScript module source for a grammar’s kind constants — and, when fields are present, the field constants plus the …NamesById map that powers fieldName()/childByField() on parsed trees.

import { emitKindModule } from '@lanexio/parser-grammar-kit';
const emitted = emitKindModule(metadata, {
exportName: 'MyKind', // PascalCase, required
fieldExportName: 'MyField', // optional; defaults to `${exportName}Field`
});
if (emitted.ok) {
writeFileSync('src/kinds.generated.ts', emitted.source);
}

Output is deterministic — kinds and fields are emitted sorted by id under a fixed license header, so generated modules are byte-reproducible and diff-stable in CI.

Generated shape (fields present):

export const MyKind = { Document: 1, /* … sorted by id … */ } as const;
export type MyKind = typeof MyKind[keyof typeof MyKind];
export const MyField = { Selector: 1, /* … */ } as const;
export type MyField = typeof MyField[keyof typeof MyField];
export const MyFieldNamesById = { [MyField.Selector]: "selector", /* … */ } as const;

Export-name validation prevents emitting modules that cannot compile: exportName and fieldExportName must be PascalCase, must not collide with each other, and exportName must not collide with the derived ${fieldExportName}NamesById identifier. Violations return invalid_export_name diagnostics rather than bad source.

Terminal window
grammar-kit --input kinds.json --output src/kinds.generated.ts
grammar-kit --input kinds.json --output src/kinds.generated.ts --export-name CssKind --field-export-name CssField
grammar-kit --help
FlagDefaultDescription
--input <file>requiredGrammar metadata JSON.
--output <file>requiredDestination for the generated module (directories are created).
--export-name <Name>LexGrammarKindKind export identifier.
--field-export-name <Name>${exportName}FieldField export identifier.
--help, -hPrint usage and exit 0.

Behavior notes:

  • Exit code 0 on success or --help; 1 for bad arguments, unreadable/invalid input, or emission diagnostics (printed one per line as code at path: message).
  • Flag values that look like another flag are rejected with a did-you-forget message instead of being swallowed.
  • The bin works under every package manager’s install layout (entrypoint detection is realpath-safe for symlinked bins).
  1. Write the grammar’s kinds.json metadata (kinds in your assigned id block; fields 1–254).
  2. grammar-kit --input kinds.json --output src/kinds.generated.ts --export-name MyKind.
  3. Implement parseMyThing(bytes): LexTree against the tree writers, passing { fieldNamesById: MyFieldNamesById } so field navigation works on your trees.
  4. Export a LanexioParserPureGrammar descriptor ({ kind: 'pure', name, protocolVersion, parse }) so createParser hosts can load your grammar — or a WASM descriptor per the parser-wasm contract.
  5. Wire your parse entry points into never-throw and fuzz harnesses; that contract is what makes your grammar a citizen of the framework.