This commit is contained in:
ericvergnaud 2014-10-19 10:59:34 +08:00
parent 3d04a72d52
commit fe0d1e43e2
59 changed files with 415 additions and 36 deletions

View File

@ -116,9 +116,168 @@ public class Generator {
list.add(buildParserExec());
list.add(buildParseTrees());
list.add(buildSemPredEvalLexer());
list.add(buildSemPredEvalParser());
return list;
}
private TestFile buildSemPredEvalParser() throws Exception {
TestFile file = new TestFile("SemPredEvalParser");
file.addParserTest(input, "SimpleValidate", "T", "s",
"x",
"",
"line 1:0 no viable alternative at input 'x'\n");
file.addParserTest(input, "SimpleValidate2", "T", "s",
"3 4 x",
"alt 2\n" + "alt 2\n",
"line 1:4 no viable alternative at input 'x'\n");
file.addParserTest(input, "AtomWithClosureInTranslatedLRRule", "T", "start",
"a+b+a",
"",
null);
file.addParserTest(input, "ValidateInDFA", "T", "s",
"x ; y",
"",
"line 1:0 no viable alternative at input 'x'\n" +
"line 1:4 no viable alternative at input 'y'\n");
file.addParserTest(input, "Simple", "T", "s",
"x y 3",
"alt 2\n" + "alt 2\n" + "alt 3\n",
null);
// Under new predicate ordering rules (see antlr/antlr4#29), the first
// alt with an acceptable config (unpredicated, or predicated and evaluates
// to true) is chosen.
file.addParserTest(input, "Order", "T", "s",
"x y",
"alt 1\n" + "alt 1\n",
null);
// We have n-2 predicates for n alternatives. pick first alt
file.addParserTest(input, "2UnpredicatedAlts", "T", "s",
"x; y",
"alt 1\n" +
"alt 1\n",
"line 1:0 reportAttemptingFullContext d=0 (a), input='x'\n" +
"line 1:0 reportAmbiguity d=0 (a): ambigAlts={1, 2}, input='x'\n" +
"line 1:3 reportAttemptingFullContext d=0 (a), input='y'\n" +
"line 1:3 reportAmbiguity d=0 (a): ambigAlts={1, 2}, input='y'\n");
file.addParserTest(input, "2UnpredicatedAltsAndOneOrthogonalAlt", "T", "s",
"34; x; y",
"alt 1\n" + "alt 2\n" + "alt 2\n",
"line 1:4 reportAttemptingFullContext d=0 (a), input='x'\n" +
"line 1:4 reportAmbiguity d=0 (a): ambigAlts={2, 3}, input='x'\n" +
"line 1:7 reportAttemptingFullContext d=0 (a), input='y'\n" +
"line 1:7 reportAmbiguity d=0 (a): ambigAlts={2, 3}, input='y'\n");
// The parser consumes ID and moves to the 2nd token INT.
// To properly evaluate the predicates after matching ID INT,
// we must correctly see come back to starting index so LT(1) works
file.addParserTest(input, "RewindBeforePredEval", "T", "s",
"y 3 x 4",
"alt 2\n" + "alt 1\n",
null);
// checks that we throw exception if all alts
// are covered with a predicate and none succeeds
file.addParserTest(input, "NoTruePredsThrowsNoViableAlt", "T", "s",
"y 3 x 4",
"",
"line 1:0 no viable alternative at input 'y'\n");
file.addParserTest(input, "ToLeft", "T", "s",
"x x y",
"alt 2\n" + "alt 2\n" + "alt 2\n",
null);
file.addParserTest(input, "UnpredicatedPathsInAlt", "T", "s",
"x 4",
"alt 1\n",
null);
file.addParserTest(input, "ActionHidesPreds", "T", "s",
"x x y",
"alt 1\n" + "alt 1\n" + "alt 1\n",
null);
/** In this case, we use predicates that depend on global information
* like we would do for a symbol table. We simply execute
* the predicates assuming that all necessary information is available.
* The i++ action is done outside of the prediction and so it is executed.
*/
file.addParserTest(input, "ToLeftWithVaryingPredicate", "T", "s",
"x x y",
"i=1\n" + "alt 2\n" + "i=2\n" + "alt 1\n" + "i=3\n" + "alt 2\n",
null);
/**
* In this case, we're passing a parameter into a rule that uses that
* information to predict the alternatives. This is the special case
* where we know exactly which context we are in. The context stack
* is empty and we have not dipped into the outer context to make a decision.
*/
file.addParserTest(input, "PredicateDependentOnArg", "T", "s",
"a b",
"alt 2\n" + "alt 1\n",
null);
/** In this case, we have to ensure that the predicates are not
tested during the closure after recognizing the 1st ID. The
closure will fall off the end of 'a' 1st time and reach into the
a[1] rule invocation. It should not execute predicates because it
does not know what the parameter is. The context stack will not
be empty and so they should be ignored. It will not affect
recognition, however. We are really making sure the ATN
simulation doesn't crash with context object issues when it
encounters preds during FOLLOW.
*/
file.addParserTest(input, "PredicateDependentOnArg2", "T", "s",
"a b",
"",
null);
// uses ID ';' or ID '.' lookahead to solve s. preds not tested.
file.addParserTest(input, "DependentPredNotInOuterCtxShouldBeIgnored", "T", "s",
"a;",
"alt 2\n",
null);
file.addParserTest(input, "IndependentPredNotPassedOuterCtxToAvoidCastException", "T", "s",
"a;",
"alt 2\n",
null);
/** During a global follow operation, we still collect semantic
* predicates as long as they are not dependent on local context
*/
file.addParserTest(input, "PredsInGlobalFOLLOW", "T", "s",
"a!",
"eval=true\n" + /* now we are parsing */ "parse\n",
null);
/** We cannot collect predicates that are dependent on local context if
* we are doing a global follow. They appear as if they were not there at all.
*/
file.addParserTest(input, "DepedentPredsInGlobalFOLLOW","T", "s",
"a!",
"eval=true\n" + "parse\n",
null);
/** Regular non-forced actions can create side effects used by semantic
* predicates and so we cannot evaluate any semantic predicate
* encountered after having seen a regular action. This includes
* during global follow operations.
*/
file.addParserTest(input, "ActionsHidePredsInGlobalFOLLOW", "T", "s",
"a!",
"eval=true\n" + "parse\n",
null);
file.addParserTestsWithErrors(input, "PredTestedEvenWhenUnAmbig", "T", "primary",
"abc", "ID abc\n", null,
"enum", "", "line 1:0 no viable alternative at input 'enum'\n");
/**
* This is a regression test for antlr/antlr4#218 "ANTLR4 EOF Related Bug".
* https://github.com/antlr/antlr4/issues/218
*/
file.addParserTest(input, "DisabledAlternative", "T", "cppCompilationUnit",
"hello",
"",
null);
/** Loopback doesn't eval predicate at start of alt */
file.addParserTestsWithErrors(input, "PredFromAltTestedInLoopBack", "T", "file_",
"s\n\n\nx\n",
"(file_ (para (paraContent s) \n \n) (para (paraContent \n x \n)) <EOF>)\n",
"line 5:2 mismatched input '<EOF>' expecting '\n'\n",
"s\n\n\nx\n\n",
"(file_ (para (paraContent s) \n \n) (para (paraContent \n x) \n \n) <EOF>)\n",
null);
return file;
}
private TestFile buildSemPredEvalLexer() throws Exception {
TestFile file = new TestFile("SemPredEvalLexer");
file.addLexerTest(input, "DisableRule", "L",

View File

@ -1,4 +1,4 @@
grammar M; // uses no rules from the import
import S;
s : 'b'{<invoke_foo()>}; // gS is import pointer
s : 'b'{<Invoke_foo()>}; // gS is import pointer
WS : (' '|'\n') -> skip ;

View File

@ -1,5 +1,5 @@
parser grammar S;
@members {
<declare_foo()>
<Declare_foo()>
}
a : B;

View File

@ -1,5 +1,5 @@
grammar <grammarName>;
s @after {<dumpDFA()>}
s @after {<DumpDFA()>}
: ID | ID {} ;
ID : 'a'..'z'+;
WS : (' '|'\t'|'\n')+ -> skip ;

View File

@ -1,5 +1,5 @@
grammar <grammarName>;
s @after {<dumpDFA()>}
s @after {<DumpDFA()>}
: '$' a | '@' b ;
a : e ID ;
b : e INT ID ;

View File

@ -1,5 +1,5 @@
grammar <grammarName>;
s @after {<dumpDFA()>}
s @after {<DumpDFA()>}
: ('$' a | '@' b)+ ;
a : e ID ;
b : e INT ID ;

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {<LL_EXACT_AMBIG_DETECTION()>}
@after {<dumpDFA()>}
@after {<DumpDFA()>}
: '{' stat* '}' ;
stat: 'if' ID 'then' stat ('else' ID)?
| 'return

View File

@ -1,5 +1,5 @@
grammar <grammarName>;
s @after {<dumpDFA()>}
s @after {<DumpDFA()>}
: a;
a : e ID ;
b : e INT ID ;

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
s @after {<ToStringTree("$ctx"):writeln()>} : a ;
a : a {<True()>}? ID
a : a {<true>}? ID
| ID
;
ID : 'a'..'z'+ ;

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
@parser::header {
<importListener(grammarName)>
<ImportListener(grammarName)>
}
@parser::members {

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
@parser::header {
<importListener(grammarName)>
<ImportListener(grammarName)>
}
@parser::members {

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
@parser::header {
<importListener(grammarName)>
<ImportListener(grammarName)>
}
@parser::members {

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
@parser::header {
<importListener(grammarName)>
<ImportListener(grammarName)>
}
@parser::members {

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
@parser::header {
<importListener(grammarName)>
<ImportListener(grammarName)>
}
@parser::members {

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>

View File

@ -1,12 +1,12 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>
}
: r=a ;
a : 'x' {
<writeRuleInvocationStack()>
<WriteRuleInvocationStack()>
} ;

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s
@init {
<buildParseTrees()>
<BuildParseTrees()>
}
@after {
<ToStringTree("$r.ctx"):writeln()>

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
@parser::members{
<declareContextListGettersFunction()>
<DeclareContextListGettersFunction()>
}
s : (a | b)+;
a : 'a' {<write("'a'")>};

View File

@ -1,6 +1,6 @@
grammar <grammarName>;
start : ID ':' expr;
expr : primary expr? {<pass()>} | expr '->' ID;
expr : primary expr? {<Pass()>} | expr '->' ID;
primary : ID;
ID : [a-z]+;
;

View File

@ -10,5 +10,5 @@ SOFTWARE: 'software';
WS : ' ' -> skip ;
acClass
@init
{<getExpectedTokenNames():writeln()>}
{<GetExpectedTokenNames():writeln()>}
: ;

View File

@ -1,2 +1,2 @@
grammar <grammarName>;
a : 'a' ('b'|'z'{<pass()>})* 'c';
a : 'a' ('b'|'z'{<Pass()>})* 'c';

View File

@ -1,2 +1,2 @@
grammar <grammarName>;
a : 'a' ('b'|'z'{<pass()>})* 'c' ;
a : 'a' ('b'|'z'{<Pass()>})* 'c' ;

View File

@ -1,2 +1,2 @@
grammar <grammarName>;
a : 'a' ('b'|'z'{<pass()>})*;
a : 'a' ('b'|'z'{<Pass()>})*;

View File

@ -1,2 +1,2 @@
grammar <grammarName>;
a : 'a' ('b'|'z'{<pass()>})* 'c' ;
a : 'a' ('b'|'z'{<Pass()>})* 'c' ;

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
ifStatement
@after {
<local()>items = $ctx.elseIfStatement()
<LocalVar()>items = $ctx.elseIfStatement()
}
: 'if' expression
( ( 'then'

View File

@ -1,7 +1,7 @@
grammar <grammarName>;
s : stmt EOF ;
stmt : ifStmt | ID;
ifStmt : 'if' ID stmt ('else' stmt | { <inputLANotEqual("1", "ELSE")> }?);
ifStmt : 'if' ID stmt ('else' stmt | { <LANotEquals("1", "ELSE")> }?);
ELSE : 'else';
ID : [a-zA-Z]+;
WS : [ \\n\\t]+ -> skip;

View File

@ -1,5 +1,5 @@
grammar <grammarName>;
s @after { <dumpDFA()> }
s @after { <DumpDFA()> }
: ID | ID INT ID ;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;

View File

@ -1,5 +1,5 @@
lexer grammar <grammarName>;
E1 : 'enum' { <False()> }? ;
E2 : 'enum' { <True()> }? ; // winner not E1 or ID
E1 : 'enum' { <false> }? ;
E2 : 'enum' { <true> }? ; // winner not E1 or ID
ID : 'a'..'z'+ ;
WS : (' '|'\n') -> skip;

View File

@ -1,4 +1,4 @@
lexer grammar <grammarName>;
ENUM : [a-z]+ { <False()> }? ;
ENUM : [a-z]+ { <false> }? ;
ID : [a-z]+ ;
WS : (' '|'\n') -> skip;

View File

@ -1,4 +1,4 @@
lexer grammar <grammarName>;
ENUM : 'enum' { <False()> }? ;
ENUM : 'enum' { <false> }? ;
ID : 'a'..'z'+ ;
WS : (' '|'\n') -> skip;

View File

@ -0,0 +1,9 @@
grammar <grammarName>;
s : {<LL_EXACT_AMBIG_DETECTION()>} a ';' a; // do 2x: once in ATN, next in DFA
a : ID {<writeln("\"alt 1\"")>}
| ID {<writeln("\"alt 2\"")>}
| {<false>}? ID {<writeln("\"alt 3\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,10 @@
grammar <grammarName>;
s : {<LL_EXACT_AMBIG_DETECTION()>} a ';' a ';' a;
a : INT {<writeln("\"alt 1\"")>}
| ID {<writeln("\"alt 2\"")>} // must pick this one for ID since pred is false
| ID {<writeln("\"alt 3\"")>}
| {<false>}? ID {console.log(\"alt 4\");}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,9 @@
grammar <grammarName>;
@members {<InitMember("i","0")>}
s : a+ ;
a : {<SetMember("i","1")>} ID {<MemberEquals("i","1")>}? {<writeln("\"alt 1\"")>}
| {<SetMember("i","2")>} ID {<MemberEquals("i","2")>}? {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,11 @@
grammar <grammarName>;
@members {
this.p = function(v) {
<Declare_pred()>
}
s : e {} {<true:Invoke_pred()>}? {<writeln("\"parse\"")>} '!' ;
t : e {} {<false:Invoke_pred()>}? ID ;
e : ID | ; // non-LL(1) so we use ATN
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,6 @@
grammar <grammarName>;
start : e[0] EOF;
e[int _p]
: ( 'a' | 'b'+ ) ( {3 >= $_p}? '+' e[4] )*
;

View File

@ -0,0 +1,11 @@
grammar <grammarName>;
@members {
<Declare_pred()>
}
s : a[99] ;
a[int i] : e {<ValEquals("$i","99"):Invoke_pred()>}? {<writeln("\"parse\"")>} '!' ;
b[int i] : e {<ValEquals("$i","99"):Invoke_pred()>}? ID ;
e : ID | ; // non-LL(1) so we use ATN
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,11 @@
grammar <grammarName>;
s : b[2] ';' | b[2] '.' ; // decision in s drills down to ctx-dependent pred in a;
b[int i] : a[i] ;
a[int i]" +
: {<ValEquals("$i","1")>}? ID {<writeln("\"alt 1\"")>}
| {<ValEquals("$i","2")>}? ID {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,5 @@
grammar <grammarName>;
cppCompilationUnit : content+ EOF;
content: anything | {<false>}? .;
anything: ANY_CHAR;
ANY_CHAR: [_a-zA-Z0-9];

View File

@ -0,0 +1,10 @@
grammar <grammarName>;
s : b ';' | b '.' ;
b : a ;
a
: {<false>}? ID {<writeln("\"alt 1\"")>}
| {<true>}? ID {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,8 @@
grammar <grammarName>;
s : a a;
a : {<false>}? ID INT {<writeln("\"alt 1\"")>}
| {<false>}? ID INT {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,10 @@
grammar <grammarName>;
s : a {} a; // do 2x: once in ATN, next in DFA;
// action blocks lookahead from falling off of 'a'
// and looking into 2nd 'a' ref. !ctx dependent pred
a : ID {<writeln("\"alt 1\"")>}
| {<true>}? ID {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,9 @@
grammar <grammarName>;
file_
@after {<ToStringTree("$ctx"):writeln()>}
: para para EOF ;
para: paraContent NL NL ;
paraContent : ('s'|'x'|{<LANotEquals("2","NL")>}? NL)+ ;
NL : '\n' ;
s : 's' ;
X : 'x' ;

View File

@ -0,0 +1,8 @@
grammar <grammarName>;
@members {<InitMember("enumKeyword",true)>}
primary
: ID {<writeln("\"ID \"+$ID.text")>}
| {!<GetMember("enumKeyword")>}? 'enum' {<writeln("\"enum\"")>}
;
ID : [a-z]+ ;
WS : [ \t\n\r]+ -> skip ;

View File

@ -0,0 +1,10 @@
grammar <grammarName>;
@members {i=0}
s : a[2] a[1];
"a[int i]" +
" : {<ValEquals("$i","1")>}? ID {<writeln("\"alt 1\"")>}
| {<ValEquals("$i","2")>}? ID {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,10 @@
grammar <grammarName>;
@members {i=0}
s : a[2] a[1];
a[int i]" +
: {<ValEquals("$i","1")>}? ID {<writeln("\"alt 1\"")>}
| {<ValEquals("$i","2")>}? ID {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,10 @@
grammar <grammarName>;
@members {
<Declare_pred()>
}
s : e {<true:Invoke_pred()>}? {<writeln("\"parse\"")>} '!' ;
t : e {<false:Invoke_pred()>}? ID ;
e : ID | ; // non-LL(1) so we use ATN
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,8 @@
grammar <grammarName>;
s : a a;
a : {<LTEquals("1", "\"x\"")>}? ID INT {<writeln("\"alt 1\"")>}
| {<LTEquals("1", "\"y\"")>}? ID INT {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,9 @@
grammar <grammarName>;
s : a a a; // do 3x: once in ATN, next in DFA then INT in ATN
a : {<false>}? ID {<writeln("\"alt 1\"")>}
| {<true>}? ID {<writeln("\"alt 2\"")>}
| INT {<writeln("\"alt 3\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,8 @@
grammar <grammarName>;
s : a ;
a : {<false>}? ID {<writeln("\"alt 1\"")>}
| {<true>}? INT {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,8 @@
grammar <grammarName>;
s : a a a;
a : {<false>}? ID {<writeln("\"alt 1\"")>}
| {<true>}? INT {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,8 @@
grammar <grammarName>;
s : a+ ;
a : {<false>}? ID {<writeln("\"alt 1\"")>}
| {<true>}? ID {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,9 @@
grammar <grammarName>;
@members {this.i=0}
s : ({<AddMember("i","1")>\n<PlusMember("\"i=\"","i"):writeln()>} a)+ ;
a : {<ModMemberEquals("i","2","0")>}? ID {<writeln("\"alt 1\"")>}
| {<ModMemberNotEquals("i","2","0")>}? ID {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,12 @@
grammar <grammarName>;
s : a {<writeln("\"alt 1\"")>}
| b {<writeln("\"alt 2\"")>}
;
a : {<false>}? ID INT
| ID INT
;
b : ID ID
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;

View File

@ -0,0 +1,11 @@
grammar <grammarName>;
s : a ';' a;
// ';' helps us to resynchronize without consuming
// 2nd 'a' reference. We our testing that the DFA also
// throws an exception if the validating predicate fails
a : {<false>}? ID {<writeln("\"alt 1\"")>}
| {<true>}? INT {<writeln("\"alt 2\"")>}
;
ID : 'a'..'z'+ ;
INT : '0'..'9'+;
WS : (' '|'\n') -> skip ;