Parser generator for JavaScript that produces fast parsers with excellent error reporting
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Built-in parser for PEG grammar syntax that converts grammar strings into abstract syntax trees for compilation into executable parsers.
Parses PEG grammar strings into abstract syntax tree representations.
/**
* Built-in grammar parser module
*/
const parser = {
/**
* Parse PEG grammar string into AST
* @param input - PEG grammar string to parse
* @param options - Optional parsing configuration
* @returns Grammar AST object
* @throws {SyntaxError} If grammar syntax is invalid
*/
parse: function(input, options),
/**
* Syntax error constructor for grammar parsing failures
*/
SyntaxError: function(message, expected, found, location)
};Usage Examples:
const peg = require("pegjs");
// Parse a simple grammar
const grammarText = `
start = "hello" "world"
`;
const ast = peg.parser.parse(grammarText);
console.log(ast); // Grammar AST object
// Parse with options
const ast = peg.parser.parse(grammarText, {
startRule: "grammar"
});Grammar parsing errors provide detailed information about syntax problems.
/**
* Grammar syntax error class
*/
class SyntaxError extends Error {
name: "SyntaxError";
message: string;
expected: ExpectedItem[];
found: string | null;
location: LocationRange;
/**
* Build human-readable error message
* @param expected - Array of expected items
* @param found - What was actually found
* @returns Formatted error message
*/
static buildMessage(expected, found): string;
}
/**
* Expected item in error reporting
*/
interface ExpectedItem {
type: "literal" | "class" | "any" | "end" | "other";
text?: string;
description?: string;
inverted?: boolean;
parts?: (string | [string, string])[];
}
/**
* Location range information
*/
interface LocationRange {
start: { offset: number; line: number; column: number };
end: { offset: number; line: number; column: number };
}Error Handling Example:
try {
const ast = peg.parser.parse("invalid grammar syntax");
} catch (error) {
if (error.name === "SyntaxError") {
console.error("Grammar syntax error:", error.message);
console.error("Expected:", error.expected);
console.error("Found:", error.found);
console.error(`At line ${error.location.start.line}, column ${error.location.start.column}`);
// Build custom error message
const customMessage = peg.parser.SyntaxError.buildMessage(
error.expected,
error.found
);
console.error("Custom message:", customMessage);
}
}PEG grammars consist of rules that define parsing patterns using various expression types.
Basic Grammar Format:
// Optional initializer (JavaScript code executed before parsing)
{
function helper() {
return "helper function";
}
}
// Rules define parsing patterns
ruleName "human readable name"
= expression
anotherRule
= expression / alternativeThe grammar parser recognizes these expression patterns:
// String literals (case-sensitive)
"exact text"
'single quotes'
// Case-insensitive literals
"text"i
'TEXT'i
// Any single character
.
// Character classes
[a-z] // lowercase letters
[^0-9] // anything except digits
[a-zA-Z0-9] // alphanumeric
[abc]i // case-insensitive class// Optional (zero or one)
expression?
// Zero or more
expression*
// One or more
expression+
// Grouping
(expression)// Positive lookahead (and predicate)
&expression
// Negative lookahead (not predicate)
!expression
// Semantic predicates
&{ return condition; }
!{ return condition; }// Sequence (all must match in order)
expression1 expression2 expression3
// Choice (first match wins)
expression1 / expression2 / expression3// Labels for capturing results
label:expression
// Actions for transforming results
expression { return transformedValue; }
// Text capture
$expression// Reference to another rule
ruleName
// Rule with human-readable name
integer "integer"
= digits:[0-9]+ { return parseInt(digits.join(""), 10); }The parser produces AST nodes with these types:
/**
* Root grammar node
*/
interface GrammarNode {
type: "grammar";
initializer?: InitializerNode;
rules: RuleNode[];
}
/**
* Rule definition node
*/
interface RuleNode {
type: "rule";
name: string;
displayName?: string;
expression: ExpressionNode;
}
/**
* Expression node types
*/
type ExpressionNode =
| ChoiceNode | SequenceNode | LabeledNode | ActionNode
| OptionalNode | ZeroOrMoreNode | OneOrMoreNode
| SimpleAndNode | SimpleNotNode | SemanticAndNode | SemanticNotNode
| TextNode | RuleRefNode | LiteralNode | ClassNode | AnyNode;
/**
* Choice expression (alternatives)
*/
interface ChoiceNode {
type: "choice";
alternatives: ExpressionNode[];
}
/**
* Sequence expression
*/
interface SequenceNode {
type: "sequence";
elements: ExpressionNode[];
}
/**
* Labeled expression
*/
interface LabeledNode {
type: "labeled";
label: string;
expression: ExpressionNode;
}AST Usage Example:
const ast = peg.parser.parse(`
start = greeting name
greeting = "hello" / "hi"
name = [a-zA-Z]+
`);
console.log(ast.type); // "grammar"
console.log(ast.rules.length); // 3
console.log(ast.rules[0].name); // "start"
console.log(ast.rules[0].expression.type); // "sequence"Grammars can include JavaScript code executed before parsing begins:
{
// Global variables and functions available in actions
var utils = {
capitalize: function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
};
}
start = word { return utils.capitalize(text()); }
word = [a-z]+Actions transform parse results using JavaScript code:
number = digits:[0-9]+ {
return parseInt(digits.join(""), 10);
}
array = "[" head:value tail:("," value)* "]" {
return [head].concat(tail.map(function(t) { return t[1]; }));
}Actions can access location information:
rule = expression {
var loc = location();
return {
value: expression,
line: loc.start.line,
column: loc.start.column
};
}Custom error messages in actions:
rule = expression {
if (!isValid(expression)) {
expected("valid expression");
}
return expression;
}Install with Tessl CLI
npx tessl i tessl/npm-pegjs