Skip to content
Notes
GitHub

第 6 章 属性文法

6.1. 属性文法

6.1.1. 定义

属性文法也称属性翻译文法,是以上下文无关文法为基础的。

定义 6.1 对文法中的每一个符号(终结符和非终结符)指派若干表达语义的“值”,这些就称为属性。它代表与文法符号相关的信息,如类型、值、代码序列、符号表等相关内容。

定义 6.2 同一条产生式规则中,符号的属性之间的计算法则,称为语义规则。对于文法的每一个产生式都配备了一组属性的语义规则,对属性进行计算和传递。

如对产生式规则 ET+FE \to T + F,从语义角度看,就是将表达式 T\displaystyle{ T }F\displaystyle{ F } 的值相加,结果作为 E\displaystyle{ E } 的值。这启发我们

  1. 给这三个符号配备一个属性 v,用于表示表达式的值
  2. 从语义的角度来看,三个符号的语义处理规则是 E.v=T.v+F.vE . \text{v} = T . \text{v} + F . \text{v}
Warning

语义规则表达了符号属性之间的一种计算或约束关系,并不总能用一个数学式子表达。更多的时候可能要用一段程序代码或伪代码表示。

定义 6.3 在上下文无关文法中配置上语义规则,这样的文法称为属性文法。

定义 6.4 设有一条产生式规则 AX1X2XnA \to X_1 X_2 \cdots X_n,相应的语义规则是 b=f(a1,a2,,ak)b=f(a_1, a_2,\cdots,a_k),如果 b\displaystyle{ b }A\displaystyle{ A } 的一个属性,则称 b\displaystyle{ b }A\displaystyle{ A }综合属性。如果 b\displaystyle{ b } 是右部某个 Xi\displaystyle{ X _{ i } } 的属性,则称 b\displaystyle{ b }Xi\displaystyle{ X _{ i } }继承属性

继承属性主要用于自上而下地传递语义信息。

例 6.1 判断下列 E.vE . \text{v}L.inL . \text{in} 是什么属性

  1. ETFE \to T \ast F,语义规则是 E.v=T.vF.vE . \text{v} = T . \text{v} \ast F . \text{v}
  2. DTLD \to T L,语义规则是 L.in=T.typeL . \text{in} = T . \text{type}
分析
  1. 由于 E.vE . \text{v} 属性 v 是属于左部符号的,是 E\displaystyle{ E } 的综合属性
  2. 由于 L.inL . \text{in} 属性 in 是属于右部符号 L\displaystyle{ L } 的,是 L\displaystyle{ L } 的继承属性

例 6.2 设有一个文法 G[L]\displaystyle{ G \left[ L \right] }

LEnEE1+TTTT1FFF(E)digit\begin{aligned} L & \to E \text{n}\\ E & \to E_1 + T \mid T\\ T & \to T_1 \ast F \mid F\\ F & \to (E) \mid \text{digit} \end{aligned}

n\text{n} 表示输入的是换行符

由于是表达式求值,从语义角度上看,每一个符号都应该有一个表达式的值,所以指派属性 val

规则语义规则及含义
LEnL \to E \text{n}print(E.val)\text{print}(E . \text{val})
EE1+TE \to E_1 + TE.val=E1.val+T.valE . \text{val} = E_1 . \text{val} + T . \text{val}
ETE \to TE.val=T.valE . \text{val} = T . \text{val}
TT1FT \to T_1 \ast FT.val=T1.valF.valT . \text{val} = T_1 . \text{val} \ast F . \text{val}
TFT \to FT.val=F.valT . \text{val} = F . \text{val}
F(E)F \to (E)F.val=E.valF . \text{val} = E . \text{val}
FdigitF \to \text{digit}F.val=digit.lexvalF .\text{val} = \text{digit.lexval}

