antlr-ng
Version:
Next generation ANTLR Tool
161 lines (160 loc) • 6.06 kB
JavaScript
import { Option, program } from "commander";
import { readFile } from "fs/promises";
import { createWriteStream } from "node:fs";
import { CharStream, CommonToken, CommonTokenStream } from "antlr4ng";
import { Tool } from "../src/Tool.js";
import { Grammar, LexerGrammar } from "../src/tool/index.js";
import { ToolListener } from "../src/tool/ToolListener.js";
import { encodings, parseBoolean } from "./cli-options.js";
import { IgnoreTokenVocabGrammar } from "./IgnoreTokenVocabGrammar.js";
program.argument("startRuleName", "Name of the parser start rule").option("--tree", "Print out the parse tree", parseBoolean, false).option("--tokens", "Print out the tokens for each input symbol", parseBoolean, false).option("--trace", "Print out tracing information (rule enter/exit etc.).", parseBoolean, false).addOption(new Option("--encoding [string]", "The input file encoding (default: utf-8)").choices(encodings).default("utf-8")).option("--profile filename.csv", "Profile the parser and generate profiling information.", "filename.csv").argument("<input-filename>", "Input file").argument("[grammar...]", "Lexer/Parser/Combined grammar files").parse();
const interpreterOptions = program.opts();
interpreterOptions.startRuleName = program.args[0];
interpreterOptions.inputFile = program.args[1];
interpreterOptions.grammars = program.args.slice(2);
class Interpreter {
static profilerColumnNames = [
"Rule",
"Invocations",
"Time (ms)",
"Total k",
"Max k",
"Ambiguities",
"DFA cache miss"
];
static getValue(decisionInfo, ruleNamesByDecision, decision, col) {
switch (col) {
// laborious but more efficient than reflection
case 0: {
return `${ruleNamesByDecision[decision]}:${decision}`;
}
case 1: {
return decisionInfo.invocations;
}
case 2: {
return decisionInfo.timeInPrediction / (1e3 * 1e3);
}
case 3: {
return decisionInfo.llTotalLook + decisionInfo.sllTotalLook;
}
case 4: {
return Math.max(decisionInfo.llMaxLook, decisionInfo.sllMaxLook);
}
case 5: {
return decisionInfo.ambiguities.length;
}
case 6: {
return decisionInfo.sllATNTransitions + decisionInfo.llATNTransitions;
}
default:
}
return "n/a";
}
async run() {
if (interpreterOptions.grammars.length === 0 || !interpreterOptions.inputFile) {
return void 0;
}
let g;
let lg = null;
const tool = new Tool();
const listener = new ToolListener(tool.errorManager);
if (interpreterOptions.grammars.length === 1) {
const grammarContent = await readFile(interpreterOptions.grammars[0], "utf8");
g = Grammar.forFile(
IgnoreTokenVocabGrammar,
interpreterOptions.grammars[0],
grammarContent,
void 0,
listener
);
g.tool.process(g, false);
} else {
const lexerGrammarContent = await readFile(interpreterOptions.grammars[1], "utf8");
lg = new LexerGrammar(lexerGrammarContent);
lg.tool.errorManager.addListener(listener);
lg.tool.process(lg, false);
const parserGrammarContent = await readFile(interpreterOptions.grammars[0], "utf8");
g = Grammar.forFile(
IgnoreTokenVocabGrammar,
interpreterOptions.grammars[0],
parserGrammarContent,
lg,
listener
);
g.tool.process(g, false);
}
const input = await readFile(interpreterOptions.inputFile, interpreterOptions.encoding);
const charStream = CharStream.fromString(input);
const lexEngine = lg ? lg.createLexerInterpreter(charStream) : g.createLexerInterpreter(charStream);
const tokens = new CommonTokenStream(lexEngine);
tokens.fill();
if (interpreterOptions.tokens) {
for (const tok of tokens.getTokens()) {
if (tok instanceof CommonToken) {
console.log(tok.toString(lexEngine));
} else {
console.log(tok.toString());
}
}
}
const parser = g.createGrammarParserInterpreter(tokens);
if (interpreterOptions.profile) {
parser.setProfile(true);
}
parser.setTrace(interpreterOptions.trace ?? false);
const r = g.rules.get(interpreterOptions.startRuleName);
if (!r) {
console.error("No such start rule: " + interpreterOptions.startRuleName);
return void 0;
}
const t = parser.parse(r.index);
const parseInfo = parser.getParseInfo();
if (interpreterOptions.tree) {
console.log(t.toStringTree(parser));
}
if (interpreterOptions.profile && parseInfo) {
this.dumpProfilerCSV(parser, parseInfo);
}
return parseInfo ?? void 0;
}
dumpProfilerCSV(parser, parseInfo) {
const ruleNamesByDecision = new Array(parser.atn.decisionToState.length);
const ruleNames = parser.ruleNames;
for (let i = 0; i < ruleNamesByDecision.length; ++i) {
ruleNamesByDecision[i] = ruleNames[parser.atn.getDecisionState(i).ruleIndex];
}
const decisionInfo = parseInfo.getDecisionInfo();
const table = [];
for (let decision = 0; decision < decisionInfo.length; ++decision) {
table.push([]);
for (let col = 0; col < Interpreter.profilerColumnNames.length; col++) {
const colVal = Interpreter.getValue(decisionInfo[decision], ruleNamesByDecision, decision, col);
table[decision].push(colVal.toString());
}
}
const writer = createWriteStream(interpreterOptions.profile);
for (let i = 0; i < Interpreter.profilerColumnNames.length; i++) {
if (i > 0) {
writer.write(",");
}
writer.write(Interpreter.profilerColumnNames[i]);
}
writer.write("\n");
for (const row of table) {
for (let i = 0; i < Interpreter.profilerColumnNames.length; i++) {
if (i > 0) {
writer.write(",");
}
writer.write(row[i]);
}
writer.write("\n");
}
writer.close();
}
}
const interpreter = new Interpreter();
await interpreter.run();
export {
Interpreter
};