EBNF Grammar to AST Parser
The `ebnf` package provides a JavaScript and TypeScript-compatible library for generating Abstract Syntax Tree (AST) parsers from formal grammars defined in either Backus-Naur Form (BNF) or W3C Extended Backus-Naur Form (EBNF). It is currently at version 1.9.1. The library differentiates itself by offering direct AST generation, browser compatibility, and built-in TypeScript type definitions, making it suitable for both Node.js and client-side applications. Releases appear to be driven by bug fixes and minor improvements, without a strict time-based cadence. It's particularly useful for projects requiring custom language parsing or syntax highlighting, such as Domain Specific Languages (DSLs) or code analysis tools.
Common errors
-
Cannot find module 'ebnf' or its corresponding type declarations.
cause The `ebnf` package is not installed, or the TypeScript compiler cannot locate its type definitions.fixRun `npm install ebnf` (or `yarn add ebnf`) to install the package. If using TypeScript, ensure `tsconfig.json` correctly includes `node_modules/@types` or that the package's bundled types are being picked up. -
TypeError: Cannot read properties of undefined (reading 'Parser')
cause This usually occurs when trying to access `Grammars.BNF.Parser` or `Grammars.W3C.Parser` but `Grammars` itself is undefined or incorrectly imported.fixVerify that `import { Grammars } from 'ebnf';` is used correctly and that the package is installed. If using CommonJS, ensure `const { Grammars } = require('ebnf');` is being used, though ESM imports are preferred. -
Error: Invalid grammar, production 'SomeRule' is not defined.
cause A non-terminal rule referenced in the grammar (e.g., `<SomeRule>`) does not have a corresponding definition in the provided grammar string.fixReview your EBNF/BNF grammar string and ensure all referenced non-terminal rules are explicitly defined. Check for typos in rule names. -
Error: Line X, column Y: Unexpected token 'Z'.
cause The parser encountered a token (character or sequence) in the input string that does not match any expected rule at that position according to the grammar.fixInspect the input string at the specified line and column. Compare it against your grammar definition to identify which rule is failing to match. This often points to incorrect grammar syntax, missing terminals, or an input string that doesn't conform to the defined language. Consider simplifying the grammar or the input to isolate the issue.
Warnings
- gotcha Version 1.9.1 included a fix to 'remove eval' from the internal implementation. While not explicitly marked as a breaking change for external API, prior versions might have used `eval` internally, which can carry security risks if not properly sandboxed, especially if user-supplied grammar definitions were possible.
- gotcha The library differentiates between two EBNF styles: standard BNF and W3C EBNF (compatible with Railroad Diagram Generator). Grammars must adhere strictly to one of these two forms for correct parsing. Mixing notations or using unsupported EBNF extensions will lead to parsing errors.
- gotcha By default, rules defined with `ALL_UPPER_AND_SNAKE_CASE` in the grammar are not emitted in the resulting AST, simplifying the tree for common use cases like whitespace or comments. This behavior can be surprising if you expect all defined rules to appear in the AST.
Install
-
npm install ebnf -
yarn add ebnf -
pnpm add ebnf
Imports
- Grammars
const Grammars = require('ebnf');import { Grammars } from 'ebnf'; - Grammars.BNF.Parser
import { BNF } from 'ebnf'; // Incorrect path, BNF is nestedimport { Grammars } from 'ebnf'; const bnfParser = new Grammars.BNF.Parser(bnfGrammar); - Grammars.W3C.Parser
import { W3CParser } from 'ebnf';import { Grammars } from 'ebnf'; const w3cParser = new Grammars.W3C.Parser(w3cGrammar);
Quickstart
import { Grammars } from 'ebnf';
const mathGrammar = `
<Equation> ::= <BinaryOperation> | <Term>
<Term> ::= "(" <RULE_WHITESPACE> <Equation> <RULE_WHITESPACE> ")" | "(" <RULE_WHITESPACE> <Number> <RULE_WHITESPACE> ")" | <RULE_WHITESPACE> <Number> <RULE_WHITESPACE>
<BinaryOperation> ::= <Term> <RULE_WHITESPACE> <Operator> <RULE_WHITESPACE> <Term>
<Number> ::= <RULE_NEGATIVE> <RULE_NON_ZERO> <RULE_NUMBER_LIST> | <RULE_NON_ZERO> <RULE_NUMBER_LIST> | <RULE_DIGIT>
<Operator> ::= "+" | "-" | "*" | "/" | "^"
<RULE_NUMBER_LIST> ::= <RULE_DIGIT> <RULE_NUMBER_LIST> | <RULE_DIGIT>
<RULE_NEGATIVE> ::= "-"
<RULE_NON_ZERO> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<RULE_DIGIT> ::= "0" | <RULE_NON_ZERO>
<RULE_WHITESPACE> ::= <RULE_WS> | ""
<RULE_WS> ::= " " <RULE_WHITESPACE> | "\n" <RULE_WHITESPACE> | " " | "\n"
`;
// Create a BNF parser instance with the defined grammar
const parser = new Grammars.BNF.Parser(mathGrammar);
// Parse an arithmetic expression and get the Abstract Syntax Tree (AST)
const ast = parser.getAST('(2 + (2 * -123)) * 5332');
console.log(JSON.stringify(ast, null, 2));
/*
Expected AST structure (simplified representation):
{
"type": "Equation",
"text": "(2 + (2 * -123)) * 5332",
"children": [
// ... detailed AST nodes for operations and numbers
]
}
*/