终结符的综合属性是由词法分析程序提供的,作为已知的值(相当于初始值非终结符可能既有综合属性,又有继承属性

6.1.2. 综合属性

综合属性用于自下而上的传递信息。

从语法规则的角度看,对于产生式规则 ET+FE \to T + F,有语义规则 E.val:=T.val+F.valE . \text{val} := T . \text{val} + F . \text{val},即用右部的属性来计算左部被定义的符号的综合属性。

从语法树角度来看,是根据子节点属性和父节点自身属性计算父节点的综合属性。从算术表达式 35+4\displaystyle{ 3 \cdot 5 + 4 } 可以得到以下的语法树。

public/compile/s06_image_1.svg

定义 6.5 在语法分析树中标记出各个节点的属性值,这种语法分析树被称为注释分析树。

根据自底向上的语法分析,按照从左到右、从叶到根的计算次序,在语法分析的同时,可以将所有的属性值全都计算出来。

6.1.3. 继承属性

继承属性主要用于自上而下地传递语义信息。

主要反映的情形:在语法分析树中,一个节点的继承属性值由其父节点的属性值或兄弟节点的属性值来确定。主要用于表达上下文的依赖性

规则语义规则
DTLD \to T LL.in=T.typeL . \text{in} = T . \text{type}
TintT \to \text{int}T.type=intT . \text{type=int}
TfloatT \to \text{float}T.type=floatT . \text{type=float}
LL1,idL \to L_1,\text{id}L1.in=L.in; addType(id.entry,L.in)L_1 . \text{in} = L . \text{in; addType(id.entry}, L \text{.in)}
LidL \to \text{id}addType(id.entry,L.in)\text{addType(id.entry},L \text{.in)}

第一条含义: L\displaystyle{ L } 的继承属性 in 存放 T\displaystyle{ T } 的综合属性 type。因为 T.typeT . \text{type} 存有变量定义的数据类型信息。这个上下文信息要通过 L.inL . \text{in}第四条含义:L1\displaystyle{ L _{ 1 } } 的继承属性由左边 L\displaystyle{ L } 的继承属性得到,即向下传递。addType 函数会在符号表中找到 id 的入口,并将类型信息设置为 L.inL. \text{in}

考虑变量声明语句 int i1, i2, i3,有下面的语法分析树

public/compile/s06_image_2.svg

6.1.3* 补充

属性依赖

对应于每个产生式 AαA \to \alpha 都有一套与之相关联的语义规则,每条规则的形式为(f\displaystyle{ f } 是一个函数)

b=f(c1,c2,,ck)b=f(c_1,c_2,\cdots,c_k)

那么我们说,属性 b\displaystyle{ b } 依赖于属性 c1,c2,,ckc_1, c_2, \cdots, c_k,有两种情况

  • b\displaystyle{ b }A\displaystyle{ A } 的一个综合属性并且 c1,c2,,ckc_1, c_2, \cdots, c_k 是产生式右边文法符号的属性(例如算术表达式中的加法)
  • b\displaystyle{ b } 是产生式右边某个文法符号的一个继承属性,并且 c1,c2,,ckc_1, c_2, \cdots, c_kA\displaystyle{ A } 或产生式右边任何文法符号的属性(例如 DTLD \to TLLidL \to \text{id}

终结符没有子节点,只有综合属性,由词法分析器提供,例如 FdigitF \to \text{digit} 中,digit 的属性 lexval 由词法分析器提供。

非终结符既可有综合属性也可有继承属性,文法开始符号的所有继承属性作为属性计算前的初始值(例 6.2)。

语义规则

对于出现在产生式右边的继承属性和出现在产生式左边的综合属性都必须提供一个计算规则。属性计算规则中只能使用相应产生式中的文法符号的属性。例如

  • ET+FE \to T + FE.val:=T.val+F.valE . \text{val} := T . \text{val} + F . \text{val}(产生式左边的综合属性)
  • DTLD \to TLL.in=T.typeL . \text{in} = T . \text{type}(产生式右边的继承属性)

出现在产生式左边的继承属性和出现在产生式右边的综合属性 不由 所给的产生式的属性计算规则进行计算,而是由其他产生式的属性规则计算或由属性计算器的参数提供。

  • LidL \to \text{id}addType(id.entry,L.in)\text{addType(id.entry},L \text{.in)}L\displaystyle{ L } 的继承属性依赖于它的兄弟节点和父节点,L\displaystyle{ L } 是由 DTLD \to TL 算得的)

语义规则所描述的工作可以包括属性计算、静态语义检查、符号表操作、代码生成等。

选择题 考虑非终结符 A,B,C\displaystyle{ A , B , C },其中,A\displaystyle{ A } 有继承属性 a 和综合属性 b,B\displaystyle{ B } 有综合属性 c,C\displaystyle{ C } 有继承属性 d。产生式 ABCA \to BC 不可能有规则(?)

  1. C.d=B.c+1C . \text{d}=B . \text{c} + 1
  2. A.b=B.c+C.dA . \text{b} = B . \text{c} + C . \text{d}
  3. B.c=A.aB . \text{c} = A . \text{a}
答案

3 选项。选项为对 B\displaystyle{ B } 的综合属性做计算,而对于出现在产生式右边的综合属性不由所给的产生式的属性计算规则给出。B\displaystyle{ B } 的综合属性应当依赖于其子节点,而非父节点或兄弟节点。

6.1.4. 属性计算

语义规则的计算所完成的任务

  • 产生代码
  • 在符号表中存放信息
  • 给出错误信息
  • 执行任何其他动作

对输入串的翻译就是根据语义规则进行计算

由源程序的语法结构所驱动的处理办法就是语法制导翻译法

  • 输入串 → 语法树 → 按照语义规则计算属性

依赖图

在一颗语法树中的节点的继承属性和综合属性之间的相互依赖关系,可由依赖图来描述。

为每一个包含过程调用的语义规则引入一个虚拟综合属性 b,这样把每一个语义规则都写成

b:=f(c1,c2,,ck)b:=f(c_1,c_2,\cdots,c_k)

这样就统一了语义规则的形式。

依赖图中为每一个属性设置一个节点,如果有属性 b 依赖于属性 c,则有一条有向边为 cbc \to b,若有 A.b=f(X.x,Y.y)A . \text{b} = f(X . \text{x}, Y . \text{y}),则有 X.xA.bX . \text{x} \to A . \text{b}Y.yA.bY . \text{y} \to A . \text{b} 两条边。

对算术表达式文法进行分析

public/compile/s06_image_3.svg

对变量声明语句进行分析

public/compile/s06_image_4.svg

一个依赖图的任何拓扑排序都给出一个语法树中节点语义规则计算的有效顺序。

6.1.5. 属性文法的计算顺序

  • 基础文法用于建立输入符号串的语法分析树
  • 根据语义规则建立依赖图
  • 根据依赖图的拓扑排序,得到计算语义规则的顺序

输入串 → 语法树 → 依赖图 → 语义规则计算次序

对变量声明语句进行分析

  • T.typeT . \text{type} 的值为 int
  • L.in=T.typeL . \text{in} = T . \text{type} 得到 L.in=intL . \text{in} = \text{int}
  • LL1,idL \to L_1, \text{id},观察⑤子树
    • ⑥的 in 属性值依赖于⑤的属性值,因此⑥的 in 值为 int
    • ⑦需要引用⑤和⑥,addType 在符号表中,找到 id3 的入口,填上属性值 int
  • ……

最终有

nametype
id3int
id2int
id1int

树遍历的属性计算方法

通过树遍历的方法计算属性的值
  • 假设语法树已经建立,且书中已带有开始符号的继承属性和终结符的综合属性
  • 以某种次序遍历语法树,直至计算出所有属性
  • 深度优先,从左到右的遍历

计算思维的典型方法:递归

while (!allComputed()) { // 如果还有未被计算的属性
calNode(S) // S 是开始符号
}
function calNode(n: Node) {
// n 是一个非终结符
if (nonTerminators.includes(n)) {
// 假设其产生式为 N → X1 X2...Xm
for (let i = 0; i < m; i++) {
// Xi 是一个非终结符
if (nonTerminators.includes(X[i])) {
// 计算 Xi 的所有能够计算的继承属性
calInherit(X[i])
// 递归调用,访问子树
calNode(X[i])
}
}
// 计算 n 的所有能够计算的综合属性
calComprehensive(n)
}
}
while (!allComputed()) { // 如果还有未被计算的属性
calNode(S) // S 是开始符号
}
function calNode(n: Node) {
// n 是一个非终结符
if (nonTerminators.includes(n)) {
// 假设其产生式为 N → X1 X2...Xm
for (let i = 0; i < m; i++) {
// Xi 是一个非终结符
if (nonTerminators.includes(X[i])) {
// 计算 Xi 的所有能够计算的继承属性
calInherit(X[i])
// 递归调用,访问子树
calNode(X[i])
}
}
// 计算 n 的所有能够计算的综合属性
calComprehensive(n)
}
}

这样可能需要执行许多次。树遍历方法计算属性值最坏情况下是 O(n2)\displaystyle{ O \left( n ^{ 2 } \right) }, 实际应用中希望语法分析的同时计算属性值,不希望构造语法分析数之后再去计算属性值,因为一遍扫描时性能最好。

一遍扫描的处理办法

在语法分析的同时计算属性值

  • 与所采用的语法分析方法相关,将属性计算穿插在分析过程中

所谓语法制导翻译法,直观上说就是为文法中每个产生式配上一组语义规则,并且在语法分析的同时执行这些语义规则。

语义规则被计算的时机

  • 自上而下分析,一个产生式匹配输入串成功时,执行相应的语义规则
  • 自下而上分析,一个产生式被用于进行归约时,执行相应的语义规则

建立抽象语法树的语义规则

产生式语义规则
EE1+TE \to E_1 + TE.nptr:=mknode(+,E1.nptr,T.nptr)E \text{.nptr}:=\text{mknode}(+,E_1 \text{.nptr}, T \text{.nptr})
EE1TE \to E_1 - TE.nptr:=mknode(,E1.nptr,T.nptr)E \text{.nptr}:=\text{mknode}(-,E_1 \text{.nptr}, T \text{.nptr})
ETE \to TE.nptr:=T.nptrE \text{.nptr}:=T \text{.nptr}
T(E)T \to (E)T.nptr:=E.nptrT \text{.nptr}:=E \text{.nptr}
TidT \to \text{id}T.nptr:=mkleaf(id, id.entry)T \text{.nptr}:=\text{mkleaf(id, id.entry)}
TnumT \to \text{num}T.nptr:=mkleaf(num, num.val)T \text{.nptr}:=\text{mkleaf(num, num.val)}

对于算术表达式 a4+c\displaystyle{ a - 4 + c },进行一遍扫描

public/compile/s06_image_5.svg

6.2. S-属性定义及其自底向上的计算

S-属性文法只含有综合属性,该方法在自下而上的分析器分析输入符号串的同时计算综合属性

  • 分析栈中保存语法符号和有关的综合属性
  • 每当进行归约时,新的语法符号的属性值就由栈中正在归约的产生式有变符号的属性值来计算

在分析栈中增加附加域存放综合属性值

假设产生式 AXYZA \to XYZ 对应的语义规则为 A.a:=f(X.x,Y.y,Z.z)A \text{.a}:=f(X \text{.x},Y \text{.y}, Z \text{.z})

public/compile/s06_image_6.svg

例6.5 用 SLR(1) 分析器实现算术表达式的 S-属性定义

规则语义规则SLR(1) 实现代码
LEnL \to E \text{n}print(E.val)\text{print}(E . \text{val})print(val[top-1])
EE1+TE \to E_1 + TE.val=E1.val+T.valE . \text{val} = E_1 . \text{val} + T . \text{val}val[ntop]=val[top-2]+val[top]
ETE \to TE.val=T.valE . \text{val} = T . \text{val}处于同一个位置,无需代码
TT1FT \to T_1 \ast FT.val=T1.valF.valT . \text{val} = T_1 . \text{val} \ast F . \text{val}val[ntop]=val[top-2]*val[top]
TFT \to FT.val=F.valT . \text{val} = F . \text{val}处于同一个位置,无需代码
F(E)F \to (E)F.val=E.valF . \text{val} = E . \text{val}val[ntop]=val[top-1]
FdigitF \to \text{digit}F.val=digit.lexvalF .\text{val} = \text{digit.lexval}处于同一个位置,无需代码

对于给定句子 #35+4 n#\#3 \ast 5 + 4 \text{ n} \#,有 SLR(1) 制导的语义翻译过程表

输入符号栈属性栈归约规则
3*5+4n##
*5+4n## digit␣ 3
*5+4n## F␣ 3FdigitF \to \text{digit}
*5+4n## T␣ 3TFT \to F
5+4n## T *␣ 3 ␣
+4n## T * digit␣ 3 ␣ 5
+4n## T * F␣ 3 ␣ 5TFT \to F
+4n## T␣ 15TTFT \to T \ast F
+4n## E␣ 15ETE \to T
4n## E +␣ 15 ␣
n## E + digit␣ 15 ␣ 4
n## E + F␣ 15 ␣ 4FdigitF \to \text{digit}
n## E + T␣ 15 ␣ 4TFT \to F
n## E␣ 19EE+TE \to E + T
## E n␣ 19 ␣
## L␣ 19LEnL \to E \text{n}

6.3. L-属性定义及其自顶向下的计算

按照深度优先遍历语法树,计算其所有的属性值。与 LL(1) 自上而下分析法结合

  • 深度优先建立语法树
  • 按照语义规则计算属性

一个属性文法称为 L-属性文法,则有:如果对以每个产生式 AX1X2XnA \to X_1X_2\cdots X_n 其每个语义规则中的每个属性,有两种情况

  • 综合属性且仅依赖于产生式中 Xi\displaystyle{ X _{ i } } 左边符号 X1,X2,,Xi1X_1, X_2,\cdots,X_{i-1} 的属性
  • Xi\displaystyle{ X _{ i } } 的一个继承属性且这个继承属性仅依赖于 A\displaystyle{ A } 的继承属性

S-属性文法一定是 L-属性文法。

翻译方案

定义 6.8 将语义动作(语义规则中的某种实现代码)放在 {} 内,并插入在文法规则右部的任何合适位置,这样的文法称为翻译方案。

例 6.7 中缀表达式翻译为后缀表达式的翻译方案

ETRRop  T{print(op.lexval)}R1RεTnum{print(num.lexval)}\begin{aligned} E & \to T R\\ R & \to \text{op}\; T\{ \text{print(op.lexval)} \} R_1\\ R & \to \varepsilon \\ T & \to \text{num} \{ \text{print(num.lexval)} \} \end{aligned}

对于输入串 12+3\displaystyle{ 1 - 2 + 3 }

public/compile/s06_image_7.svg

输出有 123+\displaystyle{ 1 \, 2 - 3 + }

建立翻译方案

设计翻译模式时,必须保证当某个动作引用一个属性时,它必须是有定义的

  • L-属性文法本身就能保证每个动作不会引用尚未计算出来的属性

当只需要综合属性时:为每个语义规则建立一个包含赋值的动作,并把这个动作放在相应产生式右边的末尾

例子

对于产生式 TT1+FT \to T_1 + F 和语义规则 T.val:=T1.val+F.valT \text{.val} := T_1 \text{.val} + F \text{.val},设计产生式和语义动作

TT1+F  {T.val:=T1.val+F.val}T \to T_1 + F \; \{ T \text{.val} := T_1 \text{.val} + F \text{.val} \}

如果既有综合属性又有继承属性,在建立翻译模式时就必须保证:

三个原则
  • 产生式右边的符号的继承属性必须在这个符号以前的动作中计算出来
  • 一个动作不能引用这个动作右边符号的综合属性。也就是说,一个动作要放在这样一个位置上,此时它所引用的所有属性都已计算出来,随时可用。
  • 产生式左边非终结符的综合属性只有在它所引用的所有属性都计算出来后才能计算。计算这种属性的动作通常可放在产生式右端的末尾。

设有文法的翻译方案

SA1A2  {A1.in:=1;A2.in:=2}Aa  {print(A.in)}\begin{aligned} S & \to A_1A_2 \; \{ A_1 \text{.in} := 1; A_2 \text{.in} := 2 \}\\ A & \to a \; \{ \text{print}(A \text{.in}) \} \end{aligned}

此时对句子 ab\displaystyle{ ab } 有分析树

public/compile/s06_image_8.svg

现在对它进行改进

S{A1.in:=1;A2.in:=2}  A1A2Aa  {print(A.in)}\begin{aligned} S & \to \{ A_1 \text{.in} := 1; A_2 \text{.in} := 2 \} \; A_1A_2\\ A & \to a \; \{ \text{print}(A \text{.in}) \} \end{aligned}

public/compile/s06_image_9.svg

此时是一个合法的翻译

消除左递归

语义动作是在相同位置上的符号被展开(匹配成功)时执行的。为了构造不带回溯的自顶向下语法分析,必须消除文法中的左递归

当消除一个翻译模式的基本文法的左递归时同时考虑属性计算(适合带综合属性的翻译模式)

对加减法文法消除左递归,构造新的翻译模式

ET  {R.i=T.val}R  {E.val=R.s}R+T  {R1.i=R.i+T.val}R1  {R.s=R1.s}RT  {R1.i=R.i=T.val}R1  {R.s=R1.s}Rε  {R.s=R.i}T(E)  {T.val=E.val}Tnum  {T.val=num.lexval}\begin{aligned} E \to &\, T \; \{ R\text{.i}=T\text{.val} \} \\ &\, R \; \{ E\text{.val}=R\text{.s} \} \\ R \to &\, {\color{red}+ }\\ &\, {\color{red}T} \; \{ R_1\text{.i}=R\text{.i}+T\text{.val} \} \\ &\, {\color{red}R_1} \; \{ R\text{.s}=R_1\text{.s} \} \\ R \to &\, {\color{red}-} \\ &\, {\color{red}T} \; \{ R_1\text{.i}=R\text{.i}=-T\text{.val} \} \\ &\, {\color{red}R_1} \; \{ R\text{.s}=R_1\text{.s} \} \\ R \to &\, \varepsilon \; \{ R\text{.s}=R\text{.i} \} \\ T \to &\, (E) \; \{ T\text{.val} = E\text{.val} \} \\ T \to &\, \text{num} \; \{ T\text{.val=num.lexval} \} \end{aligned}

其中 R.iR \text{.i} 表示 R\displaystyle{ R } 前面表达式的值(继承属性),R.sR \text{.s} 表示分析完 R\displaystyle{ R } 时子表达式的值(综合属性)

public/compile/s06_image_10.svg

总结

假设有翻译模式

AA1Y  {A.a=g(A1.a,Y.y)}AX  {A.a=f(X.x)}\begin{aligned} A & \to A_1 Y \; \{ A\text{.a} = g(A_1\text{.a}, Y\text{.y}) \}\\ A & \to X \; \{ A\text{.a} = f(X\text{.x}) \} \end{aligned}

它的每个文法符号都有一个综合属性,用小写字母表示,g\displaystyle{ g }f\displaystyle{ f } 表示任意函数。消除左递归,有这样的翻译模式:

AX{R.i=f(X.x)}  R{A.a=R.s}RY{R1.i=g(R.i,Y.y)}R1{R.s=R1.s}Rε{R.s=R.i}\begin{aligned} A \to & \, X \{ R\text{.i} = f(X \text{.x}) \} \\ & \; R \, \{ A \text{.a} = R \text{.s} \} \\ R \to & \, Y \{ R_1 \text{.i} = g(R \text{.i}, Y \text{.y}) \} \\ & \, R_1 \{ R \text{.s} = R_1 \text{.s} \} \\ R \to & \, \varepsilon \{ R \text{.s} = R \text{.i} \} \end{aligned}

同时,我们发现继承属性 i 相当于时函数的传入参数,而综合属性 s 相当于函数的返回值。

递归下降翻译器的设计

  • 对每个非终结符 A\displaystyle{ A } 构造一个函数过程
  • A\displaystyle{ A } 的属性实现为参数和变量
    • 继承属性:对 A\displaystyle{ A } 的每个继承属性设置为函数的一个形式参数
    • 综合属性:实现为函数的返回值
      • 若有多个综合属性,打包成作为结构或记录返回
      • 为了简单,我们假设每个非终结符只有一个综合属性
    • A\displaystyle{ A } 的产生式中每个文法符号的每一个属性:实现为 A\displaystyle{ A } 对应函数过程中的局部变量
  • 按照产生式右部从左到右,对于单词符号(终结符)、非终结符语义动作,分别实现
    • 对于带有综合属性 x 的终结符 X\displaystyle{ X },把 x 的值存入为 X.xX\text{.x} 设置的变量中。然后产生一个匹配 X\displaystyle{ X } 的调用,并继续读入下一个符号
    • 对于每个非终结符 B\displaystyle{ B },产生式右部带有函数调用的赋值语句 c=B(b1,b2,,bk)\text{c} = B({\text{b}_1, \text{b}_2, \cdots, \text{b}_k}),其中 bi\text{b}_iB\displaystyle{ B }继承属性设置的变量,c\text{c}B\displaystyle{ B }综合属性设置的变量
    • 对于语义动作,把动作的代码抄进分析器中,用代表属性的变量来代替对属性的每一次引用

6.4. 自底向上计算继承属性

6.4.1. 删除翻译方案中嵌入的动作

在自顶向下语法分析时,需要消除左递归。语义动作不同的执行时机给语法分析器带来了困难。

对于加减法的文法,消除左递归后有翻译,但是其执行时机没有统一。print('+')print('-') 发生在 T 归约后,R 归约前,而 print(num.val) 发生在 T 归约后最后,它们的时机不统一。

ETRR+T  {print(+)}  RRT  {print()}  RRεTnum  {print(num.val)}\begin{aligned} E & \to TR\\ R & \to + T \; \{ \text{print}('+') \} \; R \\ R & \to - T \; \{ \text{print}('-') \} \; R \\ R & \to \varepsilon \\ T & \to \text{num} \; \{ \text{print(num.val)} \} \end{aligned}

于是我们需要把所有的语义动作都放在产生式的末尾,使得语义动作的执行时机统一。

转换方法

  • 加入新产生式 MεM \to \varepsilon
  • 把嵌入在产生式中的每个语义动作不同的非终结符 M\displaystyle{ M } 代替,并把这个动作放在产生式 MεM \to \varepsilon 末尾
ETRR+TMRTNRεTnum  {print(num.val)}Mε  {print(+)}Nε  {print()}\begin{aligned} E & \to TR \\ R & \to +T { \color{red} M } R \mid -T { \color{red} N } R \mid \varepsilon\\ T & \to \text{num} \; \{ \text{print(num.val)} \} \\ {\color{red}M} & \to \varepsilon \; \{ \text{print}('+') \} \\ {\color{red}N }& \to \varepsilon \; \{ \text{print}('-') \} \end{aligned}

6.4.2. 分析栈中的继承属性

更一般的情况下,并不都能通过 6.4.1 的方法完成继承属性的计算。

对于翻译方案

DT{L.in=T.type}LTint{T.type=int}Tfloat{T.type=float}L{L1.in=L.in}L1,id{addType(id.entry,L.in))}Lid{addType(id.entry,L.in)}\begin{aligned} D & \to T \{ L \text{.in} = T \text{.type} \} L \\ T & \to \text{int} \{ T \text{.type} = \text{int} \} \\ T & \to \text{float} \{ T \text{.type} = \text{float} \} \\ L & \to \{ L_1 \text{.in} = L \text{.in} \} L_1 , \text{id} \{ \text{addType(id.entry}, L \text{.in} )) \} \\ L & \to \text{id} \{ \text{addType(id.entry}, L \text{.in}) \} \end{aligned}

