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

ast-compilation.mddocs/

AST Compilation

Advanced compilation system with multiple passes for transforming, validating, and optimizing grammar ASTs. The compiler namespace provides low-level access to Peggy's compilation pipeline and pass system.

Capabilities

Compile Function

Compiles grammar ASTs into parser objects or source code through a multi-stage pass system.

namespace compiler {
  /**
   * Compiles AST to parser object (default)
   * @param ast - Grammar AST from parser
   * @param stages - Compilation stages with passes
   * @param options - Build options
   * @returns Generated parser object
   * @throws {GrammarError} If AST contains semantic errors
   */
  function compile(
    ast: ast.Grammar,
    stages: Stages,
    options?: ParserBuildOptions
  ): Parser;
  
  /**
   * Compiles AST to source code string
   * @param ast - Grammar AST from parser
   * @param stages - Compilation stages with passes
   * @param options - Source build options
   * @returns Generated parser source code
   */
  function compile(
    ast: ast.Grammar,
    stages: Stages,
    options: SourceBuildOptions<"source">
  ): string;
  
  /**
   * Compiles AST to SourceNode with source map
   * @param ast - Grammar AST from parser
   * @param stages - Compilation stages with passes
   * @param options - Source build options
   * @returns SourceNode for code and source map generation
   */
  function compile(
    ast: ast.Grammar,
    stages: Stages,
    options: SourceBuildOptions<"source-and-map">
  ): SourceNode;
}

Usage Examples:

import { parser, compiler } from "peggy";

// Parse grammar to AST
const grammar = `start = "hello" " " name:[a-z]+ { return "Hello, " + name.join(""); }`;
const ast = parser.parse(grammar, { reservedWords: [] });

// Compile AST to parser using default passes
const parser1 = compiler.compile(ast, compiler.passes);

// Compile to source code
const sourceCode = compiler.compile(ast, compiler.passes, {
  output: "source",
  format: "commonjs"
});

// Compile with custom options
const cachedParser = compiler.compile(ast, compiler.passes, {
  cache: true,
  allowedStartRules: ["start"],
  trace: true
});

Compilation Stages

Multi-stage compilation pipeline with different types of passes for grammar processing.

namespace compiler {
  /**
   * Compilation stages with their passes
   */
  interface Stages {
    /** Preparation passes for linking and setup */
    prepare: Pass[];
    /** Validation passes for error checking */
    check: Pass[];
    /** AST transformation passes */
    transform: Pass[];
    /** Code generation passes */
    generate: Pass[];
  }
  
  /**
   * Default compilation stages
   */
  let passes: Stages;
  
  /**
   * Compilation pass function
   * @param ast - Grammar AST to process (may be modified)
   * @param options - Build options from generate() call
   * @param session - Session for error/warning reporting
   */
  type Pass = (
    ast: ast.Grammar,
    options: ParserBuildOptions,
    session: Session
  ) => void;
}

Default Passes:

The default compiler.passes object includes these built-in passes:

// Prepare stage
const prepare = [
  "addImportedRules",      // Add rules from imported grammars
  "reportInfiniteRecursion" // Detect infinite recursion
];

// Check stage  
const check = [
  "reportUndefinedRules",    // Check for undefined rule references
  "reportDuplicateRules",    // Check for duplicate rule definitions
  "reportDuplicateLabels",   // Check for duplicate labels in rules
  "reportInfiniteRepetition", // Check for infinite repetition
  "reportIncorrectPlucking", // Check for incorrect @ usage
  "reportDuplicateImports"   // Check for duplicate import statements
];

// Transform stage
const transform = [
  "fixLibraryNumbers",       // Fix library reference numbers
  "removeProxyRules",        // Remove unnecessary proxy rules
  "mergeCharacterClasses",   // Optimize character classes
  "removeUnusedRules",       // Remove unreferenced rules
  "inferenceMatchResult"     // Infer match results for expressions
];

// Generate stage
const generate = [
  "generateBytecode",        // Generate bytecode for expressions
  "generateJS"               // Generate JavaScript parser code
];

Compilation Session

Session object for pass execution that provides error reporting and diagnostic capabilities.

