antlr-ng
Version:
Next generation ANTLR Tool
420 lines (419 loc) • 15.3 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { Token } from "antlr4ng";
import { ANTLRv4Lexer } from "../generated/ANTLRv4Lexer.js";
import { ANTLRv4Parser } from "../generated/ANTLRv4Parser.js";
import { Constants } from "../Constants.js";
import { AltAST } from "../tool/ast/AltAST.js";
import { GrammarAST } from "../tool/ast/GrammarAST.js";
import { TerminalAST } from "../tool/ast/TerminalAST.js";
import { IssueCode } from "../tool/Issues.js";
import { LabelType } from "../tool/LabelType.js";
import { LeftRecursiveRule } from "../tool/LeftRecursiveRule.js";
class SymbolChecks {
static {
__name(this, "SymbolChecks");
}
g;
collector;
nameToRuleMap = /* @__PURE__ */ new Map();
tokenIDs = /* @__PURE__ */ new Set();
actionScopeToActionNames = /* @__PURE__ */ new Map();
reservedNames = /* @__PURE__ */ new Set();
constructor(g, collector) {
this.g = g;
this.collector = collector;
for (const tokenId of collector.tokenIDRefs) {
this.tokenIDs.add(tokenId.getText());
}
Constants.COMMON_CONSTANTS.forEach((value, key) => {
this.reservedNames.add(key);
});
}
process() {
for (const r of this.g.rules.values()) {
this.nameToRuleMap.set(r.name, r);
}
this.checkReservedNames(Array.from(this.g.rules.values()));
this.checkActionRedefinitions(this.collector.namedActions);
this.checkForLabelConflicts(Array.from(this.g.rules.values()));
}
checkActionRedefinitions(actions) {
let scope = this.g.getDefaultActionScope();
let name;
let nameNode;
for (const ampersandAST of actions) {
nameNode = ampersandAST.children[0];
if (ampersandAST.children.length === 2) {
name = nameNode.getText();
} else {
scope = nameNode.getText();
name = ampersandAST.children[1].getText();
}
let scopeActions = this.actionScopeToActionNames.get(scope);
if (!scopeActions) {
scopeActions = /* @__PURE__ */ new Set();
this.actionScopeToActionNames.set(scope, scopeActions);
}
if (!scopeActions.has(name)) {
scopeActions.add(name);
} else {
this.g.tool.errorManager.grammarError(
IssueCode.ActionRedefinition,
this.g.fileName,
nameNode.token,
name
);
}
}
}
/**
* Make sure a label doesn't conflict with another symbol.
* Labels must not conflict with: rules, tokens, scope names,
* return values, parameters, and rule-scope dynamic attributes
* defined in surrounding rule. Also they must have same type
* for repeated defs.
*/
checkForLabelConflicts(rules) {
for (const r of rules) {
this.checkForAttributeConflicts(r);
const labelNameSpace = /* @__PURE__ */ new Map();
for (let i = 1; i <= r.numberOfAlts; i++) {
const a = r.alt[i];
for (const pairs of a.labelDefs.values()) {
if (r.hasAltSpecificContexts()) {
const labelPairs = /* @__PURE__ */ new Map();
for (const p of pairs) {
const labelName = this.findAltLabelName(p.label);
if (labelName !== null) {
let list;
if (labelPairs.has(labelName)) {
list = labelPairs.get(labelName);
} else {
list = [];
labelPairs.set(labelName, list);
}
list.push(p);
}
}
for (const internalPairs of labelPairs.values()) {
labelNameSpace.clear();
this.checkLabelPairs(r, labelNameSpace, internalPairs);
}
} else {
this.checkLabelPairs(r, labelNameSpace, pairs);
}
}
}
}
}
checkForLabelConflict(r, labelID) {
const name = labelID.getText();
if (this.nameToRuleMap.has(name)) {
const errorType = IssueCode.LabelConflictsWithRule;
this.g.tool.errorManager.grammarError(errorType, this.g.fileName, labelID.token, name, r.name);
}
if (this.tokenIDs.has(name)) {
const errorType = IssueCode.LabelConflictsWithToken;
this.g.tool.errorManager.grammarError(errorType, this.g.fileName, labelID.token, name, r.name);
}
if (r.args?.get(name)) {
const errorType = IssueCode.LabelConflictsWithArg;
this.g.tool.errorManager.grammarError(errorType, this.g.fileName, labelID.token, name, r.name);
}
if (r.retvals?.get(name)) {
const errorType = IssueCode.LabelConflictsWithRetval;
this.g.tool.errorManager.grammarError(errorType, this.g.fileName, labelID.token, name, r.name);
}
if (r.locals?.get(name)) {
const errorType = IssueCode.LabelConflictsWithLocal;
this.g.tool.errorManager.grammarError(errorType, this.g.fileName, labelID.token, name, r.name);
}
}
checkForAttributeConflicts(r) {
this.checkDeclarationRuleConflicts(
r,
r.args,
new Set(this.nameToRuleMap.keys()),
IssueCode.ArgConflictsWithRule
);
this.checkDeclarationRuleConflicts(r, r.args, this.tokenIDs, IssueCode.ArgConflictsWithToken);
this.checkDeclarationRuleConflicts(
r,
r.retvals,
new Set(this.nameToRuleMap.keys()),
IssueCode.RetvalConflkictsWithRule
);
this.checkDeclarationRuleConflicts(r, r.retvals, this.tokenIDs, IssueCode.RetvalConflictsWithToken);
this.checkDeclarationRuleConflicts(
r,
r.locals,
new Set(this.nameToRuleMap.keys()),
IssueCode.LocalConflictsWithRule
);
this.checkDeclarationRuleConflicts(r, r.locals, this.tokenIDs, IssueCode.LocalConflictsWithToken);
this.checkLocalConflictingDeclarations(r, r.retvals, r.args, IssueCode.RetValuConflictsWithArg);
this.checkLocalConflictingDeclarations(r, r.locals, r.args, IssueCode.LocalConflictsWithArg);
this.checkLocalConflictingDeclarations(r, r.locals, r.retvals, IssueCode.LocalConflictsWithRetval);
}
checkForModeConflicts(g) {
if (g.isLexer()) {
const lexerGrammar = g;
for (const modeName of lexerGrammar.modes.keys()) {
if (modeName !== "DEFAULT_MODE" && this.reservedNames.has(modeName)) {
const rule = lexerGrammar.modes.get(modeName)[0];
this.g.tool.errorManager.grammarError(
IssueCode.ModeConflictsWithCommonConstants,
g.fileName,
rule.ast.parent.token,
modeName
);
}
if (g.getTokenType(modeName) !== Token.INVALID_TYPE) {
const rule = lexerGrammar.modes.get(modeName)[0];
this.g.tool.errorManager.grammarError(
IssueCode.ModeConflictsWithToken,
g.fileName,
rule.ast.parent.token,
modeName
);
}
}
}
}
/**
* Algorithm steps:
* 1. Collect all simple string literals (i.e. 'asdf', 'as' 'df', but not [a-z]+, 'a'..'z')
* for all lexer rules in each mode except of autogenerated tokens ({@link getSingleTokenValues})
* 2. Compare every string literal with each other ({@link checkForOverlap})
* and throw TOKEN_UNREACHABLE warning if the same string found.
* Complexity: O(m * n^2 / 2), approximately equals to O(n^2)
* where m - number of modes, n - average number of lexer rules per mode.
* See also testUnreachableTokens unit test for details.
*/
checkForUnreachableTokens(g) {
if (g.isLexer()) {
const lexerGrammar = g;
for (const rules of lexerGrammar.modes.values()) {
const stringLiteralRules = [];
const stringLiteralValues = [];
for (const rule of rules) {
const ruleStringAlts = this.getSingleTokenValues(rule);
if (ruleStringAlts.length > 0) {
stringLiteralRules.push(rule);
stringLiteralValues.push(ruleStringAlts);
}
}
for (let i = 0; i < stringLiteralRules.length; i++) {
const firstTokenStringValues = stringLiteralValues[i];
const rule1 = stringLiteralRules[i];
this.checkForOverlap(g, rule1, rule1, firstTokenStringValues, stringLiteralValues[i]);
if (!rule1.isFragment()) {
for (let j = i + 1; j < stringLiteralRules.length; j++) {
const rule2 = stringLiteralRules[j];
if (!rule2.isFragment()) {
this.checkForOverlap(g, rule1, rule2, firstTokenStringValues, stringLiteralValues[j]);
}
}
}
}
}
}
}
// CAN ONLY CALL THE TWO NEXT METHODS AFTER GRAMMAR HAS RULE DEFS (see semantic pipeline)
checkRuleArgs(g, ruleRefs) {
for (const ref of ruleRefs) {
const ruleName = ref.getText();
const r = g.getRule(ruleName);
const arg = ref.getFirstChildWithType(ANTLRv4Parser.ARG_ACTION);
if (arg !== null && r?.args === void 0) {
this.g.tool.errorManager.grammarError(IssueCode.RuleHasNoArgs, g.fileName, ref.token, ruleName);
} else if (arg === null && r?.args !== void 0) {
this.g.tool.errorManager.grammarError(IssueCode.MissingRuleArgs, g.fileName, ref.token, ruleName);
}
}
}
checkForQualifiedRuleIssues(g, qualifiedRuleRefs) {
for (const dot of qualifiedRuleRefs) {
const grammar = dot.children[0];
const rule = dot.children[1];
g.tool.logInfo({ component: "semantics", msg: grammar.getText() + "." + rule.getText() });
const delegate = g.getImportedGrammar(grammar.getText());
if (delegate === null) {
this.g.tool.errorManager.grammarError(
IssueCode.NoSuchGrammarScope,
g.fileName,
grammar.token,
grammar.getText(),
rule.getText()
);
} else if (g.getRule(grammar.getText(), rule.getText()) === null) {
this.g.tool.errorManager.grammarError(
IssueCode.NoSuchRuleInScope,
g.fileName,
rule.token,
grammar.getText(),
rule.getText()
);
}
}
}
checkDeclarationRuleConflicts(r, attributes, ruleNames, errorType) {
if (!attributes) {
return;
}
for (const attribute of attributes.attributes.values()) {
if (ruleNames.has(attribute.name)) {
this.g.tool.errorManager.grammarError(
errorType,
this.g.fileName,
attribute.token ?? r.ast.children[0].token,
attribute.name,
r.name
);
}
}
}
checkLocalConflictingDeclarations(r, attributes, referenceAttributes, errorType) {
if (!attributes || !referenceAttributes) {
return;
}
const conflictingKeys = attributes.intersection(referenceAttributes);
for (const key of conflictingKeys) {
this.g.tool.errorManager.grammarError(
errorType,
this.g.fileName,
attributes.get(key)?.token ?? r.ast.children[0].token,
key,
r.name
);
}
}
checkReservedNames(rules) {
for (const rule of rules) {
if (this.reservedNames.has(rule.name)) {
this.g.tool.errorManager.grammarError(
IssueCode.ReservedRuleName,
this.g.fileName,
rule.ast.children[0].token,
rule.name
);
}
}
}
checkLabelPairs(r, labelNameSpace, pairs) {
for (const p of pairs) {
this.checkForLabelConflict(r, p.label);
const name = p.label.getText();
const prev = labelNameSpace.get(name);
if (!prev) {
labelNameSpace.set(name, p);
} else {
this.checkForTypeMismatch(r, prev, p);
}
}
}
findAltLabelName(label) {
if (label === null) {
return null;
} else if (label instanceof AltAST) {
const altAST = label;
if (altAST.altLabel) {
return altAST.altLabel.toString();
} else {
if (altAST.leftRecursiveAltInfo) {
return altAST.leftRecursiveAltInfo.altLabel.toString();
} else {
return this.findAltLabelName(label.parent);
}
}
} else {
return this.findAltLabelName(label.parent);
}
}
checkForTypeMismatch(r, prevLabelPair, labelPair) {
if (prevLabelPair.type !== labelPair.type) {
const token = r instanceof LeftRecursiveRule ? r.ast.children[0].token : labelPair.label.token;
this.g.tool.errorManager.grammarError(
IssueCode.LabelTypeConflict,
this.g.fileName,
token,
labelPair.label.getText(),
labelPair.type + "!=" + prevLabelPair.type
);
}
if (prevLabelPair.element.getText() !== labelPair.element.getText() && (prevLabelPair.type === LabelType.RuleLabel || prevLabelPair.type === LabelType.RuleListLabel) && (labelPair.type === LabelType.RuleLabel || labelPair.type === LabelType.RuleListLabel)) {
const token = r instanceof LeftRecursiveRule ? r.ast.children[0].token : labelPair.label.token;
const prevLabelOp = prevLabelPair.type === LabelType.RuleListLabel ? "+=" : "=";
const labelOp = labelPair.type === LabelType.RuleListLabel ? "+=" : "=";
this.g.tool.errorManager.grammarError(
IssueCode.LabelTypeConflict,
this.g.fileName,
token,
labelPair.label.getText() + labelOp + labelPair.element.getText(),
prevLabelPair.label.getText() + prevLabelOp + prevLabelPair.element.getText()
);
}
}
/**
* {@return} list of simple string literals for rule {@param rule}
*/
getSingleTokenValues(rule) {
const values = [];
for (let i = 1; i < rule.alt.length; i++) {
const alt = rule.alt[i];
const rootNode = alt.ast.children.length === 2 && alt.ast.children[0] instanceof AltAST && alt.ast.children[1] instanceof GrammarAST ? alt.ast.children[0] : alt.ast;
if (rootNode.getTokenStartIndex() === -1) {
continue;
}
let ignore = false;
let currentValue = "";
for (const child of rootNode.children) {
if (!(child instanceof TerminalAST)) {
ignore = true;
break;
}
if (child.token.type !== ANTLRv4Lexer.STRING_LITERAL) {
ignore = true;
break;
} else {
const text = child.token.text;
currentValue += text.substring(1, text.length - 1);
}
}
if (!ignore) {
values.push(currentValue);
}
}
return values;
}
/**
* For same rule compare values from next index:
* TOKEN_WITH_SAME_VALUES: 'asdf' | 'asdf';
* For different rules compare from start value:
* TOKEN1: 'asdf';
* TOKEN2: 'asdf';
*/
checkForOverlap(g, rule1, rule2, firstTokenStringValues, secondTokenStringValues) {
for (let i = 0; i < firstTokenStringValues.length; i++) {
const secondTokenInd = rule1 === rule2 ? i + 1 : 0;
const str1 = firstTokenStringValues[i];
for (let j = secondTokenInd; j < secondTokenStringValues.length; j++) {
const str2 = secondTokenStringValues[j];
if (str1 === str2) {
this.g.tool.errorManager.grammarError(
IssueCode.TokenUnreachable,
g.fileName,
rule2.ast.children[0].token,
rule2.name,
str2,
rule1.name
);
}
}
}
}
}
export {
SymbolChecks
};