| /* |
| * [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.analysis.RuleClosureTransition; |
| import org.antlr.analysis.Transition; |
| import org.antlr.grammar.v3.ANTLRParser; |
| import org.antlr.runtime.tree.Tree; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** Factor out routines that check sanity of rules, alts, grammars, etc.. */ |
| public class GrammarSanity { |
| /** The checkForLeftRecursion method needs to track what rules it has |
| * visited to track infinite recursion. |
| */ |
| protected Set<Rule> visitedDuringRecursionCheck = null; |
| |
| protected Grammar grammar; |
| public GrammarSanity(Grammar grammar) { |
| this.grammar = grammar; |
| } |
| |
| /** Check all rules for infinite left recursion before analysis. Return list |
| * of troublesome rule cycles. This method has two side-effects: it notifies |
| * the error manager that we have problems and it sets the list of |
| * recursive rules that we should ignore during analysis. |
| */ |
| public List<Set<Rule>> checkAllRulesForLeftRecursion() { |
| grammar.buildNFA(); // make sure we have NFAs |
| grammar.leftRecursiveRules = new HashSet(); |
| List<Set<Rule>> listOfRecursiveCycles = new ArrayList(); |
| for (int i = 0; i < grammar.composite.ruleIndexToRuleList.size(); i++) { |
| Rule r = grammar.composite.ruleIndexToRuleList.elementAt(i); |
| if ( r!=null ) { |
| visitedDuringRecursionCheck = new HashSet(); |
| visitedDuringRecursionCheck.add(r); |
| Set visitedStates = new HashSet(); |
| traceStatesLookingForLeftRecursion(r.startState, |
| visitedStates, |
| listOfRecursiveCycles); |
| } |
| } |
| if ( listOfRecursiveCycles.size()>0 ) { |
| ErrorManager.leftRecursionCycles(listOfRecursiveCycles); |
| } |
| return listOfRecursiveCycles; |
| } |
| |
| /** From state s, look for any transition to a rule that is currently |
| * being traced. When tracing r, visitedDuringRecursionCheck has r |
| * initially. If you reach an accept state, return but notify the |
| * invoking rule that it is nullable, which implies that invoking |
| * rule must look at follow transition for that invoking state. |
| * The visitedStates tracks visited states within a single rule so |
| * we can avoid epsilon-loop-induced infinite recursion here. Keep |
| * filling the cycles in listOfRecursiveCycles and also, as a |
| * side-effect, set leftRecursiveRules. |
| */ |
| protected boolean traceStatesLookingForLeftRecursion(NFAState s, |
| Set visitedStates, |
| List<Set<Rule>> listOfRecursiveCycles) |
| { |
| if ( s.isAcceptState() ) { |
| // this rule must be nullable! |
| // At least one epsilon edge reached accept state |
| return true; |
| } |
| if ( visitedStates.contains(s) ) { |
| // within same rule, we've hit same state; quit looping |
| return false; |
| } |
| visitedStates.add(s); |
| boolean stateReachesAcceptState = false; |
| Transition t0 = s.transition[0]; |
| if ( t0 instanceof RuleClosureTransition ) { |
| RuleClosureTransition refTrans = (RuleClosureTransition)t0; |
| Rule refRuleDef = refTrans.rule; |
| //String targetRuleName = ((NFAState)t0.target).getEnclosingRule(); |
| if ( visitedDuringRecursionCheck.contains(refRuleDef) ) { |
| // record left-recursive rule, but don't go back in |
| grammar.leftRecursiveRules.add(refRuleDef); |
| /* |
| System.out.println("already visited "+refRuleDef+", calling from "+ |
| s.enclosingRule); |
| */ |
| addRulesToCycle(refRuleDef, |
| s.enclosingRule, |
| listOfRecursiveCycles); |
| } |
| else { |
| // must visit if not already visited; send new visitedStates set |
| visitedDuringRecursionCheck.add(refRuleDef); |
| boolean callReachedAcceptState = |
| traceStatesLookingForLeftRecursion((NFAState)t0.target, |
| new HashSet(), |
| listOfRecursiveCycles); |
| // we're back from visiting that rule |
| visitedDuringRecursionCheck.remove(refRuleDef); |
| // must keep going in this rule then |
| if ( callReachedAcceptState ) { |
| NFAState followingState = |
| ((RuleClosureTransition) t0).followState; |
| stateReachesAcceptState |= |
| traceStatesLookingForLeftRecursion(followingState, |
| visitedStates, |
| listOfRecursiveCycles); |
| } |
| } |
| } |
| else if ( t0.label.isEpsilon() || t0.label.isSemanticPredicate() ) { |
| stateReachesAcceptState |= |
| traceStatesLookingForLeftRecursion((NFAState)t0.target, visitedStates, listOfRecursiveCycles); |
| } |
| // else it has a labeled edge |
| |
| // now do the other transition if it exists |
| Transition t1 = s.transition[1]; |
| if ( t1!=null ) { |
| stateReachesAcceptState |= |
| traceStatesLookingForLeftRecursion((NFAState)t1.target, |
| visitedStates, |
| listOfRecursiveCycles); |
| } |
| return stateReachesAcceptState; |
| } |
| |
| /** enclosingRuleName calls targetRuleName, find the cycle containing |
| * the target and add the caller. Find the cycle containing the caller |
| * and add the target. If no cycles contain either, then create a new |
| * cycle. listOfRecursiveCycles is List<Set<String>> that holds a list |
| * of cycles (sets of rule names). |
| */ |
| protected void addRulesToCycle(Rule targetRule, |
| Rule enclosingRule, |
| List<Set<Rule>> listOfRecursiveCycles) |
| { |
| boolean foundCycle = false; |
| for (int i = 0; i < listOfRecursiveCycles.size(); i++) { |
| Set<Rule> rulesInCycle = listOfRecursiveCycles.get(i); |
| // ensure both rules are in same cycle |
| if ( rulesInCycle.contains(targetRule) ) { |
| rulesInCycle.add(enclosingRule); |
| foundCycle = true; |
| } |
| if ( rulesInCycle.contains(enclosingRule) ) { |
| rulesInCycle.add(targetRule); |
| foundCycle = true; |
| } |
| } |
| if ( !foundCycle ) { |
| Set cycle = new HashSet(); |
| cycle.add(targetRule); |
| cycle.add(enclosingRule); |
| listOfRecursiveCycles.add(cycle); |
| } |
| } |
| |
| public void checkRuleReference(GrammarAST scopeAST, |
| GrammarAST refAST, |
| GrammarAST argsAST, |
| String currentRuleName) |
| { |
| Rule r = grammar.getRule(refAST.getText()); |
| if ( refAST.getType()==ANTLRParser.RULE_REF ) { |
| if ( argsAST!=null ) { |
| // rule[args]; ref has args |
| if ( r!=null && r.argActionAST==null ) { |
| // but rule def has no args |
| ErrorManager.grammarError( |
| ErrorManager.MSG_RULE_HAS_NO_ARGS, |
| grammar, |
| argsAST.getToken(), |
| r.name); |
| } |
| } |
| else { |
| // rule ref has no args |
| if ( r!=null && r.argActionAST!=null ) { |
| // but rule def has args |
| ErrorManager.grammarError( |
| ErrorManager.MSG_MISSING_RULE_ARGS, |
| grammar, |
| refAST.getToken(), |
| r.name); |
| } |
| } |
| } |
| else if ( refAST.getType()==ANTLRParser.TOKEN_REF ) { |
| if ( grammar.type!=Grammar.LEXER ) { |
| if ( argsAST!=null ) { |
| // args on a token ref not in a lexer rule |
| ErrorManager.grammarError( |
| ErrorManager.MSG_ARGS_ON_TOKEN_REF, |
| grammar, |
| refAST.getToken(), |
| refAST.getText()); |
| } |
| return; // ignore token refs in nonlexers |
| } |
| if ( argsAST!=null ) { |
| // tokenRef[args]; ref has args |
| if ( r!=null && r.argActionAST==null ) { |
| // but token rule def has no args |
| ErrorManager.grammarError( |
| ErrorManager.MSG_RULE_HAS_NO_ARGS, |
| grammar, |
| argsAST.getToken(), |
| r.name); |
| } |
| } |
| else { |
| // token ref has no args |
| if ( r!=null && r.argActionAST!=null ) { |
| // but token rule def has args |
| ErrorManager.grammarError( |
| ErrorManager.MSG_MISSING_RULE_ARGS, |
| grammar, |
| refAST.getToken(), |
| r.name); |
| } |
| } |
| } |
| } |
| |
| /** Rules in tree grammar that use -> rewrites and are spitting out |
| * templates via output=template and then use rewrite=true must only |
| * use -> on alts that are simple nodes or trees or single rule refs |
| * that match either nodes or trees. The altAST is the ALT node |
| * for an ALT. Verify that its first child is simple. Must be either |
| * ( ALT ^( A B ) <end-of-alt> ) or ( ALT A <end-of-alt> ) or |
| * other element. |
| * |
| * Ignore predicates in front and labels. |
| */ |
| public void ensureAltIsSimpleNodeOrTree(GrammarAST altAST, |
| GrammarAST elementAST, |
| int outerAltNum) |
| { |
| if ( isValidSimpleElementNode(elementAST) ) { |
| GrammarAST next = (GrammarAST)elementAST.getNextSibling(); |
| if ( !isNextNonActionElementEOA(next)) { |
| ErrorManager.grammarWarning(ErrorManager.MSG_REWRITE_FOR_MULTI_ELEMENT_ALT, |
| grammar, |
| next.token, |
| new Integer(outerAltNum)); |
| } |
| return; |
| } |
| switch ( elementAST.getType() ) { |
| case ANTLRParser.ASSIGN : // labels ok on non-rule refs |
| case ANTLRParser.PLUS_ASSIGN : |
| if ( isValidSimpleElementNode(elementAST.getChild(1)) ) { |
| return; |
| } |
| break; |
| case ANTLRParser.ACTION : // skip past actions |
| case ANTLRParser.SEMPRED : |
| case ANTLRParser.SYN_SEMPRED : |
| case ANTLRParser.BACKTRACK_SEMPRED : |
| case ANTLRParser.GATED_SEMPRED : |
| ensureAltIsSimpleNodeOrTree(altAST, |
| (GrammarAST)elementAST.getNextSibling(), |
| outerAltNum); |
| return; |
| } |
| ErrorManager.grammarWarning(ErrorManager.MSG_REWRITE_FOR_MULTI_ELEMENT_ALT, |
| grammar, |
| elementAST.token, |
| new Integer(outerAltNum)); |
| } |
| |
| protected boolean isValidSimpleElementNode(Tree t) { |
| switch ( t.getType() ) { |
| case ANTLRParser.TREE_BEGIN : |
| case ANTLRParser.TOKEN_REF : |
| case ANTLRParser.CHAR_LITERAL : |
| case ANTLRParser.STRING_LITERAL : |
| case ANTLRParser.WILDCARD : |
| return true; |
| default : |
| return false; |
| } |
| } |
| |
| protected boolean isNextNonActionElementEOA(GrammarAST t) { |
| while ( t.getType()==ANTLRParser.ACTION || |
| t.getType()==ANTLRParser.SEMPRED ) |
| { |
| t = (GrammarAST)t.getNextSibling(); |
| } |
| if ( t.getType()==ANTLRParser.EOA ) { |
| return true; |
| } |
| return false; |
| } |
| } |