给定句子 int a,b,c 如果使用 SLR(1) 进行自底向上的分析

序号输入符号栈属性栈归约规则
1int a,b,c##
2a,b,c## int␣ int
3a,b,c## T␣ intTintT \to \text{int}
4,b,c## T a␣ int a
5,b,c## T L␣ int aLidL \to \text{id}需要 L.in
6b,c## T L ,␣ int a ␣
7,c## T L , b␣ int a ␣
8,c## T L␣ int aLL1,idL \to L_1, \text{id}需要 L.in
9c## T L ,␣ int a ␣
10## T L , c␣ int a ␣ c
11## T L␣ int aLL1,idL \to L_1, \text{id}需要 L.in
12## D␣ intDTLD \to TL

第 5 行当用 LidL \to \text{id} 对第 4 行内容进行归约前,要归约的规则左部符号 L\displaystyle{ L } 未在分析栈中,分析栈中的语义动作就已经要引用 L\displaystyle{ L } 的继承属性 in 了,这就是问题所在。继承属性总是在符号之前先被计算,归约动作是在继承属性计算之后才会发生。只有发生归约,符号才会进栈。因此,符号还没进栈就需要使用它在属性栈中的继承属性。

为此,我们不能指望在对应符号 L\displaystyle{ L } 的属性栈中保存 L.in 这个继承属性。而事实上,对应符号 L\displaystyle{ L } 的属性栈中存放的是 L\displaystyle{ L }综合属性,而不是继承属性。

