本文中内容整理西安交通大学软件学院吴晓军老师的ppt中,仅供学习使用,请勿转载或他用
参考教材:《程序设计语言 编译原理》(第3版) 陈火旺等 国防工业出版社
里面有排版问题或者公式显示错误或图片显示有问题的及时留言
文章目录

回顾
语义分析是干什么的?
其任务是对语法分析所识别出的各类语法范畴,分析其含义,并进行初步翻译
包括两个方面的工作
- 首先是对各种语法范畴进行静态语义检查。例如:变量是否定义,类型是否正确等等
- 如果语义正确,则进行中间代码的翻译
为什么我们需要属性文法?
因为语义分析所依循的是语言的语义规则,通常使用属性文法描述语义规则
本章应该掌握什么
- 属性文法的一些基本概念
- 基于属性文法的集中处理方法
- S-属性文法的自下而上的计算
- L-属性文法和自顶向下翻译
- 自下而上计算继承属性
S属性文法和L属性文法的并集不是全体的属性文法
L属性文法是一个更大的属性文法,其中包含了S属性文法,但是L文法也不是全体的属性文法
{ 属性文法 } ⊃ { L − 属性文法 } ⊃ { S − 属性文法 } \{属性文法\}\supset \{L-属性文法\} \supset \{S-属性文法\} {属性文法}⊃{L−属性文法}⊃{S−属性文法}
属性文法的基本概念
属性文法
属性文法(也称属性翻译文法)是Knuth在1968年首先提出的。他是在上下文无关文法的基础上,为每个文法符号(终结符或非终结符)配备若干相关的“值”(或者称为属性)。这些属性代表与文法符号相关信息,例如它的类型、值、代码序列、符号表内容等。属性与变量一样,可以进行计算和传递。属性加工的过程即是语义处理的过程
属性通常分为两类:
- 综合属性:“自下而上”传递信息
- 继承属性:“自上而下“传递信息
综合属性 | 继承属性 | |
---|---|---|
自下而上传递信息 | 自上而下传递信息 | |
语法规则 | 根据右部候选式中的符号的属性计算左部被定义符号的综合属性 | 根据右部候选式中的符号的属性和左部被定义符号的属性计算右部候选式中的符号**继承属性 ** |
语法树 | 根据子节点的属性和父节点自身的属性计算父节点的综合属性 | 根据父节点和兄弟节点的属性计算子节点的继承属性 |
语义规则
对于文法的每一个产生式都配备一组属性的计算规则
在一个属性文法中,对应于每个产生式 A → α A\to \alpha A→α都有一套与之相关的语义规则。每条规则的形式为: b : = f ( c 1 , c 2 , ⋯ , c k ) b:=f(c_1,c_2,\cdots,c_k) b:=f(c1,c2,⋯,ck)
这里, f f f是一个函数
- b b b是 A A A的一个综合属性并且 c 1 , c 2 , ⋯ , c k c_1,c_2,\cdots,c_k c1,c2,⋯,ck是产生式右边文法符号的属性,或者
- b b b是产生式右边某个文法符号的一个继承属性并且 c 1 , c 2 , ⋯ , c k c_1,c_2,\cdots,c_k c1,c2,⋯,ck是A或产生式右边任何文法符号的属性。
在这两种情况下,我们都说属性 b b b依赖于属性 c 1 , c 2 , ⋯ , c k c_1,c_2,\cdots,c_k c1,c2,⋯,ck
为什么终结符没有继承属性
强调
- 终结符只有综合属性,它们由词法分析器提供
- 非终结符既可以有综合属性也可以有继承属性,文法开始符号的所有继承属性作为属性计算前的初始值
- 对出现在产生式右边的继承属性和出现在产生式左边的综合属性都必须提供一个计算规则
- 出现在产生式左边的继承属性和出现在产生式右边的综合属性不由相应产生式的属性计算规则进行计算,它们由其他产生式的属性计算规则或者由属性计算器的参数提供
问题:语义规则所描述的工作可以包括下列哪些
- 属性计算
- 静态语义检查
- 符号表操作
- 代码生成
答案:ABCD
例一
综合属性
综合属性在实际中被广泛应用。在语法树中,一个节点的综合属性的值由其子结点的属性值确定。因此,通常使用自底向上的方法在每一个节点处使用语义规则计算综合属性的值。仅仅使用综合属性的属性文法称为S-属性文法
例二
下面的简单例子说明综合属性的使用和计算过程
说明:非终结符 E , T , F E,T,F E,T,F都有一个综合属性 v a l val val的整数值。符号 d i g i t digit digit有一个综合属性 l e x v a l lexval lexval,由词法分析器提供。产生式 L → E n L\to En L→En对应的语法规则为打印由E产生的算数表达式的值的过程
继承属性
语法树中,一个结点的继承属性由此结点的父结点或兄弟节点的某些属性确定
用继承属性来表示程序设计语言结构中的上下文依赖关系很方便
例三
以此为继承属性在说明中为各种标识符提供类型信息
- 非终结符T有一个综合属性type,它的值由说明中的关键字确定。
- 与产生式 D → T L D\to TL D→TL相应的语义规则 L . i n : = T . t y p e L.in:=T.type L.in:=T.type把说明中的类型赋值给继承属性 L . i n L.in L.in。
- 然后,利用语义规则把继承属性 L . i n L.in L.in沿着语法树往下传。
- 与L的产生式响应的语义规则调用过程 a d d t y p e addtype addtype把每个标识符的类型填入符号表的相应项中。
addtype 实现在符号表中找到给定标志符的入口,并将它的类型信息设定为 L.in
基于属性文法的处理方法
基于属性文法的处理过程
这种由源程序的语法结构所驱动的处理办法就是语法制导翻译法。语义规则的计算可能产生代码、在符号表中存放信息、给出错误信息或者执行任何其他动作。对输入符号串的翻译也就是根据语义规则进行计算的结果
1.依赖图的属性计算方法
依赖图中不允许有环
如果在一颗语法树中一个结点的属性b依赖于属性c,那么这个节点处计算b的语义规则必须在确定c的语义规则之后使用。
在一颗语法树中的结点的继承属性和综合属性之间的相互依赖关系可以由称作依赖图的一个有向图来描述
强调:在为一颗语法树构造依赖图以前,为每一个包括过程调用的语义规则引入一个虚综合属性b,这样把每一个语义规则都写成 b : = f ( c 1 , c 2 , ⋯ , c k ) b:=f(c_1,c_2,\cdots,c_k) b:=f(c1,c2,⋯,ck)的形式。依赖图中为每一个属性设置一个结点,如果属性b依赖于属性c,则从属性c的节点有一条有向边链接到属性b的结点
依赖图的构建算法
for 语法树中每一节点 n do
for 节点n的文法符号的每一个属性a do
为a在依赖图中建立一个结点;
for 语法树中每一个节点n do
for 结点n所用产生式对应的每一个语义规则 b := f(c1,c2,...,ck) do
for i := 1 to k do
从ci结点到b结点构造一条有向边
例四
例五
对于语法树中的每一个结点,对其文法符号每一个属性在依赖图中建立一个结点,如下图所示:
注意,在节点5用到的语义规则中有一个过程调用,所以要引入虚综合属性,为结点6
最后就比较简单了,根据语义规则来连接结点
属性的计算次序
循环依赖关系
例如: p , c 1 , c 2 p,c_1,c_2 p,c1,c2都是属性,有如下求值规则
p : = f 1 ( c 1 ) , c 1 : = f 2 ( c 2 ) , c 2 : = f 3 ( p ) p:=f1(c_1),c_1:=f2(c_2),c_2:=f3(p) p:=f1(c1),c1:=f2(c2),c2:=f3(p),此时无法对p求值
良定义属性文法
如果一属性文法不存在属性之间的循环依赖关系,那么该属性文法为良定义的。(我们只处理良定义的属性文法)
拓扑序
一个有向非循环图的拓扑序是图中节点的任何顺序 m 1 , m 2 , ⋯ , m k m_1,m_2,\cdots,m_k m1,m2,⋯,mk,使得边必须是从序列中前面的结点指向后面的节点。也就是说,如果 m i → m j m_i \to m_j mi→mj是 m i m_i mi到 m j m_j mj的一条边,那么在序列中 m i m_i mi必须出现在 m j m_j mj之前
一个依赖图中的任何拓扑序都给出一个语法树中结点的语义规则计算的有效顺序。在拓扑排序中,在一个结点上,语义规则 b : = f ( c 1 , c 2 , ⋯ , c k ) b:=f(c_1,c_2,\cdots,c_k) b:=f(c1,c2,⋯,ck)中的属性 c 1 , c 2 , ⋯ , c k c_1,c_2,\cdots,c_k c1,c2,⋯,ck在计算b以前都是可以用的。
此依赖图的拓扑排序从低序号结点到高序号节点
a4 := real
a5 := a4
addtype(id3.entry, a5)
a7 := a5
addtype(id2.entry, a7)
a9 := a7
addtype(id1,entry, a9)
2.树遍历的属性计算方法
依赖图与树遍历的异同:
同:都是通过语法分析对输入串建立语法树,且树中已经有了开始符号的继承属性和终结符的综合属性
异:依赖图是在语法树的基础上先构造依赖图,再按照拓扑顺序计算属性
语法树是直接按照某种次序遍历语法树,在遍历过程中计算属性;不能计算的,留到后面,再次遍历时进行计算,直到计算出所有属性
- 最常用的遍历方法是深度优先、从左到右的遍历方法。可以使用多次遍历(或者称为遍)
- 可能会遍历多次,一次计算可能无法计算完成
- 这个过程是一个递归过程
while 还有未被计算的属性 do
VisitNode(S) // S是开始符号 以给定参数S为根的子树进行从左到右的深度优先遍历
procedure VisitNode(N:Node);
begin
if N是一个非终结符 then
// 假设它的产生式为 N -> X1...Xm
for i := 1 to m do
if Xi 属于 Vn then
begin
计算Xi的所有能够计算的继承属性
VisitNode(Xi)
end;
计算N的所有能够计算的综合属性
end
例六
首先,画出语法树,参照数遍历的算法,对其计算属性
第一次遍历
第二次遍历
第三次遍历,遍历完成
3.一遍扫描的处理方法
依赖图和语法树都是需要多遍扫描的方法
不光完成语法分析,还要完成属性值计算
S-属性文法适合一遍扫描的自下而上分析
L–属性文法适合一遍扫描的自上而下分析
- 一遍扫描的处理方法是在语法分析的同时计算属性值,而不是语法分析构造语法树之后再进行属性的计算
- 一遍扫描处理方法与下面两个因素密切相关
- 所采用的语法分析方法
- 属性的计算次序
所谓语法制导翻译法,直观上说就是为文法中每个产生式配上一组语义规则,并且在语法分析的同时执行这些语义规则
- L-属性文法可用于一遍扫描的自上而下分析
- S-属性文法适合于一遍扫描的自下而上分析
- 语义规则被计算的时机
- 自上而下分析,一个产生式匹配输入串成功时
- 自下而上分析,一个产生式被用于进行规约时
问题:基于属性文法的处理过程有若干种。如果想采用一遍扫描的处理方法,必须考虑下列哪些因素
- 依赖图的拓扑序
- 属性的计算次序
- 语法树的遍历算法
- 所采用的语法分析方法
答案:BD
抽象语法树
在语法树中去掉哪些对翻译不必要的信息,从而获得更有效的源程序中间表示。这种经变换后的语法树称之为抽象语法树(Abstract Syntax Tree)
在抽象语法树中,操作符和关键字都不作为叶结点出现,而是把他们作为内部节点,即这些叶结点的父结点。
例子
问题:建立表达式 3 ∗ 5 + 4 3*5+4 3∗5+4的抽象语法树
这是它的带注释语法树
这是它的抽象语法树
如何建立表达式的抽象语法树
主要用到了以下三个函数
mknode(op, left, right)
:建立一个运算符号结点,标号是op,两个域left和right分别指向左子树和右子树mkleaf(id, entry)
:建立一个标识符结点,标号为id,一个域entry指向标识符在符号表中的入口,创建一个终结符叶节点mkleaf(num, val)
:建立一个数结点,标号为num,一个域val用于存放数的值,创建一个常数叶节点
注:每一个函数都返回一个指向新建立结点的指针
例七
下面一系列的函数调用建立了表达式 a − 4 + c a-4+c a−4+c的抽象语法树。在这个序列中, p 1 , p 2 , ⋯ , p 5 p_1,p_2,\cdots,p_5 p1,p2,⋯,p5是指向结点的指针,entrya和entryc分别是指向符号表的标识符a和c的指针
建立抽象语法树的语义规则
下面考虑建立抽象语法树的语义规则:
用例子来说明:下表是一个为包含运算符号+和-的表达式建立抽象语法树的S-属性文法。利用文法的基本产生式来安排函数mknode
和mkleaf
的调用来建立抽象语法树。E和T的综合属性nptr是函数调用返回的指针。
T.nptr、id.entry、num.val、都是综合属性
例八
题目见上面的描述
首先画出带注释的语法分析树
根据上表中的语义规则,各个节点分别应用,可以很容易地画出抽象语法树
抽象语法树是转换之后的语法树。在抽象语法树中,哪些类型的符号不再是树叶结点?
- 界符
- 标识符
- 运算符
- 常数
- 关键字
这里的五个选项是词法分析结果的五种类型
在处理的过程中,界符相当于是被扔掉了,如产生式 E → ( E ) E\to (E) E→(E),只做了值传递而已,对抽象语法树的个数和层次没有影响
答案:CE
S-属性文法的自下而上计算
- S-属性文法:只含有综合属性(所有文法的符号都有综合属性)
- 综合属性可以在分析输入符号串的同时由自下而上的分析器来计算。分析器可以在分析栈中保存与文法符号有关的综合属性值,每当进行规约时,新的综合属性值就由栈中正在被规约的产生式右边符号的属性值来计算
- S-属性文法的翻译通常可以借助于LR分析器来实现。在S-属性文法的基础上,LR分析器可以改造为一个翻译器,在对输入串进行语法分析的同时对属性进行计算
词法分析:扫描器
语法分析:分析器
语义分析:翻译器
分析栈中的综合属性
在自底向上的分析方法中,我们使用一个栈来存放已经分析过的子树的信息。现在我们可以在分析栈中使用一个附加的域来存放综合属性。
- 图中的栈由一对数组state和val实现。设当前的栈顶由指针top指示。我们假设综合属性刚好在每次规约前计算。
- 假设语义规则 A . a : = f ( X . x , Y . y , Z . z ) A.a := f(X.x,Y.y,Z.z) A.a:=f(X.x,Y.y,Z.z)对应产生式 A → X Y Z A\to XYZ A→XYZ。在把 X Y Z XYZ XYZ规约成A以前,属性 Z . z Z.z Z.z的值放在 v a l [ t o p ] val[top] val[top]中, Y . y Y.y Y.y的值放在 v a l [ t o p − 1 ] val[top-1] val[top−1]中, X . x X.x X.x的值放在 v a l [ t o p − 2 ] val[top-2] val[top−2]中。
- 若一个符号没有综合属性,那么数组val中相应的元素就不定义
- 规约以后,top值减2,A的状态存放在 s t a t e [ t o p ] state[top] state[top]中(即X的位置)。综合属性 A . a A.a A.a的值存放在 v a l [ t o p ] val[top] val[top]中。
这个栈是自上向下生长的
界符可以不设置综合属性,栈中的val部分留空白不定义就行
例九
此例对象是例二台式计算机的属性文法。加入代码段后是如下左表。右表是分析栈。
我们分析在输入 3 ∗ 5 + 4 n 3*5+4n 3∗5+4n的移动序列
代码段若为空表示不对附加域进行修改
问题:在S-属性文法的基础上,LR分析程序可以被改造为一个什么?
- 优化器
- 扫描器
- 翻译器
- 分析器
答案:C
L-属性文法和自顶向下翻译
- 前面介绍过深度优先的方法对语法树进行遍历,从而计算属性文法的所有属性值
- 在这里我们讨论一类属性文法,叫做L-属性文法。这类属性文法允许我们通过一次遍历就能计算出所有属性值
- 与LL(1)自上而下分析方法相结合:深度优先建立语法树;按照语义规则计算属性
L-属性文法
如果对于每个产生式 A → X 1 X 2 ⋯ X n A\to X_1X_2\cdots X_n A→X1X2⋯Xn,其每个语义规则中的每个属性或者是综合属性,或者是 X j ( 1 ≤ j ≤ n ) X_j(1\le j \le n) Xj(1≤j≤n)的一个继承属性且这个继承属性仅依赖于:
- X j X_j Xj的左边符号 X 1 , X 2 , ⋯ , X j − 1 X_1,X_2,\cdots,X_{j-1} X1,X2,⋯,Xj−1的属性
- A的继承属性
也就是不依赖于这个文法符号右边符号的属性
问题:S-属性文法是L-属性文法吗?
答案:S属性文法是L-属性文法,因为a,b两条限制只用于继承属性
例九
问题:以下语义规则是一个L-属性文法吗?
这不是一个L-属性文法,因为 Q . i : = q ( R . s ) Q.i:=q(R.s) Q.i:=q(R.s)违背了定义的要求
翻译模式简介
- 属性文法可以看作是语言翻译的高级规范说明,其中隐去实现细节,使用户从明确说明翻译顺序的工作汇总解脱出来
- 这里我们要讨论另一种适合语法制导翻译的的描述形式,称为翻译模式(Translation schemes),它给出了使用语义规则进行计算的次序
- 在翻译模式中,和文法符号相关的属性和语义规则(也称为语义动作),用花括号 { } \{\} {}括起来,插入到产生式右部的合适位置
例十
这是一个简单的翻译模式的例子,它把带加号和减号的中缀表达式翻译成相应的后缀表达式。
下图表示的是关于输入串 9 − 5 + 2 9-5+2 9−5+2的语法树,每个语义动作都作为相应产生式左部符号的结点的儿子。这样把与语义动作看做是终结符号,表示在什么时候应该执行哪些动作。
首先画出 9 − 5 + 2 9 -5 +2 9−5+2的语法树
然后,为每个产生式左部符号的结点添加一个儿子结点,表示一个相应的语义动作
当按深度有限次序执行语法树中的动作后,即可打印输出后缀式 95 − 2 + 95-2+ 95−2+
翻译模式设计
注意:保证某个动作引用一个属性时他必须有定义。L-属性文法本身就能保证每个动作不会引用尚未计算出来的属性
当只需要综合属性时,为每一个语义规则建立一个包含赋值的动作,并把这个动作放在相应的产生式右边的末尾。
例如:
有如下产生式和语义规则:
T → T 1 ∗ F T\to 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 我们建立产生式和语义动作:
T → T 1 ∗ F T\to 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}
如果既有综合属性又有继承属性
- 产生式右边的符号的继承属性必须在这个符号以前的动作中计算出来
- 一个动作不能引用这个动作右边的符号的综合属性
- 产生式左边非终结符的综合属性只有在它所引用的所有属性都计算出来之后才能计算。计算这种属性的动作通常可以放在产生式右端的末尾
下面的翻译模式不满足上述三个条件中的第一个条件:
产生式 | 语义动作 |
---|---|
S → A 1 A 2 S\to A_1A_2 S→A1A2 | { A 1 . i n : = 1 ; A 2 . i n : = 2 } \{A_1.in:=1;A_2.in:=2\} {A1.in:=1;A2.in:=2} |
A → a A\to a A→a | { p r i n t ( A . i n ) } \{print(A.in)\} {print(A.in)} |
问题:按深度优先遍历输入串 a a aa aa的语法树时,能想出会出现什么错误吗?
回答:打印第二个产生式里的继承属性 A . i n A.in A.in时,该属性还没有定义。
不符合第一条,继承属性应该在他作为产生式的右边时候就计算出来,不能在其为左侧的时候计算
解决方法:将第一条产生式变为: A → { A 1 . i n : = 1 } A 1 { A 2 . i n : = 2 } A 2 A\to \{A_1.in:=1\}A_1\{A_2.in:=2\}A_2 A→{A1.in:=1}A1{A2.in:=2}A2
问题:翻译模式时另一种适合语法制导翻译的描述形式,它给出了使用语义规则进行计算的次序,用{}表示。翻译模式中的{}具有哪个独特名称?
- 属性文法
- 翻译顺序
- 语义规则
- 语义动作
答案:D
自顶向下翻译
下面,我们讨论L-属性文法在自顶向下分析中的实现。为了说明动作的顺序和属性的计算顺序,我们用前面解释的翻译模式进行描述。
如果要使用自上而下的语法分析必须要消除文法中的左递归
上半部分是带有左递归的翻译模式,如果使用自下而上的属性文法是完全没问题的
但是如果使用自上而下时候需要消除左递归,同时翻译模式中也引入了一些继承属性
例十
E → E + T ∣ E − T ∣ T E\to E+T|E-T|T E→E+T∣E−T∣T 消除左递归后: E → T R , R → + T R ∣ − T R ∣ ε E\to TR,R\to +TR|-TR|\varepsilon E→TR,R→+TR∣−TR∣ε
消除左递归时候引入了新的非终结符以及空字表达式,同时也引入了一些继承属性
例十一
中途回顾
- S-属性文法的自下而上翻译
- S-属性文法:只有综合属性
- 带有附加域(存放综合属性的分析栈),用(一堆数组state和val)实现
- 用代码段代替语义规则
- L-属性文法和自顶向下翻译
- L-属性文法
- 翻译模式:将语义规则(即语义动作)按一定要求插入到产生式右部的合适位置
- 自顶向下翻译:消除左递归后如何构造翻译模式? (这里只需要知道有这么个东西可以这么处理就可以了)
自下而上计算继承属性
这里,我们讨论在自下而上的分析过程中实现L-属性文法的方法。这种方法可以实现任何基于LL(1)文法的L-属性文法,它还可以实现许多(不是所有)基于LR(1)文法的L-属性文法
这种方法是前面介绍的自下而上翻译技术的一般化
问题:自下而上的语法分析过程中可以实现L-属性文法。请问,这样的L-属性文法可以是下列哪些?
- 许多基于LL(1)文法的L-属性文法
- 任何基于LL(1)文法的L-属性文法
- 许多基于LR(1)文法的L-属性文法
- 任何基于LR(1)文法的L-属性文法
答案:BC
从翻译模式中去掉嵌入在产生式中间的动作
- 我们要介绍一种转换方法,它可以使所有嵌入的动作都出现在产生式的末尾,这样就可以自下而上处理继承属性。
- 转换方法为:在基础文法中加入新的产生式,这种产生式的形式为 M → ε M\to \varepsilon M→ε,其中M为新引入的一个标记非终结符。
- 我们把嵌入在产生式中的每个语义动作用不同的标记非终结符代替,并把这个动作放在产生式 M → ε M\to\varepsilon M→ε的末尾
带继承属性的自下而上的分析和翻译方法
-
前提:一个基础文法是LL(1)文法的L-属性文法
-
具体方法:为了简单期间,我们假设每一个非终结符A都有一个继承属性 A . i A.i A.i,每个文法符号X都有一个综合属性 X . s X.s X.s。
终结符没有继承属性
-
如果X是一个终结符号,那么它的综合属性就是通过词法分析器返回的词法值
-
对于每个产生式 A → X 1 X 2 ⋯ X n A\to X_1X_2\cdots X_n A→X1X2⋯Xn,引入n个新的标记非终结符 M 1 , ⋯ , M n M_1,\cdots,M_n M1,⋯,Mn用产生式 A → M 1 X 1 ⋯ M n X n A\to M_1X_1\cdots M_nX_n A→M1X1⋯MnXn代替上面的产生式。
- 综合属性 X j . s X_j.s Xj.s将放在分析栈中与 X j X_j Xj相应的数组val的表项中。
- 如果有继承属性 X j . i X_j.i Xj.i,把它放在数组val中,但放在与 M j M_j Mj相应的项中。
- 如果继承属性 A . i A.i A.i存在的话,它将放在数组val中紧挨 M 1 M_1 M1位置下面的位置中存放(因为每个文法符号对应的都是继承属性在综合属性的下面,开始符号也不例外)
-
注意:继承属性与标记非终结符 M j M_j Mj相联系,属性 X j . i X_j.i Xj.i总是在 M j M_j Mj处计算,而且发生在我们开始做规约到 X j X_j Xj的动作之前
属性可在自上而下分析中按预期那样计算,有两种情况:
-
规约到某标记非终结符 M j M_j Mj
此时已经知道这个标记非终结符属于哪个产生式 A → M 1 X 1 ⋯ M n X n A\to M_1X_1\cdots M_nX_n A→M1X1⋯MnXn,因此也就知道计算属性 X j . i X_j.i Xj.i所需的任何属性的问题之。 A . i A.i A.i在 v a l [ t o p − 2 j + 2 ] val[top-2j+2] val[top−2j+2]中, X 1 . i X_1.i X1.i在 v a l [ t o p − 2 j + 3 ] val[top-2j+3] val[top−2j+3]中, X 1 . i X_1.i X1.i在 v a l [ t o p − 2 j + 4 ] val[top-2j+4] val[top−2j+4]中, X 2 . i X_2.i X2.i在 v a l [ t o p − 2 j + 5 ] val[top-2j+5] val[top−2j+5]中等等。
因此,可以计算出 X j . i X_j.i Xj.i并把它放在 v a l [ t o p + 1 ] val[top+1] val[top+1]处,它作为规约(到 M j M_j Mj)后的新栈顶
-
规约到非标记符号
如果按 A → M 1 X 1 ⋯ M n X n A\to M_1X_1\cdots M_nX_n A→M1X1⋯MnXn进行规约。这时候只需要计算综合属性 A . s A.s A.s,而 A . i A.i A.i早已计算出来并且已经存放在栈中将插入A本身的位置的正下面的位置。规约时,计算 A . s A.s A.s所需要的属性在栈中的位置是很容易得到的
简化
- 如果 X j X_j Xj没有继承属性,则无需使用标记符 M j M_j Mj。当然,如果 M j M_j Mj被省略,栈中属性的位置会引起变化,但是这种变化可以通过对分析器稍微修改而适应
- 如果 X 1 . i X_1.i X1.i存在,但是由复写规则 X 1 . i : = A . i X_1.i :=A.i X1.i:=A.i,则可以省略 M 1 M_1 M1。因为我们知道 A i A_i Ai已经存放在栈中预定的位置,紧挨 X 1 X_1 X1下面,因此这个值也可以作为 X 1 . i X_1.i X1.i使用
可以减少属性的个数,但是确定属性下标位置时候需要稍加修改
用综合属性代替继承属性
改变基础文法可能避免继承属性。
例如:一个Pascal的说明为m,n: integer
。这样的说明文法可由下面形式的产生式构成
D
→
L
:
T
T
→
i
n
t
e
g
e
r
∣
c
h
a
r
L
→
L
,
i
d
∣
i
d
\begin{aligned} &D\to L:T\\ &T\to integer|char\\ &L\to L, id|id \end{aligned}
D→L:TT→integer∣charL→L,id∣id
因为标识符由L产生而类型不在L的子树中,不能仅仅使用综合属性就把类型与标识符连接起来。但如果非终结符L从第一个产生式中它的右边T中继承了类型,那么这个文法就不是L-属性的。
解决方法:
D
→
i
d
L
T
→
i
n
t
e
g
e
r
∣
c
h
a
r
L
→
,
i
d
L
∣
:
T
\begin{aligned} &D\to id\ L\\ &T\to integer | char\\ &L\to ,id\ L | :T \end{aligned}
D→id LT→integer∣charL→,id L∣:T
这样,类型可通过综合属性L.type
进行传递。当通过L产生每个表示符时,它的类型就可以填入符号表中
回顾:
自下而上计算继承属性(只讨论实现L-属性文法的方法)
- 从翻译模式中去掉嵌套在产生式中的动作
- 采用的方法:加入新的产生式 M → ε M\to \varepsilon M→ε,其中M为标记非终结符
- 用综合属性代替继承属性
- 采用的方法:改变基础文法
- 给出一种带继承属性的自下而上的分析和翻译方法