总览
6.1 属性文法
本节主要是引入属性文法的定义,引入综合属性和继承属性的概念
6.2 基于属性文法的处理方法
建树
6.3 S-属性文法的自下而上的计算
一种合适自下而上(栈)的属性文法
6.4 L-属性文法和自顶向下翻译
一种适合深度优先搜索的属性文法
6.5 自下而上计算继承属性
略
6.1 属性文法
- 属性文法
在上下文无关文法的基础上,为每个文法符号(终结符或非终结符)配备若干相关的“值”- 属性
与文法符号相关的“值”,属性加工即是语义处理
- 属性
- 语义规则
每个产生式对应的属性处理规则
(属性计算、静态语义检查、符号表操作、代码生成)
b : = f ( c 1 , c 2 , ⋯ , c k ) b:=f(c_1,c_2,\cdots,c_k) b:=f(c1,c2,⋯,ck)- 终结符只有综合属性,它们由词法分析器提供
- 非终结符既可能有综合属性也可能有继承属性,文法开始符号的所有继承属性作为属性计算前的初值
- 综合属性
一个结点的综合属性的值由其子节点的属性值确定
S-属性文法:仅仅使用综合属性的属性文法 - 继承属性
一个结点的继承属性由此结点的父节点和/或兄弟结点的属性确定
(书上那个例子是做变量类型声明的语句,类似(int A, B, C;),还是有点抽象,看了我好长时间)
⋆ \star ⋆ addtype:把每个标识符的类型填入符号表的相应项中
6.2 基于属性文法的处理方法
- 语法制导翻译法
源程序的语法结构所驱动的处理办法
输入串 → 语法树 → 依赖图 → 语义规则计算次序 输入串\rightarrow语法树\rightarrow依赖图\rightarrow语义规则计算次序 输入串→语法树→依赖图→语义规则计算次序
⋆ \star ⋆ L-属性文法不同显示构造语法树就可以实现翻译(与语法分析程序同步进行)
6.2.1 依赖图
- 依赖图
语义规则可表示为: b : = f ( c 1 , c 2 , ⋯ , c k ) b:=f(c_1,c_2,\cdots,c_k) b:=f(c1,c2,⋯,ck)
在依赖图中表示为, c i → b c_i\rightarrow b ci→b - 良定义的
属性文法不存在属性间的循环依赖关系(如果循环依赖,那就“死锁”了,没办法得到值) - 属性的计算次序
如果 m i → m j m_i\rightarrow m_j mi→mj,则序列中 m i m_i mi在 m j m_j mj之前
(前提优先)
6.2.2 树遍历的属性计算方法
前提:语法树已建立,且获得了开始符号的继承属性和终结符的综合属性
While 还有未被计算的属性 do
VisitNode(S) /*S是开始符号*/
procedure VisitNode(N:Node);
begin
if N是一个非终结符 then
/*假设它的产生式为N-->X1...Xm*/
for i:=1 to m do
if not Xi in VT then /*即Xi是非终结符*/
begin
计算Xi的所有能够计算的继承属性
VisitNode(Xi)
end;
计算所有N能够计算的综合属性
end
(简单说就是延S向下发展,由S传播继承属性,计算可以计算的综合属性)
6.2.3 一遍扫描的处理方法
在语法分析的同计算属性值
L-属性文法:一边扫描的自上而下分析
S-属性文法:一遍扫描的自下而上分析
6.2.4 抽象语法树
- 抽象语法树
去掉对翻译不必要的信息的语法树
抽象语法树的构建
函数
- mknode(op, left, right)
建立一个运算符号结点,标号为op,两个域left和right分别指向左子树和右子树 - mkleaf(id, entry)
建立一个标识符结点,标号为id,一个域entry指向标识符在符号表的入口(此处涉及第八章内容) - mkleaf(num, ral)
建立一个数结点,标号为num,一个域ral用于存放数的值 - nptr
函数调用返回的指针6.3 S-属性文法的自下而上计算
- S-属性文法
S-属性文法只含有综合属性,可借助LR分析器实现(栈,自底向上)
归约时,新的语法符号属性值由栈中符号的属性值进行计算
例
- S-属性文法
-
L
→
E
n
L\rightarrow En
L→En
p r i n t ( E . v a l ) print(E.val) print(E.val) ⟹ \Longrightarrow ⟹ p r i n t ( v a l [ n t o p ] ) print(val[ntop]) print(val[ntop]) -
E
→
E
1
+
T
E\rightarrow E_1+T
E→E1+T
E . v a l : = E 1 . v a l + T . v a l E.val:=E_1.val+T.val E.val:=E1.val+T.val ⟹ \Longrightarrow ⟹ v a l [ n t o p ] : = v a l [ t o p − 2 ] + v a l [ t o p ] val[ntop]:=val[top-2]+val[top] val[ntop]:=val[top−2]+val[top](val[top] 对应T.val,val[top]对应E1.val,val[top-1]内容为+) -
E
→
T
E\rightarrow T
E→T
E . v a l : = T . v a l E.val := T.val E.val:=T.val -
T
→
T
1
∗
F
T\rightarrow T_1*F
T→T1∗F
T . v a l : = T 1 . v a l ∗ F . v a l T.val := T_1.val*F.val T.val:=T1.val∗F.val ⟹ \Longrightarrow ⟹ v a l [ n t o p ] : = v a l [ t o p − 2 ] ∗ v a l [ t o p ] val[ntop]:=val[top-2]*val[top] val[ntop]:=val[top−2]∗val[top] -
T
→
F
T\rightarrow F
T→F
T . v a l : = F . v a l T.val := F.val T.val:=F.val -
F
→
(
E
)
F\rightarrow(E)
F→(E)
F . v a l : = E . v a l F.val := E.val F.val:=E.val ⟹ \Longrightarrow ⟹ v a l [ n t o p ] : = v a l [ t o p − 1 ] val[ntop]:=val[top-1] val[ntop]:=val[top−1] -
F
→
d
i
g
i
t
F\rightarrow digit
F→digit
F . v a l : = d i g i t . l e x v a l F.val := digit.lexval F.val:=digit.lexval
(
1. 操作3、5、7,数据只来自右项的第一个符号,即栈顶的值,因此可以不操作,直接将栈顶值作为归约结果
2. 操作2、4、6数据不/不只来自右项的第一个符号,因此需要在栈中找到对应数值位置进行操作,结果压入栈中
)
6.4 L-属性文法和自顶向下翻译
-
L-属性文法
对于每一个产生式 A → X 1 X 2 ⋯ X n A\rightarrow X_1X_2\cdots X_n A→X1X2⋯Xn,其每个语义规则中的每一个属性(综合属性,或 X i ( 1 ≤ i ≤ n ) X_i(1\leq i\leq n) Xi(1≤i≤n)的继承属性)仅依赖于- 产生式中 X i X_i Xi左边符号 X 1 , X 2 , ⋯ , X i − 1 X_1,X_2,\cdots,X_{i-1} X1,X2,⋯,Xi−1的属性
- A的继承属性
(简单说,全部依赖语法树中左边或上边的属性值进行计算,适合“深搜”(因为所有需要的值都在以往的深搜中计算完成了))
⋆ \star ⋆ S-属性文法一定是L-属性文法
6.4.1 翻译模式
- 翻译模式
给出使用语义规则进行计算的次序,把实现细节表示出来
⋆ \star ⋆ 设计翻译模式时,必须保证每个动作不会引用尚未被定义的属性(L-属性文法自身就满足该规则)
在规则中
- 带有综合属性
为每一条语义规则建立一个包含赋值的动作,并把这个动作放在对应的产生式右边的末尾(综合属性,放在最右位置(最后计算)) - 带有继承属性
(继承属性,放在计算结果对应符号前(在读取该符号前已计算完毕)) - 带有综合属性和继承属性
- 产生式右边符号的继承属性必须在这个符号以前的动作中计算出来
- 一个动作不能引用用这个动作右边符号的综合属性 (照这个说法,继承属性计算语句应该紧贴着对应符号左侧放)
- 产生式左边非终结符的综合属性只有在它所引用的所有属性都计算出来才能计算。计算这种属性的动作通常可放在产生式右端的末尾
例
- S → { B . p s : = 10 } B { S . h t : = B . h t } S\rightarrow\{B.ps:=10\}B\{S.ht:=B.ht\} S→{B.ps:=10}B{S.ht:=B.ht}
- B → { B 1 . p s : = B . p s } B 1 { B 2 . p s : = B . p s } B 2 { B . h t : = m a x ( B 1 . h t , B 2 . h t ) } B\rightarrow\{B_1.ps:=B.ps\}B_1\{B_2.ps:=B.ps\}B_2\{B.ht:=max(B_1.ht,B_2.ht)\} B→{B1.ps:=B.ps}B1{B2.ps:=B.ps}B2{B.ht:=max(B1.ht,B2.ht)}
- B → { B 1 . p s : = B . p s } B 1 s u b { B 2 . p s : = s h r i n k ( B . p s ) } B 2 { B . h t : = d i s p ( B 1 . h t , B 2 . h t ) } B\rightarrow \{B_1.ps:=B.ps\}B_1 sub \{B_2.ps:=shrink(B.ps)\}B_2\{B.ht:=disp(B_1.ht,B_2.ht)\} B→{B1.ps:=B.ps}B1sub{B2.ps:=shrink(B.ps)}B2{B.ht:=disp(B1.ht,B2.ht)}
- B → t e x t { B . h t : = t e x t . h × B . p s } B\rightarrow text\{B.ht:=text.h\times B.ps\} B→text{B.ht:=text.h×B.ps}
加入新产生式 M → ε M\rightarrow\varepsilon M→ε,把产生式中的每个语义动作用不同的终结符M代替,并把这个动作放在 M → ε M\rightarrow\varepsilon M→ε的末尾
6.4.2 自顶向下翻译
消除左递归
(自顶向下翻译的需求)
- A → A 1 Y A\rightarrow A_1Y A→A1Y { A . a : = g ( A 1 . a , Y . y ) } \{A.a:=g(A_1.a,Y.y)\} {A.a:=g(A1.a,Y.y)}
-
A
→
X
A\rightarrow X
A→X
{
A
.
a
:
=
f
(
X
.
x
)
}
\{A.a:=f(X.x)\}
{A.a:=f(X.x)}
修改为 - A → X { R i : = f ( X . x ) } R { A . a : = R . s } A\rightarrow X\{R_i:=f(X.x)\}R\{A.a:=R.s\} A→X{Ri:=f(X.x)}R{A.a:=R.s}
- R → Y { R 1 . i : = g ( R i , Y . y ) } R 1 { R . s : = R 1 . s } R\rightarrow Y\{R_1.i:=g(R_i,Y.y)\}R_1\{R.s:=R_1.s\} R→Y{R1.i:=g(Ri,Y.y)}R1{R.s:=R1.s}
- R → ε { R . s : = R . i } R\rightarrow \varepsilon\{R.s:=R.i\} R→ε{R.s:=R.i}
引入抽象语法树
- mknode()
- mkleaf()
- nptr
6.4.3 递归下降翻译器的设计
- 递归下降翻译器
在递归下降中实现翻译模式
分析器由一组递归子程序(函数)组成,每个非终结符对应一个子程序(函数)
设计
- 继承属性
设置为函数的形式参数 - 综合属性
设置为函数的返回值
(假设每个非终结符只有一个综合属性)
6.5 自下而上的继承属性
略