由于在自底向上的分析法中,所有属性都只能通过属性栈访问,因而问题变为:虽然 L\displaystyle{ L } 没有进栈,但属性栈中哪个符号的综合属性与 L.in 保持相等?

定义 6.9Y.yY \text{.y} 是继承属性,X.sX \text{.s} 是综合属性,且有 Y.y=X.sY \text{.y} = X \text{.s}(将后者赋值给前者),则称 Y.y=X.sY \text{.y} = X \text{.s} 为复写规则。

X.sX \text{.s} 是已经计算出来的综合属性,放在属性栈中,依据此规则,可在需要 Y.yY \text{.y} 处引用 X.sX \text{.s}

对于上表,由于有 L.in=T.typeL \text{.in} = T \text{.type},可以在需要 L.inL \text{.in} 处引用 T.typeT \text{.type},只要能够正确找出综合属性 T.typeT \text{.type} 每次归约时在属性栈中的位置即可。

翻译方案在 SLR(1) 中的实现代码

产生式规则翻译方案SLR(1) 中的实现代码
DTLD \to TLDT{L.in=T.type}D \to T \{ L \text{.in} = T \text{.type} \}
TintT \to \text{int}Tint{T.type=int}T \to \text{int} \{ T \text{.type} = \text{int} \}
TfloatT \to \text{float}Tfloat{T.type=float}T \to \text{float} \{ T \text{.type} = \text{float} \}
LL1,idL \to L_1, \text{id}L{L1.in=L.in}L1,id{addType(id.entry,L.in))}L \to \{ L_1 \text{.in} = L \text{.in} \} L_1 , \text{id} \{ \text{addType(id.entry}, L \text{.in} )) \}addType(val[top], val[top-3])
LidL \to \text{id}Lid{addType(id.entry,L.in)}L \to \text{id} \{ \text{addType(id.entry}, L \text{.in}) \}addType(val[top], val[top-1])

