CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-peggy

Parser generator for JavaScript that produces fast parsers with excellent error reporting capabilities

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

generated-parsers.mddocs/

Generated Parsers

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.

Capabilities

Parser Interface

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);
  }
}

Parsing Options

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);
}

Parse Tracing

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 });

Error Handling

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);
    }
  }
}

Library Mode Results

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);
}

Multi-Start Rule Parsers

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);

Performance Considerations

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

docs

ast-compilation.md

cli.md

generated-parsers.md

grammar-parsing.md

index.md

parser-generation.md

tile.json