/*
* [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.analysis;
import org.antlr.grammar.v3.ANTLRParser;
import org.antlr.misc.IntSet;
import org.antlr.misc.IntervalSet;
import org.antlr.tool.Grammar;
import org.antlr.tool.Rule;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Created by IntelliJ IDEA.
* User: parrt
* Date: Dec 31, 2007
* Time: 1:31:16 PM
* To change this template use File | Settings | File Templates.
*/
public class LL1Analyzer {
/** 0 if we hit end of rule and invoker should keep going (epsilon) */
public static final int DETECT_PRED_EOR = 0;
/** 1 if we found a nonautobacktracking pred */
public static final int DETECT_PRED_FOUND = 1;
/** 2 if we didn't find such a pred */
public static final int DETECT_PRED_NOT_FOUND = 2;
public Grammar grammar;
/** Used during LOOK to detect computation cycles */
protected Set<NFAState> lookBusy = new HashSet<NFAState>();
public Map<NFAState, LookaheadSet> FIRSTCache = new HashMap<NFAState, LookaheadSet>();
public Map<Rule, LookaheadSet> FOLLOWCache = new HashMap<Rule, LookaheadSet>();
public LL1Analyzer(Grammar grammar) {
this.grammar = grammar;
}
/*
public void computeRuleFIRSTSets() {
if ( getNumberOfDecisions()==0 ) {
createNFAs();
}
for (Iterator it = getRules().iterator(); it.hasNext();) {
Rule r = (Rule)it.next();
if ( r.isSynPred ) {
continue;
}
LookaheadSet s = FIRST(r);
System.out.println("FIRST("+r.name+")="+s);
}
}
*/
/*
public Set<String> getOverriddenRulesWithDifferentFIRST() {
// walk every rule in this grammar and compare FIRST set with
// those in imported grammars.
Set<String> rules = new HashSet();
for (Iterator it = getRules().iterator(); it.hasNext();) {
Rule r = (Rule)it.next();
//System.out.println(r.name+" FIRST="+r.FIRST);
for (int i = 0; i < delegates.size(); i++) {
Grammar g = delegates.get(i);
Rule importedRule = g.getRule(r.name);
if ( importedRule != null ) { // exists in imported grammar
// System.out.println(r.name+" exists in imported grammar: FIRST="+importedRule.FIRST);
if ( !r.FIRST.equals(importedRule.FIRST) ) {
rules.add(r.name);
}
}
}
}
return rules;
}
public Set<Rule> getImportedRulesSensitiveToOverriddenRulesDueToLOOK() {
Set<String> diffFIRSTs = getOverriddenRulesWithDifferentFIRST();
Set<Rule> rules = new HashSet();
for (Iterator it = diffFIRSTs.iterator(); it.hasNext();) {
String r = (String) it.next();
for (int i = 0; i < delegates.size(); i++) {
Grammar g = delegates.get(i);
Set<Rule> callers = g.ruleSensitivity.get(r);
// somebody invokes rule whose FIRST changed in subgrammar?
if ( callers!=null ) {
rules.addAll(callers);
//System.out.println(g.name+" rules "+callers+" sensitive to "+r+"; dup 'em");
}
}
}
return rules;
}
*/
/*
public LookaheadSet LOOK(Rule r) {
if ( r.FIRST==null ) {
r.FIRST = FIRST(r.startState);
}
return r.FIRST;
}
*/
/** From an NFA state, s, find the set of all labels reachable from s.
* Used to compute follow sets for error recovery. Never computes
* a FOLLOW operation. FIRST stops at end of rules, returning EOR, unless
* invoked from another rule. I.e., routine properly handles
*
* a : b A ;
*
* where b is nullable.
*
* We record with EOR_TOKEN_TYPE if we hit the end of a rule so we can
* know at runtime (when these sets are used) to start walking up the
* follow chain to compute the real, correct follow set (as opposed to
* the FOLLOW, which is a superset).
*
* This routine will only be used on parser and tree parser grammars.
*/
public LookaheadSet FIRST(NFAState s) {
//System.out.println("> FIRST("+s.enclosingRule.name+") in rule "+s.enclosingRule);
lookBusy.clear();
LookaheadSet look = _FIRST(s, false);
//System.out.println("< FIRST("+s.enclosingRule.name+") in rule "+s.enclosingRule+"="+look.toString(this.grammar));
return look;
}
public LookaheadSet FOLLOW(Rule r) {
//System.out.println("> FOLLOW("+r.name+") in rule "+r.startState.enclosingRule);
LookaheadSet f = FOLLOWCache.get(r);
if ( f!=null ) {
return f;
}
f = _FIRST(r.stopState, true);
FOLLOWCache.put(r, f);
//System.out.println("< FOLLOW("+r+") in rule "+r.startState.enclosingRule+"="+f.toString(this.grammar));
return f;
}
public LookaheadSet LOOK(NFAState s) {
if ( NFAToDFAConverter.debug ) {
System.out.println("> LOOK("+s+")");
}
lookBusy.clear();
LookaheadSet look = _FIRST(s, true);
// FOLLOW makes no sense (at the moment!) for lexical rules.
if ( grammar.type!=Grammar.LEXER && look.member(Label.EOR_TOKEN_TYPE) ) {
// avoid altering FIRST reset as it is cached
LookaheadSet f = FOLLOW(s.enclosingRule);
f.orInPlace(look);
f.remove(Label.EOR_TOKEN_TYPE);
look = f;
//look.orInPlace(FOLLOW(s.enclosingRule));
}
else if ( grammar.type==Grammar.LEXER && look.member(Label.EOT) ) {
// if this has EOT, lookahead is all char (all char can follow rule)
//look = new LookaheadSet(Label.EOT);
look = new LookaheadSet(IntervalSet.COMPLETE_SET);
}
if ( NFAToDFAConverter.debug ) {
System.out.println("< LOOK("+s+")="+look.toString(grammar));
}
return look;
}
protected LookaheadSet _FIRST(NFAState s, boolean chaseFollowTransitions) {
/*
System.out.println("_LOOK("+s+") in rule "+s.enclosingRule);
if ( s.transition[0] instanceof RuleClosureTransition ) {
System.out.println("go to rule "+((NFAState)s.transition[0].target).enclosingRule);
}
*/
if ( !chaseFollowTransitions && s.isAcceptState() ) {
if ( grammar.type==Grammar.LEXER ) {
// FOLLOW makes no sense (at the moment!) for lexical rules.
// assume all char can follow
return new LookaheadSet(IntervalSet.COMPLETE_SET);
}
return new LookaheadSet(Label.EOR_TOKEN_TYPE);
}
if ( lookBusy.contains(s) ) {
// return a copy of an empty set; we may modify set inline
return new LookaheadSet();
}
lookBusy.add(s);
Transition transition0 = s.transition[0];
if ( transition0==null ) {
return null;
}
if ( transition0.label.isAtom() ) {
int atom = transition0.label.getAtom();
return new LookaheadSet(atom);
}
if ( transition0.label.isSet() ) {
IntSet sl = transition0.label.getSet();
return new LookaheadSet(sl);
}
// compute FIRST of transition 0
LookaheadSet tset = null;
// if transition 0 is a rule call and we don't want FOLLOW, check cache
if ( !chaseFollowTransitions && transition0 instanceof RuleClosureTransition ) {
tset = FIRSTCache.get((NFAState)transition0.target);
}
// if not in cache, must compute
if ( tset==null ) {
tset = _FIRST((NFAState)transition0.target, chaseFollowTransitions);
// save FIRST cache for transition 0 if rule call
if ( !chaseFollowTransitions && transition0 instanceof RuleClosureTransition ) {
FIRSTCache.put((NFAState)transition0.target, tset);
}
}
LookaheadSet tsetCached = tset; // tset is stored in cache. We can't return the same instance
// did we fall off the end?
if ( grammar.type!=Grammar.LEXER && tset.member(Label.EOR_TOKEN_TYPE) ) {
if ( transition0 instanceof RuleClosureTransition ) {
// we called a rule that found the end of the rule.
// That means the rule is nullable and we need to
// keep looking at what follows the rule ref. E.g.,
// a : b A ; where b is nullable means that LOOK(a)
// should include A.
RuleClosureTransition ruleInvocationTrans =
(RuleClosureTransition)transition0;
// remove the EOR and get what follows
//tset.remove(Label.EOR_TOKEN_TYPE);
NFAState following = ruleInvocationTrans.followState;
LookaheadSet fset = _FIRST(following, chaseFollowTransitions);
fset.orInPlace(tset); // tset cached; or into new set
fset.remove(Label.EOR_TOKEN_TYPE);
tset = fset;
}
}
Transition transition1 = s.transition[1];
if ( transition1!=null ) {
LookaheadSet tset1 =
_FIRST((NFAState)transition1.target, chaseFollowTransitions);
tset1.orInPlace(tset);
tset = tset1;
}
// never return a cached set; clone
return tset==tsetCached ? new LookaheadSet(tset) : tset;
}
/** Is there a non-syn-pred predicate visible from s that is not in
* the rule enclosing s? This accounts for most predicate situations
* and lets ANTLR do a simple LL(1)+pred computation.
*
* TODO: what about gated vs regular preds?
*/
public boolean detectConfoundingPredicates(NFAState s) {
lookBusy.clear();
Rule r = s.enclosingRule;
return _detectConfoundingPredicates(s, r, false) == DETECT_PRED_FOUND;
}
protected int _detectConfoundingPredicates(NFAState s,
Rule enclosingRule,
boolean chaseFollowTransitions)
{
//System.out.println("_detectNonAutobacktrackPredicates("+s+")");
if ( !chaseFollowTransitions && s.isAcceptState() ) {
if ( grammar.type==Grammar.LEXER ) {
// FOLLOW makes no sense (at the moment!) for lexical rules.
// assume all char can follow
return DETECT_PRED_NOT_FOUND;
}
return DETECT_PRED_EOR;
}
if ( lookBusy.contains(s) ) {
// return a copy of an empty set; we may modify set inline
return DETECT_PRED_NOT_FOUND;
}
lookBusy.add(s);
Transition transition0 = s.transition[0];
if ( transition0==null ) {
return DETECT_PRED_NOT_FOUND;
}
if ( !(transition0.label.isSemanticPredicate()||
transition0.label.isEpsilon()) ) {
return DETECT_PRED_NOT_FOUND;
}
if ( transition0.label.isSemanticPredicate() ) {
//System.out.println("pred "+transition0.label);
SemanticContext ctx = transition0.label.getSemanticContext();
SemanticContext.Predicate p = (SemanticContext.Predicate)ctx;
if ( p.predicateAST.getType() != ANTLRParser.BACKTRACK_SEMPRED ) {
return DETECT_PRED_FOUND;
}
}
/*
if ( transition0.label.isSemanticPredicate() ) {
System.out.println("pred "+transition0.label);
SemanticContext ctx = transition0.label.getSemanticContext();
SemanticContext.Predicate p = (SemanticContext.Predicate)ctx;
// if a non-syn-pred found not in enclosingRule, say we found one
if ( p.predicateAST.getType() != ANTLRParser.BACKTRACK_SEMPRED &&
!p.predicateAST.enclosingRuleName.equals(enclosingRule.name) )
{
System.out.println("found pred "+p+" not in "+enclosingRule.name);
return DETECT_PRED_FOUND;
}
}
*/
int result = _detectConfoundingPredicates((NFAState)transition0.target,
enclosingRule,
chaseFollowTransitions);
if ( result == DETECT_PRED_FOUND ) {
return DETECT_PRED_FOUND;
}
if ( result == DETECT_PRED_EOR ) {
if ( transition0 instanceof RuleClosureTransition ) {
// we called a rule that found the end of the rule.
// That means the rule is nullable and we need to
// keep looking at what follows the rule ref. E.g.,
// a : b A ; where b is nullable means that LOOK(a)
// should include A.
RuleClosureTransition ruleInvocationTrans =
(RuleClosureTransition)transition0;
NFAState following = ruleInvocationTrans.followState;
int afterRuleResult =
_detectConfoundingPredicates(following,
enclosingRule,
chaseFollowTransitions);
if ( afterRuleResult == DETECT_PRED_FOUND ) {
return DETECT_PRED_FOUND;
}
}
}
Transition transition1 = s.transition[1];
if ( transition1!=null ) {
int t1Result =
_detectConfoundingPredicates((NFAState)transition1.target,
enclosingRule,
chaseFollowTransitions);
if ( t1Result == DETECT_PRED_FOUND ) {
return DETECT_PRED_FOUND;
}
}
return DETECT_PRED_NOT_FOUND;
}
/** Return predicate expression found via epsilon edges from s. Do
* not look into other rules for now. Do something simple. Include
* backtracking synpreds.
*/
public SemanticContext getPredicates(NFAState altStartState) {
lookBusy.clear();
return _getPredicates(altStartState, altStartState);
}
protected SemanticContext _getPredicates(NFAState s, NFAState altStartState) {
//System.out.println("_getPredicates("+s+")");
if ( s.isAcceptState() ) {
return null;
}
// avoid infinite loops from (..)* etc...
if ( lookBusy.contains(s) ) {
return null;
}
lookBusy.add(s);
Transition transition0 = s.transition[0];
// no transitions
if ( transition0==null ) {
return null;
}
// not a predicate and not even an epsilon
if ( !(transition0.label.isSemanticPredicate()||
transition0.label.isEpsilon()) ) {
return null;
}
SemanticContext p = null;
SemanticContext p0;
SemanticContext p1 = null;
if ( transition0.label.isSemanticPredicate() ) {
//System.out.println("pred "+transition0.label);
p = transition0.label.getSemanticContext();
// ignore backtracking preds not on left edge for this decision
if ( ((SemanticContext.Predicate)p).predicateAST.getType() ==
ANTLRParser.BACKTRACK_SEMPRED &&
s == altStartState.transition[0].target )
{
p = null; // don't count
}
}
// get preds from beyond this state
p0 = _getPredicates((NFAState)transition0.target, altStartState);
// get preds from other transition
Transition transition1 = s.transition[1];
if ( transition1!=null ) {
p1 = _getPredicates((NFAState)transition1.target, altStartState);
}
// join this&following-right|following-down
return SemanticContext.and(p,SemanticContext.or(p0,p1));
}
}