diff options
author | Emmanuel Bourg <ebourg@apache.org> | 2015-09-30 23:36:28 +0200 |
---|---|---|
committer | Emmanuel Bourg <ebourg@apache.org> | 2015-09-30 23:36:28 +0200 |
commit | bbb7bd0887717b544df1a50e3842ca64c3489941 (patch) | |
tree | 3423dfb0f105f33e5752dd43bb0cd83728ecd2d7 /tool/src/org/antlr/v4/semantics | |
parent | cdbf09fa7db7d25d9ce65b92d0398c3a54a5fd5d (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.java | 113 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/AttributeChecks.java | 259 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/BasicSemanticChecks.java | 637 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/BlankActionSplitterListener.java | 75 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/RuleCollector.java | 138 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/SemanticPipeline.java | 300 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/SymbolChecks.java | 312 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/SymbolCollector.java | 213 | ||||
-rw-r--r-- | tool/src/org/antlr/v4/semantics/UseDefAnalyzer.java | 119 |
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; + } + +} |