【编译原理系列】语义分析与中间代码、符号表
静态语义分析
语法制导翻译是处理语义的基本⽅法
以语法分析为基础,在语法分析得到语⾔结构的结果时,处理附着于此结构上的语义,如计算表达式的值、⽣成中间代码等语法与语义
语法是指语⾔结构,即语⾔的“样⼦”;
语义是附着于语⾔结构上的实际含义,即语⾔的“意义”
语义分析的作⽤:
检查是否结构正确的句⼦所表⽰的意思也合法
执⾏规定的语义动作
dnf异界气息装备怎么净化例如如:
表达式求值
符号表填写
中间代码⽣成等
⽅法:
语法制导翻译
语法制导翻译
基本思想:
将语⾔结构的语义以属性的形式赋予代表此结构的⽂法符号,⽽属性的计算以语义规则的形式赋予由⽂法符号组成的产⽣式。
在语法分析推导或规约的每⼀步骤中,通过语义规则实现对属性的计算,以达到对语义的处理
具体⽅法:
将⽂法符号所代表的语⾔结构的意思,⽤附着于该⽂法符号的属性表⽰
竹笋怎么保存
⽤语义规则规定产⽣式所代表的语⾔结构之间的关系(即属性之间的关系),即⽤语义规则实现属性计算
语义规则
妈妈子两种形式:
语法制导定义 (算法)
⽤抽象的属性和运算表⽰的语义规则 (公式,做什么)
翻译⽅案 (程序实现,⽅法不唯⼀)
⽤具体的属性和运算表⽰的语义规则 (程序段,如何做)
语义规则也被习惯上称为语义动作
忽略实现细节,⼆者作⽤等价(设计与实现)
属性
对于产⽣式A→α,其中α是由⽂法符号X1X2…Xn组成的序列,它的语义规则可以表⽰为关于属性的函数:b := f(c1, c2, …, ck)
语义规则中的属性存在下述性质与关系:
(1) 若b是A的属性,c1, c2, …, ck是α中⽂法符号的属性,或者A的其它属性,则称b是A的综合属性。
(2) 若b是α中某⽂法符号Xi的属性,c1, c2, …, ck是A的属性,或者是α中其它⽂法符号的属性,则称b是Xi的继承属性。
(3) 称(4.1)中属性b依赖于属性c1, c2, …, ck。
(4) 若语义规则的形式如 f(c1, c2, …, ck),则可将其想像为产⽣式左部⽂法符号A的⼀个虚拟属性。属性之间的依赖关系,在虚拟属
性上依然存在。
属性之间的计算构成了语义规则,计算的先后次序被称为属性的依赖关系
例如:E→E1+E2 E.val:=E1.val+E2.val,则表明:E的属性.val由E1和E2的相应属性计算⽽来,E的属性依赖于E1和E2的属性
注释分析树
将属性附着在分析树对应⽂法符号上,形成注释分析树;
类似的,将属性附着在语法树对应⽂法符号上,形成语法分析树
注释分析树直观地反映属性的性质和属性之间的关系,所以画树还要标属性
对于S标nc,对于M标stat,对于E标tc和fc *
继承属性:
⾃上⽽下计算的,从前辈和兄弟的属性计算得到,即“⾃上⽽下,包括兄弟”
综合属性:
⾃下⽽上计算的,从⼦孙和⾃⾝的其他属性计算得到,即“⾃下⽽上,包括⾃⾝”
LR分析翻译⽅案的设计
LR分析中的语法制导翻译实质上是对LR语法分析的扩充:
扩充LR分析器的功能:
当执⾏归约产⽣式的动作时,也执⾏产⽣式对应的语义动作。
由于是归约时执⾏语义动作,因此*限制语义动作仅能放在产⽣式右部的最右边
扩充分析栈:
增加⼀个与分析栈并列的语义栈,⽤于存放分析栈中⽂法符号所对应的属性值
递归下降分析翻译⽅案的设计
在产⽣式右部任何位置都可以嵌⼊语义动作;(与LR分析只能在最右边进⾏⽐较)
由于是根据 LR 分析拓展,所以对于继承属性采⽤回填的办法
即:产⽣式右边要⽤到继承属性的符号,在其相邻的右边多出⼀个 M(M -> ε),就是通过这个 M 的语义进⾏回填在函数返回值、参数、变量等设计存储空间
后缀式
也被称为逆波兰表⽰,操作数在前,操作符紧随其后,⽆需⽤括号限制运算的优先级和结合性
表⽰并不惟⼀
x := first_token;
while not end_of_exp
loop    if  x in operands
then push x;--操作数进栈
else pop(operands);--算符,弹出操作数
push(evaluate);--计算,并将结果进栈
end  if;
next(x);
end loop;
后缀式并不局限于⼆元运算的表达式,可以推⼴到任何语句,只要遵守操作数在前,操作符紧跟其后的原则即可
三地址码
形式接近机器指令,且具有便于优化的特征
顾名思义,是由不超过三个地址组成的⼀个运算
题⽬中的三地址码序列需要像这样:
(1) if a < b goto (3)
(2) goto (8)
(3) if c < d goto(5)
(4) goto (8)
(5) t1:= a + c
(6) x:=t1
(7) goto -
语法:
result := arg1 op arg2结果存放在result中的⼆元运算arg1 op arg2
或result := op arg1结果存放在result中⼀元运算op arg1
或 op arg1⼀元运算op arg1
或result := arg1直接拷贝
三元式
(i)(op, arg1, arg2)
序号(i)是它们在三元式表中的位置
序号的双重含义:既代表此三元式,⼜代表三元式存放的结果
存放⽅式:数组结构,三元式在数组中的位置由下标决定
弱点:给代码的优化带来困难
因为代码优化常使⽤的⽅法是删除某些代码或移动某些代码位置,⽽⼀旦进⾏了代码的删除或移动,则表⽰某三元式的序号会发⽣变化,从⽽使得其他三元式中对原序号的引⽤⽆效
语法制导翻译
1. 属性 .code:三元式代码,指⽰标识符的存储单元或三元式表中的序号
2. 属性 .name:标识符的名字
3. 函数trip( op,arg1,arg2 ):⽣成⼀个三元式,返回三元式的序号
4. 函数 entry(id.name):返回标识符在符号表中的位置或存储位置
产⽣式:语义规则:
(1) A→id:=E  {A.code:=trip(:=,entry(id.name),E.code)}
(2) E→E1+E2  {E.code:=trip(+,E1.code,E2.code)}
(3) E→E1*E2  {E.code:=trip(*,E1.code,E2.code)}
(4) E→(E1)    {E.code:=E1.code}
(5) E→-E1  {E.code:=trip(@,E1.code, )}
(6) E→id  {E.code:=entry(id.name)}
四元式
1. 四元式与三元式的唯⼀区别是将由序号所表⽰的运算结果改为了由临时变量来表⽰
2. 此改变使得四元式具有了运算结果与四元式在四元式序列中的位置⽆关的特点,它为代码的优化提供了极⼤⽅便,因为这样可以删除
或移动四元式⽽不会影响运算结果【避免了三元式的值与三元式在三元式组中的位置相关的弱点】
新乡学校3. 三地址码与四元式形式的⼀致性
四元式 (op,arg1,arg2,result) ==> 三地址码 result := arg1 op arg2
result的表⽰⽅法通常是给出⼀个临时名字,⽤它来存放运算的结果,被称为临时变量(语法制导翻译时可以随意引⼊临时变量,若⼲临时变量可以共⽤同⼀个存储空间)
语法制导翻译
1. 属性.code: 表⽰存放运算结果的变量
2. 函数newtemp:返回⼀个新的临时变量,如T1,T2,…等
3. 过程emit( op,arg1,arg2, result):⽣成⼀个四元式,若为⼀元运算,则arg2可空
产⽣式:语义规则:
1)A→id:=E {A.code:=newtemp; emit(:=, entry(id.name), E.code, A.code)}
(2)E→E1+E2 {E.code:=newtemp; emit(+,E1.de)}
(3)E→E1*E2 {E.code:=newtemp; emit(*,E1.de)}
(4)E→(E1) {E.code:=E1.code}
(5)E→-E1 {E.code:=newtemp; emit(@,E1.code, , E.code)}
(6)E→id  {E.code:=entry(id.name)}
图形表⽰
树作为中间代码,语法树真实反映句⼦结构,对语法树稍加修改(加⼊语义信息),即可以作为中间代码的⼀种形式(注释语法树)
树语法制导翻译
家电代理1. 属性.nptr:指向树节点的指针
2. 函数mknode(op,nptr1,nptr2): ⽣成⼀个根或内部节点,节点数据是op, nptr1和nptr2分别指向的左右孩⼦的⼦树。若仅有
重庆旅游攻略2013
⼀个孩⼦,则nptr2为空
3. 函数mkleaf(node): ⽣成⼀个叶⼦节点
产⽣式:语义规则:
(1) A → id := E  {A.nptr:= mknode(:=,mkleaf(entry(id.name)),E.nptr)}
(2) E → E1 + E2  {E.nptr:=mknode(+,E1.nptr,E2.nptr)}
(3) E → E1 * E2  {E.nptr:=mknode(*,E1.nptr,E2.nptr)}
(4) E → ( E1 )  {E.nptr:=E1.nptr}
(5) E → - E1  {E.nptr:=mknode(@,E1.nptr, )}
(6) E → id  {E.nptr:=mkleaf(entry((id.name))}
树的优化表⽰DAG
如果树上若⼲个节点有完全相同的孩⼦,则这些节点可以指向同⼀个孩⼦,形成⼀个有向⽆环图(Directed Acyclic Graph, DAG) DAG与树的唯⼀区别是多个⽗亲可以共享同⼀个孩⼦,从⽽达到资源(运算、代码等)共享的⽬的
仅需要在mknode和mkleaf中增加相应的查询功能
⾸先查看所要构造的节点是否已经存在,若存在则⽆需构造新的节点,直接返回指向已存在节点的指针即可
树与其他中间代码的关系
1. 树表⽰的中间代码与后缀式和三地址码之间有内在联系
2. 对树进⾏深度优先后序遍历,得到的线性序列就是后缀式,或者说后缀式是树的⼀个线性化序列
3. 树的每个内部节点和它的孩⼦对应⼀个三元式或四元式
符号表
符号表的作⽤:连接声明与引⽤的桥梁,记住每个符号的相关信息,如作⽤域和绑定等,帮助编译的各个阶段正确有效地⼯作
符号表的空间存储应该是可以动态扩充的
符号表设计的基本要求:⽬标是合理存放信息和快速准确查
正确存储各类信息
适应不同阶段的需求
便于有效地进⾏查、插⼊、删除和修改等操作;
空间可以动态扩充
逻辑上讲:
每个声明的名字在符号表中占据⼀栏,称为⼀个条⽬,⽤于存放名字的相关信息
符号表中的内容:
保留字、标识符、特殊符号(包括算符、分隔符等) 等等
多个⼦表:不同类别的符号可以存放在不同的⼦表中,如变量名表、过程名表、保留字表等
存放⽅式:关键字+属性
组合关键字⾄少应该包括三项:名字+作⽤域+类型
构成名字的字符串的存储:
定长数据/直接存放
名字:直接存储名字
变长数据(名字长度变化范围很⼤)/间接存放
名字,起始地址;名字间可以⽤特殊符号隔开,也可以在名字中添加长度