Complete parse tree representation and manipulation including visitor and listener patterns for AST traversal and processing. These classes provide the infrastructure for working with Abstract Syntax Trees (ASTs) generated by ANTLR4 parsers.
Foundation classes for all tree nodes in the parse tree hierarchy.
/**
* Base class for all tree nodes
*/
abstract class Tree {
// Empty base class - provides type hierarchy foundation
}
/**
* Base class for syntax trees (extends Tree)
*/
class SyntaxTree extends Tree {
// Base implementation for syntax tree nodes
}
/**
* Base class for parse tree nodes
*/
abstract class ParseTree extends SyntaxTree {
/**
* Get the text representation of this subtree
* @returns Text content of this parse tree node and its children
*/
getText(): string;
}Specific node types representing rules and terminal symbols.
/**
* Abstract base class for rule nodes in parse trees
*/
abstract class RuleNode extends ParseTree {
// Base class for non-terminal nodes (parser rules)
}
/**
* Leaf node representing a terminal symbol (token)
*/
class TerminalNode extends ParseTree {
/** Token represented by this terminal node */
symbol: Token;
/** Parent context of this terminal node */
parentCtx: ParserRuleContext;
}
/**
* Terminal node representing an error token
*/
class ErrorNode extends TerminalNode {
// Specialized terminal node for error recovery
}Listener pattern for depth-first traversal of parse trees.
/**
* Interface for parse tree listeners (depth-first traversal)
*/
abstract class ParseTreeListener {
/**
* Called when entering any rule node
* @param ctx - Rule context being entered
*/
enterEveryRule(ctx: ParserRuleContext): void;
/**
* Called when exiting any rule node
* @param ctx - Rule context being exited
*/
exitEveryRule(ctx: ParserRuleContext): void;
/**
* Called when visiting a terminal node
* @param node - Terminal node being visited
*/
visitTerminal(node: TerminalNode): void;
/**
* Called when visiting an error node
* @param node - Error node being visited
*/
visitErrorNode(node: ErrorNode): void;
}
/**
* Utility for walking parse trees with listeners
*/
class ParseTreeWalker {
/** Default shared instance */
static readonly DEFAULT: ParseTreeWalker;
/**
* Walk parse tree with listener
* @param listener - Parse tree listener to notify during traversal
* @param t - Parse tree root to start walking from
*/
walk<T extends ParseTreeListener>(listener: T, t: ParseTree): void;
}Visitor pattern for result-producing traversal of parse trees.
/**
* Base class for parse tree visitors
*/
class ParseTreeVisitor<Result> {
/**
* Visit a parse tree node and return result
* @param tree - Parse tree node to visit
* @returns Result of visiting the node
*/
visit(tree: ParseTree): Result;
/**
* Visit all children of a rule node
* @param node - Rule node whose children to visit
* @returns Combined result from visiting children
*/
visitChildren(node: RuleNode): Result;
/**
* Visit a terminal node
* @param node - Terminal node to visit
* @returns Result of visiting the terminal
*/
visitTerminal(node: TerminalNode): Result;
/**
* Visit an error node
* @param node - Error node to visit
* @returns Result of visiting the error node
*/
visitErrorNode(node: ErrorNode): Result;
}Namespace of utility functions for common tree operations.
/**
* Utility functions for working with parse trees
*/
namespace Trees {
/**
* Convert tree to string representation
* @param tree - Tree to convert
* @param ruleNames - Array of rule names for formatting
* @param recog - Parser for additional context
* @returns String representation of tree
*/
function toStringTree(tree: Tree, ruleNames: string[], recog: Parser): string;
/**
* Get text representation of node
* @param t - Tree node
* @param ruleNames - Array of rule names
* @param recog - Parser for context
* @returns Text representation
*/
function getNodeText(t: Tree, ruleNames: string[], recog: Parser): string;
/**
* Get direct children of a tree node
* @param t - Parent tree node
* @returns Array of child nodes
*/
function getChildren(t: Tree): Tree[];
/**
* Get all ancestor nodes up to root
* @param t - Starting tree node
* @returns Array of ancestor nodes
*/
function getAncestors(t: Tree): Tree[];
/**
* Find all terminal nodes of specific token type
* @param t - Root parse tree to search
* @param ttype - Token type to find
* @returns Array of matching terminal nodes
*/
function findAllTokenNodes(t: ParseTree, ttype: number): ParseTree[];
/**
* Find all rule nodes of specific rule index
* @param t - Root parse tree to search
* @param ruleIndex - Rule index to find
* @returns Array of matching rule nodes
*/
function findAllRuleNodes(t: ParseTree, ruleIndex: number): ParseTree[];
/**
* Find all nodes of specific type
* @param t - Root parse tree to search
* @param index - Token type or rule index to find
* @param findTokens - True to find tokens, false to find rules
* @returns Array of matching nodes
*/
function findAllNodes(t: ParseTree, index: number, findTokens: boolean): ParseTree[];
/**
* Get all descendant nodes (depth-first)
* @param t - Root parse tree
* @returns Array of all descendant nodes
*/
function descendants(t: ParseTree): ParseTree[];
}Creating a custom parse tree listener:
import { ParseTreeListener, TerminalNode, tree } from "antlr4";
const { ErrorNode } = tree;
class MyListener extends ParseTreeListener {
enterEveryRule(ctx: ParserRuleContext): void {
console.log(`Entering rule: ${ctx.constructor.name}`);
}
exitEveryRule(ctx: ParserRuleContext): void {
console.log(`Exiting rule: ${ctx.constructor.name}`);
}
visitTerminal(node: TerminalNode): void {
console.log(`Terminal: ${node.symbol.text}`);
}
visitErrorNode(node: ErrorNode): void {
console.log(`Error: ${node.symbol.text}`);
}
// Generated listeners will have specific enter/exit methods
// for each rule in your grammar, e.g.:
enterExpression(ctx: ExpressionContext): void {
console.log("Entering expression");
}
exitExpression(ctx: ExpressionContext): void {
console.log("Exiting expression");
}
}Walking a parse tree with a listener:
import { ParseTreeWalker } from "antlr4";
// Assume we have a parse tree from parsing
const tree = parser.expression(); // Your start rule
// Create listener
const listener = new MyListener();
// Walk the tree
ParseTreeWalker.DEFAULT.walk(listener, tree);Creating a custom parse tree visitor:
import { ParseTreeVisitor, TerminalNode, RuleNode, tree } from "antlr4";
const { ErrorNode } = tree;
class CalculatorVisitor extends ParseTreeVisitor<number> {
visitTerminal(node: TerminalNode): number {
// Convert terminal nodes to numbers
const text = node.symbol.text;
return parseFloat(text) || 0;
}
visitErrorNode(node: ErrorNode): number {
throw new Error(`Parse error: ${node.symbol.text}`);
}
// Generated visitors will have specific visit methods
// for each rule in your grammar, e.g.:
visitAddExpression(ctx: AddExpressionContext): number {
const left = this.visit(ctx.getChild(0));
const right = this.visit(ctx.getChild(2));
const op = ctx.getChild(1).getText();
return op === '+' ? left + right : left - right;
}
visitMultExpression(ctx: MultExpressionContext): number {
const left = this.visit(ctx.getChild(0));
const right = this.visit(ctx.getChild(2));
const op = ctx.getChild(1).getText();
return op === '*' ? left * right : left / right;
}
}Using a parse tree visitor:
// Parse expression
const tree = parser.expression();
// Create visitor
const visitor = new CalculatorVisitor();
// Visit tree and get result
const result = visitor.visit(tree);
console.log("Calculation result:", result);Using tree utility functions:
import { tree } from "antlr4";
const { Trees } = tree;
// Assume we have a parse tree and parser
const parseTree = parser.expression();
// Get string representation
const treeString = Trees.toStringTree(parseTree, parser.ruleNames, parser);
console.log("Tree:", treeString);
// Find all identifier tokens (assuming token type 5 is IDENTIFIER)
const identifiers = Trees.findAllTokenNodes(parseTree, 5);
console.log("Found identifiers:", identifiers.length);
// Find all expression rule nodes (assuming rule index 2 is expression)
const expressions = Trees.findAllRuleNodes(parseTree, 2);
console.log("Found expressions:", expressions.length);
// Get all descendants
const allNodes = Trees.descendants(parseTree);
console.log("Total nodes:", allNodes.length);
// Get direct children
const children = Trees.getChildren(parseTree);
console.log("Direct children:", children.length);Traversing parse trees manually:
import { ParseTree, TerminalNode, RuleNode } from "antlr4";
function traverseTree(node: ParseTree, depth: number = 0): void {
const indent = " ".repeat(depth);
if (node instanceof TerminalNode) {
console.log(`${indent}Terminal: ${node.symbol.text}`);
} else if (node instanceof RuleNode) {
console.log(`${indent}Rule: ${node.constructor.name}`);
// Visit children
const childCount = node.getChildCount();
for (let i = 0; i < childCount; i++) {
const child = node.getChild(i);
traverseTree(child, depth + 1);
}
}
}
// Use with parse tree
const tree = parser.expression();
traverseTree(tree);Error handling in tree traversal:
import { ErrorNode, ParseTreeListener } from "antlr4";
class ErrorCollectingListener extends ParseTreeListener {
private errors: string[] = [];
visitErrorNode(node: ErrorNode): void {
const token = node.symbol;
const error = `Syntax error at line ${token.line}:${token.column} - unexpected token '${token.text}'`;
this.errors.push(error);
}
enterEveryRule(ctx: ParserRuleContext): void {
// Check for parser exceptions in context
if (ctx.exception) {
this.errors.push(`Rule error in ${ctx.constructor.name}: ${ctx.exception.message}`);
}
}
exitEveryRule(ctx: ParserRuleContext): void {
// Implementation for exit
}
visitTerminal(node: TerminalNode): void {
// Implementation for terminals
}
getErrors(): string[] {
return this.errors.slice(); // Return copy
}
hasErrors(): boolean {
return this.errors.length > 0;
}
}
// Usage
const listener = new ErrorCollectingListener();
ParseTreeWalker.DEFAULT.walk(listener, parseTree);
if (listener.hasErrors()) {
console.error("Parse errors found:");
listener.getErrors().forEach(error => console.error(error));
}