最后几天我正在研究我的语法,能够计算出正常的表达式,如: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
答案 0 :(得分:3)
这样做的一种方法是将Concrete语法树解析为抽象语法树。然后你的函数对象存储AST并在调用时解析它,这通常要简单得多。考虑您的示例f(a, b) = 2 * a * b
,这将被解析为类似的具体语法树:
当然你可以理解它,但解析起来并不容易!表达式2 * a * b
非常类似于字符串,通过查看树来了解运算符的优先级(2 + a * b
表示2 + (a * b)
还是(2 + a) * b
?)并且以正确的顺序遍历它需要一些时间。
但是,我们只需解析一次就可以构建更可靠,更便于机器理解的东西:
哦,是的,现在它很容易解析:使用params.length
参数调用它,你可以在HashMap中设置它们或者代表你的范围,然后用参数调用“function”*
2
和表达式*(a,b)
,这是另一个函数,因此我们通过将a
和b
传递给“函数”*
来调用它。当然,这很容易扩展到用户定义的函数。
考虑函数abs (value) = max(value, -value)
,我们将构建一个类似于:
解析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
(或甚至所有节点PlusNode
,DivideNode
等)以使用基元来解决调用。
添加此内容以回答评论“如何使用构建的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}的参数功能。 a
和b
都将作为终端节点求解,然后第二次调用*
可以计算其值(通过内置函数计算a
= { {1}})并将其返回给调用者,该调用者是第一个b
函数的*
。
现在拥有4*2
范围,其中8
和b
是*
和{x: 4, y: 2, a: 2, b: 8}
以及x
的参数到y
函数。设置完所有参数后,我们可以调用f
内置函数,产生a
,这是函数的结果!