您没错,这不是一个小问题。
编译器处理该问题的经典方法是该表达式的有向无环图(DAG)表示。DAG的构建方式与抽象语法树相同(并且可以通过遍历AST来构建-
也许是表达式访问者的工作;我对C#库了解不多),除了以前发出的子图的字典被维持。在使用给定的子代生成任何给定的节点类型之前,请查阅该词典以查看是否已经存在。仅当此检查失败时,才创建新的检查,然后将其添加到字典中。
由于现在一个节点可能来自多个父节点,因此结果是DAG。
然后首先遍历DAG深度以生成代码。由于公共子表达式现在由单个节点表示,因此该值仅计算一次并存储在临时文件中,以供以后在代码生成中使用的其他表达式使用。如果原始代码包含分配,则此阶段会变得很复杂。由于您的树木没有副作用,因此DAG应该是解决问题的最直接方法。
我记得,《龙》一书中
DAG的覆盖范围特别好。
正如其他人指出的那样,如果您的树最终将由现有的编译器编译,则重做已经存在的目录是徒劳的。
加成
我在一个学生项目(我教过的)中放置了一些Java代码,因此总结了一个有关其工作原理的小例子。发布时间太长,请在此处查看要点。
在您的输入上运行它会在下面打印DAG。括号中的数字是(唯一ID,DAG父计数)。需要父计数来决定何时计算局部临时变量以及何时仅将表达式用于节点。
Binary OR (27,1) lhs: Binary OR (19,1) lhs: Binary GREATER (9,1) lhs: Binary ADD (7,2) lhs: Binary MULTIPLY (3,2) lhs: Id 'Bar' (1,1) rhs: Number 5 (2,1) rhs: Binary MULTIPLY (6,1) lhs: Id 'Baz' (4,1) rhs: Number 2 (5,1) rhs: Number 7 (8,1) rhs: Binary LESS (18,1) lhs: ref to Binary ADD (7,2) rhs: Number 3 (17,2) rhs: Binary EQUALS (26,1) lhs: Binary ADD (24,1) lhs: ref to Binary MULTIPLY (3,2) rhs: ref to Number 3 (17,2) rhs: Id 'Xyz' (25,1)
然后生成以下代码:
t3 = (Bar) * (5);t7 = (t3) + ((Baz) * (2));return (((t7) > (7)) || ((t7) < (3))) || (((t3) + (3)) == (Xyz));
您可以看到临时变量号对应于DAG节点。您可以使代码生成器更加复杂,以消除不必要的括号,但我将其留给其他人使用。



