属性文法
-
属性文法,也称属性翻译文法
-
Knuth在1968年提出
-
以上下文无关文法为基础
- 为每个文法符号(终结符或非终结符)配备若干相关的“值"(称为属性),代表与文法符号相关信息,如类型、值、代码序列、符号表内容等
- 对于文法的每个产生式都配备了一组属性的语义规则,对属性进行计算和传递
产生式 语义规则 L→En print(E.val) E→E1+T E.val := E1.val + T.val E → T E.val := T.val T→T1*F T.val := T1.val*F.val T→F T.val := F.val F→(E) F.val := E.val F→digit F.val := digit.lexval -
每一个产生式可配备一条或多条语义规则,这些语义规则说明了每一个产生式所涉及的语法单位之间的语义关系,通过属性计算表达。
综合属性
- 自下而上传递信息
- 语法规则:根据右部候选式中的符号的属性计算左部被定义符号的综合属性
- 语法树:根据子结点的属性和父结点自身的属性计算父结点的综合属性
继承属性
- 自上而下传递信息
- 语法规则:根据右部候选式中的符号的属性和左部被定义符号的属性计算右部候选式中的符号的继承属性
产生式 语义规则 D→TL L.in := T.type T → int T.type := integer T → real T.type := real T→T1*F T.val := T1.val*F.val L → L1,id L1.in := L.in
addtype(id.entry, L.in)L → id addtype(id.entry, L.in)
对于 L → L1,id,其对应的第一条语义规则 L1.in := L.in 说明 (产生式右侧的) L1的继承属性由(产生式左侧的) L 的继承属性得到;其对应的第二条语义规则 addtype(id.entry, L.in) 调用了addtype函数,实现在符号表中找到 id 在符号表的入口,并将入口的类型信息设置为 L.in 的值。 可以认为(左部的)L有一个虚拟的综合属性 L.s 用于接收该函数的返回值。
- 语法树:根据父结点和兄弟结点的属性计算当前结点的继承属性
第一层中的 L的继承属性 L.in 的值来自兄弟节点 T的type属性
第二层中的 L的继承属性 L.in 的值来自第一层的 L.in的值 (根据产生式L → L1,id对应的语义规则L1.in := L.in)
属性依赖
- 若对应于每个产生式A→a都有一套与之相关联的语义规则,每条规则的形式为(f是一个函数):
b := f(c 1, c 2…, c k) - 我们称 属性b 依赖于属性c1, c2…, ck
- 其中b是A的一个综合属性并且c1, c2…, ck是产生式右边文法符号的属性
如产生式 E → E1 + T 对应的语义规则 E.val := E1.val + T.val - 或者b是产生式右边某个文法符号的一个继承属性并且c1, c2…, ck是A(产生式左边)或产生式右边任何文法符号的属性
如产生式 L → L1,id对应的语义规则 L1.in := L.in
或产生式 D → TL 对应的语义规则 L.in := T.type
- 其中b是A的一个综合属性并且c1, c2…, ck是产生式右边文法符号的属性
- 终结符(没有子节点)只有综合属性,由词法分析器提供
- F→digit
- digit.lexval
- 非终结符既可有综合属性也可有继承属性,文法开始符号的所有继承属性作为属性计算前的初始值
语义规则
-
对出现在产生式右边的继承属性和出现在产生式左边的综合属性都必须提供一个计算规则。属性计算规则中只能使用相应产生式中的文法符号的属性。
如产生式 L → L1,id对应的语义规则 L1.in := L.in 提供了对出现在产生式右面的 L1的继承属性 L1.in的计算规则
如产生式 E → T对应的语义规则 E.val:=T.val 提供了对出现在产生式左面的 E 的综合属性 E.val 的计算规则 -
对出现在产生式左边的继承属性和出现在产生式右边的综合属性不由所给的产生式的属性计算规则进行计算,由其它产生式的属性规则计算或者由属性计算器的参数提供
如产生式 L → id 对应的语义规则addtype(id.entry, L.in)中 用到了产生式左边的L的继承属性 L.in ,这个语义规则只能使用 L.in 但不能定义如何计算, L.in 的计算应当由其他规则定义
如产生式 D → TL 对应的语义规则 L.in := T.type中 用到了产生式右边的T的综合属性 T.type,这条语义规则同样只能使用T.type而不能定义如何计算。 -
语义规则所描述的工作可以是任何能用函数调用表示的过程,包括属性计算、静态语义检查、符号表操作、代码生成等。
带注释的语法树
语法树描述了语法构成单位之间的层次关系,是一种层次结构。
对语法树进行拓广,为每一个节点标上属性,可以得到带(属性)注释的语法树:
- 在语法树中,一个结点的综合属性的值由其子结点和它本身的属性值确定
- 使用自底向上的方法在每一个结点处使用语义规则计算综合属性的值
- 仅使用综合属性的属性文法称S-属性文法
例如
产生式 | 语义规则 |
---|---|
D→TL | L.in := T.type |
T → int | T.type := integer |
T → real | T.type := real |
T→T1*F | T.val := T1.val*F.val |
L → L1,id | L1.in := L.in addtype(id.entry, L.in) |
L → id | addtype(id.entry, L.in) |
- 在语法树中,一个结点的继承属性由其父结点、其兄弟结点和其本身的某些属性确定
- 用继承属性来表示程序设计语言结构中的上下文依赖关系很方便
小结
请点击小标题打开视频观看
属性计算
基于属性文法的处理方法
- 语义规则的计算
- 产生代码
- 在符号表中存放信息
- 给出错误信息
- 执行任何其它动作
- 对输入串的翻译就是根据语义规则进行计算
- 语法制导翻译法是以语法分析为中心,为输入串构造语法树,根据语法树的结构选择对应的语义规则计算属性
输入串 → 语法树 → 按照语义规则计算属性 - 按照语义规则进行属性计算的3种方法
- 依赖图
- 树遍历
- 一遍扫描
依赖图
通过寻找属性之间的依赖关系,来确定属性计算的先后顺序,选择相应的语义规则,完成语义计算。
- 在一棵语法树中的结点的继承属性和综合属性之间的相互依赖关系可以由依赖图(有向图)来描述。
- 为每一个包含过程调用的语义规则引入一个虚综合属性b,这样把每一个语义规则都写成
b :=f(c 1,c 2…c k) 的形式 - 依赖图中为每一个属性设置一个结点,如果属性b依赖于属性c,则从属性c的结点用一条有向边连到属性b的结点。
E→E1+E2 E.val := E1.val+E2.val - 依赖图的构建算法
//第一遍循环
for (结点 n : 语法树){
for(文法符号属性 a : n){
在依赖图中为a建立结点;
}
}
//第二遍循环
for (结点 n : 语法树){
for(语义规则 b : n){
//语义规则的形式为 b := f(c1,c2,...,ck)
//ci为b所依赖的属性
for(依赖属性 c: b){
构造从 c 指向 b 的有向边;
}
}
}
- 对于属性文法:
产生式 | 语义规则 |
---|---|
D→TL | L.in := T.type |
T → int | T.type := integer |
T → real | T.type := real |
T→T1*F | T.val := T1.val*F.val |
L → L1,id | L1.in := L.in addtype(id.entry, L.in) |
L → id | addtype(id.entry, L.in) |
语句 real id1,id2,id3
的依赖图可以画成:
注意⑦⑨⑩是虚拟结点
- 良定义的属性文法
- 如果一属性文法不存在属性之间的循环依赖关系,则称该文法为良定义的
- 一个依赖图的任何拓扑排序都给出一个语法树中结点的语义规则计算的有效顺序
- 属性的计算次序
- 基础文法用于建立输入符号串的语法分析树
- 根据语义规则建立依赖图
- 根据依赖图的拓扑排序,得到计算语义规则的顺序
输入串→语法树→依赖图→语义规则计算次序
树遍历
通过树遍历的方法计算属性的值
- 假设语法树已建立,且树中已带有开始符号的继承属性和终结符的综合属性
- (不需要建立依赖图)以某种次序遍历语法树,直至计算出所有属性
- 深度优先,从左到右的遍历
输入串→语法树→遍历语法树计算属性
树遍历算法
while(还有未被计算的属性){
VisitNode(S);//S 是开始符号
}
//从根节点开始递归计算属性
void VisitNode(Node N){
if(N是非终结符){
//N的继承属性应该已经被计算出或者由编译器提供
//假定当前产生式是 N-> X1 X2 ... Xm,共m项
for(int i=1;i<=m;i++){
if(Xi 是非结符){
计算Xi 中能够计算的继承属性;
VisitNode(Xi);
计算Xi 中能够计算的综合属性;
}
}
计算N中能够计算的综合属性;
}
}
树遍历示例
反复调用VisitNode(S)直到所有继承属性和综合属性都能被计算出来或者不再有更多的属性可以被计算为止。
第一次调用可以计算出Z.h=0
和Z.g=1
第二次调用可以计算出X.c=1
、X.d=2
和S.b=0
第三次调用可以计算出Y.e=0
和Y.f=0
请直接观看视频。
一遍扫描处理方法
- 在语法分析的同时计算属性值
- 所采用的语法分析方法
- 属性的计算次序
- 所谓语法制导翻译法,直观上说就是为文法中每个产生式配上一组语义规则,并且在语法分析的同时执行这些语义规则
- 语义规则被计算的时机
- 自上而下分析,一个产生式匹配输入串成功时
- 自下而上分析,一个产生式被用于进行归约时
抽象语法树AST
抽象语法树(Abstract Syntax Tree,AST),在语法树中去掉那些对翻译不必要的信息,从而获得更有效的源程序中间表示
3*5+4:
建立表达式的抽象语法树
函数声明 | 作用 |
---|---|
mknode(op, left, right) | 建立一个运算符号结点,标号是op, 两个域left和right分别指向左子树和右子树 |
mkleaf(id, entry) | 建立一个标识符结点,标号为id, 一个域entry指向标识符在符号表中的入口 |
mkleaf(num, val) | 建立一个数结点,标号为num, 一个域val用于存放数的值 |
运用以上函数,可以写出以下产生式的语义规则:
产 生 式 | 语 义 规 则 |
---|---|
E→E1+T | E.nptr := mknode(‘+’, E1.nptr, T.nptr ) 注意:此时我们认为E1和T已经被构建成了某一语法子树的根节点 |
E→E1-T | E.nptr := mknode(‘-’, E1.nptr, T.nptr ) |
E→T | E.nptr := T.nptr |
T→ (E) | T.nptr := E.nptr 括号对于计算是多余的 |
T→id | T.nptr := mkleaf ( id, id.entry ) |
T→num | T.nptr := mkleaf ( num, num.val ) |
显然这种抽象语法树特别适合于自下而上的属性计算。
a - 4 + c
对应的抽象语法树如下:
由于考试不考 复习时间有限,后续内容不再做记录,请自行寻找国防科技大学编译原理公开课进行观看。