UNPKG

antlr-ng

Version:

Next generation ANTLR Tool

344 lines (343 loc) 11.4 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); import { fileURLToPath } from "node:url"; import { AbstractPredicateTransition, ActionTransition, AtomTransition, BlockEndState, BlockStartState, DFAState, DecisionState, NotSetTransition, PlusBlockStartState, PlusLoopbackState, RangeTransition, RuleStopState, RuleTransition, SetTransition, StarBlockStartState, StarLoopEntryState, StarLoopbackState, Token } from "antlr4ng"; import { STGroupFile } from "stringtemplate4ts"; import { Utils } from "../misc/Utils.js"; class DOTGenerator { static { __name(this, "DOTGenerator"); } /** Library of output templates; use `<attrname>` format. */ static templatePath = fileURLToPath(new URL("../../templates/dot/graphs.stg", import.meta.url)); static stLib = new STGroupFile(this.templatePath); arrowhead = "normal"; rankdir = "LR"; grammar; constructor(grammar) { this.grammar = grammar; } /** * @returns a String containing a DOT description that, when displayed, will show the incoming state machine * visually. All nodes reachable from startState will be included. */ getDOTFromState(startState, isLexer = false, ruleNames) { ruleNames ??= Array.from(this.grammar.rules.keys()); const markedStates = /* @__PURE__ */ new Set(); const dot = DOTGenerator.stLib.getInstanceOf("atn"); if (!dot) { throw new Error("no such template: atn"); } dot.add("startState", startState.stateNumber); dot.add("rankdir", this.rankdir); const work = [startState]; while (true) { const s = work.shift(); if (!s) { break; } if (markedStates.has(s)) { continue; } markedStates.add(s); if (s instanceof RuleStopState) { continue; } let edgeST; for (let i = 0; i < s.transitions.length; ++i) { const edge = s.transitions[i]; if (edge instanceof RuleTransition) { const rr = edge; edgeST = DOTGenerator.stLib.getInstanceOf("edge"); if (!edgeST) { throw new Error("no such template: edge"); } let label = "<" + ruleNames[rr.ruleIndex]; if (rr.target.isLeftRecursiveRule) { label += `[${rr.precedence}]`; } label += ">"; edgeST.add("label", label); edgeST.add("src", `s${s.stateNumber}`); edgeST.add("target", `s${rr.followState.stateNumber}`); edgeST.add("arrowhead", this.arrowhead); dot.add("edges", edgeST); work.push(rr.followState); continue; } if (edge instanceof ActionTransition) { edgeST = DOTGenerator.stLib.getInstanceOf("action-edge"); if (!edgeST) { throw new Error("no such template: action-edge"); } edgeST.add("label", this.getEdgeLabel(edge.toString())); } else if (edge instanceof AbstractPredicateTransition) { edgeST = DOTGenerator.stLib.getInstanceOf("edge"); if (!edgeST) { throw new Error("no such template: edge"); } edgeST.add("label", this.getEdgeLabel(edge.toString())); } else if (edge.isEpsilon) { edgeST = DOTGenerator.stLib.getInstanceOf("epsilon-edge"); if (!edgeST) { throw new Error("no such template: epsilon-edge"); } edgeST.add("label", this.getEdgeLabel(edge.toString())); let loopback = false; if (edge.target instanceof PlusBlockStartState) { loopback = s.equals(edge.target.loopBackState); } else { if (edge.target instanceof StarLoopEntryState) { loopback = s.equals(edge.target.loopBackState); } } edgeST.add("loopback", loopback); } else if (edge instanceof AtomTransition) { edgeST = DOTGenerator.stLib.getInstanceOf("edge"); if (!edgeST) { throw new Error("no such template: edge"); } let label = edge.labelValue.toString(); if (isLexer) { if (edge.labelValue === Token.EOF) { label = "EOF"; } else { label = "'" + this.getEdgeLabel(String.fromCodePoint(edge.labelValue)) + "'"; } } else { label = this.grammar.getTokenDisplayName(edge.labelValue); } edgeST.add("label", this.getEdgeLabel(label)); } else if (edge instanceof SetTransition) { edgeST = DOTGenerator.stLib.getInstanceOf("edge"); if (!edgeST) { throw new Error("no such template: edge"); } let label = edge.label.toString(); if (isLexer) { label = edge.label.toString(true); } else { label = edge.label.toStringWithVocabulary(this.grammar.getVocabulary()); } if (edge instanceof NotSetTransition) { label = "~" + label; } edgeST.add("label", this.getEdgeLabel(label)); } else if (edge instanceof RangeTransition) { edgeST = DOTGenerator.stLib.getInstanceOf("edge"); if (!edgeST) { throw new Error("no such template: edge"); } let label = edge.label.toString(); if (isLexer) { label = edge.toString(); } else { label = edge.label.toStringWithVocabulary(this.grammar.getVocabulary()); } edgeST.add("label", this.getEdgeLabel(label)); } else { edgeST = DOTGenerator.stLib.getInstanceOf("edge"); if (!edgeST) { throw new Error("no such template: edge"); } edgeST.add("label", this.getEdgeLabel(edge.toString())); } edgeST.add("src", `s${s.stateNumber}`); edgeST.add("target", `s${edge.target.stateNumber}`); edgeST.add("arrowhead", this.arrowhead); if (s.transitions.length > 1) { edgeST.add("transitionIndex", i); } else { edgeST.add("transitionIndex", false); } dot.add("edges", edgeST); work.push(edge.target); } } for (const s of markedStates) { if (!(s instanceof RuleStopState)) { continue; } const st = DOTGenerator.stLib.getInstanceOf("stopstate"); if (!st) { throw new Error("no such template: stopstate"); } st.add("name", `s${s.stateNumber}`); st.add("label", this.getStateLabel(s)); dot.add("states", st); } for (const s of markedStates) { if (s instanceof RuleStopState) { continue; } const st = DOTGenerator.stLib.getInstanceOf("state"); if (!st) { throw new Error("no such template: state"); } st.add("name", `s${s.stateNumber}`); st.add("label", this.getStateLabel(s)); st.add("transitions", s.transitions); dot.add("states", st); } return dot.render(); } getDOTFromDFA(dfa, isLexer) { if (!dfa.s0) { return ""; } const dot = DOTGenerator.stLib.getInstanceOf("dfa"); if (!dot) { throw new Error("no such template: dfa"); } dot.add("name", `DFA${dfa.decision}`); dot.add("startState", dfa.s0.stateNumber); dot.add("rankdir", this.rankdir); for (const d of dfa.getStates()) { if (!d.isAcceptState) { continue; } const st = DOTGenerator.stLib.getInstanceOf("stopstate"); if (!st) { throw new Error("no such template: stopstate"); } st.add("name", `s${d.stateNumber}`); st.add("label", this.getStateLabel(d)); dot.add("states", st); } for (const d of dfa.getStates()) { if (d.isAcceptState) { continue; } if (d.stateNumber === Number.MAX_VALUE) { continue; } const st = DOTGenerator.stLib.getInstanceOf("state"); if (!st) { throw new Error("no such template: state"); } st.add("name", `s${d.stateNumber}`); st.add("label", this.getStateLabel(d)); dot.add("states", st); } for (const d of dfa.getStates()) { for (let i = 0; i < d.edges.length; i++) { const target = d.edges[i]; if (target.stateNumber === Number.MAX_VALUE) { continue; } const ttype = i - 1; let label = ttype.toString(); if (isLexer) { label = "'" + this.getEdgeLabel(String.fromCodePoint(i)) + "'"; } else { label = this.grammar.getTokenDisplayName(ttype); } const st = DOTGenerator.stLib.getInstanceOf("edge"); if (!st) { throw new Error("no such template: edge"); } st.add("label", label); st.add("src", `s${d.stateNumber}`); st.add("target", `s${target.stateNumber}`); st.add("arrowhead", this.arrowhead); dot.add("edges", st); } } const output = dot.render(); return Utils.sortLinesInString(output); } getStateLabel(s) { if (s instanceof DFAState) { let buf = `s${s.stateNumber}`; if (s.isAcceptState) { buf += `=>${s.prediction}`; } if (s.requiresFullContext) { buf += "^"; } const alts = s.getAltSet(); if (alts !== null) { buf += "\\n"; const altList = Array.from(alts); altList.sort(); const configurations = s.configs; for (let altIndex = 0; altIndex < altList.length; altIndex++) { const alt = altList[altIndex]; if (altIndex > 0) { buf += "\\n"; } buf += `alt${alt}:`; const configsInAlt = new Array(); for (const c of configurations) { if (c.alt !== alt) { continue; } configsInAlt.push(c); } let n = 0; for (let cIndex = 0; cIndex < configsInAlt.length; cIndex++) { const c = configsInAlt[cIndex]; ++n; buf += c.toString(null, false); if (cIndex + 1 < configsInAlt.length) { buf += ", "; } if (n % 5 === 0 && configsInAlt.length - cIndex > 3) { buf += "\\n"; } } } } return buf; } else { let stateLabel = ""; if (s instanceof BlockStartState) { stateLabel += "&rarr;\\n"; } else if (s instanceof BlockEndState) { stateLabel += "&larr;\\n"; } stateLabel += s.stateNumber.toString(); if (s instanceof PlusBlockStartState || s instanceof PlusLoopbackState) { stateLabel += "+"; } else if (s instanceof StarBlockStartState || s instanceof StarLoopEntryState || s instanceof StarLoopbackState) { stateLabel += "*"; } if (s instanceof DecisionState && s.decision >= 0) { stateLabel += `\\nd=${s.decision}`; } return stateLabel; } } /** * Fixes edge strings so they print out in DOT properly. */ getEdgeLabel(label) { label = label.replaceAll("\\", "\\\\"); label = label.replaceAll('"', '\\"'); label = label.replaceAll("\n", "\\\\n"); label = label.replaceAll("\r", ""); return label; } } export { DOTGenerator };