本文主要是对 哈工大编译原理课件 的学习和总结。
在语法制导翻译过程中,将生成中间代码的代码(抽象语法树的代码)嵌入到语义动作中,即可完成中间代码(抽象语法树)的生成。
经典的中间代码通常包括以下几种:
本文将分别介绍各种类型的语句的翻译。
介绍申明语句的翻译前,需要先了解下类型表达式。
首先,基本类型是类型表达式。例如:
再者,将类型构造符 (type constructor) 作用于类型表达式可以构成新的类型表达式。例如:
| 类型 | 类型表达式 |
|---|---|
| int[3] | array(3, int) |
| int[2][3] | array(2, array(3, int)) |
例如,下面的C程序片段:
struct stype {
char[8] name;
int score;
};
stype[50] table;
stype* p;
对于声明语句,语义分析的主要任务就是收集标识符的类型等属性信息,并为每一个名字分配一个相对地址:
而名字的类型和相对地址信息保存在相应的符号表记录中。
下面看一个变量申明语句的SDT:
对于上述文法,可以计算出有相同左部产生式的可选集:
| 产生式 | 可选集(Select) |
|---|---|
| D → T i d ; D D\rightarrow T\ id;D D→T id;D | { ↑ , i n t , r e a l } \{\uparrow,int, real\} {↑,int,real} |
| D → ϵ D\rightarrow \epsilon D→ϵ | {$} |
| T → B C T\rightarrow BC T→BC | { i n t , r e a l } \{int, real\} {int,real} |
| T → ↑ T 1 T\rightarrow {\uparrow}\ T_1 T→↑ T1 | { ↑ } \{\uparrow\} {↑} |
| B → i n t B\rightarrow int B→int | { i n t } \{int\} {int} |
| B → r e a l B\rightarrow real B→real | { r e a l } \{real\} {real} |
| C → ϵ C\rightarrow \epsilon C→ϵ | { i d } \{id\} {id} |
| C → [ n u m ] C 1 C\rightarrow [num]C_1 C→[num]C1 | { [ } |
可见,具有相同左部产生式的可选集是正交的,因此该文法是LL(1)文法,可以采用自顶向下的文法进行分析。分析过程如下:
赋值语句翻译的主要任务是生成对表达式求值的三地址码。例如:
x = ( a + b ) * c ;
// 翻译成三地址码
t1 = a + b
t2 = t1 * c
x = t2
下面看一个简单赋值语句的翻译过程:
符号的属性为:
| 符号 | 综合属性 |
|---|---|
| S | code |
| E | code addr |
这个文法是LR文法,可以采用自底向上的LR语法分析方法。语义动作中函数说明如下:
将数组引用翻译成三地址码时要解决的主要问题是确定数组元素的存放地址,也就是数组元素的寻址。
例如:ype(a)= array(3, array(5, array(8, int) ) ),一个整型变量占用4个字节。则:
a
d
d
r
(
a
[
i
1
]
[
i
2
]
[
i
3
]
)
=
b
a
s
e
+
i
1
∗
w
1
+
i
2
∗
w
2
+
i
3
∗
w
3
=
b
a
s
e
+
i
1
∗
160
+
i
2
∗
32
+
i
3
∗
4
addr(a[i_1][i_2][i_3]) = base + i_1 * w_1 + i_2 * w_2 + i_3 * w_3 \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ = base + i_1 * 160 + i_2 * 32 + i_3 *4
addr(a[i1][i2][i3])=base+i1∗w1+i2∗w2+i3∗w3 =base+i1∗160+i2∗32+i3∗4
c = a [ i 1 ] [ i 2 ] [ i 3 ] c= a[i_1][i_2][i_3] c=a[i1][i2][i3] 对应的三地址码为:
t1 = i1 * 160;
t2 = i2 * 32;
t3 = t1 + t2;
t4 = i3 * 40;
t5 = t3 + t4;
t6 = a[t5]
c = t
为数组L定义综合属性如下:
控制流语句的基本文法:
P
→
S
S
→
S
1
S
2
S
→
i
d
=
E
;
∣
L
=
E
;
S
→
i
f
B
t
h
e
n
S
1
∣
i
f
B
t
h
e
n
S
1
e
l
s
e
S
2
∣
w
h
i
l
e
B
d
o
S
1
控制流语句的代码结构:

很容易得到控制流语句的SDT:
布尔表达式的基本文法:
B
→
B
o
r
B
∣
B
a
n
d
B
∣
n
o
t
B
∣
(
B
)
∣
E
r
e
l
o
p
E
∣
t
r
u
e
∣
f
a
l
s
e
其中,relop(关系运算符)分别为 <、 <=、 >、 >=、==、 ! = 。
在跳转代码中,逻辑运算符 &&、|| 和 ! 被翻译成跳转指令。运算符本身不出现在代码中,布尔表达式的值是通过代码序列中的位置来表示的。例如:
// C 源码
if (x<100 || x>200 && x!=y)
x = 0;
// 三地址代码:
if x<100 goto L2;
goto L3;
L3: if x>200 goto L4;
goto L1;
L4: if x != y goto L2;
goto L1;
L2: x = 0;
L1:
则比较容易得出布尔表达式的SDT:
任何SDT都可以这样实现:首先建立一棵语法分析树(比如使用LR语法分析构建),然后按照从左到右的深度优先顺序来执行这些动作。
上面在生成跳转指令的时候,目标指令的标号还不确定,是通过将存放标号的地址作为继承属性传递到跳转指令生成的地方,这样做会增加一趟处理:将标号同具体的地址绑定起来。
这里介绍一种称为回填的技术来解决这个问题。它的基本思想为:生成一个跳转指令时,暂时不指定该跳转指令的目标标号。这样的指令都被放入由跳转指令组成的列表中。同一个列表中的所有跳转指令具有相同的目标标号。等到能够确定正确的目标标号时,才去填充这些指令的目标标号。
增加非终结符B的综合属性:
使用到的函数:
布尔表达式的回填:
B → E 1 r e l o p E 2 B \rightarrow E_1\ relop\ E_2 B→E1 relop E2,对应的语义动作为:
{
B.truelist = makelist(nextquad);
B.falselist = makelist(nextquad+1);
gen(‘if ’ E 1 .addr relop E 2 .addr ‘goto _’);
gen(‘goto _’);
}
B → t r u e B \rightarrow true B→true,对应的语义动作为:
{
B.truelist = makelist(nextquad);
gen(‘goto _’);
}
B → f a l s e B \rightarrow false B→false,对应的语义动作为:
{
B.falselist = makelist(nextquad);
gen(‘goto _’);
}
B → ( B 1 ) B \rightarrow (B_1) B→(B1),对应的语义动作为:
{
B.truelist = B1.truelist ;
B.falselist = B1.falselist ;
}
B → n o t B 1 B \rightarrow not\ B_1 B→not B1,对应的语义动作为:
{
B.truelist = B1.falselist ;
B.falselist = B1.truelist ;
}
B → B 1 o r B 2 B \rightarrow B_1 \ or\ B_2 B→B1 or B2,需要转换为 B → B 1 o r M B 2 B \rightarrow B_1 \ or\ M\ B_2 B→B1 or M B2。标记终结符M的任务是用于记录B2第一条指令的标号。类似地, B → B 1 a n d B 2 B \rightarrow B_1 \ and\ B_2 B→B1 and B2,需要转换为 B → B 1 a n d M B 2 B \rightarrow B_1 \ and\ M\ B_2 B→B1 and M B2。
下面看一个例子:
注:1、假设指令从100号开始执行。2、生成的指令中剩余标号待B的真假出口确定即可回填。
控制流语句的回填:
为控制语句增添综合属性:
S → i f B t h e n S 1 S\rightarrow if\ B\ then\ S_1 S→if B then S1 语句和 S → i f B t h e n S 1 e l s e S 2 S\rightarrow if\ B\ then\ S_1\ else\ S_2 S→if B then S1 else S2 语句的SDT:
S → w h i l e B d o S 1 S\rightarrow while\ B\ do\ S_1 S→while B do S1 语句和 S → S 1 S 2 S\rightarrow S_1S_2 S→S1S2 语句的SDT:
例:
while a < b do
if c < 5 then
while x > y do z = x + 1;
else
x = y;
过程调用语句的文法:
S
→
c
a
l
l
i
d
(
E
l
i
s
t
)
E
l
i
s
t
→
E
l
i
s
t
,
E
E
l
i
s
t
→
E
过程调用语句的SDT:
用一个队列 q 存放 E1.addr、E2.addr、…、En.addr。
例:
// 源码
f ( b*c-1, x+y, x, y )
// 对应的三地址代码
t1 = b*c;
t2 = t1 - 1;
t3 = x + y;
param t2
param t3
param x
param y
call f, 4
参考