ANTLR4访问者模式简单的算术例子

我是一个完整的ANTLR4新手,所以请原谅我的无知。 我遇到了这个演示文稿 ,其中定义了一个非常简单的算术表达式语法。 看起来像:

grammar Expressions; start : expr ; expr : left=expr op=('*'|'/') right=expr #opExpr | left=expr op=('+'|'-') right=expr #opExpr | atom=INT #atomExpr ; INT : ('0'..'9')+ ; WS : [ \t\r\n]+ -> skip ; 

这很好,因为它会生成一个非常简单的二叉树,可以使用访问者模式遍历,如幻灯片中所述,例如,这是访问expr的函数:

 public Integer visitOpExpr(OpExprContext ctx) { int left = visit(ctx.left); int right = visit(ctx.right); String op = ctx.op.getText(); switch (op.charAt(0)) { case '*': return left * right; case '/': return left / right; case '+': return left + right; case '-': return left - right; default: throw new IllegalArgumentException("Unkown opeator " + op); } } 

我想补充的另一件事是支持括号。 所以我修改了expr如下:

 expr : '(' expr ')' #opExpr | left=expr op=('*'|'/') right=expr #opExpr | left=expr op=('+'|'-') right=expr #opExpr | atom=INT #atomExpr ; 

不幸的是,上面的代码失败了,因为当遇到括号时, opleftright这三个属性为null(NPE失败)。

我想我可以通过定义一个新属性来解决这个问题,例如, parenthesized='(' expr ')' ,然后在访问者代码中处理它。 但是,如果有一个额外的节点类型来表示括号中的表达式,那对我来说似乎有点过分了。 一个更简单但更丑陋的解决方案是在visitOpExpr方法的开头添加以下代码行:

 if (ctx.op == null) return visit(ctx.getChild(1)); // 0 and 2 are the parentheses! 

我完全不喜欢上述内容,因为它非常脆弱并且高度依赖于语法结构。

我想知道是否有办法告诉ANTLR只是“吃掉”括号并像对待孩子一样对待表达。 在那儿? 有一个更好的方法吗?