public/compile/s06_image_11.svg

6.4.3. 模拟继承属性的计算

上述继承属性是复写规则型,而且属性栈中综合属性的位置在每次归约时都是固定的。如每次使用产生式规则 LidL \to \text{id} 归约时,综合属性位置总是固定在 val[top-1] 处。而每次使用产生式规则 LL1,idL \to L_1, \text{id} 时,综合属性的位置固定在 val[top-3] 处。

但综合属性的位置不可能总是这样固定的,因此需要改造文法,使得综合属性的位置固定。

设有文法 G\displaystyle{ G }

SaACC.i=A.sSbABCC.i=A.sCcC.s=g(C.i)\begin{aligned} S & \to aAC & C \text{.i} =A \text{.s} \\ S & \to bABC & C \text{.i} =A \text{.s} \\ C & \to c & C \text{.s} = g(C \text{.i}) \end{aligned}

继承属性 C.i 通过复写规则继承综合属性 A.s 的值。当用 CcC \to c 归约时,C.i 值对应的 A.s 可能在 val[top-1](按 SaACS \to aAC 推导),也可能在 val[top-2](按 SbABCS \to bABC 推导)

文法改造为

SaACC.i=A.sSbABMCM.i=A.s;C.i=M.sCcC.s=g(C.i)MεM.s=M.i\begin{aligned} S & \to aAC && C \text{.i} =A \text{.s} \\ S & \to bABMC && M \text{.i} =A \text{.s}; C \text{.i} = M \text{.s} \\ C & \to c && C \text{.s} = g(C \text{.i}) \\ M & \to \varepsilon && M \text{.s} = M \text{.i} \end{aligned}

