Java 简单算术示例上的ANTLR4访问者模式
我是一个完全的ANTLR4新手,所以请原谅我的无知。我遇到了一个定义了非常简单的算术表达式语法的地方。它看起来像:Java 简单算术示例上的ANTLR4访问者模式,java,antlr,antlr4,lexer,Java,Antlr,Antlr4,Lexer,我是一个完全的ANTLR4新手,所以请原谅我的无知。我遇到了一个定义了非常简单的算术表达式语法的地方。它看起来像: grammar Expressions; start : expr ; expr : left=expr op=('*'|'/') right=expr #opExpr | left=expr op=('+'|'-') right=expr #opExpr | atom=INT #atomExpr ; INT : ('0'..'9')
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 : left=expr op=('*'|'/') right=expr #opExpr
| left=expr op=('+'|'-') right=expr #opExpr
| '(' expr ')' #parenExpr
| atom=INT #atomExpr
;
接下来我要添加的是对括号的支持。因此,我修改了expr的expr
,如下所示:
expr : '(' expr ')' #opExpr
| left=expr op=('*'|'/') right=expr #opExpr
| left=expr op=('+'|'-') right=expr #opExpr
| atom=INT #atomExpr
;
不幸的是,上面的代码失败了,因为当遇到括号时,op
,left
和right
三个属性都为空(NPE失败)
我想我可以通过定义一个新属性来解决这个问题,例如,圆括号=”('expr')”
,然后在访问者代码中处理这个问题。然而,对于我来说,用一个完整的额外节点类型来表示括号中的表达式似乎有些过分。一个更简单但更丑陋的解决方案是在visitoexpr
方法的开头添加以下代码行:
if (ctx.op == null) return visit(ctx.getChild(1)); // 0 and 2 are the parentheses!
我一点也不喜欢上面的内容,因为它非常脆弱,并且高度依赖于语法结构
我想知道是否有一种方法可以告诉ANTLR只“吃”括号,并像对待孩子一样对待表达式。有?有更好的方法吗
注意:我的最终目标是扩展该示例,以包括本身可以包含算术表达式的布尔表达式,例如,(2+4*3)/10>=11
,也就是说,算术表达式之间的关系(,=,~=,等等)可以定义原子布尔表达式。这是直截了当的,我已经草拟了语法,但我对括号也有同样的问题,也就是说,我需要能够写一些东西,比如(我还将添加对变量的支持):
((2+4*x)/10>=11)|(x>1&x当然,只要给它贴上不同的标签就行了。毕竟,替代的('expr')
不是#opExpr
:
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 : left=expr op=('*'|'/') right=expr #opExpr
| left=expr op=('+'|'-') right=expr #opExpr
| '(' expr ')' #parenExpr
| atom=INT #atomExpr
;
在你的访客中,你会这样做:
公共类EvalVisitor扩展ExpressionBaseVisitor{
@凌驾
公共整数visitOpExpr(@NotNull ExpressionsParser.OpExprContext ctx){
int left=访问(ctx.left);
int right=访问(ctx.right);
字符串op=ctx.op.getText();
开关(操作字符(0)){
大小写“*”:返回左*右;
大小写“/”:返回左/右;
大小写“+”:返回左+右;
案例“-”:返回左-右;
默认值:抛出新的IllegalArgumentException(“未知运算符”+op);
}
}
@凌驾
公共整数visitStart(@NotNull ExpressionsParser.StartContext ctx){
返回此文件。访问(ctx.expr());
}
@凌驾
公共整数visitAtomExpr(@NotNull ExpressionsParser.AtomExprContext ctx){
返回Integer.valueOf(ctx.getText());
}
@凌驾
公共整数VisitPartnerExpr(@NotNull ExpressionsParser.ParenExprContext ctx){
返回此文件。访问(ctx.expr());
}
公共静态void main(字符串[]args){
字符串表达式=“2*(3+4)”;
ExpressionsLexer lexer=新的ExpressionsLexer(CharStreams.fromString(expression));
ExpressionsParser=newexpressionsparser(newcommontokenstream(lexer));
ParseTree=parser.start();
整数答案=新建EvalVisitor()。访问(树);
System.out.printf(“%s=%s\n”,表达式,答案);
}
}
如果运行上面的类,您将看到以下输出:
2 * (3 + 4) = 14
2*(3+4)=14我已经将以上内容移植到Python访问者,甚至Python侦听器
Python侦听器
Python访问者
我明白了。所以我想没有办法让ANTLR去掉中间节点?原则上这里也有一个空间效率的问题,不是吗?我不完全确定我是否知道你的意思,但没有办法处理括号中的替代方案:ANTLR只是用于解析,所有其他逻辑/求值都必须通过解释来处理我的意思是,如果你写这篇文章:((((((((((((((2+3))的话)
,而不是2+3
,语法显然是有效的,但由于所有的括号节点,树占用的空间要大得多。我只是很惊讶没有一种方法来定义短路,以便将前一个表达式转换为后一个表达式。啊,我明白了。是的,抱歉,没有这样的功能来重写解析tr明白了。你建议的改变还不错。谢谢!
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()