使用带有访问者的语法来计算算术表达式

Using a grammar with a visitor to calculate arithmetic expressions

我们在 class 中得到的语法如下所示:

grammar Calculator;

@header {
import java.util.*;
}

@parser::members {
/** "memory" for our calculator; variable/value pairs go here */
Map<String, Double> memory = new HashMap<String, Double>();
}

statlist    :   stat+   ;

stat        :   vgl NL          #printCompare                   
            |   ass NL          #printAssign                        
            |   NL              #blank
            ;

ass         : <assoc=right> VAR ('=') vgl #assign       
            ;               

vgl         :   sum(op=('<'|'>') sum)*  #compare
            ;

sum         :   prod(op=('+'|'-') prod)*    #addSub
            ;

prod        :   pot(op=('*'|'/') pot)*      #mulDiv     
            ;

pot         :<assoc=right> term(op='^' pot)?    #poten
            ;

term        :   '+' term    #add    
            |   '-' term    #subtract
            |   '(' sum ')' #parens
            |   VAR         #var
            |   INT         #int
            ;

/*Rules for the lexer */
MUL :   '*' ; 
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
BIG :   '>' ;
SML :   '<' ;
POT :   '^' ;
VAR :   [a-zA-Z]+   ;
NL  :   [\n]        ;
INT :   [0-9]+      ;


WS : [ \r\t]+ -> skip ; // skip spaces, tabs

我在翻译这些结构时遇到问题

sum         :   prod(op=('+'|'-') prod)*    #addSub

进入工作代码。目前对应的方法是这样的:

    /** prod(op=('+'|'-') prod)* */
@Override
public Double visitAddSub(CalculatorParser.AddSubContext ctx) {
    double left = visit(ctx.prod(0));
    if(ctx.op == null){
        return left;
    }
    double right = visit(ctx.prod(1));
    return (ctx.op.getType() == CalculatorParser.ADD) ? left+right : left-right;
}

当前输出如下所示

3+3+3
6.0

这显然是错误的。如何让访问者在不触及语法的情况下正确访问节点?

看看规则:

prod(op=('+'|'-') prod)*

看到 * 了吗?这意味着括号内的内容可以出现 0 次或更多次。

您的访客代码假定只有一个或两个 child prod,但不会更多。这就是您看到 6.0 的原因:解析器将 3+3+3 放入上下文中,但您的访问者只处理了 3+3 并留下了最后的 +3

所以只需对所有 opprod children 使用 while 循环,并将它们累加到结果中。

好的,在 Lucas 的帮助和 op+= 的使用下,我设法解决了我的问题。它看起来很复杂,但它确实有效。

/** prod(op+=('+'|'-') prod)* */
@Override
public Double visitAddSub(CalculatorParser.AddSubContext ctx) {
    Stack<Double> temp = new Stack<Double>();
    switch(ctx.children.size()){
    case 1: return visit(ctx.prod(0));      
    default:
        Double ret = 0.0;
        for(int i = 0; i < ctx.op.size(); i++){
            if(ctx.op.get(i).getType()==CalculatorParser.ADD){
                if(temp.isEmpty()) {
                    ret = visit(ctx.prod(i)) + visit(ctx.prod(i+1));
                    temp.push(ret);
                } else {
                    ret = temp.pop() + visit(ctx.prod(i+1));
                    temp.push(ret);
                }
            } else {
                if(temp.isEmpty()) {
                    ret = visit(ctx.prod(i)) - visit(ctx.prod(i+1));
                    temp.push(ret);
                } else {
                    ret = temp.pop() - visit(ctx.prod(i+1));
                    temp.push(ret);
                }
            }
        }
    }
    return temp.pop();
}

我们正在使用 switch-case 来确定此上下文有多少 children。如果超过 3 个,我们至少有 2 个操作员。然后我们使用单个运算符和堆栈来确定结果。