Antlr4:评估数学函数f(x)

时间:2014-11-04 18:30:33

标签: java eclipse parsing antlr evaluate

最后几天我正在研究我的语法,能够计算出正常的表达式,如:2 + 2 * 5; 2 ^ 2或设置变量,如y = 5 + 5;等。

现在我要解析像f(a,b)= 2 * a * b这样的函数;然后像f(5,7)一样打电话给他们; 我有一些麻烦。

所以我想说我尝试解析这样的函数声明:

function:name=Var'(' varNames=(MultVar|Var) ')' '=' (Var|Number) (operator=('*'|'/'|'+'|'-') (Var|Number))* SEMI;

所以这种(有点)有效,但我认为它有点“脏”或其他什么。 因此我正在与一个听众合作,当我在“exitFunction”时,我真的不知道如何最好地处理这个函数,所以我可以评估像f(5,7)这样的东西;很容易。

我有一个名为“Function.java”的Java类,其方法为"public double eval(double... args)"

所以现在我拥有属性String arguments; String expression; String name;然后我需要在Listener中花费大量时间并尝试在String中找到正确的参数等。所以很多substring()和indexOf()等只是试图找到名称,参数和表达式。然后我将函数保存在Hashmap中。

在我的解析器中,函数调用看起来像:

functioncall: name=Vart '('para=(MultipleNumbers) ')' SEMI;

MultipleNumbers将是:

MultipleNumber: Number(',' Number)+;
Lexer中的

。 那么我尝试从String中获取参数,并在函数中替换它们。然后我有一个正常的表达式,我的程序可以再次解决。

由于这对我来说似乎太难了,而且几乎不可能获得所有正确的“子串”等,我认为必须有更好的方法来实现这样的事情。 特别是当我想做的事情:

 f(a,b)=2*a+b; a=5; f(a,5)
它越来越难,因为我不能混合数字和变量。那么有没有一种很好的方法来实现“功能评估器”。也许我可以在Hashmap中保存整个树,然后只需更改树中的变量并解析它或? 认为我的语法是非常可怕的以及任务。

由于我过去并没有真正使用过antlr,所以我很感激每一个帮助。 希望可以有人帮帮我。抱歉我的英语不好,我希望你能理解我。

亲切的问候

FelRPI

1 个答案:

答案 0 :(得分:3)

这样做的一种方法是将Concrete语法树解析为抽象语法树。然后你的函数对象存储AST并在调用时解析它,这通常要简单得多。考虑您的示例f(a, b) = 2 * a * b,这将被解析为类似的具体语法树:

Concrete Syntax Tree

当然你可以理解它,但解析起来并不容易!表达式2 * a * b非常类似于字符串,通过查看树来了解运算符的优先级(2 + a * b表示2 + (a * b)还是(2 + a) * b?)并且以正确的顺序遍历它需要一些时间。

但是,我们只需解析一次就可以构建更可靠,更便于机器理解的东西:

Abstract Syntax Tree

哦,是的,现在它很容易解析:使用params.length参数调用它,你可以在HashMap中设置它们或者代表你的范围,然后用参数调用“function”* 2和表达式*(a,b),这是另一个函数,因此我们通过将ab传递给“函数”*来调用它。当然,这很容易扩展到用户定义的函数。

考虑函数abs (value) = max(value, -value),我们将构建一个类似于:

的AST

Abs function AST

解析AST很简单,没问题。但是建造它呢?如果我们将所有运算符都视为函数(大多数类型为(num, num) -> num,某些(一元)类型为(num) -> num),则也不要太难。我们对此树中的节点有一个非常稳定的定义:

interface AstNode {
   double eval(Scope scope); // you can look at scope as a HashMap<String, double>
}

class TerminalNode : AstNode {
   String varName;
   double constantValue;

   public TerminalNode(String varName) {
      this.varName = varName;
   }
   public TerminalNode(double constantValue) {
      this.constantValue = constantValue;
      this.varName = null;
   }

   public double eval(Scope scope) {
      if (varName == null) return constantValue;
      return scope.get(varName);
   }
}

class CallNode : AstNode {
   Function target;
   String[] parameters;
   AstNode[] children;

   public CallNode(Function target, String[] parameters, AstNode[] children) {
      this.target = target;
      this.parameters = parameters;
      this.children = children;
   }

   public double eval(Scope scope) {
      double[] subExpressions = new double[children.length];
      Scope innerScope = new Scope(scope); // creates a copy
      for (int i = 0; i < children.length; i++) {
         // I'm using the copy here because of the edge-case: f(x) = g(x) + x; g(x) = x * 2;
         // In this case, the x variable is overriden in the innerScope when we resolve g(x)
         // but we "stored" the previous x value in the "outerScope" so we can add it later
         subExpressions[i] = children[i].eval(scope);
         innerScope.set(target.getParamName(i), subExpressions[i]);
      }
      // usually, you could do target.getNode().eval(innerScope)
      // however, this would limit the target function to only be user-defined functions
      // we leave this way so you can override the Function's eval method to a built-in
      return target.eval(innerScope);
   }
}

这可能过于简单化,但却是一个很好的起点。正如您所注意到的,CallNode有其他AstNode个孩子,所以它有点无限递归,当每个孩子都是TerminalNode(变量或常数)时会被破坏。如代码中所述,您可以通过实例化或扩展类来构建运算符作为Function类的成员。当然,这取决于您的Function实施。另一种方法是构建另一个AstNode类,BuiltinNode(或甚至所有节点PlusNodeDivideNode等)以使用基元来解决调用。


添加此内容以回答评论“如何使用构建的AST”。假设您有一个名为Function的{​​{1}}对象,它存储表达式g的AST。如何实现f(x, y) = 2 * a * b的价值?

为此,我们使用f(4, 2)对象(或Scope来解决它的问题)。我们为确定其参数的函数创建了一个范围,然后我们使用AST调用它,它将使用此范围作为其内部级别。

代码看起来像:

HashMap<String, double>

要解决此Scope scope = new Scope(); scope.set("x", 4); scope.set("y", 2); // remember I stored the function f on the object g, just to make a distinction // also, note this is equivalent to g.getNode().eval(scope), because the function stored in g is not a built-in! System.out.println(g.eval(scope)); 查询,AST将预先设置范围eval(我们创建它),并使用{x: 4, y: 2}和{调用函数*(a, b) {1}}。要解决第一个a=2函数调用,我们需要解决它的参数(b=x*y*)。解决a很容易,因为它是一个终端节点(b将立即返回终端)。要解决a,我们需要调用内部函数的eval,生成一个新范围eval,其中b{x: 4, y: 2, a: x, b: y}是第二个{{1}的参数功能。 ab都将作为终端节点求解,然后第二次调用*可以计算其值(通过内置函数计算a = { {1}})并将其返回给调用者,该调用者是第一个b函数的*

现在拥有4*2范围,其中8b*{x: 4, y: 2, a: 2, b: 8}以及x的参数到y函数。设置完所有参数后,我们可以调用f内置函数,产生a,这是函数的结果!

http://ironcreek.net/phpsyntaxtree 生成的图片