Consistent handling of element options applied to alternatives (fixes #542)

This commit is contained in:
Sam Harwell 2014-06-16 12:37:48 -05:00
parent 8f08802716
commit 6eeafacb88
9 changed files with 63 additions and 14 deletions

View File

@ -285,6 +285,12 @@ public class LeftRecursiveRuleAnalyzer extends LeftRecursiveRuleWalker {
GrammarAST alt = (GrammarAST)blk.getChildren().get(i); GrammarAST alt = (GrammarAST)blk.getChildren().get(i);
Tree first = alt.getChild(0); Tree first = alt.getChild(0);
if ( first==null ) continue; if ( first==null ) continue;
if (first.getType() == ELEMENT_OPTIONS) {
first = alt.getChild(1);
if (first == null) {
continue;
}
}
if ( first.getType()==RULE_REF && first.getText().equals(ruleName) ) return true; if ( first.getType()==RULE_REF && first.getText().equals(ruleName) ) return true;
Tree rref = first.getChild(1); Tree rref = first.getChild(1);
if ( rref!=null && rref.getType()==RULE_REF && rref.getText().equals(ruleName) ) return true; if ( rref!=null && rref.getType()==RULE_REF && rref.getText().equals(ruleName) ) return true;

View File

@ -769,8 +769,8 @@ public class ParserATNFactory implements ATNFactory {
for (Object alt : block.getChildren()) { for (Object alt : block.getChildren()) {
if ( !(alt instanceof AltAST) ) continue; if ( !(alt instanceof AltAST) ) continue;
AltAST altAST = (AltAST)alt; AltAST altAST = (AltAST)alt;
if ( altAST.getChildCount()==1 ) { if ( altAST.getChildCount()==1 || (altAST.getChildCount() == 2 && altAST.getChild(0).getType() == ANTLRParser.ELEMENT_OPTIONS) ) {
Tree e = altAST.getChild(0); Tree e = altAST.getChild(altAST.getChildCount() - 1);
if ( e.getType()==ANTLRParser.WILDCARD ) { if ( e.getType()==ANTLRParser.WILDCARD ) {
return true; return true;
} }

View File

@ -99,9 +99,9 @@ alt[boolean outerMost] returns [CodeBlockForAlt altCodeBlock, List<SrcOp> ops]
$altCodeBlock.ops = $ops = elems; $altCodeBlock.ops = $ops = elems;
controller.setCurrentBlock($altCodeBlock); controller.setCurrentBlock($altCodeBlock);
} }
^( ALT ( element {if ($element.omos!=null) elems.addAll($element.omos);} )+ ) ^( ALT elementOptions? ( element {if ($element.omos!=null) elems.addAll($element.omos);} )+ )
| ^(ALT EPSILON) | ^(ALT elementOptions? EPSILON)
{$altCodeBlock = controller.epsilon(controller.getCurrentOuterMostAlt(), outerMost);} {$altCodeBlock = controller.epsilon(controller.getCurrentOuterMostAlt(), outerMost);}
; ;

View File

@ -658,10 +658,11 @@ alternative
paraphrases.pop(); paraphrases.pop();
Grammar.setNodeOptions($tree, $o.tree); Grammar.setNodeOptions($tree, $o.tree);
} }
: o=elementOptions? : o=elementOptions?
e+=element+ -> ^(ALT<AltAST> elementOptions? $e+) ( e+=element+ -> ^(ALT<AltAST> elementOptions? $e+)
| -> ^(ALT<AltAST> EPSILON) // empty alt | -> ^(ALT<AltAST> elementOptions? EPSILON) // empty alt
; )
;
element element
@init { @init {

View File

@ -104,8 +104,8 @@ alternative returns [ATNFactory.Handle p]
@init {List<ATNFactory.Handle> els = new ArrayList<ATNFactory.Handle>();} @init {List<ATNFactory.Handle> els = new ArrayList<ATNFactory.Handle>();}
: ^(LEXER_ALT_ACTION a=alternative lexerCommands) : ^(LEXER_ALT_ACTION a=alternative lexerCommands)
{$p = factory.lexerAltCommands($a.p,$lexerCommands.p);} {$p = factory.lexerAltCommands($a.p,$lexerCommands.p);}
| ^(ALT EPSILON) {$p = factory.epsilon($EPSILON);} | ^(ALT elementOptions? EPSILON) {$p = factory.epsilon($EPSILON);}
| ^(ALT (e=element {els.add($e.p);})+) {$p = factory.alt(els);} | ^(ALT elementOptions? (e=element {els.add($e.p);})+) {$p = factory.alt(els);}
; ;
lexerCommands returns [ATNFactory.Handle p] lexerCommands returns [ATNFactory.Handle p]
@ -200,3 +200,15 @@ terminal returns [ATNFactory.Handle p]
| ^(TOKEN_REF .) {$p = factory.tokenRef((TerminalAST)$start);} | ^(TOKEN_REF .) {$p = factory.tokenRef((TerminalAST)$start);}
| TOKEN_REF {$p = factory.tokenRef((TerminalAST)$start);} | TOKEN_REF {$p = factory.tokenRef((TerminalAST)$start);}
; ;
elementOptions
: ^(ELEMENT_OPTIONS elementOption*)
;
elementOption
: ID
| ^(ASSIGN ID ID)
| ^(ASSIGN ID STRING_LITERAL)
| ^(ASSIGN ID ACTION)
| ^(ASSIGN ID INT)
;

View File

@ -95,10 +95,10 @@ boolean inLexer = Grammar.isTokenName(currentRuleName);
GrammarTransformPipeline.setGrammarPtr(g, $tree); GrammarTransformPipeline.setGrammarPtr(g, $tree);
} }
: {inContext("RULE")}? // top-level: rule block and > 1 alt : {inContext("RULE")}? // top-level: rule block and > 1 alt
^(BLOCK ^(alt=ALT {((AltAST)$alt).altLabel==null}? setElement[inLexer]) ( ^(ALT setElement[inLexer]) )+) ^(BLOCK ^(alt=ALT elementOptions? {((AltAST)$alt).altLabel==null}? setElement[inLexer]) ( ^(ALT elementOptions? setElement[inLexer]) )+)
-> ^(BLOCK<BlockAST>[$BLOCK.token] ^(ALT<AltAST>[$BLOCK.token,"ALT"] ^(SET[$BLOCK.token, "SET"] setElement+))) -> ^(BLOCK<BlockAST>[$BLOCK.token] ^(ALT<AltAST>[$BLOCK.token,"ALT"] ^(SET[$BLOCK.token, "SET"] setElement+)))
| {!inContext("RULE")}? // if not rule block and > 1 alt | {!inContext("RULE")}? // if not rule block and > 1 alt
^(BLOCK ^(ALT setElement[inLexer]) ( ^(ALT setElement[inLexer]) )+) ^(BLOCK ^(ALT elementOptions? setElement[inLexer]) ( ^(ALT elementOptions? setElement[inLexer]) )+)
-> ^(SET[$BLOCK.token, "SET"] setElement+) -> ^(SET[$BLOCK.token, "SET"] setElement+)
; ;

View File

@ -767,7 +767,7 @@ alternative
exitAlternative((AltAST)$start); exitAlternative((AltAST)$start);
} }
: ^(ALT elementOptions? element+) : ^(ALT elementOptions? element+)
| ^(ALT EPSILON) | ^(ALT elementOptions? EPSILON)
; ;
lexerCommand lexerCommand

View File

@ -139,7 +139,7 @@ suffix
; ;
nonLeftRecur nonLeftRecur
: ^(ALT element+) // no assoc for these; ignore if <assoc=...> present : ^(ALT elementOptions? element+)
; ;
recurse recurse

View File

@ -152,6 +152,36 @@ public class TestLeftRecursion extends BaseTest {
runTests(grammar, tests, "s"); runTests(grammar, tests, "s");
} }
/**
* This is a regression test for antlr/antlr4#542 "First alternative cannot
* be right-associative".
* https://github.com/antlr/antlr4/issues/542
*/
@Test public void testTernaryExprExplicitAssociativity() throws Exception {
String grammar =
"grammar T;\n" +
"s @after {System.out.println($ctx.toStringTree(this));} : e EOF ;\n" + // must indicate EOF can follow or 'a<EOF>' won't match
"e :<assoc=right> e '*' e" +
" |<assoc=right> e '+' e" +
" |<assoc=right> e '?' e ':' e" +
" |<assoc=right> e '=' e" +
" | ID" +
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') -> skip ;\n";
String[] tests = {
"a", "(s (e a) <EOF>)",
"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 testExpressions() throws Exception { @Test public void testExpressions() throws Exception {
String grammar = String grammar =