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
Multi-pass compiler architecture for transforming grammar ASTs into executable parser code with validation, optimization, and code generation capabilities.
Compiles grammar ASTs into executable parsers through configurable compilation passes.
/**
* Compiler module interface
*/
const compiler = {
/**
* Compile grammar AST into executable parser
* @param ast - Grammar AST from parser
* @param passes - Compilation passes organized by stage
* @param options - Compilation options
* @returns Generated parser object or source code string
* @throws {GrammarError} If AST contains semantic errors
*/
compile: function(ast, passes, options),
/**
* AST visitor builder for traversing syntax trees
*/
visitor: {
build: function(functions)
},
/**
* Built-in compilation passes organized by stage
*/
passes: {
check: Object, // Validation passes
transform: Object, // AST transformation passes
generate: Object // Code generation passes
}
};Usage Examples:
const peg = require("pegjs");
// Parse grammar into AST
const ast = peg.parser.parse("start = 'hello'");
// Compile with default passes
const parser = peg.compiler.compile(
ast,
peg.compiler.passes,
{ output: "parser" }
);
// Compile with custom pass configuration
const customPasses = {
check: [
peg.compiler.passes.check.reportUndefinedRules,
peg.compiler.passes.check.reportDuplicateRules
],
transform: [
peg.compiler.passes.transform.removeProxyRules
],
generate: [
peg.compiler.passes.generate.generateBytecode,
peg.compiler.passes.generate.generateJS
]
};
const source = peg.compiler.compile(ast, customPasses, {
output: "source",
format: "commonjs"
});Configuration options for the compilation process.
/**
* Compilation options interface
*/
interface CompileOptions {
/** Rules parser can start from (default: [first rule]) */
allowedStartRules?: string[];
/** Enable result caching (default: false) */
cache?: boolean;
/** Module dependencies map (default: {}) */
dependencies?: { [key: string]: string };
/** Global variable name for globals/umd format (default: null) */
exportVar?: string;
/** Output format (default: "bare") */
format?: "amd" | "bare" | "commonjs" | "globals" | "umd";
/** Optimization target (default: "speed") */
optimize?: "speed" | "size";
/** Output type (default: "parser") */
output?: "parser" | "source";
/** Enable tracing (default: false) */
trace?: boolean;
}Validation passes that detect semantic errors in grammar ASTs.
/**
* Validation passes for semantic analysis
*/
const checkPasses = {
/**
* Report references to undefined rules
* @param ast - Grammar AST to analyze
* @param options - Compilation options
* @throws {GrammarError} If undefined rules found
*/
reportUndefinedRules: function(ast, options),
/**
* Report duplicate rule definitions
* @param ast - Grammar AST to analyze
* @param options - Compilation options
* @throws {GrammarError} If duplicate rules found
*/
reportDuplicateRules: function(ast, options),
/**
* Report duplicate labels within rules
* @param ast - Grammar AST to analyze
* @param options - Compilation options
* @throws {GrammarError} If duplicate labels found
*/
reportDuplicateLabels: function(ast, options),
/**
* Report infinite recursion patterns
* @param ast - Grammar AST to analyze
* @param options - Compilation options
* @throws {GrammarError} If infinite recursion detected
*/
reportInfiniteRecursion: function(ast, options),
/**
* Report infinite repetition patterns
* @param ast - Grammar AST to analyze
* @param options - Compilation options
* @throws {GrammarError} If infinite repetition detected
*/
reportInfiniteRepetition: function(ast, options)
};Check Pass Example:
// These passes will throw GrammarError if issues are found
try {
peg.compiler.passes.check.reportUndefinedRules(ast, options);
peg.compiler.passes.check.reportDuplicateRules(ast, options);
} catch (error) {
if (error.name === "GrammarError") {
console.error("Grammar validation error:", error.message);
}
}AST transformation passes that optimize or modify the syntax tree.
/**
* AST transformation passes
*/
const transformPasses = {
/**
* Remove proxy rules that just forward to other rules
* Optimizes the AST by eliminating unnecessary indirection
* @param ast - Grammar AST to transform
* @param options - Compilation options
*/
removeProxyRules: function(ast, options)
};Transform Pass Example:
// Grammar with proxy rule:
// start = expression
// expression = number
// number = [0-9]+
// After removeProxyRules:
// start = number
// number = [0-9]+
// (expression rule removed as it just forwards to number)
peg.compiler.passes.transform.removeProxyRules(ast, options);Code generation passes that produce executable parser code.
/**
* Code generation passes
*/
const generatePasses = {
/**
* Generate intermediate bytecode representation
* First stage of code generation process
* @param ast - Grammar AST to compile
* @param options - Compilation options
*/
generateBytecode: function(ast, options),
/**
* Generate final JavaScript parser code
* Second stage that converts bytecode to JavaScript
* @param ast - Grammar AST with bytecode
* @param options - Compilation options
*/
generateJS: function(ast, options)
};Generate Pass Example:
// Two-stage generation process
peg.compiler.passes.generate.generateBytecode(ast, options);
peg.compiler.passes.generate.generateJS(ast, options);
// After generation, ast.code contains the JavaScript parser source
console.log(ast.code); // Generated parser codeCreates visitor functions for traversing and manipulating ASTs.
/**
* AST visitor builder
*/
const visitor = {
/**
* Build visitor function from handler map
* @param functions - Map of node types to handler functions
* @returns Visitor function for traversing ASTs
*/
build: function(functions): (node: ASTNode) => any
};
/**
* Visitor function interface
*/
interface VisitorFunction {
(node: ASTNode, ...extraArgs: any[]): any;
}
/**
* Handler function map for different node types
*/
interface VisitorHandlers {
grammar?: (node: GrammarNode, ...args: any[]) => any;
initializer?: (node: InitializerNode, ...args: any[]) => any;
rule?: (node: RuleNode, ...args: any[]) => any;
choice?: (node: ChoiceNode, ...args: any[]) => any;
sequence?: (node: SequenceNode, ...args: any[]) => any;
labeled?: (node: LabeledNode, ...args: any[]) => any;
action?: (node: ActionNode, ...args: any[]) => any;
optional?: (node: OptionalNode, ...args: any[]) => any;
zero_or_more?: (node: ZeroOrMoreNode, ...args: any[]) => any;
one_or_more?: (node: OneOrMoreNode, ...args: any[]) => any;
simple_and?: (node: SimpleAndNode, ...args: any[]) => any;
simple_not?: (node: SimpleNotNode, ...args: any[]) => any;
semantic_and?: (node: SemanticAndNode, ...args: any[]) => any;
semantic_not?: (node: SemanticNotNode, ...args: any[]) => any;
rule_ref?: (node: RuleRefNode, ...args: any[]) => any;
literal?: (node: LiteralNode, ...args: any[]) => any;
class?: (node: ClassNode, ...args: any[]) => any;
any?: (node: AnyNode, ...args: any[]) => any;
text?: (node: TextNode, ...args: any[]) => any;
named?: (node: NamedNode, ...args: any[]) => any;
group?: (node: GroupNode, ...args: any[]) => any;
}Visitor Usage Examples:
// Create a visitor that counts rule references
const countRefs = peg.compiler.visitor.build({
rule_ref: function(node, counts) {
counts[node.name] = (counts[node.name] || 0) + 1;
}
});
const refCounts = {};
countRefs(ast, refCounts);
console.log(refCounts); // { ruleName: count, ... }
// Create a visitor that collects all string literals
const collectLiterals = peg.compiler.visitor.build({
literal: function(node, literals) {
literals.push(node.value);
}
});
const literals = [];
collectLiterals(ast, literals);
console.log(literals); // ["string1", "string2", ...]
// Create a visitor that transforms nodes
const transformRefs = peg.compiler.visitor.build({
rule_ref: function(node) {
// Transform rule references to uppercase
node.name = node.name.toUpperCase();
}
});
transformRefs(ast); // Modifies AST in placeThe visitor builder provides default traversal behavior for all node types:
// Default handlers automatically traverse child nodes
// You only need to override specific node types you want to handle
const visitor = peg.compiler.visitor.build({
// Only handle literals, all other nodes traversed automatically
literal: function(node) {
console.log("Found literal:", node.value);
}
});You can create custom compilation passes for specialized validation or transformation:
// Custom validation pass
function reportLongRuleNames(ast, options) {
const visitor = peg.compiler.visitor.build({
rule: function(node) {
if (node.name.length > 20) {
throw new peg.GrammarError(
`Rule name too long: ${node.name}`,
node.location
);
}
}
});
visitor(ast);
}
// Custom transformation pass
function prefixRuleNames(ast, options) {
const prefix = options.rulePrefix || "rule_";
const visitor = peg.compiler.visitor.build({
rule: function(node) {
if (!node.name.startsWith(prefix)) {
node.name = prefix + node.name;
}
},
rule_ref: function(node) {
if (!node.name.startsWith(prefix)) {
node.name = prefix + node.name;
}
}
});
visitor(ast);
}
// Use custom passes
const customPasses = {
check: [
peg.compiler.passes.check.reportUndefinedRules,
reportLongRuleNames // Custom validation
],
transform: [
prefixRuleNames, // Custom transformation
peg.compiler.passes.transform.removeProxyRules
],
generate: [
peg.compiler.passes.generate.generateBytecode,
peg.compiler.passes.generate.generateJS
]
};
const parser = peg.compiler.compile(ast, customPasses, {
rulePrefix: "my_",
output: "parser"
});Install with Tessl CLI
npx tessl i tessl/npm-pegjs