UNPKG

antlr-ng

Version:

Next generation ANTLR Tool

420 lines (419 loc) 15.3 kB
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 };