* 1+2*3 now gives new parse tree: (e (e 1) + (e (e 2) * (e 3))) See CHANGES.txt now too

[git-p4: depot-paths = "//depot/code/antlr4/main/": change = 9833]
This commit is contained in:
parrt 2012-01-05 12:27:14 -08:00
parent 2e8a5f391a
commit 4ae23f4a64
11 changed files with 216 additions and 180 deletions

View File

@ -1,9 +1,15 @@
ANTLR v4 Honey Badger early access
Jan 5, 2012
* Deleted code to call specific listeners by mistake. added back.
Jan 4, 2012
* '_' was allowed first in symbol names in grammar
* fix unit tests
* Allow labels in left-recursive rules
* lr rules now gen only 1 rule e->e not e->e_ etc... altered tests to build parse trees.
* no more -trace, use Parser.setTrace(true)
* no more -trace, use Parser.setTrace(true)
* add context rule option; not hooked up
* 1+2*3 now gives new parse tree: (e (e 1) + (e (e 2) * (e 3)))

View File

@ -173,6 +173,22 @@ public abstract class Parser extends Recognizer<Token, v2ParserATNSimulator<Toke
public void removeParseListeners() { if ( _parseListeners!=null ) _parseListeners.clear(); }
public void triggerEnterRuleEvent() {
for (ParseTreeListener<Token> l : _parseListeners) {
l.enterEveryRule(_ctx);
_ctx.enterRule(l);
}
}
public void triggerExitRuleEvent() {
// reverse order walk of listeners
for (int i = _parseListeners.size()-1; i >= 0; i--) {
ParseTreeListener<Token> l = _parseListeners.get(i);
_ctx.exitRule(l);
l.exitEveryRule(_ctx);
}
}
/** Get number of recognition errors (lexer, parser, tree parser). Each
* recognizer tracks its own number. So parser and lexer each have
* separate count. Does not count the spurious errors found between
@ -302,22 +318,12 @@ public abstract class Parser extends Recognizer<Token, v2ParserATNSimulator<Toke
_ctx.start = _input.LT(1);
_ctx.ruleIndex = ruleIndex;
if (_buildParseTrees) addContextToParseTree();
if ( _parseListeners != null) {
for (ParseTreeListener<Token> l : _parseListeners) {
_ctx.enterRule(l);
l.enterEveryRule(_ctx);
}
}
if ( _parseListeners != null) triggerEnterRuleEvent();
}
public void exitRule(int ruleIndex) {
public void exitRule() {
// trigger event on _ctx, before it reverts to parent
if ( _parseListeners != null) {
for (ParseTreeListener<Token> l : _parseListeners) {
_ctx.exitRule(l);
l.exitEveryRule(_ctx);
}
}
if ( _parseListeners != null) triggerExitRuleEvent();
_ctx = (ParserRuleContext<Token>)_ctx.parent;
}
@ -333,6 +339,31 @@ public abstract class Parser extends Recognizer<Token, v2ParserATNSimulator<Toke
_ctx.altNum = altNum;
}
/* like enterRule but for recursive rules; no enter events for recursive rules. */
public void pushNewRecursionContext(ParserRuleContext<Token> localctx, int ruleIndex) {
_ctx = localctx;
_ctx.start = _input.LT(1);
_ctx.ruleIndex = ruleIndex;
}
public void unrollRecursionContexts(ParserRuleContext<Token> _parentctx) {
ParserRuleContext<Token> retctx = _ctx; // save current ctx (return value)
// unroll so _ctx is as it was before call to recursive method
if ( _parseListeners != null ) {
while ( _ctx != _parentctx ) {
triggerExitRuleEvent();
_ctx = (ParserRuleContext<Token>)_ctx.parent;
}
}
else {
_ctx = _parentctx;
}
// hook into tree
retctx.parent = _parentctx;
if (_buildParseTrees) _parentctx.addChild(retctx); // add return ctx into invoking rule's tree
}
public ParserRuleContext<Token> getInvokingContext(int ruleIndex) {
ParserRuleContext<Token> p = _ctx;
while ( p!=null ) {

View File

@ -37,6 +37,7 @@ recRule(ruleName, precArgDef, argName, primaryAlts, opAlts, setResultAction,
userRetvals, userRetvalAssignments) ::=
<<
<ruleName>[<precArgDef>]<if(userRetvals)> returns [<userRetvals>]<endif>
options {simrecursion_=true;}
: ( <primaryAlts; separator="\n | ">
)
<if(userRetvals)>
@ -44,7 +45,8 @@ recRule(ruleName, precArgDef, argName, primaryAlts, opAlts, setResultAction,
<userRetvalAssignments; separator="\n">
}
<endif>
( <opAlts; separator="\n | ">
( options {simrecursion_=true;}
: <opAlts; separator="\n | ">
)*
;
>>

View File

@ -175,7 +175,36 @@ RuleFunction(currentRule,code,locals,ruleCtx,altLabelCtxs,namedActions,finallyAc
}
finally {
<finallyAction>
exitRule(RULE_<currentRule.name>);
exitRule();
}
return _localctx;
}
>>
LRecursiveRuleFunction(currentRule,code,locals,ruleCtx,altLabelCtxs,namedActions,finallyAction,postamble) ::= <<
<ruleCtx>
<altLabelCtxs; separator="\n">
<if(currentRule.modifiers)><currentRule.modifiers:{f | <f> }><else>public final <endif><currentRule.ctxType> <currentRule.name>(<currentRule.args; separator=",">) throws RecognitionException {
ParserRuleContext\<Token> _parentctx = _ctx;
<currentRule.ctxType> _localctx = new <currentRule.ctxType>(_ctx, <currentRule.startState><currentRule.args:{a | , <a.name>}>);
pushNewRecursionContext(_localctx, RULE_<currentRule.name>);
<namedActions.init>
<locals; separator="\n">
try {
<code>
_localctx.stop = _input.LT(-1);
<postamble; separator="\n">
<namedActions.after>
}
catch (RecognitionException re) {
_errHandler.reportError(this, re);
_errHandler.recover(this, re);
}
finally {
<finallyAction>
unrollRecursionContexts(_parentctx);
}
return _localctx;
}
@ -322,12 +351,15 @@ setState(<choice.stateNumber>);
_errHandler.sync(this);
int _alt<choice.uniqueID> = getInterpreter().adaptivePredict(_input,<choice.decision>,_ctx);
while ( _alt<choice.uniqueID>!=<choice.exitAlt> && _alt<choice.uniqueID>!=-1 ) {
switch ( _alt<choice.uniqueID> ) {
<alts:{alt|
case <i>:
<alt>
break;}; separator="\n">
}
if ( _alt<choice.uniqueID>==1 ) {
<if(choice.isForRecursiveRule)>
if ( _parseListeners!=null ) triggerExitRuleEvent();
_localctx = new <currentRule.name>Context(_parentctx, <currentRule.startState>, _p);
_localctx.addChild(_ctx);
pushNewRecursionContext(_localctx, RULE_<currentRule.name>);
<endif>
<alts> <! should only be one !>
}
setState(<choice.loopBackStateNumber>);
_errHandler.sync(this);
_alt<choice.uniqueID> = getInterpreter().adaptivePredict(_input,<choice.decision>,_ctx);
@ -512,7 +544,7 @@ labelref(x) ::= "<if(!x.isLocal)>_localctx.<endif><x.name>"
recRuleDefArg() ::= "int _p"
recRuleArg() ::= "$_p"
recRuleAltPredicate(ruleName,opPrec) ::= "<opPrec> >= <recRuleArg()>"
recRuleSetResultAction() ::= "$tree=$<ruleName>_primary.tree;"
recRuleSetResultAction() ::= "$tree=$<ruleName>.tree;"
recRuleSetReturnAction(src,name) ::= "$<name>=$<src>.<name>;"
LexerFile(lexerFile, lexer, namedActions) ::= <<

View File

@ -61,7 +61,13 @@ public class ParserFactory extends DefaultOutputModelFactory {
}
public RuleFunction rule(Rule r) {
return new RuleFunction(this, r);
GrammarAST optionNode = r.ast.getOption("simrecursion_");
if ( optionNode!=null && optionNode.getText().equals("true") ) {
return new LRecursiveRuleFunction(this, r);
}
else {
return new RuleFunction(this, r);
}
}
public CodeBlockForAlt epsilon() { return new CodeBlockForAlt(this); }

View File

@ -0,0 +1,39 @@
/*
[The "BSD license"]
Copyright (c) 2012 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.v4.codegen.model;
import org.antlr.v4.codegen.OutputModelFactory;
import org.antlr.v4.tool.Rule;
public class LRecursiveRuleFunction extends RuleFunction {
public LRecursiveRuleFunction(OutputModelFactory factory, Rule r) {
super(factory, r);
}
}

View File

@ -31,18 +31,23 @@ package org.antlr.v4.codegen.model;
import org.antlr.v4.codegen.OutputModelFactory;
import org.antlr.v4.runtime.atn.StarLoopEntryState;
import org.antlr.v4.tool.ast.BlockAST;
import org.antlr.v4.tool.ast.GrammarAST;
import java.util.List;
public class StarBlock extends Loop {
public String loopLabel;
public boolean isForRecursiveRule;
public StarBlock(OutputModelFactory factory,
GrammarAST blkOrEbnfRootAST,
List<CodeBlockForAlt> alts)
{
super(factory, blkOrEbnfRootAST, alts);
BlockAST blk = (BlockAST)ast.getChild(0);
GrammarAST optionNode = blk.getOption("simrecursion_");
isForRecursiveRule = optionNode!=null && optionNode.getText().equals("true");
loopLabel = factory.getGenerator().target.getLoopLabel(blkOrEbnfRootAST);
StarLoopEntryState star = (StarLoopEntryState)blkOrEbnfRootAST.atnState;
loopBackStateNumber = star.loopBackState.stateNumber;

View File

@ -54,6 +54,7 @@ public class StructDecl extends Decl {
public StructDecl(OutputModelFactory factory, Rule r) {
super(factory, factory.getGenerator().target.getRuleFunctionContextStructName(r));
addVisitorDispatchMethods(r);
}
public void addVisitorDispatchMethods(Rule r) {

View File

@ -233,7 +233,7 @@ public class LeftRecursiveRuleAnalyzer extends LeftRecursiveRuleWalker {
ST setResultST = codegenTemplates.getInstanceOf("recRuleSetResultAction");
ruleST.add("setResultAction", setResultST);
ruleST.add("userRetvals", retvals);
fillRetValAssignments(ruleST, "recPrimaryName");
fillRetValAssignments(ruleST);
LinkedHashMap<Integer, String> opPrecRuleAlts = new LinkedHashMap<Integer, String>();
opPrecRuleAlts.putAll(binaryAlts);
@ -354,7 +354,7 @@ public class LeftRecursiveRuleAnalyzer extends LeftRecursiveRuleWalker {
return p;
}
public void fillRetValAssignments(ST ruleST, String srcName) {
public void fillRetValAssignments(ST ruleST) {
if ( retvals==null ) return;
// complicated since we must be target-independent
@ -363,9 +363,7 @@ public class LeftRecursiveRuleAnalyzer extends LeftRecursiveRuleWalker {
for (String name : args.attributes.keySet()) {
ST setRetValST =
codegenTemplates.getInstanceOf("recRuleSetReturnAction");
ST ruleNameST = recRuleTemplates.getInstanceOf(srcName);
ruleNameST.add("ruleName", ruleName);
setRetValST.add("src", ruleNameST);
setRetValST.add("src", ruleName);
setRetValST.add("name", name);
ruleST.add("userRetvalAssignments",setRetValST);
}

View File

@ -95,12 +95,18 @@ public class BasicSemanticChecks extends GrammarTreeVisitor {
public static final Set<String> legalRuleOptions =
new HashSet<String>() {
{
add("context");
add("simrecursion_");
}
};
public static final Set<String> legalBlockOptions =
new HashSet<String>() {{add("greedy");}};
new HashSet<String>() {
{
add("greedy");
add("simrecursion_");
}
};
/** Legal options for terminal refs like ID<node=MyVarNode> */
public static final Set<String> legalTokenOptions =

View File

@ -17,7 +17,7 @@ public class TestLeftRecursion extends BaseTest {
"WS : (' '|'\\n') {skip();} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"s", "x y z", debug);
String expecting = "(s (a x y z))\n";
String expecting = "(s (a (a (a x) y) z))\n";
assertEquals(expecting, found);
}
@ -32,7 +32,7 @@ public class TestLeftRecursion extends BaseTest {
"WS : (' '|'\\n') {skip();} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"s", "x y z", debug);
String expecting = "(s (a x y z))\n";
String expecting = "(s (a (a (a x) y) z))\n";
assertEquals(expecting, found);
}
@ -50,81 +50,20 @@ public class TestLeftRecursion extends BaseTest {
"WS : (' '|'\\n') {skip();} ;\n";
String[] tests = {
"a", "(s (e a) <EOF>)",
"a+b", "(s (e a + (e b)) <EOF>)",
"a*b", "(* a b)",
"a?b:c", "(? a b c)",
"a=b=c", "(= a (= b c))",
"a?b+c:d", "(? a (+ b c) d)",
"a?b=c:d", "(? a (= b c) d)",
"a? b?c:d : e", "(? a (? b c d) e)",
"a?b: c?d:e", "(? a b (? c d e))",
"a+b", "(s (e (e a) + (e b)) <EOF>)",
"a*b", "(s (e (e a) * (e b)) <EOF>)",
"a?b:c", "(s (e (e a) ? (e b) : (e c)) <EOF>)",
"a=b=c", "(s (e (e a) = (e (e b) = (e c))) <EOF>)",
"a?b+c:d", "(s (e (e a) ? (e (e b) + (e c)) : (e d)) <EOF>)",
"a?b=c:d", "(s (e (e a) ? (e (e b) = (e c)) : (e d)) <EOF>)",
"a? b?c:d : e", "(s (e (e a) ? (e (e b) ? (e c) : (e d)) : (e e)) <EOF>)",
"a?b: c?d:e", "(s (e (e a) ? (e b) : (e (e c) ? (e d) : (e e))) <EOF>)",
};
runTests(grammar, tests, "s");
}
@Test public void testDeclarationsUsingASTOperators() throws Exception {
String grammar =
"grammar T;\n" +
"s @after {System.out.println($ctx.toStringTree(this));} : declarator EOF ;\n" + // must indicate EOF can follow
"declarator\n" +
" : declarator '[' e ']'\n" +
" | declarator '[' ']'\n" +
" | declarator '(' ')'\n" +
" | '*' declarator\n" + // binds less tight than suffixes
" | '(' declarator ')'\n" +
" | ID\n" +
" ;\n" +
"e : INT ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+ ;\n" +
"WS : (' '|'\\n') {skip();} ;\n";
String[] tests = {
"a", "a",
"*a", "(* a)",
"**a", "(* (* a))",
"a[3]", "([ a 3)",
"b[]", "([ b)",
"(a)", "a",
"a[]()", "(( ([ a))",
"a[][]", "([ ([ a))",
"*a[]", "(* ([ a))",
"(*a)[]", "([ (* a))",
};
runTests(grammar, tests, "s");
}
@Test public void testDeclarationsUsingRewriteOperators() throws Exception {
String grammar =
"grammar T;\n" +
"s @after {System.out.println($ctx.toStringTree(this));} : declarator EOF ;\n" + // must indicate EOF can follow
"declarator\n" +
" : declarator '[' e ']'" +
" | declarator '[' ']'" +
" | declarator '(' ')'" +
" | '*' declarator" + // binds less tight than suffixes
" | '(' declarator ')'" +
" | ID" +
" ;\n" +
"e : INT ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+ ;\n" +
"WS : (' '|'\\n') {skip();} ;\n";
String[] tests = {
"a", "a",
"*a", "(* a)",
"**a", "(* (* a))",
"a[3]", "([ a 3)",
"b[]", "([ b)",
"(a)", "a",
"a[]()", "(( ([ a))",
"a[][]", "([ ([ a))",
"*a[]", "(* ([ a))",
"(*a)[]", "([ (* a))",
};
runTests(grammar, tests, "s");
}
@Test public void testExpressionsUsingASTOperators() throws Exception {
@Test public void testExpressions() throws Exception {
String grammar =
"grammar T;\n" +
"s @after {System.out.println($ctx.toStringTree(this));} : e EOF ;\n" + // must indicate EOF can follow
@ -140,59 +79,17 @@ public class TestLeftRecursion extends BaseTest {
"INT : '0'..'9'+ ;\n" +
"WS : (' '|'\\n') {skip();} ;\n";
String[] tests = {
"a", "a",
"1", "1",
"a+1", "(+ a 1)",
"a*1", "(* a 1)",
"a.b", "(. a b)",
"a.this", "(. a this)",
"a-b+c", "(+ (- a b) c)",
"a+b*c", "(+ a (* b c))",
"a.b+1", "(+ (. a b) 1)",
"-a", "(- a)",
"-a+b", "(+ (- a) b)",
"-a.b", "(- (. a b))",
"a", "(s (e a) <EOF>)",
"1", "(s (e 1) <EOF>)",
"a-1", "(s (e (e a) - (e 1)) <EOF>)",
"a.b", "(s (e (e a) . b) <EOF>)",
"a.this", "(s (e (e a) . this) <EOF>)",
"-a", "(s (e - (e a)) <EOF>)",
"-a+b", "(s (e (e - (e a)) + (e b)) <EOF>)",
};
runTests(grammar, tests, "s");
}
@Test public void testExpressionAssociativity() throws Exception {
String grammar =
"grammar T;\n" +
"s @after {System.out.println($ctx.toStringTree(this));} : e EOF ;\n" + // must indicate EOF can follow
"e\n" +
" : e '.' ID\n" +
" | '-' e\n" +
" | e ''<assoc=right> e\n" +
" | e '*' e\n" +
" | e ('+'|'-') e\n" +
" | e ('='<assoc=right> |'+='<assoc=right>) e\n" +
" | INT\n" +
" | ID\n" +
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+ ;\n" +
"WS : (' '|'\\n') {skip();} ;\n";
String[] tests = {
"a", "a",
"1", "1",
"a+1", "(+ a 1)",
"a*1", "(* a 1)",
"a.b", "(. a b)",
"a-b+c", "(+ (- a b) c)",
"a+b*c", "(+ a (* b c))",
"a.b+1", "(+ (. a b) 1)",
"-a", "(- a)",
"-a+b", "(+ (- a) b)",
"-a.b", "(- (. a b))",
"a^b^c", "(^ a (^ b c))",
"a=b=c", "(= a (= b c))",
"a=b=c+d.e","(= a (= b (+ c (. d e))))",
};
runTests(grammar, tests, "e");
}
@Test public void testJavaExpressions() throws Exception {
// Generates about 7k in bytecodes for generated e_ rule;
// Well within the 64k method limit. e_primary compiles
@ -255,34 +152,47 @@ public class TestLeftRecursion extends BaseTest {
"INT : '0'..'9'+ ;\n" +
"WS : (' '|'\\n') {skip();} ;\n";
String[] tests = {
"a", "a",
"1", "1",
"a+1", "(+ a 1)",
"a*1", "(* a 1)",
"a.b", "(. a b)",
"a-b+c", "(+ (- a b) c)",
"a|b&c", "(s (e (e a) | (e (e b) & (e c))) <EOF>)",
"(a|b)&c", "(s (e (e ( (e (e a) | (e b)) )) & (e c)) <EOF>)",
"a > b", "(s (e (e a) > (e b)) <EOF>)",
"a >> b", "(s (e (e a) >> (e b)) <EOF>)",
"(T)x", "(s (e ( (type T) ) (e x)) <EOF>)",
"new A().b", "(s (e (e new (type A) ( )) . b) <EOF>)",
"(T)t.f()", "(s (e (e ( (type T) ) (e (e t) . f)) ( )) <EOF>)",
"a.f(x)==T.c", "(s (e (e (e (e a) . f) ( (expressionList (e x)) )) == (e (e T) . c)) <EOF>)",
"a.f().g(x,1)", "(s (e (e (e (e (e a) . f) ( )) . g) ( (expressionList (e x) , (e 1)) )) <EOF>)",
"new T[((n-1) * x) + 1]", "(s (e new (type T) [ (e (e ( (e (e ( (e (e n) - (e 1)) )) * (e x)) )) + (e 1)) ]) <EOF>)",
};
runTests(grammar, tests, "s");
}
"a+b*c", "(+ a (* b c))",
"a.b+1", "(+ (. a b) 1)",
"-a", "(- a)",
"-a+b", "(+ (- a) b)",
"-a.b", "(- (. a b))",
"a^b^c", "(^ a (^ b c))",
"a=b=c", "(= a (= b c))",
"a=b=c+d.e","(= a (= b (+ c (. d e))))",
"a|b&c", "(| a (& b c))",
"(a|b)&c", "(& (| a b) c)",
"a > b", "(> a b)",
"a > 0", "(> a 0)",
"a >> b", "(>> a b)", // text is from one token
"a < b", "(< a b)",
"(T)x", "(( T x)",
"new A().b", "(. (new A () b)",
"(T)t.f()", "(( (( T (. t f)))",
"a.f(x)==T.c", "(== (( (. a f) x) (. T c))",
"a.f().g(x,1)", "(( (. (( (. a f)) g) x 1)",
"new T[((n-1) * x) + 1]", "(new T [ (+ (* (- n 1) x) 1))",
@Test public void testDeclarations() throws Exception {
String grammar =
"grammar T;\n" +
"s @after {System.out.println($ctx.toStringTree(this));} : declarator EOF ;\n" + // must indicate EOF can follow
"declarator\n" +
" : declarator '[' e ']'\n" +
" | declarator '[' ']'\n" +
" | declarator '(' ')'\n" +
" | '*' declarator\n" + // binds less tight than suffixes
" | '(' declarator ')'\n" +
" | ID\n" +
" ;\n" +
"e : INT ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+ ;\n" +
"WS : (' '|'\\n') {skip();} ;\n";
String[] tests = {
"a", "(s (declarator a) <EOF>)",
"*a", "(s (declarator * (declarator a)) <EOF>)",
"**a", "(s (declarator * (declarator * (declarator a))) <EOF>)",
"a[3]", "(s (declarator (declarator a) [ (e 3) ]) <EOF>)",
"b[]", "(s (declarator (declarator b) [ ]) <EOF>)",
"(a)", "(s (declarator ( (declarator a) )) <EOF>)",
"a[]()", "(s (declarator (declarator (declarator a) [ ]) ( )) <EOF>)",
"a[][]", "(s (declarator (declarator (declarator a) [ ]) [ ]) <EOF>)",
"*a[]", "(s (declarator * (declarator (declarator a) [ ])) <EOF>)",
"(*a)[]", "(s (declarator (declarator ( (declarator * (declarator a)) )) [ ]) <EOF>)",
};
runTests(grammar, tests, "s");
}
@ -290,7 +200,7 @@ public class TestLeftRecursion extends BaseTest {
@Test public void testReturnValueAndActions() throws Exception {
String grammar =
"grammar T;\n" +
"s @after {System.out.println($ctx.toStringTree(this));} : e {System.out.println($e.v);} ;\n" +
"s : e {System.out.println($e.v);} ;\n" +
"e returns [int v, List<String> ignored]\n" +
" : e '*' b=e {$v *= $b.v;}\n" +
" | e '+' b=e {$v += $b.v;}\n" +