blob: 0c141f3b13bedf24740301ef1f4cec521263c488 [file] [log] [blame]
/*
* [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<String> 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<String, Grammar.LabelElementPair> labelNameSpace =
new HashMap<String, Grammar.LabelElementPair>();
/** 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<String, GrammarAST> actions =
new HashMap<String, GrammarAST>();
/** 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<GrammarAST> inlineActions = new ArrayList<GrammarAST>();
public int numberOfAlts;
/** Each alt has a Map<tokenRefName,List<tokenRefAST>>; 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<String, List<GrammarAST>>[] altToTokenRefMap;
/** Each alt has a Map<ruleRefName,List<ruleRefAST>>; 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<String, List<GrammarAST>>[] 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<String>();
altToTokenRefMap = new Map[numberOfAlts+1];
altToRuleRefMap = new Map[numberOfAlts+1];
for (int alt=1; alt<=numberOfAlts; alt++) {
altToTokenRefMap[alt] = new HashMap<String, List<GrammarAST>>();
altToRuleRefMap[alt] = new HashMap<String, List<GrammarAST>>();
}
}
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<String> tokens = new HashSet<String>();
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<String, List<GrammarAST>> 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<GrammarAST> 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<String, GrammarAST> getActions() {
return actions;
}
public void setActions(Map<String, GrammarAST> 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()+"]";
}
}