antlr-ng
Version:
Next generation ANTLR Tool
95 lines (94 loc) • 3.21 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { HashSet, RuleStopState, RuleTransition } from "antlr4ng";
import { LeftRecursionCyclesMessage } from "../tool/LeftRecursionCyclesMessage.js";
class LeftRecursionDetector {
static {
__name(this, "LeftRecursionDetector");
}
/** Holds a list of cycles (sets of rule names). */
listOfRecursiveCycles = [];
/** Which rule start states have we visited while looking for a single left-recursion check? */
rulesVisitedPerRuleCheck = new HashSet();
g;
atn;
constructor(g, atn) {
this.g = g;
this.atn = atn;
}
check(...args) {
if (args.length === 0) {
for (const start of this.atn.ruleToStartState) {
this.rulesVisitedPerRuleCheck.clear();
this.rulesVisitedPerRuleCheck.add(start);
this.check(this.g.getRule(start.ruleIndex), start, /* @__PURE__ */ new Set());
}
if (this.listOfRecursiveCycles.length > 0) {
this.leftRecursionCycles(this.g.fileName, this.listOfRecursiveCycles);
}
return;
}
const [enclosingRule, s, visitedStates] = args;
if (s instanceof RuleStopState) {
return true;
}
if (visitedStates.has(s)) {
return false;
}
visitedStates.add(s);
const n = s.transitions.length;
let stateReachesStopState = false;
for (let i = 0; i < n; i++) {
const t = s.transitions[i];
if (t instanceof RuleTransition) {
const rt = t;
const r = this.g.getRule(rt.ruleIndex);
if (this.rulesVisitedPerRuleCheck.contains(t.target)) {
this.addRulesToCycle(enclosingRule, r);
} else {
this.rulesVisitedPerRuleCheck.add(t.target);
const nullable = this.check(r, t.target, /* @__PURE__ */ new Set());
this.rulesVisitedPerRuleCheck.remove(t.target);
if (nullable) {
stateReachesStopState ||= this.check(enclosingRule, rt.followState, visitedStates);
}
}
} else {
if (t.isEpsilon) {
stateReachesStopState ||= this.check(enclosingRule, t.target, visitedStates);
}
}
}
return stateReachesStopState;
}
/**
* enclosingRule calls targetRule. Find the cycle containing the target and add the caller. Find the cycle
* containing the caller and add the target. If no cycles contain either, then create a new cycle.
*/
addRulesToCycle(enclosingRule, targetRule) {
let foundCycle = false;
for (const rulesInCycle of this.listOfRecursiveCycles) {
if (rulesInCycle.includes(targetRule)) {
rulesInCycle.push(enclosingRule);
foundCycle = true;
}
if (rulesInCycle.includes(enclosingRule)) {
rulesInCycle.push(targetRule);
foundCycle = true;
}
}
if (!foundCycle) {
const cycle = new Array();
cycle.push(targetRule);
cycle.push(enclosingRule);
this.listOfRecursiveCycles.push(cycle);
}
}
leftRecursionCycles(fileName, cycles) {
const msg = new LeftRecursionCyclesMessage(fileName, cycles);
this.g.tool.errorManager.error(msg);
}
}
export {
LeftRecursionDetector
};