ANTLR:减去表达式优先级和与 Grun 不同的结果
ANTLR: minus expression precedence and different results with Grun
我有这样的语法:
/* entry point */
parse: expr EOF;
expr
: value # argumentArithmeticExpr
| l=expr operator=(MULT|DIV) r=expr # multdivArithmeticExpr
| l=expr operator=(PLUS|MINUS) r=expr # addsubtArithmeticExpr
| operator=('-'|'+') r=expr # minusPlusArithmeticExpr
| IDENTIFIER '(' (expr ( COMMA expr )* ) ? ')'# functionExpr
| LPAREN expr RPAREN # parensArithmeticExpr
;
value
: number
| variable
| string // contains date
| bool
| null_value
;
/* Atomes */
bool
: BOOL
;
variable
: VARIABLE
;
string
: STRING_LITERAL
;
number
: ('+'|'-')? NUMERIC_LITERAL
;
null_value
: NULL // TODO: test this
;
IDENTIFIER
: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*
;
NUMERIC_LITERAL
: DIGIT+ ( '.' DIGIT* )? ( E [-+]? DIGIT+ )? // ex: 0.05e3
| '.' DIGIT+ ( E [-+]? DIGIT+ )? // ex: .05e3
;
INT: DIGIT+;
STRING_LITERAL
: '\'' ( ~'\'' | '\'\'' )* '\''
| '"' ( ~'"' | '""' )* '"'
;
VARIABLE
: LBRACKET ( ~']' | ' ')* RBRACKET
;
现在,我想解析这个:
-1.3 * 5 + -2 * 7
有了 Grun,我明白了:
antlr4 formula.g4 && javac *.java && time grun formula parse -gui
-1.3*5 + -2*7
^D
看起来不错,我会很高兴。
但是在我的 Java 代码中,我使用访问者模式被这样调用:
visitMinusPlusArithmeticExpr -1.3*5+-2*7 // ugh ?? sees "- (1.3 * 5 + - 2 * 7 )" instead of "(-1.3*5) + (-2*7)"
visitAddsubtArithmeticExpr 1.3*5+-2*7
visitMultdivArithmeticExpr 1.3*5
visitArgumentArithmeticExpr 1.3
visitNumber 1.3
visitArgumentArithmeticExpr 5
visitValue 5
visitNumber 5
visitMinusPlusArithmeticExpr -2*7 // UHG? should see a MultDiv with -2 and 7
visitMultdivArithmeticExpr 2*7
visitArgumentArithmeticExpr 2
visitValue 2
visitNumber 2
visitArgumentArithmeticExpr 7
visitValue 7
visitNumber 7
这意味着我没有得到我的负数 (-1.3),而是我不应该得到的 'minus expression'。
为什么我的 Java 结果与 Grun 不同?我已验证语法已重新编译,并且我这样使用我的解析器:
formulaLexer lexer = new formulaLexer(new ANTLRInputStream(s));
formulaParser parser = new formulaParser(new CommonTokenStream(lexer));
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
parser.setErrorHandler(new BailErrorStrategy()); // will throw exceptions on failure
formula = tryParse(parser);
if( formula == null && errors.isEmpty() ){
// the parsing failed, retry in LL mode
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
parser.reset();
tryParse(parser);
}
我已禁用 SLL 模式以验证这是否不是问题所在,结果是一样的。
我认为这可能是一个优先级问题,但在我的 expr
中,我指定首先匹配 value
,然后仅匹配 minusPlusArithmeticExpr
.
我不明白如何检测这个 'minus' 表达式而不是我的 'negative value'。你能检查一下吗?
此外,为什么 Grun 显示正确的行为而不是我的 Java 代码?
编辑
按照评论的建议,我修改了语法如下:
expr
: value # argumentArithmeticExpr
| (PLUS|MINUS) expr # plusMinusExpr
| l=expr operator=(MULT|DIV) r=expr # multdivArithmeticExpr
| l=expr operator=(PLUS|MINUS) r=expr # addsubtArithmeticExpr
| function=IDENTIFIER '(' (expr ( COMMA expr )* ) ? ')'# functionExpr
| '(' expr ')' # parensArithmeticExpr
;
但是现在,我想优化我在某处有一个“-1.3”的情况。
我不知道如何正确执行,因为当我登陆 visitMinusPlusAritmeticExpr
时,我必须检查终端节点是否为数字。
这是我在调试时得到的:
ctx = {formulaParser$PlusMinusExprContext@929} "[16]"
children = {ArrayList@955} size = 2
0 = {TerminalNodeImpl@962} "-"
1 = {formulaParser$ArgumentArithmeticExprContext@963} "[21 16]"
children = {ArrayList@967} size = 1
0 = {formulaParser$ValueContext@990} "[22 21 16]"
children = {ArrayList@992} size = 1
0 = {formulaParser$NumberContext@997} "[53 22 21 16]"
children = {ArrayList@999} size = 1
0 = {TerminalNodeImpl@1004} "1.3"
我想我应该沿着树走下去,并判断终端节点是否是一个数字,但这看起来很麻烦。你知道如何在不影响我的代码易读性的情况下做到这一点吗?
好的,对于那些感兴趣的人,卢卡斯和巴特得到了答案,我的实现是这样的:
expr
: value # argumentArithmeticExpr
| (PLUS|MINUS) expr # plusMinusExpr
| l=expr operator=(MULT|DIV) r=expr # multdivArithmeticExpr
| l=expr operator=(PLUS|MINUS) r=expr # addsubtArithmeticExpr
| function=IDENTIFIER '(' (expr ( COMMA expr )* ) ? ')'# functionExpr
| '(' expr ')' # parensArithmeticExpr
;
而在 plusMinusExpr 的访问者中:
@Override
public Formula visitPlusMinusExpr(formulaParser.PlusMinusExprContext ctx) {
if( debug ) LOG.log(Level.INFO, "visitPlusMinusExpr " + ctx.getText());
Formula formulaExpr = visit(ctx.expr());
if( ctx.MINUS() == null ) return formulaExpr;
else {
if(formulaExpr instanceof DoubleFormula){
// optimization for numeric values: we don't return "(0.0 MINUS THEVALUE)" but directly "-THEVALUE"
Double v = - ((DoubleFormula) formulaExpr).getValue();
return new DoubleFormula( v );
} else {
return ArithmeticOperator.MINUS( 0, formulaExpr);
}
}
}
我有这样的语法:
/* entry point */
parse: expr EOF;
expr
: value # argumentArithmeticExpr
| l=expr operator=(MULT|DIV) r=expr # multdivArithmeticExpr
| l=expr operator=(PLUS|MINUS) r=expr # addsubtArithmeticExpr
| operator=('-'|'+') r=expr # minusPlusArithmeticExpr
| IDENTIFIER '(' (expr ( COMMA expr )* ) ? ')'# functionExpr
| LPAREN expr RPAREN # parensArithmeticExpr
;
value
: number
| variable
| string // contains date
| bool
| null_value
;
/* Atomes */
bool
: BOOL
;
variable
: VARIABLE
;
string
: STRING_LITERAL
;
number
: ('+'|'-')? NUMERIC_LITERAL
;
null_value
: NULL // TODO: test this
;
IDENTIFIER
: ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*
;
NUMERIC_LITERAL
: DIGIT+ ( '.' DIGIT* )? ( E [-+]? DIGIT+ )? // ex: 0.05e3
| '.' DIGIT+ ( E [-+]? DIGIT+ )? // ex: .05e3
;
INT: DIGIT+;
STRING_LITERAL
: '\'' ( ~'\'' | '\'\'' )* '\''
| '"' ( ~'"' | '""' )* '"'
;
VARIABLE
: LBRACKET ( ~']' | ' ')* RBRACKET
;
现在,我想解析这个:
-1.3 * 5 + -2 * 7
有了 Grun,我明白了:
antlr4 formula.g4 && javac *.java && time grun formula parse -gui
-1.3*5 + -2*7
^D
看起来不错,我会很高兴。
但是在我的 Java 代码中,我使用访问者模式被这样调用:
visitMinusPlusArithmeticExpr -1.3*5+-2*7 // ugh ?? sees "- (1.3 * 5 + - 2 * 7 )" instead of "(-1.3*5) + (-2*7)"
visitAddsubtArithmeticExpr 1.3*5+-2*7
visitMultdivArithmeticExpr 1.3*5
visitArgumentArithmeticExpr 1.3
visitNumber 1.3
visitArgumentArithmeticExpr 5
visitValue 5
visitNumber 5
visitMinusPlusArithmeticExpr -2*7 // UHG? should see a MultDiv with -2 and 7
visitMultdivArithmeticExpr 2*7
visitArgumentArithmeticExpr 2
visitValue 2
visitNumber 2
visitArgumentArithmeticExpr 7
visitValue 7
visitNumber 7
这意味着我没有得到我的负数 (-1.3),而是我不应该得到的 'minus expression'。
为什么我的 Java 结果与 Grun 不同?我已验证语法已重新编译,并且我这样使用我的解析器:
formulaLexer lexer = new formulaLexer(new ANTLRInputStream(s));
formulaParser parser = new formulaParser(new CommonTokenStream(lexer));
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
parser.setErrorHandler(new BailErrorStrategy()); // will throw exceptions on failure
formula = tryParse(parser);
if( formula == null && errors.isEmpty() ){
// the parsing failed, retry in LL mode
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
parser.reset();
tryParse(parser);
}
我已禁用 SLL 模式以验证这是否不是问题所在,结果是一样的。
我认为这可能是一个优先级问题,但在我的 expr
中,我指定首先匹配 value
,然后仅匹配 minusPlusArithmeticExpr
.
我不明白如何检测这个 'minus' 表达式而不是我的 'negative value'。你能检查一下吗?
此外,为什么 Grun 显示正确的行为而不是我的 Java 代码?
编辑
按照评论的建议,我修改了语法如下:
expr
: value # argumentArithmeticExpr
| (PLUS|MINUS) expr # plusMinusExpr
| l=expr operator=(MULT|DIV) r=expr # multdivArithmeticExpr
| l=expr operator=(PLUS|MINUS) r=expr # addsubtArithmeticExpr
| function=IDENTIFIER '(' (expr ( COMMA expr )* ) ? ')'# functionExpr
| '(' expr ')' # parensArithmeticExpr
;
但是现在,我想优化我在某处有一个“-1.3”的情况。
我不知道如何正确执行,因为当我登陆 visitMinusPlusAritmeticExpr
时,我必须检查终端节点是否为数字。
这是我在调试时得到的:
ctx = {formulaParser$PlusMinusExprContext@929} "[16]"
children = {ArrayList@955} size = 2
0 = {TerminalNodeImpl@962} "-"
1 = {formulaParser$ArgumentArithmeticExprContext@963} "[21 16]"
children = {ArrayList@967} size = 1
0 = {formulaParser$ValueContext@990} "[22 21 16]"
children = {ArrayList@992} size = 1
0 = {formulaParser$NumberContext@997} "[53 22 21 16]"
children = {ArrayList@999} size = 1
0 = {TerminalNodeImpl@1004} "1.3"
我想我应该沿着树走下去,并判断终端节点是否是一个数字,但这看起来很麻烦。你知道如何在不影响我的代码易读性的情况下做到这一点吗?
好的,对于那些感兴趣的人,卢卡斯和巴特得到了答案,我的实现是这样的:
expr
: value # argumentArithmeticExpr
| (PLUS|MINUS) expr # plusMinusExpr
| l=expr operator=(MULT|DIV) r=expr # multdivArithmeticExpr
| l=expr operator=(PLUS|MINUS) r=expr # addsubtArithmeticExpr
| function=IDENTIFIER '(' (expr ( COMMA expr )* ) ? ')'# functionExpr
| '(' expr ')' # parensArithmeticExpr
;
而在 plusMinusExpr 的访问者中:
@Override
public Formula visitPlusMinusExpr(formulaParser.PlusMinusExprContext ctx) {
if( debug ) LOG.log(Level.INFO, "visitPlusMinusExpr " + ctx.getText());
Formula formulaExpr = visit(ctx.expr());
if( ctx.MINUS() == null ) return formulaExpr;
else {
if(formulaExpr instanceof DoubleFormula){
// optimization for numeric values: we don't return "(0.0 MINUS THEVALUE)" but directly "-THEVALUE"
Double v = - ((DoubleFormula) formulaExpr).getValue();
return new DoubleFormula( v );
} else {
return ArithmeticOperator.MINUS( 0, formulaExpr);
}
}
}