namespace compiler {
  /**
   * Compilation session for pass execution
   */
  interface Session {
    /** All problems registered during compilation */
    problems: Problem[];
    
    /**
     * Report compilation error
     * @param message - Error description
     * @param location - Source location if applicable
     * @param notes - Additional diagnostic information
     */
    error(
      message: string,
      location?: LocationRange,
      notes?: DiagnosticNote[]
    ): void;
    
    /**
     * Report compilation warning
     * @param message - Warning description
     * @param location - Source location if applicable
     * @param notes - Additional diagnostic information
     */
    warning(
      message: string,
      location?: LocationRange,
      notes?: DiagnosticNote[]
    ): void;
    
    /**
     * Report informational message
     * @param message - Information description
     * @param location - Source location if applicable
     * @param notes - Additional diagnostic information
     */
    info(
      message: string,
      location?: LocationRange,
      notes?: DiagnosticNote[]
    ): void;
  }
  
  /**
   * Problem reported during compilation
   */
  type Problem = [
    severity: "error" | "info" | "warning",
    message: string,
    location?: LocationRange,
    diagnostics?: DiagnosticNote[]
  ];
  
  /**
   * Additional diagnostic note with location
   */
  interface DiagnosticNote {
    message: string;
    location: LocationRange;
  }
}

Custom Pass Example:

import { parser, compiler } from "peggy";

// Custom pass that logs rule information
const logRulesPass = (ast, options, session) => {
  session.info(`Processing ${ast.rules.length} rules`);
  
  ast.rules.forEach(rule => {
    if (rule.name.startsWith("_")) {
      session.warning(`Private rule detected: ${rule.name}`, rule.nameLocation);
    }
  });
};

// Create custom stages with additional pass
const customStages = {
  ...compiler.passes,
  check: [...compiler.passes.check, logRulesPass]
};

// Use custom stages
const ast = parser.parse(grammar, { reservedWords: [] });
const parser1 = compiler.compile(ast, customStages, {
  info: (stage, message) => console.log(`[${stage}] ${message}`),
  warning: (stage, message, location) => console.warn(`Warning: ${message}`)
});

AST Visitor System

Visitor pattern implementation for traversing and manipulating grammar ASTs.

namespace compiler {
  namespace visitor {
    /**
     * Visitor function builder
     * @param functions - Object with visitor functions for node types
     * @returns Visitor function for AST traversal
     */
    function build<F extends NodeTypes>(functions: F): Visitor<F>;
    
    /**
     * Visitor function for AST nodes
     */
    interface Visitor<F extends NodeTypes> {
      /**
       * Visit AST node with type-specific visitor
       * @param node - AST node to visit
       * @param args - Additional arguments for visitor
       * @returns Result from visitor function
       */
      <T extends keyof NodeTypes>(
        node: ast.Node<T>,
        ...args: any[]
      ): ReturnType<AnyFunction & F[T]>;
    }
    
    /**
     * Visitor functions for different node types
     */
    interface NodeTypes {
      /** Visit grammar root */
      grammar?(node: ast.Grammar, ...args: any[]): any;
      /** Visit import statement */
      grammar_import?(node: ast.GrammarImport, ...args: any[]): any;
      /** Visit top-level initializer */
      top_level_initializer?(node: ast.TopLevelInitializer, ...args: any[]): any;
      /** Visit per-parse initializer */
      initializer?(node: ast.Initializer, ...args: any[]): any;
      /** Visit rule */
      rule?(node: ast.Rule, ...args: any[]): any;
      /** Visit named expression */
      named?(node: ast.Named, ...args: any[]): any;
      /** Visit choice expression */
      choice?(node: ast.Choice, ...args: any[]): any;
      /** Visit action expression */
      action?(node: ast.Action, ...args: any[]): any;
      /** Visit sequence expression */
      sequence?(node: ast.Sequence, ...args: any[]): any;
      /** Visit labeled expression */
      labeled?(node: ast.Labeled, ...args: any[]): any;
      /** Visit text expression */
      text?(node: ast.Prefixed, ...args: any[]): any;
      /** Visit positive lookahead */
      simple_and?(node: ast.Prefixed, ...args: any[]): any;
      /** Visit negative lookahead */
      simple_not?(node: ast.Prefixed, ...args: any[]): any;
      /** Visit optional expression */
      optional?(node: ast.Suffixed, ...args: any[]): any;
      /** Visit zero-or-more expression */
      zero_or_more?(node: ast.Suffixed, ...args: any[]): any;
      /** Visit one-or-more expression */
      one_or_more?(node: ast.Suffixed, ...args: any[]): any;
      /** Visit repeated expression */
      repeated?(node: ast.Repeated, ...args: any[]): any;
      /** Visit group expression */
      group?(node: ast.Group, ...args: any[]): any;
      /** Visit semantic AND predicate */
      semantic_and?(node: ast.SemanticPredicate, ...args: any[]): any;
      /** Visit semantic NOT predicate */
      semantic_not?(node: ast.SemanticPredicate, ...args: any[]): any;
      /** Visit rule reference */
      rule_ref?(node: ast.RuleReference, ...args: any[]): any;
      /** Visit library reference */
      library_ref?(node: ast.LibraryReference, ...args: any[]): any;
      /** Visit literal */
      literal?(node: ast.Literal, ...args: any[]): any;
      /** Visit character class */
      class?(node: ast.CharacterClass, ...args: any[]): any;
      /** Visit any-character */
      any?(node: ast.Any, ...args: any[]): any;
    }
  }
}

