Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/390.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 简单算术示例上的ANTLR4访问者模式_Java_Antlr_Antlr4_Lexer - Fatal编程技术网

Java 简单算术示例上的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')

我是一个完全的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  : 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()