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);
        }
    }
}