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).
GrammarMetadata
Section titled “GrammarMetadata”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:
| Rule | Constraint |
|---|---|
| Kind names | PascalCase identifiers, unique. |
| Kind ids | Integers 1–65535 (protocol u16), unique. |
| Field names | camelCase identifiers, unique. |
| Field ids | Integers 1–255 (protocol u8; 0 is reserved for “no field”), unique. |
protocolVersion | Must match the core runtime’s PROTOCOL_VERSION. |
validateGrammarMetadata
Section titled “validateGrammarMetadata”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
emitKindModule
Section titled “emitKindModule”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.
The grammar-kit CLI
Section titled “The grammar-kit CLI”grammar-kit --input kinds.json --output src/kinds.generated.tsgrammar-kit --input kinds.json --output src/kinds.generated.ts --export-name CssKind --field-export-name CssFieldgrammar-kit --help| Flag | Default | Description |
|---|---|---|
--input <file> | required | Grammar metadata JSON. |
--output <file> | required | Destination for the generated module (directories are created). |
--export-name <Name> | LexGrammarKind | Kind export identifier. |
--field-export-name <Name> | ${exportName}Field | Field export identifier. |
--help, -h | — | Print usage and exit 0. |
Behavior notes:
- Exit code
0on success or--help;1for bad arguments, unreadable/invalid input, or emission diagnostics (printed one per line ascode 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).
Authoring workflow
Section titled “Authoring workflow”- Write the grammar’s
kinds.jsonmetadata (kinds in your assigned id block; fields1–254). grammar-kit --input kinds.json --output src/kinds.generated.ts --export-name MyKind.- Implement
parseMyThing(bytes): LexTreeagainst the tree writers, passing{ fieldNamesById: MyFieldNamesById }so field navigation works on your trees. - Export a
LanexioParserPureGrammardescriptor ({ kind: 'pure', name, protocolVersion, parse }) socreateParserhosts can load your grammar — or a WASM descriptor per the parser-wasm contract. - Wire your parse entry points into never-throw and fuzz harnesses; that contract is what makes your grammar a citizen of the framework.