/* * [The "BSD license"] * Copyright (c) 2010 Terence Parr * 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.tool; import org.antlr.analysis.NFAState; import org.antlr.codegen.CodeGenerator; import org.antlr.grammar.v3.ANTLRParser; import org.antlr.runtime.CommonToken; import org.antlr.runtime.Token; import java.util.*; /** Combine the info associated with a rule. */ public class Rule { public String name; public int index; public String modifier; public NFAState startState; public NFAState stopState; /** This rule's options */ protected Map options; public static final Set legalOptions = new HashSet() { { add("k"); add("greedy"); add("memoize"); add("backtrack"); } }; /** The AST representing the whole rule */ public GrammarAST tree; /** To which grammar does this belong? */ public Grammar grammar; /** For convenience, track the argument def AST action node if any */ public GrammarAST argActionAST; public GrammarAST EORNode; /** The return values of a rule and predefined rule attributes */ public AttributeScope returnScope; public AttributeScope parameterScope; /** the attributes defined with "scope {...}" inside a rule */ public AttributeScope ruleScope; /** A list of scope names (String) used by this rule */ public List useScopes; /** Exceptions that this rule can throw */ public Set throwsSpec; /** A list of all LabelElementPair attached to tokens like id=ID */ public LinkedHashMap tokenLabels; /** A list of all LabelElementPair attached to tokens like x=. in tree grammar */ public LinkedHashMap wildcardTreeLabels; /** A list of all LabelElementPair attached to tokens like x+=. in tree grammar */ public LinkedHashMap wildcardTreeListLabels; /** A list of all LabelElementPair attached to single char literals like x='a' */ public LinkedHashMap charLabels; /** A list of all LabelElementPair attached to rule references like f=field */ public LinkedHashMap ruleLabels; /** A list of all Token list LabelElementPair like ids+=ID */ public LinkedHashMap tokenListLabels; /** A list of all rule ref list LabelElementPair like ids+=expr */ public LinkedHashMap ruleListLabels; /** All labels go in here (plus being split per the above lists) to * catch dup label and label type mismatches. */ protected Map labelNameSpace = new HashMap(); /** Map a name to an action for this rule. Currently init is only * one we use, but we can add more in future. * The code generator will use this to fill holes in the rule template. * I track the AST node for the action in case I need the line number * for errors. A better name is probably namedActions, but I don't * want everyone to have to change their code gen templates now. */ protected Map actions = new HashMap(); /** Track all executable actions other than named actions like @init. * Also tracks exception handlers, predicates, and rewrite rewrites. * We need to examine these actions before code generation so * that we can detect refs to $rule.attr etc... */ protected List inlineActions = new ArrayList(); public int numberOfAlts; /** Each alt has a Map>; range 1..numberOfAlts. * So, if there are 3 ID refs in a rule's alt number 2, you'll have * altToTokenRef[2].get("ID").size()==3. This is used to see if $ID is ok. * There must be only one ID reference in the alt for $ID to be ok in * an action--must be unique. * * This also tracks '+' and "int" literal token references * (if not in LEXER). * * Rewrite rules force tracking of all tokens. */ protected Map>[] altToTokenRefMap; /** Each alt has a Map>; range 1..numberOfAlts * So, if there are 3 expr refs in a rule's alt number 2, you'll have * altToRuleRef[2].get("expr").size()==3. This is used to see if $expr is ok. * There must be only one expr reference in the alt for $expr to be ok in * an action--must be unique. * * Rewrite rules force tracking of all rule result ASTs. 1..n */ protected Map>[] altToRuleRefMap; /** Do not generate start, stop etc... in a return value struct unless * somebody references $r.start somewhere. */ public boolean referencedPredefinedRuleAttributes = false; public boolean isSynPred = false; public boolean imported = false; public Rule(Grammar grammar, String ruleName, int ruleIndex, int numberOfAlts) { this.name = ruleName; this.index = ruleIndex; this.numberOfAlts = numberOfAlts; this.grammar = grammar; throwsSpec = new HashSet(); altToTokenRefMap = new Map[numberOfAlts+1]; altToRuleRefMap = new Map[numberOfAlts+1]; for (int alt=1; alt<=numberOfAlts; alt++) { altToTokenRefMap[alt] = new HashMap>(); altToRuleRefMap[alt] = new HashMap>(); } } public static int getRuleType(String ruleName){ if (ruleName == null || ruleName.length() == 0) throw new IllegalArgumentException("The specified rule name is not valid."); return Character.isUpperCase(ruleName.charAt(0)) ? Grammar.LEXER : Grammar.PARSER; } public void defineLabel(Token label, GrammarAST elementRef, int type) { Grammar.LabelElementPair pair = grammar.new LabelElementPair(label,elementRef); pair.type = type; labelNameSpace.put(label.getText(), pair); switch ( type ) { case Grammar.TOKEN_LABEL : if ( tokenLabels==null ) tokenLabels = new LinkedHashMap(); tokenLabels.put(label.getText(), pair); break; case Grammar.WILDCARD_TREE_LABEL : if ( wildcardTreeLabels==null ) wildcardTreeLabels = new LinkedHashMap(); wildcardTreeLabels.put(label.getText(), pair); break; case Grammar.WILDCARD_TREE_LIST_LABEL : if ( wildcardTreeListLabels==null ) wildcardTreeListLabels = new LinkedHashMap(); wildcardTreeListLabels.put(label.getText(), pair); break; case Grammar.RULE_LABEL : if ( ruleLabels==null ) ruleLabels = new LinkedHashMap(); ruleLabels.put(label.getText(), pair); break; case Grammar.TOKEN_LIST_LABEL : if ( tokenListLabels==null ) tokenListLabels = new LinkedHashMap(); tokenListLabels.put(label.getText(), pair); break; case Grammar.RULE_LIST_LABEL : if ( ruleListLabels==null ) ruleListLabels = new LinkedHashMap(); ruleListLabels.put(label.getText(), pair); break; case Grammar.CHAR_LABEL : if ( charLabels==null ) charLabels = new LinkedHashMap(); charLabels.put(label.getText(), pair); break; } } public Grammar.LabelElementPair getLabel(String name) { return (Grammar.LabelElementPair)labelNameSpace.get(name); } public Grammar.LabelElementPair getTokenLabel(String name) { Grammar.LabelElementPair pair = null; if ( tokenLabels!=null ) { return (Grammar.LabelElementPair)tokenLabels.get(name); } return pair; } public Map getRuleLabels() { return ruleLabels; } public Map getRuleListLabels() { return ruleListLabels; } public Grammar.LabelElementPair getRuleLabel(String name) { Grammar.LabelElementPair pair = null; if ( ruleLabels!=null ) { return (Grammar.LabelElementPair)ruleLabels.get(name); } return pair; } public Grammar.LabelElementPair getTokenListLabel(String name) { Grammar.LabelElementPair pair = null; if ( tokenListLabels!=null ) { return (Grammar.LabelElementPair)tokenListLabels.get(name); } return pair; } public Grammar.LabelElementPair getRuleListLabel(String name) { Grammar.LabelElementPair pair = null; if ( ruleListLabels!=null ) { return (Grammar.LabelElementPair)ruleListLabels.get(name); } return pair; } /** Track a token ID or literal like '+' and "void" as having been referenced * somewhere within the alts (not rewrite sections) of a rule. * * This differs from Grammar.altReferencesTokenID(), which tracks all * token IDs to check for token IDs without corresponding lexer rules. */ public void trackTokenReferenceInAlt(GrammarAST refAST, int outerAltNum) { List refs = (List)altToTokenRefMap[outerAltNum].get(refAST.getText()); if ( refs==null ) { refs = new ArrayList(); altToTokenRefMap[outerAltNum].put(refAST.getText(), refs); } refs.add(refAST); } public List getTokenRefsInAlt(String ref, int outerAltNum) { if ( altToTokenRefMap[outerAltNum]!=null ) { List tokenRefASTs = (List)altToTokenRefMap[outerAltNum].get(ref); return tokenRefASTs; } return null; } public void trackRuleReferenceInAlt(GrammarAST refAST, int outerAltNum) { List refs = (List)altToRuleRefMap[outerAltNum].get(refAST.getText()); if ( refs==null ) { refs = new ArrayList(); altToRuleRefMap[outerAltNum].put(refAST.getText(), refs); } refs.add(refAST); } public List getRuleRefsInAlt(String ref, int outerAltNum) { if ( altToRuleRefMap[outerAltNum]!=null ) { List ruleRefASTs = (List)altToRuleRefMap[outerAltNum].get(ref); return ruleRefASTs; } return null; } public Set getTokenRefsInAlt(int altNum) { return altToTokenRefMap[altNum].keySet(); } /** For use with rewrite rules, we must track all tokens matched on the * left-hand-side; so we need Lists. This is a unique list of all * token types for which the rule needs a list of tokens. This * is called from the rule template not directly by the code generator. */ public Set getAllTokenRefsInAltsWithRewrites() { String output = (String)grammar.getOption("output"); Set tokens = new HashSet(); if ( output==null || !output.equals("AST") ) { // return nothing if not generating trees; i.e., don't do for templates return tokens; } //System.out.println("blk "+tree.findFirstType(ANTLRParser.BLOCK).toStringTree()); for (int i = 1; i <= numberOfAlts; i++) { if ( hasRewrite(i) ) { Map> m = altToTokenRefMap[i]; for (String tokenName : m.keySet()) { // convert token name like ID to ID, "void" to 31 int ttype = grammar.getTokenType(tokenName); String label = grammar.generator.getTokenTypeAsTargetLabel(ttype); tokens.add(label); } } } return tokens; } public Set getRuleRefsInAlt(int outerAltNum) { return altToRuleRefMap[outerAltNum].keySet(); } /** For use with rewrite rules, we must track all rule AST results on the * left-hand-side; so we need Lists. This is a unique list of all * rule results for which the rule needs a list of results. */ public Set getAllRuleRefsInAltsWithRewrites() { Set rules = new HashSet(); for (int i = 1; i <= numberOfAlts; i++) { if ( hasRewrite(i) ) { Map m = altToRuleRefMap[i]; rules.addAll(m.keySet()); } } return rules; } public List getInlineActions() { return inlineActions; } public boolean hasRewrite(int i) { GrammarAST blk = tree.findFirstType(ANTLRParser.BLOCK); GrammarAST alt = blk.getBlockALT(i); GrammarAST rew = (GrammarAST)alt.getNextSibling(); if ( rew!=null && rew.getType()==ANTLRParser.REWRITES ) return true; if ( alt.findFirstType(ANTLRParser.REWRITES)!=null ) return true; return false; } /** Return the scope containing name */ public AttributeScope getAttributeScope(String name) { AttributeScope scope = getLocalAttributeScope(name); if ( scope!=null ) { return scope; } if ( ruleScope!=null && ruleScope.getAttribute(name)!=null ) { scope = ruleScope; } return scope; } /** Get the arg, return value, or predefined property for this rule */ public AttributeScope getLocalAttributeScope(String name) { AttributeScope scope = null; if ( returnScope!=null && returnScope.getAttribute(name)!=null ) { scope = returnScope; } else if ( parameterScope!=null && parameterScope.getAttribute(name)!=null ) { scope = parameterScope; } else { AttributeScope rulePropertiesScope = RuleLabelScope.grammarTypeToRulePropertiesScope[grammar.type]; if ( rulePropertiesScope.getAttribute(name)!=null ) { scope = rulePropertiesScope; } } return scope; } /** For references to tokens rather than by label such as $ID, we * need to get the existing label for the ID ref or create a new * one. */ public String getElementLabel(String refdSymbol, int outerAltNum, CodeGenerator generator) { GrammarAST uniqueRefAST; if ( grammar.type != Grammar.LEXER && Character.isUpperCase(refdSymbol.charAt(0)) ) { // symbol is a token List tokenRefs = getTokenRefsInAlt(refdSymbol, outerAltNum); uniqueRefAST = (GrammarAST)tokenRefs.get(0); } else { // symbol is a rule List ruleRefs = getRuleRefsInAlt(refdSymbol, outerAltNum); uniqueRefAST = (GrammarAST)ruleRefs.get(0); } if ( uniqueRefAST.code==null ) { // no code? must not have gen'd yet; forward ref return null; } String labelName = null; String existingLabelName = (String)uniqueRefAST.code.getAttribute("label"); // reuse any label or list label if it exists if ( existingLabelName!=null ) { labelName = existingLabelName; } else { // else create new label labelName = generator.createUniqueLabel(refdSymbol); CommonToken label = new CommonToken(ANTLRParser.ID, labelName); if ( grammar.type != Grammar.LEXER && Character.isUpperCase(refdSymbol.charAt(0)) ) { grammar.defineTokenRefLabel(name, label, uniqueRefAST); } else { grammar.defineRuleRefLabel(name, label, uniqueRefAST); } uniqueRefAST.code.add("label", labelName); } return labelName; } /** If a rule has no user-defined return values and nobody references * it's start/stop (predefined attributes), then there is no need to * define a struct; otherwise for now we assume a struct. A rule also * has multiple return values if you are building trees or templates. */ public boolean getHasMultipleReturnValues() { return referencedPredefinedRuleAttributes || grammar.buildAST() || grammar.buildTemplate() || (returnScope!=null && returnScope.attributes.size()>1); } public boolean getHasSingleReturnValue() { return !(referencedPredefinedRuleAttributes || grammar.buildAST() || grammar.buildTemplate()) && (returnScope!=null && returnScope.attributes.size()==1); } public boolean getHasReturnValue() { return referencedPredefinedRuleAttributes || grammar.buildAST() || grammar.buildTemplate() || (returnScope!=null && returnScope.attributes.size()>0); } public String getSingleValueReturnType() { if ( returnScope!=null && returnScope.attributes.size()==1 ) { Collection retvalAttrs = returnScope.attributes.values(); Object[] javaSucks = retvalAttrs.toArray(); return ((Attribute)javaSucks[0]).type; } return null; } public String getSingleValueReturnName() { if ( returnScope!=null && returnScope.attributes.size()==1 ) { Collection retvalAttrs = returnScope.attributes.values(); Object[] javaSucks = retvalAttrs.toArray(); return ((Attribute)javaSucks[0]).name; } return null; } /** Given @scope::name {action} define it for this grammar. Later, * the code generator will ask for the actions table. */ public void defineNamedAction(GrammarAST ampersandAST, GrammarAST nameAST, GrammarAST actionAST) { //System.out.println("rule @"+nameAST.getText()+"{"+actionAST.getText()+"}"); String actionName = nameAST.getText(); GrammarAST a = (GrammarAST)actions.get(actionName); if ( a!=null ) { ErrorManager.grammarError( ErrorManager.MSG_ACTION_REDEFINITION,grammar, nameAST.getToken(),nameAST.getText()); } else { actions.put(actionName,actionAST); } } public void trackInlineAction(GrammarAST actionAST) { inlineActions.add(actionAST); } public Map getActions() { return actions; } public void setActions(Map actions) { this.actions = actions; } /** Save the option key/value pair and process it; return the key * or null if invalid option. */ public String setOption(String key, Object value, Token optionsStartToken) { if ( !legalOptions.contains(key) ) { ErrorManager.grammarError(ErrorManager.MSG_ILLEGAL_OPTION, grammar, optionsStartToken, key); return null; } if ( options==null ) { options = new HashMap(); } if ( key.equals("memoize") && value.toString().equals("true") ) { grammar.atLeastOneRuleMemoizes = true; } if ( key.equals("backtrack") && value.toString().equals("true") ) { grammar.composite.getRootGrammar().atLeastOneBacktrackOption = true; } if ( key.equals("k") ) { grammar.numberOfManualLookaheadOptions++; } options.put(key, value); return key; } public void setOptions(Map options, Token optionsStartToken) { if ( options==null ) { this.options = null; return; } Set keys = options.keySet(); for (Iterator it = keys.iterator(); it.hasNext();) { String optionName = (String) it.next(); Object optionValue = options.get(optionName); String stored=setOption(optionName, optionValue, optionsStartToken); if ( stored==null ) { it.remove(); } } } /** Used during grammar imports to see if sets of rules intersect... This * method and hashCode use the String name as the key for Rule objects. public boolean equals(Object other) { return this.name.equals(((Rule)other).name); } */ /** Used during grammar imports to see if sets of rules intersect... public int hashCode() { return name.hashCode(); } * */ public String toString() { // used for testing return "["+grammar.name+"."+name+",index="+index+",line="+tree.getToken().getLine()+"]"; } }