注意 :我的最终目标是扩展示例以包含本身可以包含算术表达式的布尔表达式,例如, (2+4*3)/10 >= 11 ,即关系(,==,〜算术表达式之间的=等等可以定义primefaces布尔表达式。 这是直截了当的,我已经草拟了语法,但我的括号有同样的问题,即我需要能够编写类似的内容(我还将添加对变量的支持):

 ((2+4*x)/10 >= 11) | ( x>1 & x<3 ) 

编辑 :修复括号表达式的优先级,括号始终具有更高的优先级。

当然,只是以不同的方式标记它。 毕竟,替代'(' expr ')'不是#opExpr

 expr : left=expr op=('*'|'/') right=expr #opExpr | left=expr op=('+'|'-') right=expr #opExpr | '(' expr ')' #parenExpr | atom=INT #atomExpr ; 

在你的访问者中,你会做这样的事情:

 public class EvalVisitor extends ExpressionsBaseVisitor { @Override public Integer visitOpExpr(@NotNull ExpressionsParser.OpExprContext ctx) { int left = visit(ctx.left); int right = visit(ctx.right); String op = ctx.op.getText(); switch (op.charAt(0)) { case '*': return left * right; case '/': return left / right; case '+': return left + right; case '-': return left - right; default: throw new IllegalArgumentException("Unknown operator " + op); } } @Override public Integer visitStart(@NotNull ExpressionsParser.StartContext ctx) { return this.visit(ctx.expr()); } @Override public Integer visitAtomExpr(@NotNull ExpressionsParser.AtomExprContext ctx) { return Integer.valueOf(ctx.getText()); } @Override public Integer visitParenExpr(@NotNull ExpressionsParser.ParenExprContext ctx) { return this.visit(ctx.expr()); } public static void main(String[] args) { String expression = "2 * (3 + 4)"; ExpressionsLexer lexer = new ExpressionsLexer(new ANTLRInputStream(expression)); ExpressionsParser parser = new ExpressionsParser(new CommonTokenStream(lexer)); ParseTree tree = parser.start(); Integer answer = new EvalVisitor().visit(tree); System.out.printf("%s = %s\n", expression, answer); } } 

如果您运行上面的类,您将看到以下输出:

  2 *(3 + 4)= 14 

我已将上面的内容移植到Python Visitor甚至是Python Listener

Python监听器

 from antlr4 import * from arithmeticLexer import arithmeticLexer from arithmeticListener import arithmeticListener from arithmeticParser import arithmeticParser import sys ## grammar arithmetic; ## ## start : expr ; ## ## expr : left=expr op=('*'|'/') right=expr #opExpr ## | left=expr op=('+'|'-') right=expr #opExpr ## | '(' expr ')' #parenExpr ## | atom=INT #atomExpr ## ; ## ## INT : ('0'..'9')+ ; ## ## WS : [ \t\r\n]+ -> skip ; import codecs import sys def dump(obj): for attr in dir(obj): print("obj.%s = %r" % (attr, getattr(obj, attr))) def is_number(s): try: float(s) return True except ValueError: return False class arithmeticPrintListener(arithmeticListener): def __init__(self): self.stack = [] # Exit a parse tree produced by arithmeticParser#opExpr. def exitOpExpr(self, ctx:arithmeticParser.OpExprContext): print('exitOpExpr INP',ctx.op.text,ctx.left.getText(),ctx.right.getText()) op = ctx.op.text opchar1=op[0] right= self.stack.pop() left= self.stack.pop() if opchar1 == '*': val = left * right elif opchar1 == '/': val = left / right elif opchar1 == '+': val = left + right elif opchar1 == '-': val = left - right else: raise ValueError("Unknown operator " + op) print("exitOpExpr OUT",opchar1,left,right,val) self.stack.append(val) # Exit a parse tree produced by arithmeticParser#atomExpr. def exitAtomExpr(self, ctx:arithmeticParser.AtomExprContext): val=int(ctx.getText()) print('exitAtomExpr',val) self.stack.append(val) def main(): #lexer = arithmeticLexer(StdinStream()) expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))" lexer = arithmeticLexer(InputStream(expression)) stream = CommonTokenStream(lexer) parser = arithmeticParser(stream) tree = parser.start() printer = arithmeticPrintListener() walker = ParseTreeWalker() walker.walk(printer, tree) if __name__ == '__main__': main() 

Python访客

 from antlr4 import * from arithmeticLexer import arithmeticLexer from arithmeticVisitor import arithmeticVisitor from arithmeticParser import arithmeticParser import sys from pprint import pprint ## grammar arithmetic; ## ## start : expr ; ## ## expr : left=expr op=('*'|'/') right=expr #opExpr ## | left=expr op=('+'|'-') right=expr #opExpr ## | '(' expr ')' #parenExpr ## | atom=INT #atomExpr ## ; ## ## INT : ('0'..'9')+ ; ## ## WS : [ \t\r\n]+ -> skip ; import codecs import sys class EvalVisitor(arithmeticVisitor): def visitOpExpr(self, ctx): #print("visitOpExpr",ctx.getText()) left = self.visit(ctx.left) right = self.visit(ctx.right) op = ctx.op.text; # for attr in dir(ctx.op): ########### BEST # print("ctx.op.%s = %r" % (attr, getattr(ctx.op, attr))) #print("visitOpExpr",dir(ctx.op),left,right) opchar1=op[0] if opchar1 == '*': val = left * right elif opchar1 == '/': val = left / right elif opchar1 == '+': val = left + right elif opchar1 == '-': val = left - right else: raise ValueError("Unknown operator " + op) print("visitOpExpr",opchar1,left,right,val) return val def visitStart(self, ctx): print("visitStart",ctx.getText()) return self.visit(ctx.expr()) def visitAtomExpr(self, ctx): print("visitAtomExpr",int(ctx.getText())) return int(ctx.getText()) def visitParenExpr(self, ctx): print("visitParenExpr",ctx.getText()) return self.visit(ctx.expr()) def main(): #lexer = arithmeticLexer(StdinStream()) expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))" lexer = arithmeticLexer(InputStream(expression)) stream = CommonTokenStream(lexer) parser = arithmeticParser(stream) tree = parser.start() answer = EvalVisitor().visit(tree) print(answer) if __name__ == '__main__': main()