summaryrefslogtreecommitdiff
path: root/tool/src/org/antlr/v4/semantics
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2015-09-30 23:36:28 +0200
committerEmmanuel Bourg <ebourg@apache.org>2015-09-30 23:36:28 +0200
commitbbb7bd0887717b544df1a50e3842ca64c3489941 (patch)
tree3423dfb0f105f33e5752dd43bb0cd83728ecd2d7 /tool/src/org/antlr/v4/semantics
parentcdbf09fa7db7d25d9ce65b92d0398c3a54a5fd5d (diff)
Imported Upstream version 4.5.1
Diffstat (limited to 'tool/src/org/antlr/v4/semantics')
-rw-r--r--tool/src/org/antlr/v4/semantics/ActionSniffer.java113
-rw-r--r--tool/src/org/antlr/v4/semantics/AttributeChecks.java259
-rw-r--r--tool/src/org/antlr/v4/semantics/BasicSemanticChecks.java637
-rw-r--r--tool/src/org/antlr/v4/semantics/BlankActionSplitterListener.java75
-rw-r--r--tool/src/org/antlr/v4/semantics/RuleCollector.java138
-rw-r--r--tool/src/org/antlr/v4/semantics/SemanticPipeline.java300
-rw-r--r--tool/src/org/antlr/v4/semantics/SymbolChecks.java312
-rw-r--r--tool/src/org/antlr/v4/semantics/SymbolCollector.java213
-rw-r--r--tool/src/org/antlr/v4/semantics/UseDefAnalyzer.java119
9 files changed, 2166 insertions, 0 deletions
diff --git a/tool/src/org/antlr/v4/semantics/ActionSniffer.java b/tool/src/org/antlr/v4/semantics/ActionSniffer.java
new file mode 100644
index 0000000..2d48e25
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/ActionSniffer.java
@@ -0,0 +1,113 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.runtime.ANTLRStringStream;
+import org.antlr.runtime.Token;
+import org.antlr.v4.parse.ActionSplitter;
+import org.antlr.v4.tool.Alternative;
+import org.antlr.v4.tool.ErrorManager;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.ActionAST;
+import org.antlr.v4.tool.ast.GrammarAST;
+import org.antlr.v4.tool.ast.TerminalAST;
+
+import java.util.List;
+
+/** Find token and rule refs plus refs to them in actions;
+ * side-effect: update Alternatives
+ */
+public class ActionSniffer extends BlankActionSplitterListener {
+ public Grammar g;
+ public Rule r; // null if action outside of rule
+ public Alternative alt; // null if action outside of alt; could be in rule
+ public ActionAST node;
+ public Token actionToken; // token within action
+ public ErrorManager errMgr;
+
+ public ActionSniffer(Grammar g, Rule r, Alternative alt, ActionAST node, Token actionToken) {
+ this.g = g;
+ this.r = r;
+ this.alt = alt;
+ this.node = node;
+ this.actionToken = actionToken;
+ this.errMgr = g.tool.errMgr;
+ }
+
+ public void examineAction() {
+ //System.out.println("examine "+actionToken);
+ ANTLRStringStream in = new ANTLRStringStream(actionToken.getText());
+ in.setLine(actionToken.getLine());
+ in.setCharPositionInLine(actionToken.getCharPositionInLine());
+ ActionSplitter splitter = new ActionSplitter(in, this);
+ // forces eval, triggers listener methods
+ node.chunks = splitter.getActionTokens();
+ }
+
+ public void processNested(Token actionToken) {
+ ANTLRStringStream in = new ANTLRStringStream(actionToken.getText());
+ in.setLine(actionToken.getLine());
+ in.setCharPositionInLine(actionToken.getCharPositionInLine());
+ ActionSplitter splitter = new ActionSplitter(in, this);
+ // forces eval, triggers listener methods
+ splitter.getActionTokens();
+ }
+
+
+ @Override
+ public void attr(String expr, Token x) { trackRef(x); }
+
+ @Override
+ public void qualifiedAttr(String expr, Token x, Token y) { trackRef(x); }
+
+ @Override
+ public void setAttr(String expr, Token x, Token rhs) {
+ trackRef(x);
+ processNested(rhs);
+ }
+
+ @Override
+ public void setNonLocalAttr(String expr, Token x, Token y, Token rhs) {
+ processNested(rhs);
+ }
+
+ public void trackRef(Token x) {
+ List<TerminalAST> xRefs = alt.tokenRefs.get(x.getText());
+ if ( xRefs!=null ) {
+ alt.tokenRefsInActions.map(x.getText(), node);
+ }
+ List<GrammarAST> rRefs = alt.ruleRefs.get(x.getText());
+ if ( rRefs!=null ) {
+ alt.ruleRefsInActions.map(x.getText(), node);
+ }
+ }
+}
diff --git a/tool/src/org/antlr/v4/semantics/AttributeChecks.java b/tool/src/org/antlr/v4/semantics/AttributeChecks.java
new file mode 100644
index 0000000..f702185
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/AttributeChecks.java
@@ -0,0 +1,259 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.runtime.ANTLRStringStream;
+import org.antlr.runtime.Token;
+import org.antlr.v4.parse.ActionSplitter;
+import org.antlr.v4.parse.ActionSplitterListener;
+import org.antlr.v4.tool.Alternative;
+import org.antlr.v4.tool.ErrorManager;
+import org.antlr.v4.tool.ErrorType;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.LabelElementPair;
+import org.antlr.v4.tool.LabelType;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.ActionAST;
+import org.antlr.v4.tool.ast.GrammarAST;
+
+import java.util.List;
+
+/** Trigger checks for various kinds of attribute expressions.
+ * no side-effects.
+ */
+public class AttributeChecks implements ActionSplitterListener {
+ public Grammar g;
+ public Rule r; // null if action outside of rule
+ public Alternative alt; // null if action outside of alt; could be in rule
+ public ActionAST node;
+ public Token actionToken; // token within action
+ public ErrorManager errMgr;
+
+ public AttributeChecks(Grammar g, Rule r, Alternative alt, ActionAST node, Token actionToken) {
+ this.g = g;
+ this.r = r;
+ this.alt = alt;
+ this.node = node;
+ this.actionToken = actionToken;
+ this.errMgr = g.tool.errMgr;
+ }
+
+ public static void checkAllAttributeExpressions(Grammar g) {
+ for (ActionAST act : g.namedActions.values()) {
+ AttributeChecks checker = new AttributeChecks(g, null, null, act, act.token);
+ checker.examineAction();
+ }
+
+ for (Rule r : g.rules.values()) {
+ for (ActionAST a : r.namedActions.values()) {
+ AttributeChecks checker = new AttributeChecks(g, r, null, a, a.token);
+ checker.examineAction();
+ }
+ for (int i=1; i<=r.numberOfAlts; i++) {
+ Alternative alt = r.alt[i];
+ for (ActionAST a : alt.actions) {
+ AttributeChecks checker =
+ new AttributeChecks(g, r, alt, a, a.token);
+ checker.examineAction();
+ }
+ }
+ for (GrammarAST e : r.exceptions) {
+ ActionAST a = (ActionAST)e.getChild(1);
+ AttributeChecks checker = new AttributeChecks(g, r, null, a, a.token);
+ checker.examineAction();
+ }
+ if ( r.finallyAction!=null ) {
+ AttributeChecks checker =
+ new AttributeChecks(g, r, null, r.finallyAction, r.finallyAction.token);
+ checker.examineAction();
+ }
+ }
+ }
+
+ public void examineAction() {
+ //System.out.println("examine "+actionToken);
+ ANTLRStringStream in = new ANTLRStringStream(actionToken.getText());
+ in.setLine(actionToken.getLine());
+ in.setCharPositionInLine(actionToken.getCharPositionInLine());
+ ActionSplitter splitter = new ActionSplitter(in, this);
+ // forces eval, triggers listener methods
+ node.chunks = splitter.getActionTokens();
+ }
+
+ // LISTENER METHODS
+
+ // $x.y
+ @Override
+ public void qualifiedAttr(String expr, Token x, Token y) {
+ if ( g.isLexer() ) {
+ errMgr.grammarError(ErrorType.ATTRIBUTE_IN_LEXER_ACTION,
+ g.fileName, x, x.getText()+"."+y.getText(), expr);
+ return;
+ }
+ if ( node.resolver.resolveToAttribute(x.getText(), node)!=null ) {
+ // must be a member access to a predefined attribute like $ctx.foo
+ attr(expr, x);
+ return;
+ }
+
+ if ( node.resolver.resolveToAttribute(x.getText(), y.getText(), node)==null ) {
+ Rule rref = isolatedRuleRef(x.getText());
+ if ( rref!=null ) {
+ if ( rref.args!=null && rref.args.get(y.getText())!=null ) {
+ g.tool.errMgr.grammarError(ErrorType.INVALID_RULE_PARAMETER_REF,
+ g.fileName, y, y.getText(), rref.name, expr);
+ }
+ else {
+ errMgr.grammarError(ErrorType.UNKNOWN_RULE_ATTRIBUTE,
+ g.fileName, y, y.getText(), rref.name, expr);
+ }
+ }
+ else if ( !node.resolver.resolvesToAttributeDict(x.getText(), node) ) {
+ errMgr.grammarError(ErrorType.UNKNOWN_SIMPLE_ATTRIBUTE,
+ g.fileName, x, x.getText(), expr);
+ }
+ else {
+ errMgr.grammarError(ErrorType.UNKNOWN_ATTRIBUTE_IN_SCOPE,
+ g.fileName, y, y.getText(), expr);
+ }
+ }
+ }
+
+ @Override
+ public void setAttr(String expr, Token x, Token rhs) {
+ if ( g.isLexer() ) {
+ errMgr.grammarError(ErrorType.ATTRIBUTE_IN_LEXER_ACTION,
+ g.fileName, x, x.getText(), expr);
+ return;
+ }
+ if ( node.resolver.resolveToAttribute(x.getText(), node)==null ) {
+ ErrorType errorType = ErrorType.UNKNOWN_SIMPLE_ATTRIBUTE;
+ if ( node.resolver.resolvesToListLabel(x.getText(), node) ) {
+ // $ids for ids+=ID etc...
+ errorType = ErrorType.ASSIGNMENT_TO_LIST_LABEL;
+ }
+
+ errMgr.grammarError(errorType,
+ g.fileName, x, x.getText(), expr);
+ }
+ new AttributeChecks(g, r, alt, node, rhs).examineAction();
+ }
+
+ @Override
+ public void attr(String expr, Token x) {
+ if ( g.isLexer() ) {
+ errMgr.grammarError(ErrorType.ATTRIBUTE_IN_LEXER_ACTION,
+ g.fileName, x, x.getText(), expr);
+ return;
+ }
+ if ( node.resolver.resolveToAttribute(x.getText(), node)==null ) {
+ if ( node.resolver.resolvesToToken(x.getText(), node) ) {
+ return; // $ID for token ref or label of token
+ }
+ if ( node.resolver.resolvesToListLabel(x.getText(), node) ) {
+ return; // $ids for ids+=ID etc...
+ }
+ if ( isolatedRuleRef(x.getText())!=null ) {
+ errMgr.grammarError(ErrorType.ISOLATED_RULE_REF,
+ g.fileName, x, x.getText(), expr);
+ return;
+ }
+ errMgr.grammarError(ErrorType.UNKNOWN_SIMPLE_ATTRIBUTE,
+ g.fileName, x, x.getText(), expr);
+ }
+ }
+
+ @Override
+ public void nonLocalAttr(String expr, Token x, Token y) {
+ Rule r = g.getRule(x.getText());
+ if ( r==null ) {
+ errMgr.grammarError(ErrorType.UNDEFINED_RULE_IN_NONLOCAL_REF,
+ g.fileName, x, x.getText(), y.getText(), expr);
+ }
+ else if ( r.resolveToAttribute(y.getText(), null)==null ) {
+ errMgr.grammarError(ErrorType.UNKNOWN_RULE_ATTRIBUTE,
+ g.fileName, y, y.getText(), x.getText(), expr);
+
+ }
+ }
+
+ @Override
+ public void setNonLocalAttr(String expr, Token x, Token y, Token rhs) {
+ Rule r = g.getRule(x.getText());
+ if ( r==null ) {
+ errMgr.grammarError(ErrorType.UNDEFINED_RULE_IN_NONLOCAL_REF,
+ g.fileName, x, x.getText(), y.getText(), expr);
+ }
+ else if ( r.resolveToAttribute(y.getText(), null)==null ) {
+ errMgr.grammarError(ErrorType.UNKNOWN_RULE_ATTRIBUTE,
+ g.fileName, y, y.getText(), x.getText(), expr);
+
+ }
+ }
+
+ @Override
+ public void text(String text) { }
+
+ // don't care
+ public void templateInstance(String expr) { }
+ public void indirectTemplateInstance(String expr) { }
+ public void setExprAttribute(String expr) { }
+ public void setSTAttribute(String expr) { }
+ public void templateExpr(String expr) { }
+
+ // SUPPORT
+
+ public Rule isolatedRuleRef(String x) {
+ if ( node.resolver instanceof Grammar ) return null;
+
+ if ( x.equals(r.name) ) return r;
+ List<LabelElementPair> labels = null;
+ if ( node.resolver instanceof Rule ) {
+ labels = r.getElementLabelDefs().get(x);
+ }
+ else if ( node.resolver instanceof Alternative ) {
+ labels = ((Alternative)node.resolver).labelDefs.get(x);
+ }
+ if ( labels!=null ) { // it's a label ref. is it a rule label?
+ LabelElementPair anyLabelDef = labels.get(0);
+ if ( anyLabelDef.type==LabelType.RULE_LABEL ) {
+ return g.getRule(anyLabelDef.element.getText());
+ }
+ }
+ if ( node.resolver instanceof Alternative ) {
+ if ( ((Alternative)node.resolver).ruleRefs.get(x)!=null ) {
+ return g.getRule(x);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/tool/src/org/antlr/v4/semantics/BasicSemanticChecks.java b/tool/src/org/antlr/v4/semantics/BasicSemanticChecks.java
new file mode 100644
index 0000000..a578a66
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/BasicSemanticChecks.java
@@ -0,0 +1,637 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.runtime.Token;
+import org.antlr.runtime.tree.CommonTree;
+import org.antlr.runtime.tree.Tree;
+import org.antlr.v4.misc.Utils;
+import org.antlr.v4.parse.ANTLRParser;
+import org.antlr.v4.parse.GrammarTreeVisitor;
+import org.antlr.v4.tool.ErrorManager;
+import org.antlr.v4.tool.ErrorType;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.ActionAST;
+import org.antlr.v4.tool.ast.AltAST;
+import org.antlr.v4.tool.ast.BlockAST;
+import org.antlr.v4.tool.ast.GrammarAST;
+import org.antlr.v4.tool.ast.GrammarASTWithOptions;
+import org.antlr.v4.tool.ast.GrammarRootAST;
+import org.antlr.v4.tool.ast.RuleAST;
+import org.antlr.v4.tool.ast.RuleRefAST;
+import org.antlr.v4.tool.ast.TerminalAST;
+import org.stringtemplate.v4.misc.MultiMap;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/** No side-effects except for setting options into the appropriate node.
+ * TODO: make the side effects into a separate pass this
+ *
+ * Invokes check rules for these:
+ *
+ * FILE_AND_GRAMMAR_NAME_DIFFER
+ * LEXER_RULES_NOT_ALLOWED
+ * PARSER_RULES_NOT_ALLOWED
+ * CANNOT_ALIAS_TOKENS
+ * ARGS_ON_TOKEN_REF
+ * ILLEGAL_OPTION
+ * REWRITE_OR_OP_WITH_NO_OUTPUT_OPTION
+ * NO_RULES
+ * REWRITE_FOR_MULTI_ELEMENT_ALT
+ * HETERO_ILLEGAL_IN_REWRITE_ALT
+ * AST_OP_WITH_NON_AST_OUTPUT_OPTION
+ * AST_OP_IN_ALT_WITH_REWRITE
+ * CONFLICTING_OPTION_IN_TREE_FILTER
+ * WILDCARD_AS_ROOT
+ * INVALID_IMPORT
+ * TOKEN_VOCAB_IN_DELEGATE
+ * IMPORT_NAME_CLASH
+ * REPEATED_PREQUEL
+ * TOKEN_NAMES_MUST_START_UPPER
+ */
+public class BasicSemanticChecks extends GrammarTreeVisitor {
+ /** Set of valid imports. Maps delegate to set of delegator grammar types.
+ * validDelegations.get(LEXER) gives list of the kinds of delegators
+ * that can import lexers.
+ */
+ public static MultiMap<Integer,Integer> validImportTypes =
+ new MultiMap<Integer,Integer>() {
+ {
+ map(ANTLRParser.LEXER, ANTLRParser.LEXER);
+ map(ANTLRParser.LEXER, ANTLRParser.COMBINED);
+
+ map(ANTLRParser.PARSER, ANTLRParser.PARSER);
+ map(ANTLRParser.PARSER, ANTLRParser.COMBINED);
+
+ map(ANTLRParser.COMBINED, ANTLRParser.COMBINED);
+ }
+ };
+
+ public Grammar g;
+ public RuleCollector ruleCollector;
+ public ErrorManager errMgr;
+
+ /**
+ * When this is {@code true}, the semantic checks will report
+ * {@link ErrorType#UNRECOGNIZED_ASSOC_OPTION} where appropriate. This may
+ * be set to {@code false} to disable this specific check.
+ *
+ * <p>The default value is {@code true}.</p>
+ */
+ public boolean checkAssocElementOption = true;
+
+ /**
+ * This field is used for reporting the {@link ErrorType#MODE_WITHOUT_RULES}
+ * error when necessary.
+ */
+ protected int nonFragmentRuleCount;
+
+ /**
+ * This is {@code true} from the time {@link #discoverLexerRule} is called
+ * for a lexer rule with the {@code fragment} modifier until
+ * {@link #exitLexerRule} is called.
+ */
+ private boolean inFragmentRule;
+
+ public BasicSemanticChecks(Grammar g, RuleCollector ruleCollector) {
+ this.g = g;
+ this.ruleCollector = ruleCollector;
+ this.errMgr = g.tool.errMgr;
+ }
+
+ @Override
+ public ErrorManager getErrorManager() { return errMgr; }
+
+ public void process() { visitGrammar(g.ast); }
+
+ // Routines to route visitor traffic to the checking routines
+
+ @Override
+ public void discoverGrammar(GrammarRootAST root, GrammarAST ID) {
+ checkGrammarName(ID.token);
+ }
+
+ @Override
+ public void finishPrequels(GrammarAST firstPrequel) {
+ if ( firstPrequel==null ) return;
+ GrammarAST parent = (GrammarAST)firstPrequel.parent;
+ List<GrammarAST> options = parent.getAllChildrenWithType(OPTIONS);
+ List<GrammarAST> imports = parent.getAllChildrenWithType(IMPORT);
+ List<GrammarAST> tokens = parent.getAllChildrenWithType(TOKENS_SPEC);
+ checkNumPrequels(options, imports, tokens);
+ }
+
+ @Override
+ public void importGrammar(GrammarAST label, GrammarAST ID) {
+ checkImport(ID.token);
+ }
+
+ @Override
+ public void discoverRules(GrammarAST rules) {
+ checkNumRules(rules);
+ }
+
+ @Override
+ protected void enterMode(GrammarAST tree) {
+ nonFragmentRuleCount = 0;
+ }
+
+ @Override
+ protected void exitMode(GrammarAST tree) {
+ if (nonFragmentRuleCount == 0) {
+ Token token = tree.getToken();
+ String name = "?";
+ if (tree.getChildCount() > 0) {
+ name = tree.getChild(0).getText();
+ if (name == null || name.isEmpty()) {
+ name = "?";
+ }
+
+ token = ((GrammarAST)tree.getChild(0)).getToken();
+ }
+
+ g.tool.errMgr.grammarError(ErrorType.MODE_WITHOUT_RULES, g.fileName, token, name, g);
+ }
+ }
+
+ @Override
+ public void modeDef(GrammarAST m, GrammarAST ID) {
+ if ( !g.isLexer() ) {
+ g.tool.errMgr.grammarError(ErrorType.MODE_NOT_IN_LEXER, g.fileName,
+ ID.token, ID.token.getText(), g);
+ }
+ }
+
+ @Override
+ public void discoverRule(RuleAST rule, GrammarAST ID,
+ List<GrammarAST> modifiers,
+ ActionAST arg, ActionAST returns,
+ GrammarAST thrws, GrammarAST options,
+ ActionAST locals,
+ List<GrammarAST> actions, GrammarAST block)
+ {
+ // TODO: chk that all or no alts have "# label"
+ checkInvalidRuleDef(ID.token);
+ }
+
+ @Override
+ public void discoverLexerRule(RuleAST rule, GrammarAST ID, List<GrammarAST> modifiers,
+ GrammarAST block)
+ {
+ checkInvalidRuleDef(ID.token);
+
+ if (modifiers != null) {
+ for (GrammarAST tree : modifiers) {
+ if (tree.getType() == ANTLRParser.FRAGMENT) {
+ inFragmentRule = true;
+ }
+ }
+ }
+
+ if (!inFragmentRule) {
+ nonFragmentRuleCount++;
+ }
+ }
+
+ @Override
+ protected void exitLexerRule(GrammarAST tree) {
+ inFragmentRule = false;
+ }
+
+ @Override
+ public void ruleRef(GrammarAST ref, ActionAST arg) {
+ checkInvalidRuleRef(ref.token);
+ }
+
+ @Override
+ public void ruleOption(GrammarAST ID, GrammarAST valueAST) {
+ checkOptions((GrammarAST)ID.getAncestor(RULE), ID.token, valueAST);
+ }
+
+ @Override
+ public void blockOption(GrammarAST ID, GrammarAST valueAST) {
+ checkOptions((GrammarAST)ID.getAncestor(BLOCK), ID.token, valueAST);
+ }
+
+ @Override
+ public void grammarOption(GrammarAST ID, GrammarAST valueAST) {
+ boolean ok = checkOptions(g.ast, ID.token, valueAST);
+ //if ( ok ) g.ast.setOption(ID.getText(), value);
+ }
+
+ @Override
+ public void defineToken(GrammarAST ID) {
+ checkTokenDefinition(ID.token);
+ }
+
+ @Override
+ protected void enterChannelsSpec(GrammarAST tree) {
+ if (g.isParser()) {
+ g.tool.errMgr.grammarError(ErrorType.CHANNELS_BLOCK_IN_PARSER_GRAMMAR, g.fileName, tree.token);
+ }
+ else if (g.isCombined()) {
+ g.tool.errMgr.grammarError(ErrorType.CHANNELS_BLOCK_IN_COMBINED_GRAMMAR, g.fileName, tree.token);
+ }
+ }
+
+ @Override
+ public void defineChannel(GrammarAST ID) {
+ checkChannelDefinition(ID.token);
+ }
+
+ @Override
+ public void elementOption(GrammarASTWithOptions elem, GrammarAST ID, GrammarAST valueAST) {
+ String v = null;
+ boolean ok = checkElementOptions(elem, ID, valueAST);
+// if ( ok ) {
+// if ( v!=null ) {
+// t.setOption(ID.getText(), v);
+// }
+// else {
+// t.setOption(TerminalAST.defaultTokenOption, v);
+// }
+// }
+ }
+
+ @Override
+ public void finishRule(RuleAST rule, GrammarAST ID, GrammarAST block) {
+ if ( rule.isLexerRule() ) return;
+ BlockAST blk = (BlockAST)rule.getFirstChildWithType(BLOCK);
+ int nalts = blk.getChildCount();
+ GrammarAST idAST = (GrammarAST)rule.getChild(0);
+ for (int i=0; i< nalts; i++) {
+ AltAST altAST = (AltAST)blk.getChild(i);
+ if ( altAST.altLabel!=null ) {
+ String altLabel = altAST.altLabel.getText();
+ // first check that label doesn't conflict with a rule
+ // label X or x can't be rule x.
+ Rule r = ruleCollector.rules.get(Utils.decapitalize(altLabel));
+ if ( r!=null ) {
+ g.tool.errMgr.grammarError(ErrorType.ALT_LABEL_CONFLICTS_WITH_RULE,
+ g.fileName, altAST.altLabel.token,
+ altLabel,
+ r.name);
+ }
+ // Now verify that label X or x doesn't conflict with label
+ // in another rule. altLabelToRuleName has both X and x mapped.
+ String prevRuleForLabel = ruleCollector.altLabelToRuleName.get(altLabel);
+ if ( prevRuleForLabel!=null && !prevRuleForLabel.equals(rule.getRuleName()) ) {
+ g.tool.errMgr.grammarError(ErrorType.ALT_LABEL_REDEF,
+ g.fileName, altAST.altLabel.token,
+ altLabel,
+ rule.getRuleName(),
+ prevRuleForLabel);
+ }
+ }
+ }
+ List<GrammarAST> altLabels = ruleCollector.ruleToAltLabels.get(rule.getRuleName());
+ int numAltLabels = 0;
+ if ( altLabels!=null ) numAltLabels = altLabels.size();
+ if ( numAltLabels>0 && nalts != numAltLabels ) {
+ g.tool.errMgr.grammarError(ErrorType.RULE_WITH_TOO_FEW_ALT_LABELS,
+ g.fileName, idAST.token, rule.getRuleName());
+ }
+ }
+
+ // Routines to do the actual work of checking issues with a grammar.
+ // They are triggered by the visitor methods above.
+
+ void checkGrammarName(Token nameToken) {
+ String fullyQualifiedName = nameToken.getInputStream().getSourceName();
+ if (fullyQualifiedName == null) {
+ // This wasn't read from a file.
+ return;
+ }
+
+ File f = new File(fullyQualifiedName);
+ String fileName = f.getName();
+ if ( g.originalGrammar!=null ) return; // don't warn about diff if this is implicit lexer
+ if ( !Utils.stripFileExtension(fileName).equals(nameToken.getText()) &&
+ !fileName.equals(Grammar.GRAMMAR_FROM_STRING_NAME)) {
+ g.tool.errMgr.grammarError(ErrorType.FILE_AND_GRAMMAR_NAME_DIFFER,
+ fileName, nameToken, nameToken.getText(), fileName);
+ }
+ }
+
+ void checkNumRules(GrammarAST rulesNode) {
+ if ( rulesNode.getChildCount()==0 ) {
+ GrammarAST root = (GrammarAST)rulesNode.getParent();
+ GrammarAST IDNode = (GrammarAST)root.getChild(0);
+ g.tool.errMgr.grammarError(ErrorType.NO_RULES, g.fileName,
+ null, IDNode.getText(), g);
+ }
+ }
+
+ void checkNumPrequels(List<GrammarAST> options,
+ List<GrammarAST> imports,
+ List<GrammarAST> tokens)
+ {
+ List<Token> secondOptionTokens = new ArrayList<Token>();
+ if ( options!=null && options.size()>1 ) {
+ secondOptionTokens.add(options.get(1).token);
+ }
+ if ( imports!=null && imports.size()>1 ) {
+ secondOptionTokens.add(imports.get(1).token);
+ }
+ if ( tokens!=null && tokens.size()>1 ) {
+ secondOptionTokens.add(tokens.get(1).token);
+ }
+ for (Token t : secondOptionTokens) {
+ String fileName = t.getInputStream().getSourceName();
+ g.tool.errMgr.grammarError(ErrorType.REPEATED_PREQUEL,
+ fileName, t);
+ }
+ }
+
+ void checkInvalidRuleDef(Token ruleID) {
+ String fileName = null;
+ if ( ruleID.getInputStream()!=null ) {
+ fileName = ruleID.getInputStream().getSourceName();
+ }
+ if ( g.isLexer() && Character.isLowerCase(ruleID.getText().charAt(0)) ) {
+ g.tool.errMgr.grammarError(ErrorType.PARSER_RULES_NOT_ALLOWED,
+ fileName, ruleID, ruleID.getText());
+ }
+ if ( g.isParser() &&
+ Grammar.isTokenName(ruleID.getText()) )
+ {
+ g.tool.errMgr.grammarError(ErrorType.LEXER_RULES_NOT_ALLOWED,
+ fileName, ruleID, ruleID.getText());
+ }
+ }
+
+ void checkInvalidRuleRef(Token ruleID) {
+ String fileName = ruleID.getInputStream().getSourceName();
+ if ( g.isLexer() && Character.isLowerCase(ruleID.getText().charAt(0)) ) {
+ g.tool.errMgr.grammarError(ErrorType.PARSER_RULE_REF_IN_LEXER_RULE,
+ fileName, ruleID, ruleID.getText(), currentRuleName);
+ }
+ }
+
+ void checkTokenDefinition(Token tokenID) {
+ String fileName = tokenID.getInputStream().getSourceName();
+ if ( !Grammar.isTokenName(tokenID.getText()) ) {
+ g.tool.errMgr.grammarError(ErrorType.TOKEN_NAMES_MUST_START_UPPER,
+ fileName,
+ tokenID,
+ tokenID.getText());
+ }
+ }
+
+ void checkChannelDefinition(Token tokenID) {
+ }
+
+ @Override
+ protected void enterLexerElement(GrammarAST tree) {
+ }
+
+ @Override
+ protected void enterLexerCommand(GrammarAST tree) {
+ checkElementIsOuterMostInSingleAlt(tree);
+
+ if (inFragmentRule) {
+ String fileName = tree.token.getInputStream().getSourceName();
+ String ruleName = currentRuleName;
+ g.tool.errMgr.grammarError(ErrorType.FRAGMENT_ACTION_IGNORED, fileName, tree.token, ruleName);
+ }
+ }
+
+ @Override
+ public void actionInAlt(ActionAST action) {
+ if (inFragmentRule) {
+ String fileName = action.token.getInputStream().getSourceName();
+ String ruleName = currentRuleName;
+ g.tool.errMgr.grammarError(ErrorType.FRAGMENT_ACTION_IGNORED, fileName, action.token, ruleName);
+ }
+ }
+
+ /**
+ Make sure that action is last element in outer alt; here action,
+ a2, z, and zz are bad, but a3 is ok:
+ (RULE A (BLOCK (ALT {action} 'a')))
+ (RULE B (BLOCK (ALT (BLOCK (ALT {a2} 'x') (ALT 'y')) {a3})))
+ (RULE C (BLOCK (ALT 'd' {z}) (ALT 'e' {zz})))
+ */
+ protected void checkElementIsOuterMostInSingleAlt(GrammarAST tree) {
+ CommonTree alt = tree.parent;
+ CommonTree blk = alt.parent;
+ boolean outerMostAlt = blk.parent.getType() == RULE;
+ Tree rule = tree.getAncestor(RULE);
+ String fileName = tree.getToken().getInputStream().getSourceName();
+ if ( !outerMostAlt || blk.getChildCount()>1 )
+ {
+ ErrorType e = ErrorType.LEXER_COMMAND_PLACEMENT_ISSUE;
+ g.tool.errMgr.grammarError(e,
+ fileName,
+ tree.getToken(),
+ rule.getChild(0).getText());
+
+ }
+ }
+
+ @Override
+ public void label(GrammarAST op, GrammarAST ID, GrammarAST element) {
+ switch (element.getType()) {
+ // token atoms
+ case TOKEN_REF:
+ case STRING_LITERAL:
+ case RANGE:
+ // token sets
+ case SET:
+ case NOT:
+ // rule atoms
+ case RULE_REF:
+ case WILDCARD:
+ return;
+
+ default:
+ String fileName = ID.token.getInputStream().getSourceName();
+ g.tool.errMgr.grammarError(ErrorType.LABEL_BLOCK_NOT_A_SET, fileName, ID.token, ID.getText());
+ break;
+ }
+ }
+
+ @Override
+ protected void enterLabeledLexerElement(GrammarAST tree) {
+ Token label = ((GrammarAST)tree.getChild(0)).getToken();
+ g.tool.errMgr.grammarError(ErrorType.V3_LEXER_LABEL,
+ g.fileName,
+ label,
+ label.getText());
+ }
+
+ /** Check option is appropriate for grammar, rule, subrule */
+ boolean checkOptions(GrammarAST parent,
+ Token optionID,
+ GrammarAST valueAST)
+ {
+ boolean ok = true;
+ if ( parent.getType()==ANTLRParser.BLOCK ) {
+ if ( g.isLexer() && !Grammar.LexerBlockOptions.contains(optionID.getText()) ) { // block
+ g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
+ g.fileName,
+ optionID,
+ optionID.getText());
+ ok = false;
+ }
+ if ( !g.isLexer() && !Grammar.ParserBlockOptions.contains(optionID.getText()) ) { // block
+ g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
+ g.fileName,
+ optionID,
+ optionID.getText());
+ ok = false;
+ }
+ }
+ else if ( parent.getType()==ANTLRParser.RULE ) {
+ if ( !Grammar.ruleOptions.contains(optionID.getText()) ) { // rule
+ g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
+ g.fileName,
+ optionID,
+ optionID.getText());
+ ok = false;
+ }
+ }
+ else if ( parent.getType()==ANTLRParser.GRAMMAR &&
+ !legalGrammarOption(optionID.getText()) ) { // grammar
+ g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
+ g.fileName,
+ optionID,
+ optionID.getText());
+ ok = false;
+ }
+
+ return ok;
+ }
+
+ /** Check option is appropriate for elem; parent of ID is ELEMENT_OPTIONS */
+ boolean checkElementOptions(GrammarASTWithOptions elem,
+ GrammarAST ID,
+ GrammarAST valueAST)
+ {
+ if (checkAssocElementOption && ID != null && "assoc".equals(ID.getText())) {
+ if (elem.getType() != ANTLRParser.ALT) {
+ Token optionID = ID.token;
+ String fileName = optionID.getInputStream().getSourceName();
+ g.tool.errMgr.grammarError(ErrorType.UNRECOGNIZED_ASSOC_OPTION,
+ fileName,
+ optionID,
+ currentRuleName);
+ }
+ }
+
+ if ( elem instanceof RuleRefAST ) {
+ return checkRuleRefOptions((RuleRefAST)elem, ID, valueAST);
+ }
+ if ( elem instanceof TerminalAST ) {
+ return checkTokenOptions((TerminalAST)elem, ID, valueAST);
+ }
+ if ( elem.getType()==ANTLRParser.ACTION ) {
+ return false;
+ }
+ if ( elem.getType()==ANTLRParser.SEMPRED ) {
+ Token optionID = ID.token;
+ String fileName = optionID.getInputStream().getSourceName();
+ if ( valueAST!=null && !Grammar.semPredOptions.contains(optionID.getText()) ) {
+ g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
+ fileName,
+ optionID,
+ optionID.getText());
+ return false;
+ }
+ }
+ return false;
+ }
+
+ boolean checkRuleRefOptions(RuleRefAST elem, GrammarAST ID, GrammarAST valueAST) {
+ Token optionID = ID.token;
+ String fileName = optionID.getInputStream().getSourceName();
+ // don't care about id<SimpleValue> options
+ if ( valueAST!=null && !Grammar.ruleRefOptions.contains(optionID.getText()) ) {
+ g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
+ fileName,
+ optionID,
+ optionID.getText());
+ return false;
+ }
+ // TODO: extra checks depending on rule kind?
+ return true;
+ }
+
+ boolean checkTokenOptions(TerminalAST elem, GrammarAST ID, GrammarAST valueAST) {
+ Token optionID = ID.token;
+ String fileName = optionID.getInputStream().getSourceName();
+ // don't care about ID<ASTNodeName> options
+ if ( valueAST!=null && !Grammar.tokenOptions.contains(optionID.getText()) ) {
+ g.tool.errMgr.grammarError(ErrorType.ILLEGAL_OPTION,
+ fileName,
+ optionID,
+ optionID.getText());
+ return false;
+ }
+ // TODO: extra checks depending on terminal kind?
+ return true;
+ }
+
+ boolean legalGrammarOption(String key) {
+ switch ( g.getType() ) {
+ case ANTLRParser.LEXER :
+ return Grammar.lexerOptions.contains(key);
+ case ANTLRParser.PARSER :
+ return Grammar.parserOptions.contains(key);
+ default :
+ return Grammar.parserOptions.contains(key);
+ }
+ }
+
+ void checkImport(Token importID) {
+ Grammar delegate = g.getImportedGrammar(importID.getText());
+ if ( delegate==null ) return;
+ List<Integer> validDelegators = validImportTypes.get(delegate.getType());
+ if ( validDelegators!=null && !validDelegators.contains(g.getType()) ) {
+ g.tool.errMgr.grammarError(ErrorType.INVALID_IMPORT,
+ g.fileName,
+ importID,
+ g, delegate);
+ }
+ if ( g.isCombined() &&
+ (delegate.name.equals(g.name+Grammar.getGrammarTypeToFileNameSuffix(ANTLRParser.LEXER))||
+ delegate.name.equals(g.name+Grammar.getGrammarTypeToFileNameSuffix(ANTLRParser.PARSER))) )
+ {
+ g.tool.errMgr.grammarError(ErrorType.IMPORT_NAME_CLASH,
+ g.fileName,
+ importID,
+ g, delegate);
+ }
+ }
+}
diff --git a/tool/src/org/antlr/v4/semantics/BlankActionSplitterListener.java b/tool/src/org/antlr/v4/semantics/BlankActionSplitterListener.java
new file mode 100644
index 0000000..e919890
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/BlankActionSplitterListener.java
@@ -0,0 +1,75 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.runtime.Token;
+import org.antlr.v4.parse.ActionSplitterListener;
+
+public class BlankActionSplitterListener implements ActionSplitterListener {
+ @Override
+ public void qualifiedAttr(String expr, Token x, Token y) {
+ }
+
+ @Override
+ public void setAttr(String expr, Token x, Token rhs) {
+ }
+
+ @Override
+ public void attr(String expr, Token x) {
+ }
+
+ public void templateInstance(String expr) {
+ }
+
+ @Override
+ public void nonLocalAttr(String expr, Token x, Token y) {
+ }
+
+ @Override
+ public void setNonLocalAttr(String expr, Token x, Token y, Token rhs) {
+ }
+
+ public void indirectTemplateInstance(String expr) {
+ }
+
+ public void setExprAttribute(String expr) {
+ }
+
+ public void setSTAttribute(String expr) {
+ }
+
+ public void templateExpr(String expr) {
+ }
+
+ @Override
+ public void text(String text) {
+ }
+}
diff --git a/tool/src/org/antlr/v4/semantics/RuleCollector.java b/tool/src/org/antlr/v4/semantics/RuleCollector.java
new file mode 100644
index 0000000..fa7eb2d
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/RuleCollector.java
@@ -0,0 +1,138 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.v4.analysis.LeftRecursiveRuleAnalyzer;
+import org.antlr.v4.misc.OrderedHashMap;
+import org.antlr.v4.misc.Utils;
+import org.antlr.v4.parse.GrammarTreeVisitor;
+import org.antlr.v4.parse.ScopeParser;
+import org.antlr.v4.tool.AttributeDict;
+import org.antlr.v4.tool.ErrorManager;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.LeftRecursiveRule;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.ActionAST;
+import org.antlr.v4.tool.ast.AltAST;
+import org.antlr.v4.tool.ast.GrammarAST;
+import org.antlr.v4.tool.ast.RuleAST;
+import org.stringtemplate.v4.misc.MultiMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RuleCollector extends GrammarTreeVisitor {
+ /** which grammar are we checking */
+ public Grammar g;
+ public ErrorManager errMgr;
+
+ // stuff to collect. this is the output
+ public OrderedHashMap<String, Rule> rules = new OrderedHashMap<String, Rule>();
+ public MultiMap<String,GrammarAST> ruleToAltLabels = new MultiMap<String, GrammarAST>();
+ public Map<String,String> altLabelToRuleName = new HashMap<String, String>();
+
+ public RuleCollector(Grammar g) {
+ this.g = g;
+ this.errMgr = g.tool.errMgr;
+ }
+
+ @Override
+ public ErrorManager getErrorManager() { return errMgr; }
+
+ public void process(GrammarAST ast) { visitGrammar(ast); }
+
+ @Override
+ public void discoverRule(RuleAST rule, GrammarAST ID,
+ List<GrammarAST> modifiers, ActionAST arg,
+ ActionAST returns, GrammarAST thrws,
+ GrammarAST options, ActionAST locals,
+ List<GrammarAST> actions,
+ GrammarAST block)
+ {
+ int numAlts = block.getChildCount();
+ Rule r;
+ if ( LeftRecursiveRuleAnalyzer.hasImmediateRecursiveRuleRefs(rule, ID.getText()) ) {
+ r = new LeftRecursiveRule(g, ID.getText(), rule);
+ }
+ else {
+ r = new Rule(g, ID.getText(), rule, numAlts);
+ }
+ rules.put(r.name, r);
+
+ if ( arg!=null ) {
+ r.args = ScopeParser.parseTypedArgList(arg, arg.getText(), g);
+ r.args.type = AttributeDict.DictType.ARG;
+ r.args.ast = arg;
+ arg.resolver = r.alt[currentOuterAltNumber];
+ }
+
+ if ( returns!=null ) {
+ r.retvals = ScopeParser.parseTypedArgList(returns, returns.getText(), g);
+ r.retvals.type = AttributeDict.DictType.RET;
+ r.retvals.ast = returns;
+ }
+
+ if ( locals!=null ) {
+ r.locals = ScopeParser.parseTypedArgList(locals, locals.getText(), g);
+ r.locals.type = AttributeDict.DictType.LOCAL;
+ r.locals.ast = locals;
+ }
+
+ for (GrammarAST a : actions) {
+ // a = ^(AT ID ACTION)
+ ActionAST action = (ActionAST) a.getChild(1);
+ r.namedActions.put(a.getChild(0).getText(), action);
+ action.resolver = r;
+ }
+ }
+
+ @Override
+ public void discoverOuterAlt(AltAST alt) {
+ if ( alt.altLabel!=null ) {
+ ruleToAltLabels.map(currentRuleName, alt.altLabel);
+ String altLabel = alt.altLabel.getText();
+ altLabelToRuleName.put(Utils.capitalize(altLabel), currentRuleName);
+ altLabelToRuleName.put(Utils.decapitalize(altLabel), currentRuleName);
+ }
+ }
+
+ @Override
+ public void discoverLexerRule(RuleAST rule, GrammarAST ID, List<GrammarAST> modifiers,
+ GrammarAST block)
+ {
+ int numAlts = block.getChildCount();
+ Rule r = new Rule(g, ID.getText(), rule, numAlts);
+ r.mode = currentModeName;
+ if ( !modifiers.isEmpty() ) r.modifiers = modifiers;
+ rules.put(r.name, r);
+ }
+}
diff --git a/tool/src/org/antlr/v4/semantics/SemanticPipeline.java b/tool/src/org/antlr/v4/semantics/SemanticPipeline.java
new file mode 100644
index 0000000..3561f91
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/SemanticPipeline.java
@@ -0,0 +1,300 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.v4.analysis.LeftRecursiveRuleTransformer;
+import org.antlr.v4.parse.ANTLRParser;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.Pair;
+import org.antlr.v4.tool.ErrorType;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.LexerGrammar;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.GrammarAST;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Do as much semantic checking as we can and fill in grammar
+ * with rules, actions, and token definitions.
+ * The only side effects are in the grammar passed to process().
+ * We consume a bunch of memory here while we build up data structures
+ * to perform checking, but all of it goes away after this pipeline object
+ * gets garbage collected.
+ *
+ * After this pipeline finishes, we can be sure that the grammar
+ * is syntactically correct and that it's semantically correct enough for us
+ * to attempt grammar analysis. We have assigned all token types.
+ * Note that imported grammars bring in token and rule definitions
+ * but only the root grammar and any implicitly created lexer grammar
+ * get their token definitions filled up. We are treating the
+ * imported grammars like includes.
+ *
+ * The semantic pipeline works on root grammars (those that do the importing,
+ * if any). Upon entry to the semantic pipeline, all imported grammars
+ * should have been loaded into delegate grammar objects with their
+ * ASTs created. The pipeline does the BasicSemanticChecks on the
+ * imported grammar before collecting symbols. We cannot perform the
+ * simple checks such as undefined rule until we have collected all
+ * tokens and rules from the imported grammars into a single collection.
+ */
+public class SemanticPipeline {
+ public Grammar g;
+
+ public SemanticPipeline(Grammar g) {
+ this.g = g;
+ }
+
+ public void process() {
+ if ( g.ast==null ) return;
+
+ // COLLECT RULE OBJECTS
+ RuleCollector ruleCollector = new RuleCollector(g);
+ ruleCollector.process(g.ast);
+
+ // DO BASIC / EASY SEMANTIC CHECKS
+ BasicSemanticChecks basics = new BasicSemanticChecks(g, ruleCollector);
+ basics.process();
+
+ // TRANSFORM LEFT-RECURSIVE RULES
+ int prevErrors = g.tool.errMgr.getNumErrors();
+ LeftRecursiveRuleTransformer lrtrans =
+ new LeftRecursiveRuleTransformer(g.ast, ruleCollector.rules.values(), g);
+ lrtrans.translateLeftRecursiveRules();
+
+ // don't continue if we got errors during left-recursion elimination
+ if ( g.tool.errMgr.getNumErrors()>prevErrors ) return;
+
+ // STORE RULES IN GRAMMAR
+ for (Rule r : ruleCollector.rules.values()) {
+ g.defineRule(r);
+ }
+
+ // COLLECT SYMBOLS: RULES, ACTIONS, TERMINALS, ...
+ SymbolCollector collector = new SymbolCollector(g);
+ collector.process(g.ast);
+
+ // CHECK FOR SYMBOL COLLISIONS
+ SymbolChecks symcheck = new SymbolChecks(g, collector);
+ symcheck.process(); // side-effect: strip away redef'd rules.
+
+ for (GrammarAST a : collector.namedActions) {
+ g.defineAction(a);
+ }
+
+ // LINK (outermost) ALT NODES WITH Alternatives
+ for (Rule r : g.rules.values()) {
+ for (int i=1; i<=r.numberOfAlts; i++) {
+ r.alt[i].ast.alt = r.alt[i];
+ }
+ }
+
+ // ASSIGN TOKEN TYPES
+ g.importTokensFromTokensFile();
+ if ( g.isLexer() ) {
+ assignLexerTokenTypes(g, collector.tokensDefs);
+ }
+ else {
+ assignTokenTypes(g, collector.tokensDefs,
+ collector.tokenIDRefs, collector.terminals);
+ }
+
+ assignChannelTypes(g, collector.channelDefs);
+
+ // CHECK RULE REFS NOW (that we've defined rules in grammar)
+ symcheck.checkRuleArgs(g, collector.rulerefs);
+ identifyStartRules(collector);
+ symcheck.checkForQualifiedRuleIssues(g, collector.qualifiedRulerefs);
+
+ // don't continue if we got symbol errors
+ if ( g.tool.getNumErrors()>0 ) return;
+
+ // CHECK ATTRIBUTE EXPRESSIONS FOR SEMANTIC VALIDITY
+ AttributeChecks.checkAllAttributeExpressions(g);
+
+ UseDefAnalyzer.trackTokenRuleRefsInActions(g);
+ }
+
+ void identifyStartRules(SymbolCollector collector) {
+ for (GrammarAST ref : collector.rulerefs) {
+ String ruleName = ref.getText();
+ Rule r = g.getRule(ruleName);
+ if ( r!=null ) r.isStartRule = false;
+ }
+ }
+
+ void assignLexerTokenTypes(Grammar g, List<GrammarAST> tokensDefs) {
+ Grammar G = g.getOutermostGrammar(); // put in root, even if imported
+ for (GrammarAST def : tokensDefs) {
+ // tokens { id (',' id)* } so must check IDs not TOKEN_REF
+ if ( Grammar.isTokenName(def.getText()) ) {
+ G.defineTokenName(def.getText());
+ }
+ }
+
+ /* Define token types for nonfragment rules which do not include a 'type(...)'
+ * or 'more' lexer command.
+ */
+ for (Rule r : g.rules.values()) {
+ if ( !r.isFragment() && !hasTypeOrMoreCommand(r) ) {
+ G.defineTokenName(r.name);
+ }
+ }
+
+ // FOR ALL X : 'xxx'; RULES, DEFINE 'xxx' AS TYPE X
+ List<Pair<GrammarAST,GrammarAST>> litAliases =
+ Grammar.getStringLiteralAliasesFromLexerRules(g.ast);
+ Set<String> conflictingLiterals = new HashSet<String>();
+ if ( litAliases!=null ) {
+ for (Pair<GrammarAST,GrammarAST> pair : litAliases) {
+ GrammarAST nameAST = pair.a;
+ GrammarAST litAST = pair.b;
+ if ( !G.stringLiteralToTypeMap.containsKey(litAST.getText()) ) {
+ G.defineTokenAlias(nameAST.getText(), litAST.getText());
+ }
+ else {
+ // oops two literal defs in two rules (within or across modes).
+ conflictingLiterals.add(litAST.getText());
+ }
+ }
+ for (String lit : conflictingLiterals) {
+ // Remove literal if repeated across rules so it's not
+ // found by parser grammar.
+ Integer value = G.stringLiteralToTypeMap.remove(lit);
+ if (value != null && value > 0 && value < G.typeToStringLiteralList.size() && lit.equals(G.typeToStringLiteralList.get(value))) {
+ G.typeToStringLiteralList.set(value, null);
+ }
+ }
+ }
+
+ }
+
+ boolean hasTypeOrMoreCommand(Rule r) {
+ GrammarAST ast = r.ast;
+ if (ast == null) {
+ return false;
+ }
+
+ GrammarAST altActionAst = (GrammarAST)ast.getFirstDescendantWithType(ANTLRParser.LEXER_ALT_ACTION);
+ if (altActionAst == null) {
+ // the rule isn't followed by any commands
+ return false;
+ }
+
+ // first child is the alt itself, subsequent are the actions
+ for (int i = 1; i < altActionAst.getChildCount(); i++) {
+ GrammarAST node = (GrammarAST)altActionAst.getChild(i);
+ if (node.getType() == ANTLRParser.LEXER_ACTION_CALL) {
+ if ("type".equals(node.getChild(0).getText())) {
+ return true;
+ }
+ }
+ else if ("more".equals(node.getText())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void assignTokenTypes(Grammar g, List<GrammarAST> tokensDefs,
+ List<GrammarAST> tokenIDs, List<GrammarAST> terminals)
+ {
+ //Grammar G = g.getOutermostGrammar(); // put in root, even if imported
+
+ // create token types for tokens { A, B, C } ALIASES
+ for (GrammarAST alias : tokensDefs) {
+ if (g.getTokenType(alias.getText()) != Token.INVALID_TYPE) {
+ g.tool.errMgr.grammarError(ErrorType.TOKEN_NAME_REASSIGNMENT, g.fileName, alias.token, alias.getText());
+ }
+
+ g.defineTokenName(alias.getText());
+ }
+
+ // DEFINE TOKEN TYPES FOR TOKEN REFS LIKE ID, INT
+ for (GrammarAST idAST : tokenIDs) {
+ if (g.getTokenType(idAST.getText()) == Token.INVALID_TYPE) {
+ g.tool.errMgr.grammarError(ErrorType.IMPLICIT_TOKEN_DEFINITION, g.fileName, idAST.token, idAST.getText());
+ }
+
+ g.defineTokenName(idAST.getText());
+ }
+
+ // VERIFY TOKEN TYPES FOR STRING LITERAL REFS LIKE 'while', ';'
+ for (GrammarAST termAST : terminals) {
+ if (termAST.getType() != ANTLRParser.STRING_LITERAL) {
+ continue;
+ }
+
+ if (g.getTokenType(termAST.getText()) == Token.INVALID_TYPE) {
+ g.tool.errMgr.grammarError(ErrorType.IMPLICIT_STRING_DEFINITION, g.fileName, termAST.token, termAST.getText());
+ }
+ }
+
+ g.tool.log("semantics", "tokens="+g.tokenNameToTypeMap);
+ g.tool.log("semantics", "strings="+g.stringLiteralToTypeMap);
+ }
+
+ /**
+ * Assign constant values to custom channels defined in a grammar.
+ *
+ * @param g The grammar.
+ * @param channelDefs A collection of AST nodes defining individual channels
+ * within a {@code channels{}} block in the grammar.
+ */
+ void assignChannelTypes(Grammar g, List<GrammarAST> channelDefs) {
+ Grammar outermost = g.getOutermostGrammar();
+ for (GrammarAST channel : channelDefs) {
+ String channelName = channel.getText();
+
+ // Channel names can't alias tokens or modes, because constant
+ // values are also assigned to them and the ->channel(NAME) lexer
+ // command does not distinguish between the various ways a constant
+ // can be declared. This method does not verify that channels do not
+ // alias rules, because rule names are not associated with constant
+ // values in ANTLR grammar semantics.
+
+ if (g.getTokenType(channelName) != Token.INVALID_TYPE) {
+ g.tool.errMgr.grammarError(ErrorType.CHANNEL_CONFLICTS_WITH_TOKEN, g.fileName, channel.token, channelName);
+ }
+
+ if (outermost instanceof LexerGrammar) {
+ LexerGrammar lexerGrammar = (LexerGrammar)outermost;
+ if (lexerGrammar.modes.containsKey(channelName)) {
+ g.tool.errMgr.grammarError(ErrorType.CHANNEL_CONFLICTS_WITH_MODE, g.fileName, channel.token, channelName);
+ }
+ }
+
+ outermost.defineChannelName(channel.getText());
+ }
+ }
+}
diff --git a/tool/src/org/antlr/v4/semantics/SymbolChecks.java b/tool/src/org/antlr/v4/semantics/SymbolChecks.java
new file mode 100644
index 0000000..b280f40
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/SymbolChecks.java
@@ -0,0 +1,312 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.v4.parse.ANTLRParser;
+import org.antlr.v4.tool.Alternative;
+import org.antlr.v4.tool.Attribute;
+import org.antlr.v4.tool.AttributeDict;
+import org.antlr.v4.tool.ErrorManager;
+import org.antlr.v4.tool.ErrorType;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.LabelElementPair;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.GrammarAST;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Check for symbol problems; no side-effects. Inefficient to walk rules
+ * and such multiple times, but I like isolating all error checking outside
+ * of code that actually defines symbols etc...
+ *
+ * Side-effect: strip away redef'd rules.
+ */
+public class SymbolChecks {
+ Grammar g;
+ SymbolCollector collector;
+ Map<String, Rule> nameToRuleMap = new HashMap<String, Rule>();
+ Set<String> tokenIDs = new HashSet<String>();
+ Map<String, Set<String>> actionScopeToActionNames = new HashMap<String, Set<String>>();
+// DoubleKeyMap<String, String, GrammarAST> namedActions =
+// new DoubleKeyMap<String, String, GrammarAST>();
+
+ public ErrorManager errMgr;
+
+ protected final Set<String> reservedNames = new HashSet<String>();
+ {
+ reservedNames.add("EOF");
+ }
+
+ public SymbolChecks(Grammar g, SymbolCollector collector) {
+ this.g = g;
+ this.collector = collector;
+ this.errMgr = g.tool.errMgr;
+
+ for (GrammarAST tokenId : collector.tokenIDRefs) {
+ tokenIDs.add(tokenId.getText());
+ }
+ /*
+ System.out.println("rules="+collector.rules);
+ System.out.println("rulerefs="+collector.rulerefs);
+ System.out.println("tokenIDRefs="+collector.tokenIDRefs);
+ System.out.println("terminals="+collector.terminals);
+ System.out.println("strings="+collector.strings);
+ System.out.println("tokensDef="+collector.tokensDefs);
+ System.out.println("actions="+collector.actions);
+ System.out.println("scopes="+collector.scopes);
+ */
+ }
+
+ public void process() {
+ // methods affect fields, but no side-effects outside this object
+ // So, call order sensitive
+ // First collect all rules for later use in checkForLabelConflict()
+ if ( g.rules!=null ) {
+ for (Rule r : g.rules.values()) nameToRuleMap.put(r.name, r);
+ }
+ checkReservedNames(g.rules.values());
+ checkActionRedefinitions(collector.namedActions);
+ checkForTokenConflicts(collector.tokenIDRefs); // sets tokenIDs
+ checkForLabelConflicts(g.rules.values());
+ }
+
+ public void checkActionRedefinitions(List<GrammarAST> actions) {
+ if ( actions==null ) return;
+ String scope = g.getDefaultActionScope();
+ String name;
+ GrammarAST nameNode;
+ for (GrammarAST ampersandAST : actions) {
+ nameNode = (GrammarAST)ampersandAST.getChild(0);
+ if ( ampersandAST.getChildCount()==2 ) {
+ name = nameNode.getText();
+ }
+ else {
+ scope = nameNode.getText();
+ name = ampersandAST.getChild(1).getText();
+ }
+ Set<String> scopeActions = actionScopeToActionNames.get(scope);
+ if ( scopeActions==null ) { // init scope
+ scopeActions = new HashSet<String>();
+ actionScopeToActionNames.put(scope, scopeActions);
+ }
+ if ( !scopeActions.contains(name) ) {
+ scopeActions.add(name);
+ }
+ else {
+ errMgr.grammarError(ErrorType.ACTION_REDEFINITION,
+ g.fileName, nameNode.token, name);
+ }
+ }
+ }
+
+ public void checkForTokenConflicts(List<GrammarAST> tokenIDRefs) {
+// for (GrammarAST a : tokenIDRefs) {
+// Token t = a.token;
+// String ID = t.getText();
+// tokenIDs.add(ID);
+// }
+ }
+
+ /** 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.
+ */
+ public void checkForLabelConflicts(Collection<Rule> rules) {
+ for (Rule r : rules) {
+ checkForAttributeConflicts(r);
+ Map<String, LabelElementPair> labelNameSpace =
+ new HashMap<String, LabelElementPair>();
+ for (int i=1; i<=r.numberOfAlts; i++) {
+ if (r.hasAltSpecificContexts()) {
+ labelNameSpace.clear();
+ }
+
+ Alternative a = r.alt[i];
+ for (List<LabelElementPair> pairs : a.labelDefs.values() ) {
+ for (LabelElementPair p : pairs) {
+ checkForLabelConflict(r, p.label);
+ String name = p.label.getText();
+ LabelElementPair prev = labelNameSpace.get(name);
+ if ( prev==null ) labelNameSpace.put(name, p);
+ else checkForTypeMismatch(prev, p);
+ }
+ }
+ }
+ }
+ }
+
+ void checkForTypeMismatch(LabelElementPair prevLabelPair,
+ LabelElementPair labelPair)
+ {
+ // label already defined; if same type, no problem
+ if ( prevLabelPair.type != labelPair.type ) {
+ String typeMismatchExpr = labelPair.type+"!="+prevLabelPair.type;
+ errMgr.grammarError(
+ ErrorType.LABEL_TYPE_CONFLICT,
+ g.fileName,
+ labelPair.label.token,
+ labelPair.label.getText(),
+ typeMismatchExpr);
+ }
+ }
+
+ public void checkForLabelConflict(Rule r, GrammarAST labelID) {
+ String name = labelID.getText();
+ if (nameToRuleMap.containsKey(name)) {
+ ErrorType etype = ErrorType.LABEL_CONFLICTS_WITH_RULE;
+ errMgr.grammarError(etype, g.fileName, labelID.token, name, r.name);
+ }
+
+ if (tokenIDs.contains(name)) {
+ ErrorType etype = ErrorType.LABEL_CONFLICTS_WITH_TOKEN;
+ errMgr.grammarError(etype, g.fileName, labelID.token, name, r.name);
+ }
+
+ if (r.args != null && r.args.get(name) != null) {
+ ErrorType etype = ErrorType.LABEL_CONFLICTS_WITH_ARG;
+ errMgr.grammarError(etype, g.fileName, labelID.token, name, r.name);
+ }
+
+ if (r.retvals != null && r.retvals.get(name) != null) {
+ ErrorType etype = ErrorType.LABEL_CONFLICTS_WITH_RETVAL;
+ errMgr.grammarError(etype, g.fileName, labelID.token, name, r.name);
+ }
+
+ if (r.locals != null && r.locals.get(name) != null) {
+ ErrorType etype = ErrorType.LABEL_CONFLICTS_WITH_LOCAL;
+ errMgr.grammarError(etype, g.fileName, labelID.token, name, r.name);
+ }
+ }
+
+ public void checkForAttributeConflicts(Rule r) {
+ checkDeclarationRuleConflicts(r, r.args, nameToRuleMap.keySet(), ErrorType.ARG_CONFLICTS_WITH_RULE);
+ checkDeclarationRuleConflicts(r, r.args, tokenIDs, ErrorType.ARG_CONFLICTS_WITH_TOKEN);
+
+ checkDeclarationRuleConflicts(r, r.retvals, nameToRuleMap.keySet(), ErrorType.RETVAL_CONFLICTS_WITH_RULE);
+ checkDeclarationRuleConflicts(r, r.retvals, tokenIDs, ErrorType.RETVAL_CONFLICTS_WITH_TOKEN);
+
+ checkDeclarationRuleConflicts(r, r.locals, nameToRuleMap.keySet(), ErrorType.LOCAL_CONFLICTS_WITH_RULE);
+ checkDeclarationRuleConflicts(r, r.locals, tokenIDs, ErrorType.LOCAL_CONFLICTS_WITH_TOKEN);
+
+ checkLocalConflictingDeclarations(r, r.retvals, r.args, ErrorType.RETVAL_CONFLICTS_WITH_ARG);
+ checkLocalConflictingDeclarations(r, r.locals, r.args, ErrorType.LOCAL_CONFLICTS_WITH_ARG);
+ checkLocalConflictingDeclarations(r, r.locals, r.retvals, ErrorType.LOCAL_CONFLICTS_WITH_RETVAL);
+ }
+
+ protected void checkDeclarationRuleConflicts(Rule r, AttributeDict attributes, Set<String> ruleNames, ErrorType errorType) {
+ if (attributes == null) {
+ return;
+ }
+
+ for (Attribute attribute : attributes.attributes.values()) {
+ if (ruleNames.contains(attribute.name)) {
+ errMgr.grammarError(
+ errorType,
+ g.fileName,
+ attribute.token != null ? attribute.token : ((GrammarAST)r.ast.getChild(0)).token,
+ attribute.name,
+ r.name);
+ }
+ }
+ }
+
+ protected void checkLocalConflictingDeclarations(Rule r, AttributeDict attributes, AttributeDict referenceAttributes, ErrorType errorType) {
+ if (attributes == null || referenceAttributes == null) {
+ return;
+ }
+
+ Set<String> conflictingKeys = attributes.intersection(referenceAttributes);
+ for (String key : conflictingKeys) {
+ errMgr.grammarError(
+ errorType,
+ g.fileName,
+ attributes.get(key).token != null ? attributes.get(key).token : ((GrammarAST) r.ast.getChild(0)).token,
+ key,
+ r.name);
+ }
+ }
+
+ protected void checkReservedNames(Collection<Rule> rules) {
+ for (Rule rule : rules) {
+ if (reservedNames.contains(rule.name)) {
+ errMgr.grammarError(ErrorType.RESERVED_RULE_NAME, g.fileName, ((GrammarAST)rule.ast.getChild(0)).getToken(), rule.name);
+ }
+ }
+ }
+
+ // CAN ONLY CALL THE TWO NEXT METHODS AFTER GRAMMAR HAS RULE DEFS (see semanticpipeline)
+
+ public void checkRuleArgs(Grammar g, List<GrammarAST> rulerefs) {
+ if ( rulerefs==null ) return;
+ for (GrammarAST ref : rulerefs) {
+ String ruleName = ref.getText();
+ Rule r = g.getRule(ruleName);
+ GrammarAST arg = (GrammarAST)ref.getFirstChildWithType(ANTLRParser.ARG_ACTION);
+ if ( arg!=null && (r==null || r.args==null) ) {
+ errMgr.grammarError(ErrorType.RULE_HAS_NO_ARGS,
+ g.fileName, ref.token, ruleName);
+
+ }
+ else if ( arg==null && (r!=null&&r.args!=null) ) {
+ errMgr.grammarError(ErrorType.MISSING_RULE_ARGS,
+ g.fileName, ref.token, ruleName);
+ }
+ }
+ }
+
+ public void checkForQualifiedRuleIssues(Grammar g, List<GrammarAST> qualifiedRuleRefs) {
+ for (GrammarAST dot : qualifiedRuleRefs) {
+ GrammarAST grammar = (GrammarAST)dot.getChild(0);
+ GrammarAST rule = (GrammarAST)dot.getChild(1);
+ g.tool.log("semantics", grammar.getText()+"."+rule.getText());
+ Grammar delegate = g.getImportedGrammar(grammar.getText());
+ if ( delegate==null ) {
+ errMgr.grammarError(ErrorType.NO_SUCH_GRAMMAR_SCOPE,
+ g.fileName, grammar.token, grammar.getText(),
+ rule.getText());
+ }
+ else {
+ if ( g.getRule(grammar.getText(), rule.getText())==null ) {
+ errMgr.grammarError(ErrorType.NO_SUCH_RULE_IN_SCOPE,
+ g.fileName, rule.token, grammar.getText(),
+ rule.getText());
+ }
+ }
+ }
+ }
+}
diff --git a/tool/src/org/antlr/v4/semantics/SymbolCollector.java b/tool/src/org/antlr/v4/semantics/SymbolCollector.java
new file mode 100644
index 0000000..b0c43a4
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/SymbolCollector.java
@@ -0,0 +1,213 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.v4.parse.GrammarTreeVisitor;
+import org.antlr.v4.tool.ErrorManager;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.LabelElementPair;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.ActionAST;
+import org.antlr.v4.tool.ast.AltAST;
+import org.antlr.v4.tool.ast.GrammarAST;
+import org.antlr.v4.tool.ast.GrammarASTWithOptions;
+import org.antlr.v4.tool.ast.PredAST;
+import org.antlr.v4.tool.ast.RuleAST;
+import org.antlr.v4.tool.ast.TerminalAST;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Collects (create) rules, terminals, strings, actions, scopes etc... from AST
+ * side-effects: sets resolver field of asts for actions and
+ * defines predicates via definePredicateInAlt(), collects actions and stores
+ * in alts.
+ * TODO: remove side-effects!
+ */
+public class SymbolCollector extends GrammarTreeVisitor {
+ /** which grammar are we checking */
+ public Grammar g;
+
+ // stuff to collect
+ public List<GrammarAST> rulerefs = new ArrayList<GrammarAST>();
+ public List<GrammarAST> qualifiedRulerefs = new ArrayList<GrammarAST>();
+ public List<GrammarAST> terminals = new ArrayList<GrammarAST>();
+ public List<GrammarAST> tokenIDRefs = new ArrayList<GrammarAST>();
+ public Set<String> strings = new HashSet<String>();
+ public List<GrammarAST> tokensDefs = new ArrayList<GrammarAST>();
+ public List<GrammarAST> channelDefs = new ArrayList<GrammarAST>();
+
+ /** Track action name node in @parser::members {...} or @members {...} */
+ List<GrammarAST> namedActions = new ArrayList<GrammarAST>();
+
+ public ErrorManager errMgr;
+
+ // context
+ public Rule currentRule;
+
+ public SymbolCollector(Grammar g) {
+ this.g = g;
+ this.errMgr = g.tool.errMgr;
+ }
+
+ @Override
+ public ErrorManager getErrorManager() { return errMgr; }
+
+ public void process(GrammarAST ast) { visitGrammar(ast); }
+
+ @Override
+ public void globalNamedAction(GrammarAST scope, GrammarAST ID, ActionAST action) {
+ namedActions.add((GrammarAST)ID.getParent());
+ action.resolver = g;
+ }
+
+ @Override
+ public void defineToken(GrammarAST ID) {
+ terminals.add(ID);
+ tokenIDRefs.add(ID);
+ tokensDefs.add(ID);
+ }
+
+ @Override
+ public void defineChannel(GrammarAST ID) {
+ channelDefs.add(ID);
+ }
+
+ @Override
+ public void discoverRule(RuleAST rule, GrammarAST ID,
+ List<GrammarAST> modifiers, ActionAST arg,
+ ActionAST returns, GrammarAST thrws,
+ GrammarAST options, ActionAST locals,
+ List<GrammarAST> actions,
+ GrammarAST block)
+ {
+ currentRule = g.getRule(ID.getText());
+ }
+
+ @Override
+ public void discoverLexerRule(RuleAST rule, GrammarAST ID, List<GrammarAST> modifiers,
+ GrammarAST block)
+ {
+ currentRule = g.getRule(ID.getText());
+ }
+
+ @Override
+ public void discoverOuterAlt(AltAST alt) {
+ currentRule.alt[currentOuterAltNumber].ast = alt;
+ }
+
+ @Override
+ public void actionInAlt(ActionAST action) {
+ currentRule.defineActionInAlt(currentOuterAltNumber, action);
+ action.resolver = currentRule.alt[currentOuterAltNumber];
+ }
+
+ @Override
+ public void sempredInAlt(PredAST pred) {
+ currentRule.definePredicateInAlt(currentOuterAltNumber, pred);
+ pred.resolver = currentRule.alt[currentOuterAltNumber];
+ }
+
+ @Override
+ public void ruleCatch(GrammarAST arg, ActionAST action) {
+ GrammarAST catchme = (GrammarAST)action.getParent();
+ currentRule.exceptions.add(catchme);
+ action.resolver = currentRule;
+ }
+
+ @Override
+ public void finallyAction(ActionAST action) {
+ currentRule.finallyAction = action;
+ action.resolver = currentRule;
+ }
+
+ @Override
+ public void label(GrammarAST op, GrammarAST ID, GrammarAST element) {
+ LabelElementPair lp = new LabelElementPair(g, ID, element, op.getType());
+ currentRule.alt[currentOuterAltNumber].labelDefs.map(ID.getText(), lp);
+ }
+
+ @Override
+ public void stringRef(TerminalAST ref) {
+ terminals.add(ref);
+ strings.add(ref.getText());
+ if ( currentRule!=null ) {
+ currentRule.alt[currentOuterAltNumber].tokenRefs.map(ref.getText(), ref);
+ }
+ }
+
+ @Override
+ public void tokenRef(TerminalAST ref) {
+ terminals.add(ref);
+ tokenIDRefs.add(ref);
+ if ( currentRule!=null ) {
+ currentRule.alt[currentOuterAltNumber].tokenRefs.map(ref.getText(), ref);
+ }
+ }
+
+ @Override
+ public void ruleRef(GrammarAST ref, ActionAST arg) {
+// if ( inContext("DOT ...") ) qualifiedRulerefs.add((GrammarAST)ref.getParent());
+ rulerefs.add(ref);
+ if ( currentRule!=null ) {
+ currentRule.alt[currentOuterAltNumber].ruleRefs.map(ref.getText(), ref);
+ }
+ }
+
+ @Override
+ public void grammarOption(GrammarAST ID, GrammarAST valueAST) {
+ setActionResolver(valueAST);
+ }
+
+ @Override
+ public void ruleOption(GrammarAST ID, GrammarAST valueAST) {
+ setActionResolver(valueAST);
+ }
+
+ @Override
+ public void blockOption(GrammarAST ID, GrammarAST valueAST) {
+ setActionResolver(valueAST);
+ }
+
+ @Override
+ public void elementOption(GrammarASTWithOptions t, GrammarAST ID, GrammarAST valueAST) {
+ setActionResolver(valueAST);
+ }
+
+ /** In case of option id={...}, set resolve in case they use $foo */
+ private void setActionResolver(GrammarAST valueAST) {
+ if ( valueAST instanceof ActionAST) {
+ ((ActionAST)valueAST).resolver = currentRule.alt[currentOuterAltNumber];
+ }
+ }
+}
diff --git a/tool/src/org/antlr/v4/semantics/UseDefAnalyzer.java b/tool/src/org/antlr/v4/semantics/UseDefAnalyzer.java
new file mode 100644
index 0000000..ac788e1
--- /dev/null
+++ b/tool/src/org/antlr/v4/semantics/UseDefAnalyzer.java
@@ -0,0 +1,119 @@
+/*
+ * [The "BSD license"]
+ * Copyright (c) 2012 Terence Parr
+ * Copyright (c) 2012 Sam Harwell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.antlr.v4.semantics;
+
+import org.antlr.runtime.ANTLRStringStream;
+import org.antlr.runtime.Token;
+import org.antlr.v4.parse.ANTLRParser;
+import org.antlr.v4.parse.ActionSplitter;
+import org.antlr.v4.parse.ActionSplitterListener;
+import org.antlr.v4.tool.Alternative;
+import org.antlr.v4.tool.Grammar;
+import org.antlr.v4.tool.LexerGrammar;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.ActionAST;
+import org.antlr.v4.tool.ast.GrammarAST;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Look for errors and deadcode stuff */
+public class UseDefAnalyzer {
+ // side-effect: updates Alternative with refs in actions
+ public static void trackTokenRuleRefsInActions(Grammar g) {
+ for (Rule r : g.rules.values()) {
+ for (int i=1; i<=r.numberOfAlts; i++) {
+ Alternative alt = r.alt[i];
+ for (ActionAST a : alt.actions) {
+ ActionSniffer sniffer = new ActionSniffer(g, r, alt, a, a.token);
+ sniffer.examineAction();
+ }
+ }
+ }
+ }
+
+ public static boolean actionIsContextDependent(ActionAST actionAST) {
+ ANTLRStringStream in = new ANTLRStringStream(actionAST.token.getText());
+ in.setLine(actionAST.token.getLine());
+ in.setCharPositionInLine(actionAST.token.getCharPositionInLine());
+ final boolean[] dependent = new boolean[] {false}; // can't be simple bool with anon class
+ ActionSplitterListener listener = new BlankActionSplitterListener() {
+ @Override
+ public void nonLocalAttr(String expr, Token x, Token y) { dependent[0] = true; }
+ @Override
+ public void qualifiedAttr(String expr, Token x, Token y) { dependent[0] = true; }
+ @Override
+ public void setAttr(String expr, Token x, Token rhs) { dependent[0] = true; }
+ @Override
+ public void setExprAttribute(String expr) { dependent[0] = true; }
+ @Override
+ public void setNonLocalAttr(String expr, Token x, Token y, Token rhs) { dependent[0] = true; }
+ @Override
+ public void attr(String expr, Token x) { dependent[0] = true; }
+ };
+ ActionSplitter splitter = new ActionSplitter(in, listener);
+ // forces eval, triggers listener methods
+ splitter.getActionTokens();
+ return dependent[0];
+ }
+
+ /** Find all rules reachable from r directly or indirectly for all r in g */
+ public static Map<Rule, Set<Rule>> getRuleDependencies(Grammar g) {
+ return getRuleDependencies(g, g.rules.values());
+ }
+
+ public static Map<Rule, Set<Rule>> getRuleDependencies(LexerGrammar g, String modeName) {
+ return getRuleDependencies(g, g.modes.get(modeName));
+ }
+
+ public static Map<Rule, Set<Rule>> getRuleDependencies(Grammar g, Collection<Rule> rules) {
+ Map<Rule, Set<Rule>> dependencies = new HashMap<Rule, Set<Rule>>();
+
+ for (Rule r : rules) {
+ List<GrammarAST> tokenRefs = r.ast.getNodesWithType(ANTLRParser.TOKEN_REF);
+ for (GrammarAST tref : tokenRefs) {
+ Set<Rule> calls = dependencies.get(r);
+ if ( calls==null ) {
+ calls = new HashSet<Rule>();
+ dependencies.put(r, calls);
+ }
+ calls.add(g.getRule(tref.getText()));
+ }
+ }
+
+ return dependencies;
+ }
+
+}