From 1bec176eaa9f7797c641b805524265142c0fe0e5 Mon Sep 17 00:00:00 2001 From: Terence Parr Date: Sat, 4 Aug 2012 14:18:57 -0700 Subject: [PATCH] Impl Sam's no viable alt avoidance idea that chooses min alt that dips into outer context. unit test --- .../v4/runtime/atn/ParserATNSimulator.java | 64 +++++++++++-------- tool/playground/T.g | 22 ++++++- .../org/antlr/v4/test/TestParseErrors.java | 19 ++++++ 3 files changed, 77 insertions(+), 28 deletions(-) diff --git a/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java b/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java index e9e96558d..25265e1f7 100755 --- a/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java +++ b/runtime/Java/src/org/antlr/v4/runtime/atn/ParserATNSimulator.java @@ -441,13 +441,12 @@ public class ParserATNSimulator extends ATNSimulator { greedy, loopsSimulateTailRecursion, fullCtx); retry_with_context_from_dfa++; - ATNConfigSet fullCtxSet = - execATNWithFullContext(dfa, s, s0_closure, - input, startIndex, - outerContext, - ATN.INVALID_ALT_NUMBER, - greedy); - return fullCtxSet.uniqueAlt; + int alt = execATNWithFullContext(dfa, s, s0_closure, + input, startIndex, + outerContext, + ATN.INVALID_ALT_NUMBER, + greedy); + return alt; } if ( s.isAcceptState ) { if ( s.predicates!=null ) { @@ -609,13 +608,18 @@ public class ParserATNSimulator extends ATNSimulator { if ( reach==null ) { // TODO: if any configs in previous dipped into outer context, that // means that input up to t actually finished entry rule - // at least for LL decision. Full LL doesn't dip into outer + // at least for SLL decision. Full LL doesn't dip into outer // so don't need special case. // We will get an error no matter what so delay until after // decision; better error message. Also, no reachable target // ATN states in SLL implies LL will also get nowhere. // If conflict in states that dip out, choose min since we // will get error no matter what. + int alt = getAltThatFinishedDecisionEntryRule(previousD.configs); + if ( alt!=ATN.INVALID_ALT_NUMBER ) { + // return w/o altering DFA + return alt; + } throw noViableAlt(input, outerContext, previous, startIndex); } @@ -680,14 +684,13 @@ public class ParserATNSimulator extends ATNSimulator { loopsSimulateTailRecursion, true); // we have write lock already, no need to relock - fullCtxSet = execATNWithFullContext(dfa, D, s0_closure, - input, startIndex, - outerContext, - D.configs.conflictingAlts.getMinElement(), - greedy); + predictedAlt = execATNWithFullContext(dfa, D, s0_closure, + input, startIndex, + outerContext, + D.configs.conflictingAlts.getMinElement(), + greedy); // not accept state: isCtxSensitive D.isCtxSensitive = true; // always force DFA to ATN simulate - predictedAlt = fullCtxSet.uniqueAlt; D.prediction = ATN.INVALID_ALT_NUMBER; addDFAEdge(dfa, previousD, t, D); return predictedAlt; // all done with preds, etc... @@ -775,13 +778,13 @@ public class ParserATNSimulator extends ATNSimulator { } // comes back with reach.uniqueAlt set to a valid alt - public ATNConfigSet execATNWithFullContext(DFA dfa, - DFAState D, // how far we got before failing over - @NotNull ATNConfigSet s0, - @NotNull TokenStream input, int startIndex, - ParserRuleContext outerContext, - int SLL_min_alt, // todo: is this in D as min ambig alts? - boolean greedy) + public int execATNWithFullContext(DFA dfa, + DFAState D, // how far we got before failing over + @NotNull ATNConfigSet s0, + @NotNull TokenStream input, int startIndex, + ParserRuleContext outerContext, + int SLL_min_alt, // todo: is this in D as min ambig alts? + boolean greedy) { // caller must have write lock on dfa retry_with_context++; @@ -810,6 +813,10 @@ public class ParserATNSimulator extends ATNSimulator { // ATN states in SLL implies LL will also get nowhere. // If conflict in states that dip out, choose min since we // will get error no matter what. + int alt = getAltThatFinishedDecisionEntryRule(previous); + if ( alt!=ATN.INVALID_ALT_NUMBER ) { + return alt; + } throw noViableAlt(input, outerContext, previous, startIndex); } reach.uniqueAlt = getUniqueAlt(reach); @@ -827,7 +834,7 @@ public class ParserATNSimulator extends ATNSimulator { if ( reach.uniqueAlt == SLL_min_alt ) { retry_with_context_predicts_same_as_alt++; } - return reach; + return reach.uniqueAlt; } // We do not check predicates here because we have checked them @@ -836,9 +843,7 @@ public class ParserATNSimulator extends ATNSimulator { // must have conflict reportAmbiguity(dfa, D, startIndex, input.index(), reach.conflictingAlts, reach); - reach.uniqueAlt = reach.conflictingAlts.getMinElement(); - - return reach; + return reach.conflictingAlts.getMinElement(); } protected ATNConfigSet computeReachSet(ATNConfigSet closure, int t, @@ -1020,6 +1025,15 @@ public class ParserATNSimulator extends ATNSimulator { return pairs; } + public int getAltThatFinishedDecisionEntryRule(ATNConfigSet configs) { + IntervalSet alts = new IntervalSet(); + for (ATNConfig c : configs) { + if ( c.reachesIntoOuterContext>0 ) alts.add(c.alt); + } + if ( alts.size()==0 ) return ATN.INVALID_ALT_NUMBER; + return alts.getMinElement(); + } + /** Look through a list of predicate/alt pairs, returning alts for the * pairs that win. A {@code NONE} predicate indicates an alt containing an * unpredicated config which behaves as "always true." If !complete diff --git a/tool/playground/T.g b/tool/playground/T.g index 2921c2f2b..79ed766b3 100644 --- a/tool/playground/T.g +++ b/tool/playground/T.g @@ -1,4 +1,20 @@ -lexer grammar T; +/* +dead end configs: +(12,1,[$]):Atom 'b'<1> +(8,2,[$],up=1):Atom '!'<4> +(20,2,[$],up=1):Atom 'a'<2> +line 1:1 no viable alternative at input 'a.' +line 1:1 mismatched input '.' expecting '!' +*/ +grammar T; -A : 'a' ; -B : 'a' ; +s : e '!' ; + +e : 'a' 'b' + | 'a' + ; + +//x : e 'a' 'c' ; + +DOT : '.' ; +WS : [ \t\r\n]+ -> skip; diff --git a/tool/test/org/antlr/v4/test/TestParseErrors.java b/tool/test/org/antlr/v4/test/TestParseErrors.java index 9a004e0d3..175de144b 100644 --- a/tool/test/org/antlr/v4/test/TestParseErrors.java +++ b/tool/test/org/antlr/v4/test/TestParseErrors.java @@ -277,4 +277,23 @@ public class TestParseErrors extends BaseTest { assertNull(this.stderrDuringParse); } + @Test public void testNoViableAltAvoidance() throws Exception { + // "a." matches 'a' to rule e but then realizes '.' won't match. + // previously would cause noviablealt. now prediction pretends to + // have "a' predict 2nd alt of e. Will get syntax error later so + // let it get farther. + String grammar = + "grammar T;\n" + + "s : e '!' ;\n" + + "e : 'a' 'b'\n" + + " | 'a'\n" + + " ;\n" + + "DOT : '.' ;\n" + + "WS : [ \\t\\r\\n]+ -> skip;\n"; + String found = execParser("T.g4", grammar, "TParser", "TLexer", "s", "a.", false); + String expecting = + "exec stderrVacuum: line 1:1 mismatched input '.' expecting '!'"; + String result = stderrDuringParse; + assertEquals(expecting, result); + } }