Visitor Usage Example:

import { parser, compiler } from "peggy";

// Create visitor that counts different expression types
const expressionCounter = compiler.visitor.build({
  rule(node, counts) {
    counts.rules++;
    return this(node.expression, counts);
  },
  
  literal(node, counts) {
    counts.literals++;
  },
  
  class(node, counts) {
    counts.classes++;
  },
  
  rule_ref(node, counts) {
    counts.references++;
  },
  
  choice(node, counts) {
    counts.choices++;
    node.alternatives.forEach(alt => this(alt, counts));
  },
  
  sequence(node, counts) {
    counts.sequences++;
    node.elements.forEach(elem => this(elem, counts));
  }
});

// Use visitor on parsed grammar
const ast = parser.parse(complexGrammar, { reservedWords: [] });
const counts = {
  rules: 0,
  literals: 0,
  classes: 0,
  references: 0,
  choices: 0,
  sequences: 0
};

expressionCounter(ast, counts);
console.log("Expression counts:", counts);

AST Manipulation with Visitors

The visitor system allows efficient traversal and manipulation of grammar ASTs with type-safe node visiting.

namespace compiler {
  namespace visitor {
    /**
     * Build visitor function from node type handlers
     * @param functions - Object mapping node types to visitor functions
     * @returns Visitor function for AST traversal
     */
    function build<F extends NodeTypes>(functions: F): Visitor<F>;
    
    /**
     * Visitor function interface
     */
    interface Visitor<F extends NodeTypes> {
      /**
       * Visit AST node with appropriate handler
       * @param node - AST node to visit
       * @param args - Additional arguments for visitor
       * @returns Result from node handler
       */
      <T extends keyof NodeTypes>(
        node: ast.Node<T>,
        ...args: any[]
      ): ReturnType<AnyFunction & F[T]>;
    }
    
