UNPKG

antlr-ng

Version:

Next generation ANTLR Tool

161 lines (160 loc) 6.06 kB
#!/usr/bin/env node 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 };