Utility classes and functions for common operations including bit sets, intervals, string processing, and debugging support. These utilities support the core ANTLR4 functionality and provide helpful tools for parser development.
Efficient set implementation for integer values, commonly used for representing sets of token types or alternatives.
/**
* Efficient bit set implementation for integers
*/
class BitSet {
/** Number of bits in the set (read-only) */
readonly length: number;
/**
* Get bit value at specific index
* @param index - Bit index to check
* @returns 1 if bit is set, 0 if not set
*/
get(index: number): number;
/**
* Get array of all set bit positions
* @returns Array of indices where bits are set
*/
values(): Array<number>;
/**
* Get minimum set bit position
* @returns Minimum index with set bit
*/
minValue(): number;
/**
* Calculate hash code for the bit set
* @returns Hash code value
*/
hashCode(): number;
/**
* Check equality with another bit set
* @returns True if bit sets are equal
*/
equals(): boolean;
/**
* Convert bit set to string representation
* @returns String representation of bit set
*/
toString(): string;
/**
* Count number of set bits in a value (static utility)
* @param l - Long value to count bits in
* @returns Number of set bits
*/
static _bitCount(l: number): number;
}Classes for representing ranges and sets of ranges, used for character ranges and token type ranges.
/**
* Represents a range with start and stop positions
*/
class Interval {
/** Start position of interval */
start: number;
/** Stop position of interval */
stop: number;
/**
* Create interval from start and stop numbers
* @param start - Start position
* @param stop - Stop position
*/
constructor(start: number, stop: number);
/**
* Create interval from tokens
* @param start - Start token
* @param stop - Stop token (optional)
*/
constructor(start: Token, stop: Token | undefined);
}
/**
* Set of intervals for representing character or token ranges
*/
class IntervalSet {
/** True if the set is empty */
isNil: boolean;
/** Number of elements in all intervals combined */
size: number;
/** Minimum element across all intervals */
minElement: number;
/** Maximum element across all intervals */
maxElement: number;
/** Array of intervals in the set */
intervals: Interval[];
/**
* Check if set contains specific value
* @param i - Value to check
* @returns True if value is in any interval
*/
contains(i: number): boolean;
/**
* Convert interval set to string representation
* @param literalNames - Array of literal token names (optional)
* @param symbolicNames - Array of symbolic token names (optional)
* @param elemsAreChar - Whether elements represent characters (optional)
* @returns String representation of interval set
*/
toString(literalNames?: (string | null)[], symbolicNames?: string[], elemsAreChar?: boolean): string;
}Helper functions for string and array operations.
/**
* Convert array to string representation
* @param value - Array to convert
* @returns String representation of array
*/
function arrayToString(value: any[]): string;
/**
* Convert string to character array (Uint16Array)
* @param str - String to convert
* @returns Uint16Array of character codes
*/
function stringToCharArray(str: string): Uint16Array;Abstract interface for output printing, useful for debugging and logging.
/**
* Abstract interface for printing output
*/
abstract class Printer {
/**
* Print text without newline
* @param s - Text to print
*/
print(s: string): void;
/**
* Print text with newline
* @param s - Text to print
*/
println(s: string): void;
}Working with BitSet:
import { BitSet } from "antlr4";
// Create a bit set for tracking token types
function createTokenTypeSet(tokenTypes: number[]): BitSet {
const bitSet = new BitSet();
// Note: BitSet constructor and methods may vary
// This is conceptual usage based on the API
for (const tokenType of tokenTypes) {
// Conceptual - actual BitSet API may differ
bitSet.set(tokenType);
}
return bitSet;
}
// Check if token type is in set
function isTokenTypeInSet(bitSet: BitSet, tokenType: number): boolean {
return bitSet.get(tokenType) === 1;
}
// Get all token types in set
function getSetTokenTypes(bitSet: BitSet): number[] {
return bitSet.values();
}
// Usage example
const importantTokens = [1, 5, 10, 15]; // Token types
const tokenSet = createTokenTypeSet(importantTokens);
console.log("Token types in set:", getSetTokenTypes(tokenSet));
console.log("Contains token 5:", isTokenTypeInSet(tokenSet, 5));
console.log("Contains token 7:", isTokenTypeInSet(tokenSet, 7));Working with Intervals and IntervalSets:
import { Interval, IntervalSet } from "antlr4";
// Create intervals for different ranges
const letterRange = new Interval(65, 90); // A-Z
const digitRange = new Interval(48, 57); // 0-9
const spaceRange = new Interval(32, 32); // Space
// Create interval set for valid identifier characters
const identifierChars = new IntervalSet();
// Note: Actual IntervalSet API for adding intervals may differ
// This shows conceptual usage
console.log("Letter range contains 'A' (65):", letterRange.contains?.(65));
console.log("Digit range contains '5' (53):", digitRange.contains?.(53));
// Working with token intervals
const startToken = new CommonToken([null, null], 1, 0, 0, 4);
const stopToken = new CommonToken([null, null], 2, 0, 6, 10);
const tokenInterval = new Interval(startToken, stopToken);
console.log("Token interval:", tokenInterval.start, "to", tokenInterval.stop);Using string and array utilities:
import { arrayToString, Utils } from "antlr4";
const { stringToCharArray } = Utils;
// Convert arrays to strings for debugging
const tokens = [1, 2, 3, 4, 5];
const tokenString = arrayToString(tokens);
console.log("Tokens as string:", tokenString);
const rules = ["expression", "term", "factor"];
const rulesString = arrayToString(rules);
console.log("Rules as string:", rulesString);
// Convert string to character array
const input = "hello";
const charArray = stringToCharArray(input);
console.log("Character codes:", Array.from(charArray));
// Useful for lexer input processing
function processCharacters(text: string): void {
const chars = stringToCharArray(text);
for (let i = 0; i < chars.length; i++) {
const charCode = chars[i];
console.log(`Char ${i}: ${String.fromCharCode(charCode)} (${charCode})`);
}
}
processCharacters("ABC123");Creating a custom printer for debugging:
import { Printer } from "antlr4";
class DebugPrinter extends Printer {
private logLevel: string;
constructor(logLevel: string = "DEBUG") {
super();
this.logLevel = logLevel;
}
print(s: string): void {
process.stdout.write(`[${this.logLevel}] ${s}`);
}
println(s: string): void {
console.log(`[${this.logLevel}] ${s}`);
}
}
class FilePrinter extends Printer {
private lines: string[] = [];
print(s: string): void {
// Append to current line
if (this.lines.length === 0) {
this.lines.push(s);
} else {
this.lines[this.lines.length - 1] += s;
}
}
println(s: string): void {
if (this.lines.length === 0) {
this.lines.push(s);
} else {
this.lines[this.lines.length - 1] += s;
}
this.lines.push(""); // Start new line
}
getOutput(): string {
return this.lines.join("\n");
}
clear(): void {
this.lines = [];
}
}
// Usage examples
const debugPrinter = new DebugPrinter("PARSER");
debugPrinter.println("Starting parse");
debugPrinter.print("Current token: ");
debugPrinter.println("IDENTIFIER");
const filePrinter = new FilePrinter();
filePrinter.println("Parse tree:");
filePrinter.println(" expression");
filePrinter.println(" term");
console.log(filePrinter.getOutput());Utility functions for parser debugging:
import { IntervalSet, arrayToString } from "antlr4";
class ParserDebugUtils {
/**
* Format expected tokens for error messages
*/
static formatExpectedTokens(expectedTokens: IntervalSet, tokenNames: string[]): string {
const tokens: string[] = [];
for (const interval of expectedTokens.intervals) {
for (let i = interval.start; i <= interval.stop; i++) {
const tokenName = tokenNames[i] || `Token${i}`;
tokens.push(tokenName);
}
}
if (tokens.length === 0) return "no valid tokens";
if (tokens.length === 1) return tokens[0];
const last = tokens.pop();
return `${tokens.join(", ")} or ${last}`;
}
/**
* Format rule stack for debugging
*/
static formatRuleStack(ruleNames: string[], ruleStack: number[]): string {
const names = ruleStack.map(ruleIndex =>
ruleNames[ruleIndex] || `rule${ruleIndex}`
);
return names.join(" -> ");
}
/**
* Create a simple parse tree string
*/
static formatParseTree(tree: ParseTree, ruleNames: string[], depth: number = 0): string {
const indent = " ".repeat(depth);
if (tree instanceof TerminalNode) {
return `${indent}"${tree.getText()}"`;
} else if (tree instanceof ParserRuleContext) {
const ruleName = ruleNames[tree.getRuleIndex()] || "unknown";
let result = `${indent}${ruleName}`;
if (tree.children) {
for (const child of tree.children) {
result += "\n" + this.formatParseTree(child, ruleNames, depth + 1);
}
}
return result;
}
return `${indent}${tree.constructor.name}`;
}
/**
* Analyze token distribution
*/
static analyzeTokens(tokens: Token[], tokenNames: string[]): Map<string, number> {
const distribution = new Map<string, number>();
for (const token of tokens) {
const tokenName = tokenNames[token.type] || `Token${token.type}`;
distribution.set(tokenName, (distribution.get(tokenName) || 0) + 1);
}
return distribution;
}
}
// Usage examples
const expectedTokens = new IntervalSet();
// ... populate expectedTokens
const tokenNames = ["EOF", "PLUS", "MINUS", "NUMBER", "IDENTIFIER"];
const expectedMessage = ParserDebugUtils.formatExpectedTokens(expectedTokens, tokenNames);
console.log(`Expected: ${expectedMessage}`);
// Rule stack formatting
const ruleNames = ["expression", "term", "factor"];
const ruleStack = [0, 1, 2];
const stackString = ParserDebugUtils.formatRuleStack(ruleNames, ruleStack);
console.log(`Rule stack: ${stackString}`);
// Token analysis
const tokensList = [/* array of tokens */];
const tokenDistribution = ParserDebugUtils.analyzeTokens(tokensList, tokenNames);
console.log("Token distribution:");
for (const [tokenName, count] of tokenDistribution) {
console.log(` ${tokenName}: ${count}`);
}Performance monitoring utilities:
class PerformanceMonitor {
private timings = new Map<string, number[]>();
time<T>(name: string, fn: () => T): T {
const start = performance.now();
try {
return fn();
} finally {
const duration = performance.now() - start;
this.recordTiming(name, duration);
}
}
async timeAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
const start = performance.now();
try {
return await fn();
} finally {
const duration = performance.now() - start;
this.recordTiming(name, duration);
}
}
private recordTiming(name: string, duration: number): void {
if (!this.timings.has(name)) {
this.timings.set(name, []);
}
this.timings.get(name)!.push(duration);
}
getStats(name: string): { count: number; total: number; average: number; min: number; max: number } | null {
const times = this.timings.get(name);
if (!times || times.length === 0) return null;
const total = times.reduce((sum, time) => sum + time, 0);
const min = Math.min(...times);
const max = Math.max(...times);
return {
count: times.length,
total,
average: total / times.length,
min,
max
};
}
printReport(): void {
console.log("Performance Report:");
console.log("==================");
for (const [name, times] of this.timings) {
const stats = this.getStats(name);
if (stats) {
console.log(`${name}:`);
console.log(` Count: ${stats.count}`);
console.log(` Total: ${stats.total.toFixed(2)}ms`);
console.log(` Average: ${stats.average.toFixed(2)}ms`);
console.log(` Min: ${stats.min.toFixed(2)}ms`);
console.log(` Max: ${stats.max.toFixed(2)}ms`);
console.log();
}
}
}
clear(): void {
this.timings.clear();
}
}
// Usage with parsing
const monitor = new PerformanceMonitor();
// Time lexing
const tokens = monitor.time("lexing", () => {
const lexer = new MyLexer(input);
const tokenStream = new CommonTokenStream(lexer);
tokenStream.fill();
return tokenStream.tokens;
});
// Time parsing
const parseTree = monitor.time("parsing", () => {
const parser = new MyParser(new CommonTokenStream(new MyLexer(input)));
return parser.expression();
});
// Time tree walking
monitor.time("walking", () => {
const listener = new MyListener();
ParseTreeWalker.DEFAULT.walk(listener, parseTree);
});
monitor.printReport();