    /**
     * Node type visitor functions
     */
    interface NodeTypes {
      /** Visit grammar root - default: visit imports, initializers, rules */
      grammar?(node: ast.Grammar, ...args: any[]): any;
      /** Visit import statement - default: do nothing */
      grammar_import?(node: ast.GrammarImport, ...args: any[]): any;
      /** Visit top-level initializer - default: do nothing */
      top_level_initializer?(node: ast.TopLevelInitializer, ...args: any[]): any;
      /** Visit per-parse initializer - default: do nothing */
      initializer?(node: ast.Initializer, ...args: any[]): any;
      /** Visit rule - default: visit expression */
      rule?(node: ast.Rule, ...args: any[]): any;
      /** Visit named expression - default: visit expression */
      named?(node: ast.Named, ...args: any[]): any;
      /** Visit choice - default: visit all alternatives */
      choice?(node: ast.Choice, ...args: any[]): any;
      /** Visit action - default: visit expression */
      action?(node: ast.Action, ...args: any[]): any;
      /** Visit sequence - default: visit all elements */
      sequence?(node: ast.Sequence, ...args: any[]): any;
      /** Visit labeled expression - default: visit expression */
      labeled?(node: ast.Labeled, ...args: any[]): any;
      /** Visit text operator - default: visit expression */
      text?(node: ast.Prefixed, ...args: any[]): any;
      /** Visit positive lookahead - default: visit expression */
      simple_and?(node: ast.Prefixed, ...args: any[]): any;
      /** Visit negative lookahead - default: visit expression */
      simple_not?(node: ast.Prefixed, ...args: any[]): any;
      /** Visit optional - default: visit expression */
      optional?(node: ast.Suffixed, ...args: any[]): any;
      /** Visit zero-or-more - default: visit expression */
      zero_or_more?(node: ast.Suffixed, ...args: any[]): any;
      /** Visit one-or-more - default: visit expression */
      one_or_more?(node: ast.Suffixed, ...args: any[]): any;
      /** Visit repeated - default: visit delimiter then expression */
      repeated?(node: ast.Repeated, ...args: any[]): any;
      /** Visit group - default: visit expression */
      group?(node: ast.Group, ...args: any[]): any;
      /** Visit semantic AND predicate - default: do nothing */
      semantic_and?(node: ast.SemanticPredicate, ...args: any[]): any;
      /** Visit semantic NOT predicate - default: do nothing */
      semantic_not?(node: ast.SemanticPredicate, ...args: any[]): any;
      /** Visit rule reference - default: do nothing */
      rule_ref?(node: ast.RuleReference, ...args: any[]): any;
      /** Visit library reference - default: do nothing */
      library_ref?(node: ast.LibraryReference, ...args: any[]): any;
      /** Visit literal - default: do nothing */
      literal?(node: ast.Literal, ...args: any[]): any;
      /** Visit character class - default: do nothing */
      class?(node: ast.CharacterClass, ...args: any[]): any;
      /** Visit any-character - default: do nothing */
      any?(node: ast.Any, ...args: any[]): any;
    }
    
    type AnyFunction = (...args: any[]) => any;
  }
}

Advanced Compilation Options

Extended options for controlling specific aspects of the compilation process.

/**
 * Stage name type
 */
type Stage = keyof Stages;

/**
 * Diagnostic callback for compilation messages
 */
type DiagnosticCallback = (
  stage: Stage,
  message: string,
  location?: LocationRange,
  notes?: DiagnosticNote[]
) => void;

/**
 * Parser dependencies mapping for module systems
 */
interface Dependencies {
  [variable: string]: string;
}

/**
 * Source output format types
 */
type SourceOutputs = "parser" | "source-and-map" | "source-with-inline-map" | "source" | "ast";

Advanced Compilation Example:

import { parser, compiler, GrammarError } from "peggy";

try {
  const ast = parser.parse(grammar, { reservedWords: [] });
  
  // Custom compilation with detailed diagnostics
  const result = compiler.compile(ast, compiler.passes, {
    output: "source-and-map",
    format: "es",
    dependencies: {
      "validator": "./validator.js"
    },
    grammarSource: "my-grammar.peggy",
    cache: true,
    trace: true,
    allowedStartRules: ["*"], // All rules as start rules
    error: (stage, message, location, notes) => {
      console.error(`[${stage}] ERROR: ${message}`);
      if (location) {
        console.error(`  at line ${location.start.line}, column ${location.start.column}`);
      }
      notes?.forEach(note => {
        console.error(`  note: ${note.message}`);
      });
    },
    warning: (stage, message, location) => {
      console.warn(`[${stage}] WARNING: ${message}`);
    },
    info: (stage, message) => {
      console.log(`[${stage}] ${message}`);
    }
  });
  
  if (typeof result === "string") {
    console.log("Generated source code");
  } else {
    // SourceNode
    const { code, map } = result.toStringWithSourceMap({
      file: "generated-parser.js"
    });
    console.log("Generated code with source map");
  }
  
} catch (error) {
  if (error instanceof GrammarError) {
    console.error("Compilation failed:");
    error.problems.forEach(([severity, message, location]) => {
      console.error(`${severity.toUpperCase()}: ${message}`);
    });
  }
}

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