具有一个变量的数学表达式的C解析器并将其保存到函数中

时间:2017-02-27 16:08:03

标签: c parsing

我知道有像boost.spirit这样的库或来自http://jamesgregson.blogspot.de/2012/06/mathematical-expression-parser-in-c.html的表达式解析器以及许多其他库。

但是,我的表达式在循环中被评估了很多次,每次运行解析器似乎效率很低。

假设我的表达式字符串是"exp(-0.5*(t-t_0)^2/(b^2))"(简单高斯), 其中t_0b是常量,且t(=时间)在循环内变化。

而不是数百万次调用解析器,我希望能够以某种方式将表达式保存到接受一个参数(对于变量t)的函数中,该函数然后计算表达式。

您是否知道如何做到这一点或是否可以完成?

3 个答案:

答案 0 :(得分:6)

您想要达到的目标是完全可行的。例如,您可以从表达式构建AST(https://en.wikipedia.org/wiki/Abstract_syntax_tree),其中树的某些节点表示变量。

然后,对某些变量值的表达式的评估对应于该树的评估。

实际上,大多数解析器都会在内部生成这样的树,您可能会找到一些库来生成一个符合您需要的AST,或者您可能想自己编写它(另请参阅https://en.wikipedia.org/wiki/Shunting-yard_algorithm)。 Here是一个生成AST的表达式解析器的简单示例(虽然在Java中)。

答案 1 :(得分:2)

将(string)表达式转换为抽象语法树,并在每次迭代中解释它,而不是每次都解析它。如果这还不够,那么找到合适的字节代码表示并将抽象语法树编译为字节代码列表并将其提供给(字节代码)解释器。如果仍然没有足够的性能,那么编译您的抽象语法树或您的字节代码到机器代码,使用(平台相关的)方式让操作系统知道它的可执行代码并调用它。

正如您可能已经猜到的那样,每一步都会增加难度和复杂性。但这是一个很好的学习项目。对于生产代码,您可以考虑使用llvm作为机器代码编译任务的字节代码。

答案 2 :(得分:2)

我完全同意上面提到的关于创建AST并在每次迭代时对其进行评估的所有答案。到目前为止,这是做你想做的最好(也是最正确)的方式。

但是我为了生活而编写编译器,我不禁提出另一个有趣的解决方案。当你说要创建一个接受1个参数并返回结果的“函数”时,我抬起眉头。

让我们尝试做到这一点。

我们将首先为我们的“功能”分配一些内存。

现在让我们假设4k字节就足够了。 所以我们从做

开始
void* my_func = malloc(4k);

现在这是我们需要使区域可执行的真正功能。这取决于您的操作系统,您必须调用正确的系统调用。 您所要做的就是为此页面授予执行权限。

现在我们解析表示表达式的字符串。 我在这里假设WIN 64 fastcall调用约定。你可以使用自己的。 所以参数t将在%rcx中,thr结果将在%rax中返回。

现在让我们举个例子 - t * 2 + 5

所以我们将有汇编 -

imulq $2, %rcx, %rcx
addq $5, %rcx
movq %rcx, %rax
retq

现在我们将它组装成my_func

中的等效字节

所以你会得到一些 -

strcpy((char*)my_func, "\x48\x6B\xC9\x02\x48\x83\xC1\x05\x48\x89\C8\C3\0");

只需要一个字符串,您就可以在解析时构建缓冲区。 但是你明白了。

如果需要更多内存,您可以分配两倍大小并复制内容。

最后你需要做的就是在循环中调用你的“函数” -

typedef int (*func_type)(int);
for(t=0; t<N; t++)
    s=(func_type)(my_func)(t);

虽然这是最不切实际且难以实施的方法,但我向您保证,这将为您提供最佳性能(假设您生成了有效的装配)。

这是一项有趣的练习,不应该被认真对待。很高兴看到一个库为简单的表达式做这个。

另外,不要忘记释放内存并删除执行标记。

编辑:一个半最优但易于生成的策略是将堆栈用于所有操作。基本上,一旦你在解析之后构建AST,你就会从堆栈中弹出每个节点的参数,使用寄存器计算结果并推回堆栈。最终值可以弹出到%rax中。这种策略对AST的任何运行时评估仍然是有效的。 为您节省寄存器分配和指令调度的所有负担。