antlr-ng
Version:
Next generation ANTLR Tool
344 lines (343 loc) • 11.4 kB
JavaScript
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 += "→\\n";
} else if (s instanceof BlockEndState) {
stateLabel += "←\\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
};