Parser generator for JavaScript that produces fast parsers with excellent error reporting capabilities
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Interface and capabilities of parsers generated by Peggy, including parsing options, error handling, and tracing functionality. Generated parsers provide a consistent API regardless of the original grammar complexity.
The standard interface implemented by all generated parsers, providing parsing functionality and metadata access.
interface Parser {
/** Array of rule names that can be used as start rules */
StartRules: string[];
/** Syntax error class for this specific parser */
SyntaxError: typeof parser.SyntaxError;
/**
* Parse input text using the generated parser
* @param input - Text to parse
* @param options - Parsing options
* @returns Parsed result (type depends on grammar actions)
* @throws {SyntaxError} If input doesn't match grammar
*/
parse(input: string, options?: ParserOptions): any;
/**
* Parse input in library mode (for internal use)
* @param input - Text to parse
* @param options - Parsing options with library mode enabled
* @returns Library results with internal state
*/
parse(
input: string,
options: Omit<ParserOptions, "peg$library"> & { peg$library: true }
): LibraryResults;
}Basic Parser Usage:
import { generate } from "peggy";
// Generate parser from grammar
const grammar = `
Expression
= Term (("+" / "-") Term)*
Term
= Factor (("*" / "/") Factor)*
Factor
= "(" Expression ")"
/ Number
Number
= digits:[0-9]+ { return parseInt(digits.join(""), 10); }
`;
const parser = generate(grammar);
// Check available start rules
console.log("Start rules:", parser.StartRules); // ["Expression"]
// Parse expressions
try {
const result1 = parser.parse("2 + 3 * 4");
const result2 = parser.parse("(1 + 2) * 3");
const result3 = parser.parse("42");
console.log("Results:", result1, result2, result3);
} catch (error) {
if (error instanceof parser.SyntaxError) {
console.error("Parse error:", error.message);
}
}Configuration options for controlling parser behavior during parsing operations.
interface ParserOptions {
/** Additional options for parser extensions */
[key: string]: any;
/** Source identifier for error reporting */
grammarSource?: any;
/** Rule name to start parsing from */
startRule?: string;
/** Tracer for debugging parse operations */
tracer?: ParserTracer;
// Internal options (advanced usage):
/** Enable library mode for internal operations */
peg$library?: boolean;
/** Starting position offset in input */
peg$currPos?: number;
/** Silent failure count for internal use */
peg$silentFails?: number;
/** Expected items for error reporting */
peg$maxFailExpected?: parser.Expectation[];
}Advanced Parsing Options:
import { generate } from "peggy";
const grammar = `
program = statement*
statement = assignment / expression
assignment = name:identifier "=" value:expression { return { type: "assign", name, value }; }
expression = number / identifier
identifier = [a-zA-Z][a-zA-Z0-9]* { return text(); }
number = [0-9]+ { return parseInt(text(), 10); }
`;
const parser = generate(grammar, {
allowedStartRules: ["program", "statement", "expression"]
});
// Parse with different start rules
const program = parser.parse("x = 42", { startRule: "program" });
const statement = parser.parse("x = 42", { startRule: "statement" });
const expression = parser.parse("42", { startRule: "expression" });
// Parse with source information for better errors
try {
parser.parse("invalid syntax", {
grammarSource: "user-input.txt",
startRule: "program"
});
} catch (error) {
console.error(`Error in ${error.location.source}:`, error.message);
}Debugging functionality that allows monitoring of parse operations and rule matching.
interface ParserTracer {
/**
* Called for each significant parse event
* @param event - Parse event information
*/
trace(event: ParserTracerEvent): void;
}
/**
* Parse event types for tracing
*/
type ParserTracerEvent
= { type: "rule.enter"; rule: string; location: LocationRange }
| { type: "rule.fail"; rule: string; location: LocationRange }
| { type: "rule.match"; rule: string; result: any; location: LocationRange };Tracing Usage Example:
import { generate } from "peggy";
const grammar = `
start = word+
word = letters:[a-z]+ " "? { return letters.join(""); }
`;
const parser = generate(grammar, { trace: true });
// Custom tracer for debugging
const tracer = {
trace(event) {
const pos = `${event.location.start.line}:${event.location.start.column}`;
switch (event.type) {
case "rule.enter":
console.log(`→ ${event.rule} at ${pos}`);
break;
case "rule.match":
console.log(`✓ ${event.rule} matched:`, event.result);
break;
case "rule.fail":
console.log(`✗ ${event.rule} failed at ${pos}`);
break;
}
}
};
// Parse with tracing enabled
const result = parser.parse("hello world", { tracer });Comprehensive error reporting with location information and expectation details.
/**
* Parse error with detailed information
*/
class SyntaxError extends globalThis.SyntaxError {
/** Location where parse failed */
location: LocationRange;
/** What the parser expected at the failure point */
expected: parser.Expectation[] | null;
/** What was actually found at the failure point */
found: string | null;
constructor(
message: string,
expected: parser.Expectation[] | null,
found: string | null,
location: LocationRange
);
/**
* Format error with source context
* @param sources - Source text mapping for formatting
* @returns Formatted error message with source lines
*/
format(sources: SourceText[]): string;
/**
* Build human-readable message from expectations
* @param expected - Array of expected items
* @param found - What was found instead
* @returns Human-readable error description
*/
static buildMessage(expected: parser.Expectation[], found: string): string;
}Error Handling Example:
import { generate } from "peggy";
const grammar = `
json_value = object / array / string / number / boolean / null
object = "{" "}" { return {}; }
array = "[" "]" { return []; }
string = '"' chars:[^"]* '"' { return chars.join(""); }
number = digits:[0-9]+ { return parseInt(digits.join(""), 10); }
boolean = "true" { return true; } / "false" { return false; }
null = "null" { return null; }
`;
const parser = generate(grammar);
try {
const result = parser.parse('{"invalid": json}');
} catch (error) {
if (error instanceof parser.SyntaxError) {
console.log("Parse failed at line", error.location.start.line);
console.log("Column", error.location.start.column);
console.log("Expected:", error.expected?.map(e => e.type));
console.log("Found:", error.found);
// Format with source context
const formatted = error.format([{
source: "input.json",
text: '{"invalid": json}'
}]);
console.log("\nFormatted error:\n", formatted);
// Build custom message
if (error.expected && error.found) {
const message = parser.SyntaxError.buildMessage(error.expected, error.found);
console.log("Custom message:", message);
}
}
}Internal results structure returned when using library mode parsing (advanced usage).
interface LibraryResults {
/** Parse result or failure marker */
peg$result: any;
/** Current position after parsing */
peg$currPos: number;
/** Failure marker object */
peg$FAILED: object;
/** Expected items at maximum failure position */
peg$maxFailExpected: parser.Expectation[];
/** Position of maximum failure */
peg$maxFailPos: number;
}Library Mode Example (Advanced):
import { generate } from "peggy";
const grammar = `
start = "test" { return "success"; }
`;
const parser = generate(grammar);
// Library mode parsing (for advanced integration)
const result = parser.parse("test", { peg$library: true });
if (result.peg$result !== result.peg$FAILED) {
console.log("Parse succeeded:", result.peg$result);
console.log("Consumed", result.peg$currPos, "characters");
} else {
console.log("Parse failed at position", result.peg$maxFailPos);
console.log("Expected:", result.peg$maxFailExpected);
}Working with parsers that allow multiple entry points for parsing different grammar constructs.
// Generated parser with multiple start rules
interface MultiStartParser extends Parser {
StartRules: string[]; // Multiple rule names
}Multi-Start Rule Example:
import { generate } from "peggy";
const grammar = `
program = statement*
statement = assignment / expression / declaration
assignment = identifier "=" expression
declaration = "let" identifier ("=" expression)?
expression = addition
addition = multiplication (("+" / "-") multiplication)*
multiplication = primary (("*" / "/") primary)*
primary = number / identifier / "(" expression ")"
identifier = [a-zA-Z][a-zA-Z0-9]* { return text(); }
number = [0-9]+ { return parseInt(text(), 10); }
`;
const parser = generate(grammar, {
allowedStartRules: ["program", "statement", "expression"]
});
console.log("Available start rules:", parser.StartRules);
// Parse different constructs
const fullProgram = parser.parse("let x = 2 + 3", { startRule: "program" });
const singleStatement = parser.parse("x = 42", { startRule: "statement" });
const justExpression = parser.parse("2 + 3 * 4", { startRule: "expression" });
console.log("Program:", fullProgram);
console.log("Statement:", singleStatement);
console.log("Expression:", justExpression);Understanding parser performance characteristics and optimization options.
// Caching can improve performance for complex grammars with backtracking
const cachedParser = generate(complexGrammar, { cache: true });
// Trace mode adds overhead - only use during development
const debugParser = generate(grammar, { trace: true });
// For production, use minimal options
const productionParser = generate(grammar, {
cache: false, // Disable if not needed
trace: false // Always disable in production
});Performance Example:
import { generate } from "peggy";
const complexGrammar = `
expression = choice+
choice = "a" expression / "b" expression / "c" expression / simple
simple = [0-9]+
`;
// Test with and without caching
const noCacheParser = generate(complexGrammar, { cache: false });
const cachedParser = generate(complexGrammar, { cache: true });
const input = "a".repeat(100) + "123"; // Pathological input
console.time("Without cache");
try {
noCacheParser.parse(input);
} catch (e) {
// Expected to fail, but measures backtracking time
}
console.timeEnd("Without cache");
console.time("With cache");
try {
cachedParser.parse(input);
} catch (e) {
// Expected to fail, but should be faster with cache
}
console.timeEnd("With cache");Install with Tessl CLI
npx tessl i tessl/npm-peggy