阅读更多
1 布尔表达式的回填
1.1 回填
基本思想:生成一个跳转指令时,暂时不指定该跳转指令的目标标号。这样的指令都被放入由跳转指令组成的列表中。同一个列表中的所有跳转指令具有相同的目标标号。等到能够确定正确的目标标号时,才去填充这些指令的目标标号
1.2 非终结符$B$的综合属性
- $B.truelist$:指向一个包含跳转指令的列表,这些指令最终获得的目标标号就是当B为真时控制流应该转向的指令的标号
- $B.falselist$:指向一个包含跳转指令的列表,这些指令最终获得的目标标号就是当B为假时控制流应该转向的指令的标号
1.3 函数
- $makelist(i)$:创建一个只包含$i$的列表,$i$是跳转指令的标号,函数返回指向新创建的列表的指针
- $merge(p_1, p_2)$:将$p_1$和$p_2$指向的列表进行合并,返回指向合并后的列表的指针
- $backpatch(p, i)$:将$i$作为目标标号插入到$p$所指列表中的各指令中
1.4 布尔表达式的回填
1.4.1 $B \to E_1\;relop\;E_2$
1.4.2 $B \to true$
1.4.3 $B \to false$
1.4.4 $B \to (B_1)$
1.4.5 $B \to not B_1$
1.4.6 $B \to B_1\;or\;B_2$
1.4.7 $B \to B_1\;and\;B_2$
1.5 例子
输入的布尔表达式如下(假设下一条指令的编号是100)
$$a < b\;or\;c < d\;and\;e < f$$- 首先通过产生式$B \to E_1\;relop\;E_2$,将输入符号$a < b$进行归约,然后执行该产生式的语义动作
- 为终结符$B$生成一个$B.truelist$队列,将放编号为100的$goto$指令压入该队列
- 为终结符$B$生成一个$B.falselist$队列,将放编号为101的$goto$指令压入该队列
- 产生一条条件跳转指令$\;\;if\;a < b\;goto\;\_\;\;$,编号为100
- 产生一条跳转指令$\;\;goto\;\_\;\;$,编号为101
- 同理,通过产生式$B \to E_1\;relop\;E_2$,将输入符号$c < d$进行归约,然后执行该产生式的语义动作
- 为终结符$B$生成一个$B.truelist$队列,将放编号为102的$goto$指令压入该队列
- 为终结符$B$生成一个$B.falselist$队列,将放编号为103的$goto$指令压入该队列
- 产生一条条件跳转指令$\;\;if\;c < d\;goto\;\_\;\;$,编号为102
- 产生一条跳转指令$\;\;goto\;\_\;\;$,编号为103
- 由于$and$的运算优先级大于$or$,于是移入而不是归约。然后通过产生式$B \to E_1\;relop\;E_2$,将输入符号$e < f$进行归约,然后执行该产生式的语义动作
- 为终结符$B$生成一个$B.truelist$队列,将放编号为104的$goto$指令压入该队列
- 为终结符$B$生成一个$B.falselist$队列,将放编号为105的$goto$指令压入该队列
- 产生一条条件跳转指令$\;\;if\;e < f\;goto\;\_\;\;$,编号为104
- 产生一条跳转指令$\;\;goto\;\_\;\;$,编号为105
- 然后通过产生式$B \to B_1\;and\;M\;B_2$,继续进行归约,然后执行该产生式的语义动作
- 用$M.quad$回填$B_1.truelist$队列中所有$goto$指令的跳转目标标号
- $B.truelist=B_2.truelist$
- 将$B_1.falselist$与$B_2.falselist$合并,作为$B.falselist$
- 然后通过产生式$B \to B_1\;or\;M\;B_2$,继续进行归约,然后执行该产生式的语义动作
- 用$M.quad$回填$B_1.falselist$队列中所有$goto$指令的跳转目标标号
- 将$B_1.truelist$与$B_2.truelist$合并,作为$B.truelist$
- $B.falselist=B_2.falselist$
2 控制流语句的回填
回顾一下控制流语句的文法
$$\begin{split} P &\to S \\ S &\to S_1S_2 \\ S &\to id = E; | L = E; \\ S &\to\;if\;B\;then \;S_1\;|\;if\;B\;then\;S_1\;else\;S_2\;|\;while\;B\;do\;S_1 \end{split}$$2.1 非终结符$S$的综合属性
- $S.nextlist$:指向一个包含跳转指令的列表,这些指令最终获得的目标标号就是按照运行顺序紧跟在S代码之后的指令的标号
2.2 控制流语句的回填
2.2.1 $S \to if\;B\;then\;S_1$
- 用$M.quad$来记录$S_1$的第一条指令,用于回填$B.truelist$
2.2.2 $S \to if\;B\;then\;S_1\;else\;S_2$
- 用$M_1.quad$来记录$S_1$的第一条指令,用于回填$B.truelist$
- 用$M_2.quad$来记录$S_2$的第一条指令,用于回填$B.falselist$
- 用$N$来产生$S_1$之后的跳转指令
2.2.3 $S \to while\;B\;do\;S_1$
- 用$M_1.quad$来记录$while$循环的的第一条指令,用于回填$S_1.nextlist$
- 用$M_2.quad$来记录$S_1$的第一条指令,用于回填$B.truelist$
2.2.4 $S \to S_1S_2$
- 用$M.quad$来记录$S_2$的第一条指令,用于回填$S_1.nextlist$
2.2.5 $S \to id = E; | L = E;$
2.3 例子
有如下程序片段
1 | while a < b do |
采用自底向上的分析法,这里直接给出了整棵语法分析树。以从左到右的深度优先顺序来查看所有叶节点,然后按照相应的产生式执行相关的语义动作,不再仔细分析
3 switch语句的翻译
3.1 方式1
3.2 方式2
3.3 增加一种$case$指令
指令$case\;t\;V_iL_i$和$if\;t = V_i\;goto\;L_i$的含义相同,但是$case$指令更加容易被最终的代码生成器探测到,从而对这些指令进行特殊处理
4 过程调用语句的翻译
过程调用翻译的文法如下
$$\begin{split} S &\to call\;id\;(Elist) \\ Elist &\to Elist, E \\ Elist &\to E \end{split}$$4.1 过程调用语句的代码结构
需要一个队列$q$存放$E_1.addr 、E_2.addr、...、E_n.addr$
4.2 过程调用语句的SDD
4.3 例子
翻译如下函数调用
$$f(b*c-1, x+y, x, y)$$5 参考
- 《MOOC-编译原理-陈鄞》