一种方式是通过解析您的具体语法树成抽象语法树。然后你的函数对象存储AST并在被调用时解析它,这通常要简单得多。考虑到你的榜样,f(a, b) = 2 * a * b
,这会被解析成一个类似的具体语法树:
当然,你可以把它理解很好,但它是不容易的解析!表达式2 * a * b
写得很像一个字符串,您不知道操作符的优先顺序是通过查看树(2 + a * b
表示2 + (a * b)
还是(2 + a) * b
?),并且需要一些时间以正确的顺序遍历它。
但是,我们可以分析它只有一次建立的东西更可靠,更容易为机器理解:
哦,是的,现在是很容易解析:它的调用params.length
参数,你将它们设置在一个HashMap中或代表你的示波器的任何东西,然后你调用参数2
和*(a,b)
这是另一个函数的“函数”*
,所以我们称之为“函数”*
。当然这很容易扩展到用户定义的功能。
考虑到功能abs (value) = max(value, -value)
,我们将建立类似于AST:
解析AST是容易的,OK。但是建设它呢?如果我们将所有操作符视为函数(大部分类型为(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
(变量或常量)时破坏。正如代码中所评论的那样,您可以通过实例化或扩展类来构建运算符,使其成为类的成员。当然,这取决于你的实现。另一种方法是构建另一个AstNode
类,BuiltinNode
(或甚至所有节点PlusNode
,DivideNode
等)来解决使用原语的调用。
添加此回答评论,“如何使用内置的AST”。假设您有一个名为g
的对象Function
,该对象存储表达式f(x, y) = 2 * a * b
的AST。如何达到f(4, 2)
的价值?
要做到这一点,我们使用Scope
对象(或重要的是HashMap<String, double>
)。我们为参数已经确定的函数创建一个范围,然后我们使用AST来调用它,它将使用这个范围作为它的内层。
的代码可以看起来像:
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));
为了解决这个eval
查询时,AST将有范围{x: 4, y: 2}
提前(我们创造了它),并调用该函数*(a, b)
,与a=2
和b=x*y
。要解决第一个函数调用*
,我们需要解决它的两个参数(a
和b
)。解决a
很容易,因为它是一个终端节点(eval
立即返回终端)。为了解决b
,我们需要调用内函数的eval,生成一个新的范围{x: 4, y: 2, a: x, b: y}
,其中a
和b
是第二个函数的参数。既a
和b
就解决了为终端节点,则所述第二呼叫,以*
可以计算出其值(由内置函数,其计算4*2
= 8
),并将其返回给调用者,这是b
用于第一*
功能。
现在具有像{x: 4, y: 2, a: 2, b: 8}
,其中x
和y
是f
和a
和b
的参数是在参数给*
功能的范围。通过设置所有参数,我们可以调用*
内置函数,产生16
,这是函数的结果!通过http://ironcreek.net/phpsyntaxtree
哇产生
图片,真的详细的解答,非常感谢。 但是,我用Antlr做了一个艰难的noob,所以我们可以说我发现了一个语法规则,Antlr会生成这样的发辫:http://i.stack.imgur.com/r0W8w.png 然后我用f(2 ,4)。我不完全确定,那么我需要专门做什么。这就像我的主要问题。我不知道如何“告诉”程序使用变量a和b的参数2和4。 – FelRPI 2014-11-05 16:50:39
@FelRPI添加了一个新的部分,用法说明:) – Mephy 2014-11-05 17:20:18
再次感谢,我相当印象深刻。只是想知道我是否需要自己构建AST,或者如果这是一个antlr4语法,它构建了看起来像这个http://i.stack.imgur.com/G6Q7Y.png的AST,所以我可以跳过我自己构建它们的步骤。 也许我只是困惑。现在在这个项目上工作了很多小时。今天只是一个紧张的沉船,对不起回合:P – FelRPI 2014-11-05 18:37:31