最近在实现一个
表达式的evaluator,
具体来说,就是对表达式求值,算出最终的结果。
目前的这个版本的evaluator只针对常量表达式,对变量表达式则不去处理。因为对于变量表达式,是无
法在编译期间求出其最终结果的,只有等到执行阶段才具备算出结果的全部条件。
Evaluator的实现机理并不太复杂,简单来说,就是一个 递归下降的求值过程。如果把表达式的语法结构
表征为一棵语法树,那么,Evaluator的求值过程就类似于对这棵树作 后序遍历。
以常量表达式 1+2+3*4-5 为例,其相应的语法树如下:
对这个常量表达式进行evaluate求值的过程就是一个近似于后序遍历的过程: 先求出左子树的结果,再
求出右子树的结果,然后再应用根结点上标识的操作符对左右子树的结果作运算。对左右子树的计算则递
归使用该计算过程。
具体到例子表达式中,就是先求出子树 (1+2)的结果,再求出子树 (3*4)的结果,再求子树 (1+2)+
(3*4)的结果,最后再求出语法树 ((1+2)+(3*4))-5的结果。
看起来实现这个evaluator的思路还是比较直接的,但是再往深入想,就发现还会存在一些问题。
对于常量表达式而言,上面给出的evaluate求值过程是没有什么问题的。但是如果是 一个包含常量表达
式的变量表达式,对其中的常量子表达式,我们又应该怎样处理呢?具体来说,就是像这样的表达
式: a+2+1+b+(3*4),其中a,b皆为变量。在这个变量表达式中,存在了常量子表达式(如 3*4),对
于这样的常量子表达式,也是应该计算出其值的。但是考虑到这个变量表达式的语法树会长成下面这个样
子:
从这棵语法树可以看出,可以看作常量子表达式的子语法树只有 (3*4)。所以在evaluate的过程中,我
们也需要将(3*4)的结果算出来,并用于替换掉(3*4)这个子表达式,算出来的结果是 a+2+1+b+12。
但是稍微一分析就会发现,这个结果并不是最优的结果,在a+2+1+b+(3*4)中,可以视为常量子表达
式的实际上还有 2+1,并且再进一步,2+1的结果,还可以再与 3*4的结果进行加法运算,如果通过人
肉分析,将这个变量表达式里的所有常量元素全部进行求值处理的话,最优计算结果应该是 a+b+15,
其中15是 ((2+1)+(3*4))的计算结果。可以看出,表达式的原始语法树表示形式限制了对变量表达式中
的常量分量的计算工作。想要解决这个问题,就需要对变量表达式的语法树进行变形和转换。如果能够将
原始的语法树变换成下面这个模样:
再对其进行evaluate,算出来的结果就会是 15+a+b,符合最优结果的要求了。
但是对语法树进行变形和转换,并不是一件非常容易的事情。如果表达式运算过程涉及到的运算符全部是
满足结合率的运算,如: 加法, 乘法的话,会很方便作变换,比如,对于变量表达式 a+1+2,我们大
可通过合并常量分量的方式将其变换为 a+(1+2),从而将常量分量集中在一起,便于对常量子表达式求
值。但如果变量表达式中还包括 不满足结合律的运算,如 减法, 除法, 取模运算,情况就变得复杂得多
了。比如,对于 a-1-2,想要将常量1和2结合起来,就需要变换成 a-(1+2)的形式,这就在合并常量分
量的同时引入了一些变化了。再考虑除法,再考虑求模运算,再考虑移位呢?情况就变得更复杂一些了。
这个时候,就很希望自己的数学背景好一些,能够对这种问题作一个理论上的抽象分析。仅仅通过实证的
方式来分析这种情况,很容易陷入实例爆炸的状态,而且即使在给出了相当多的实例之后,还是担心自己
会遗漏了某些点。而如果从数学角度,在抽象层面作一个定性的分析,就很有帮助了。
P.S. 拿gcc试了一下,对这样的表达式: a-1+a-(2-1)*2,打开-O1选项以后,gcc就会将其优化为
a+a-3了,所以我想这种常量表达式的优化技术在现代编译器里应该是比较经典的技术了,只不过因为
现在自己还欠缺这方面的积累,所以一时没有太好的办法,也许自己会在某个时候好好研究一下gcc或是
lcc这样的现代编译器的实现来作为自己的参考吧。
目前的这个版本的evaluator只针对常量表达式,对变量表达式则不去处理。因为对于变量表达式,是无
法在编译期间求出其最终结果的,只有等到执行阶段才具备算出结果的全部条件。
Evaluator的实现机理并不太复杂,简单来说,就是一个 递归下降的求值过程。如果把表达式的语法结构
表征为一棵语法树,那么,Evaluator的求值过程就类似于对这棵树作 后序遍历。
以常量表达式 1+2+3*4-5 为例,其相应的语法树如下:
![](http://hiphotos.baidu.com/yjpro/pic/item/b8631eee591c02352df53433.jpg)
对这个常量表达式进行evaluate求值的过程就是一个近似于后序遍历的过程: 先求出左子树的结果,再
求出右子树的结果,然后再应用根结点上标识的操作符对左右子树的结果作运算。对左右子树的计算则递
归使用该计算过程。
具体到例子表达式中,就是先求出子树 (1+2)的结果,再求出子树 (3*4)的结果,再求子树 (1+2)+
(3*4)的结果,最后再求出语法树 ((1+2)+(3*4))-5的结果。
看起来实现这个evaluator的思路还是比较直接的,但是再往深入想,就发现还会存在一些问题。
对于常量表达式而言,上面给出的evaluate求值过程是没有什么问题的。但是如果是 一个包含常量表达
式的变量表达式,对其中的常量子表达式,我们又应该怎样处理呢?具体来说,就是像这样的表达
式: a+2+1+b+(3*4),其中a,b皆为变量。在这个变量表达式中,存在了常量子表达式(如 3*4),对
于这样的常量子表达式,也是应该计算出其值的。但是考虑到这个变量表达式的语法树会长成下面这个样
子:
![](http://hiphotos.baidu.com/yjpro/pic/item/c3341b8fe3ea30e6513d923e.jpg)
从这棵语法树可以看出,可以看作常量子表达式的子语法树只有 (3*4)。所以在evaluate的过程中,我
们也需要将(3*4)的结果算出来,并用于替换掉(3*4)这个子表达式,算出来的结果是 a+2+1+b+12。
但是稍微一分析就会发现,这个结果并不是最优的结果,在a+2+1+b+(3*4)中,可以视为常量子表达
式的实际上还有 2+1,并且再进一步,2+1的结果,还可以再与 3*4的结果进行加法运算,如果通过人
肉分析,将这个变量表达式里的所有常量元素全部进行求值处理的话,最优计算结果应该是 a+b+15,
其中15是 ((2+1)+(3*4))的计算结果。可以看出,表达式的原始语法树表示形式限制了对变量表达式中
的常量分量的计算工作。想要解决这个问题,就需要对变量表达式的语法树进行变形和转换。如果能够将
原始的语法树变换成下面这个模样:
![](https://i-blog.csdnimg.cn/blog_migrate/9f1c5c4ff42cc7c7c679150d81a254b6.gif)
再对其进行evaluate,算出来的结果就会是 15+a+b,符合最优结果的要求了。
但是对语法树进行变形和转换,并不是一件非常容易的事情。如果表达式运算过程涉及到的运算符全部是
满足结合率的运算,如: 加法, 乘法的话,会很方便作变换,比如,对于变量表达式 a+1+2,我们大
可通过合并常量分量的方式将其变换为 a+(1+2),从而将常量分量集中在一起,便于对常量子表达式求
值。但如果变量表达式中还包括 不满足结合律的运算,如 减法, 除法, 取模运算,情况就变得复杂得多
了。比如,对于 a-1-2,想要将常量1和2结合起来,就需要变换成 a-(1+2)的形式,这就在合并常量分
量的同时引入了一些变化了。再考虑除法,再考虑求模运算,再考虑移位呢?情况就变得更复杂一些了。
这个时候,就很希望自己的数学背景好一些,能够对这种问题作一个理论上的抽象分析。仅仅通过实证的
方式来分析这种情况,很容易陷入实例爆炸的状态,而且即使在给出了相当多的实例之后,还是担心自己
会遗漏了某些点。而如果从数学角度,在抽象层面作一个定性的分析,就很有帮助了。
P.S. 拿gcc试了一下,对这样的表达式: a-1+a-(2-1)*2,打开-O1选项以后,gcc就会将其优化为
a+a-3了,所以我想这种常量表达式的优化技术在现代编译器里应该是比较经典的技术了,只不过因为
现在自己还欠缺这方面的积累,所以一时没有太好的办法,也许自己会在某个时候好好研究一下gcc或是
lcc这样的现代编译器的实现来作为自己的参考吧。