M.i 继承属性是复写规则型继承 A.s,每次用 MεM \to \varepsilon 归约时,M.i 中对应的 A.s 总是处于 val[top-1] 处(MεM \to \varepsilon 右部是空的,归约之前 top 指向 B),这样 M\displaystyle{ M } 的综合属性 M.s 通过 M.i 这个继承属性间接的得到了 A.s 的值(M.s=M.i)

除了位置不固定的情形外,还有可能继承属性不是复写规则型

如对 SaACS \to aAC 的语义规则 C.i=f(A.s)C \text{.i} = f(A \text{.s}),其继承属性 C.i 不是复写型,而是对 A.s 计算后再赋值,这时必须将计算部分改造成综合属性方式来计算:

SaANCN.i=A.s;C.i=N.sNεN.s=f(N.i)\begin{aligned} S & \to aANC && N \text{.i} = A \text{.s}; C \text{.i} = N \text{.s} \\ N & \to \varepsilon && N \text{.s} = f(N \text{.i}) \end{aligned}

这样当用 NεN \to \varepsilon 归约时,继承属性 N.i 对应综合属性 A.s 总是处于 val[top] 处(NεN \to \varepsilon 右部为空,归约之前 top 指向 A)。当用 CcC \to c 归约时,C.i 对应的综合属性 N.s 总在 val[top-1] 处。