【编译原理】语义分析

第四章 语义分析

该章节建议观看国防科技大学MOOC。讲解的太棒了!

本章节所提到的LL分析都可以理解为自上而下的分析,LR分析都可以理解为自下而上的分析。

语法制导定义: 语法制导定义是带属性和语义规则的上下文无关文法,其中每个文法符号都有一组属性,每个产生式有一组语义规则。


4.1 综合属性和继承属性

在语法制导定义中,每个文法符号有一组属性,每个文法产生式 A → α A→α Aα 有一组形式为 b = f ( c 1 , c 2 , … , c k ) b=f( c_1,c_2,…,c_k) b=f(c1,c2,,ck) 的语义规则,其中 f f f 是函数, b b b c 1 c_1 c1 c 2 c_2 c2 … … c k c_k ck 是该产生式的文法符号的属性,该规则定义属性 b b b,并且:

(1)如果 b b b A A A 的属性, c 1 c_1 c1 c 2 c_2 c2 … … c k c_k ck 是产生式右部文法符号的属性或 A A A 的其他属性,那么 b b b 称为 A A A综合属性

(2)如果 b b b 是产生式右部某个文法符号 X X X 的属性, c 1 c_1 c1 c 2 c_2 c2 … … c k c_k ck 是A的属性或右部文法符号的属性,那么 b b b 称为 X X X继承属性

一般来说,一个结点的综合属性的值是通过分析树中其子结点的属性值来计算继承属性的值由结点的兄弟结点、父结点和自己的属性值来计算终结符只有综合属性,它由词法分析器提供,即记号的属性。因此在语法制导定义中没有计算终结符的语义规则。

语义规则的函数 f f f 通常是表达式。在实际使用的语法制导定义中,经常还会出现一些规则的目的是打印值、输出中间代码或修改全程量等,它们是一些产生副作用的操作。这样的语义规则写成过程调用或程序段。可以把它们想象成定义了产生式左部非终结符的一个虚拟综合属性,这个虚拟属性和赋值符号 = = = 在该规则中没有显式表示出来。

重写语法制导定义使之仅使用综合属性总是可能的,但有时会使得重写后的文法失去了简洁和直观,反而不如使用带继承属性的语法制导定义自然。

※※※ 对综合属性和继承属性的理解:

非终结符的综合属性可以理解为保存分析完(扩展或者归约)该非终结符后得到的值,即分析完该非终结符之前(含该非终结符)左侧的表达式后计算得到的值。

非终结符的继承属性可以理解为保存分析(扩展或者归约)该非终结符之前的值,即分析完该非终结符之前(不含该非终结符)左侧的表达式后计算得到的值。

也可以理解为继承属性是递归前的值,综合属性是递归完成后的值。

4.2 S S S 属性定义和 L L L 属性定义

S属性定义:仅仅使用综合属性的语法制导定义称为S属性定义。

L属性定义:如果每个产生式 A → X 1 X 2 … X n A→X_1X_2…X_n AX1X2Xn 的每条语义规则计算的属性是 A A A 的综合属性;或者计算的是 X j X_j Xj 的继承属性( 1 ≤ j ≤ n 1≤j ≤n 1jn),它仅依赖:

(1)该产生式中 X X X 左边符号 X 1 X_1 X1 X 2 X_2 X2 … … X j − 1 X_{j-1} Xj1 的属性

(2) A A A 的继承属性

显然, S S S 属性定义属于 L L L 属性定义,因为限制(1)和(2)仅对继承属性进行限制。

由于语法制导定义仅说明了语义规则而未详细说明语义动作(语义规则)执行的时机,所以引入语法制导翻译方案来解决这一问题。

语法制导翻译方案是语法制导定义的扩展,因为语法制导翻译方案在语法制导定义的基础上说明执行语义动作的时机。

通过后文的讲解会发现,语法制导翻译方案的引入实际上是为了解决L属性定义无法使用语法制导定义来翻译的问题,又由于S属性定义属于L属性定义,所以语法制导翻译方案也可以用于翻译S属性定义。


4.3 注释分析树、依赖图

注释分析树的每个节点都是一个文法符号的属性和属性值。

依赖图的任何拓扑排序都是分析树中结点属性计算的一个正确次序,也就是说,通过构建拓扑图我们可以了解注释分析树中每个节点的值计算的次序,以保证语义规则中赋值语句右侧的值在使用前被获取。

依赖图的作用是说明属性计算的顺序,但是我们往往不采用依赖图确定计算属性计算的顺序,因为获取依赖图需要对输入串进行一次扫描,使用依赖图得到顺序后需要再对输入串进行一次扫描,这显然非常低效。类似地,还有通过树遍历计算节点属性值的方法,也是需要多次扫描,效率低。这两部分的思路可以看“依赖图”、“树遍历算法”、“树遍历示例“。


接下来提出更高效的语义分析思路,即仅通过一遍扫描,或者说是边进行语法分析边计算属性值的方法。

并不是任何形式的语义规则都适合一遍扫描的分析(在语法分析建立分析树的同时计算每个属性的值称为一遍扫描)

S属性定义适合自下而上的语法分析过程中实现属性值计算(并不意味着S属性定义不能通过自上而下分析进行边分析边扫描

L属性定义适合自上而下的语法分析过程中实现属性值计算(并不意味着L属性定义不能通过自下而上分析进行边分析边扫描

只有这两类采用特定类型的语法规则的文法(或称为语法分析制导定义)才能适合一遍扫描(即边分析边扫描)

如果我们对语法制导的定义要求的更严格一些,使L属性定义变为S属性定义,那么我们也就可以放弃自下而上的分析方法,采用更合适的自上而下的分析方法了。


上面说到”S属性定义适合自下而上的语法分析过程中实现属性值计算“但并不代表S属性定义只能在自下而上的语法分析中实现属性值的计算,L属性也是同理的。下面将先介绍两个理解比较容易的”S属性定义的自下而上计算“和”L属性定义的自上而下计算“,之后再讨论”S属性定义的自上而下计算“和”L属性定义的自下而上计算“。

4.4 S S S 属性定义的自下而上计算

综合属性可以由自下而上的分析器在分析输入的同时完成计算。分析器可以把文法符号的综合属性放在栈中,每当归约时,根据出现在栈顶的产生式右部符号的属性来计算左部符号的综合属性。下面说明如何扩展分析器的栈,使之能够保存综合属性。

S属性定义的翻译器可以借助LR分析器的生成器来实现。这里暂时就把自下而上理解为与LR分析等价即可,方便其他内容的理解。

直接将语义规则放在每个产生式右部的末尾,每当一个产生式完成归约后都要执行末尾的语义动作(语义规则)。

4.5 L L L 属性定义的自上而下计算

消除左递归的问题

预测分析器实现自上向下分析语法分析,为实现语法分析和语义分析一同进行,我们要将预测分析器修改为预测翻译器。

由于骨架依然是采用预测分析器,而实现预测分析器最重要的一点就是消除左递归。同理,对于预测翻译器,我们也要消除其中的左递归,但是采用之前学习的消除左递归的算法会引入新的非终结符,这时语义规则也要进行改变。

下面只考虑文法最初是S属性定义的。但是经过消除左递归操作后,会导致语义规则出现对继承属性的操作,所以变为了L属性定义。

L属性定义计算属性值已经不能再采用上面的简单方式了,必须要确定执行语义动作的时机,由此引入语法制导的翻译方案。

※ 为L属性定义建立翻译方案的方法:

已知语法制导定义,构建翻译方案的思路,其中语法制导定义为S属性定义或L属性定义。

  • 当某个产生式的语义规则只涉及综合属性时(即S属性定义),为每个语义规则建立一个包含赋值的动作,并把这个动作放在产生式右部的末尾。

    此情况下,将动作放在末尾是一定可行的。其实,只要保证动作涉及的非终结符的属性已经计算完成,那么动作执行的位置还可以提前。

  • 当某个产生式的语义规则既涉及综合属性,又涉及继承属性时,在建立翻译方案时就必须保证:

    • 产生式右边的符号的继承属性必须在进入这个符号以前的动作中计算出来。

      所谓”进入“,在自上而下分析中是指该符号前面的符号均以扩展完成,该符号即将扩展;在自下而上分析中是指该符号前面的符号均以归约完成,在以该字符为根的子树进行归约之前。

      更直观地,体现在翻译方案中,对某个文法符号的继承属性进行赋值的语义动作(花括号)必须放在产生式中该符号之前。

    • 一个动作不能引用这个动作右边的符号的综合属性。

      综合属性要等到对该字符扫描完成才能确定综合属性的值,此时动作右边符号的综合属性值还未计算。

    • 产生式左边非终结符的综合属性只有在它所引用的所有属性都计算出来以后才能计算。计算这种属性的动作通常可放在产生式右端的末尾。

      这个要求和对S属性定义的处理方式一样,本质上这就是S属性定义的情况。

根据以上方法可以将语法制导定义转换为语法制导的翻译方案。

※ 语义动作的执行时机:

在自上而下的分析中,如果语义动作在某个非终结符之后,则必须要等待该非终结符扩展完成后(当然,由于是最左推导,该非终结符左边的非终结符此时都已完成扩展)才能执行;

在自下而上的分析中,如果语义动作在某个非终结符之后,则必须要等待该非终结符归约完成后(当然,由于是最左归约,该非终结符左边的非终结符此时都已完成归约)才能执行。

语法制导到的翻译方案示例

消除左递归

消除左递归需要引入新的非终结符,新的非终结符包含一个继承属性和一个综合属性,通过老师对下图S属性定义进行讲解来熟悉要加入的语义动作,以及语义动作要添加到产生式的哪个位置。

一定看看老师讲解的过程!

总结一下,在变换后新的文法中在产生式右部的新添加的非终结符前面根据含义加上对其继承属性的赋值语句,后面加上对其综合属性的赋值语句或使用其综合属性对其他产生式左部的非终结符的综合属性进行赋值的语句。

举例:

下面的文法是S属性定义的,但是存在左递归,如果使用自上而下计算,可能会产生回溯,因此需要消除左递归。

【编译原理】语义分析_第1张图片

消除左递归后得到的翻译方案如下。

【编译原理】语义分析_第2张图片

消除左递归方法总结:

假设有翻译方案:

依然是保证初始为S属性定义。

A → A 1 Y { A . a = g ( A 1 . a , Y . y ) } A → X { A . a = f ( X . x ) } \left. \begin{array}{l} A\rightarrow A_1Y& \{A.a = g(A_1.a,Y.y)\} \\ A\rightarrow X& \{A.a = f(X.x)\} \\ \end{array} \right. AA1YAX{A.a=g(A1.a,Y.y)}{A.a=f(X.x)}

它的每个文法符号都有一个综合属性,用小写字母表示, g g g f f f 是任意函数。

基础文法消除左递归∶
A → X R R → Y R   ∣   ε A\rightarrow XR\\ R\rightarrow YR\space|\space \varepsilon AXRRYR  ε
翻译方案消除左递归:
A → X { R . i = 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 → ε { R . s = R . i } \left. \begin{array}{l} A\rightarrow& X & \{R.i=f(X.x)\} \\ &R& \{A.a = R.s\} \\ R\rightarrow &Y&\{R_1.i=g(R.i,Y.y)\}\\ & R_1 & {R.s = R_1.s} \\ R\rightarrow&\varepsilon&\{R.s=R.i\}\\ \end{array} \right. ARRXRYR1ε{R.i=f(X.x)}{A.a=R.s}{R1.i=g(R.i,Y.y)}R.s=R1.s{R.s=R.i}

递归下降预测翻译器的设计

递归下降预测翻译器的设计:

  • 对每个非终结符 A A A 构造一个函数过程(与递归预测分析器的思路一致)
  • A A A 的属性实现为参数和变量:
    • A A A 的继承属性作为函数 A A A 的实参传入(多个继承属性就定义多个函数参数;因为继承属性就表示处理非终结符 A A A 前表达式的值)
    • A A A 的综合属性作为函数 A A A 的返回值(可能返回多个综合属性,为了方便理解,我们下面只考虑一个的情况;因为综合属性就表示处理完非终结符 A A A 后表达式的值)
    • A A A 的产生式中的每一个文法符号的每一个属性:实现为A对应的函数过程中的局部变量(本质上这些变量都是临时的,最终还是为了用这些变量的值从继承属性计算出综合属性)

举例:

还是对上面的翻译方案设计递归预测翻译器。为 R R R 构建函数,考虑 R R R 为左部的产生式 R → + T R   ∣   ε R\rightarrow +TR\space|\space \varepsilon R+TR  ε 。先创建其分析器中的函数,再以此为骨架进行修改即可。

【编译原理】语义分析_第3张图片

对应着翻译方案修改后:

【编译原理】语义分析_第4张图片

4.6 S S S 属性定义的自上而下计算

上面讲解了”L属性定义的自上而下计算“,我们知道S属性是属于L属性的,如果想要实现S属性定义的自上而下计算,那么其实可以由L属性的自上而下计算的实现方法来实现。无非就是S属性定义只存在综合属性,所以只需要将语义动作添加到产生式右部的末尾。

直接举个例子说明一下正确性吧。

存在如下语法制导定义:

【编译原理】语义分析_第5张图片

若输入是表达式 8 + 5 ∗ 2 8+5*2 8+52,并跟随一个换行符 n \textbf{n} n ,建立分析树的过程应该如下:

【编译原理】语义分析_第6张图片

4.7 L L L 属性定义的自下而上计算

自下而上分析适合于属性值的计算在归约过程中完成,也就是将语义动作放在每个产生式的末尾。因此,我们考虑通过文法变换,将语义动作都放在产生式的末尾,保证语义动作执行时机统一。

删除嵌入的语义动作

所谓”删除嵌入的语义动作“就是指将语义动作都变换到产生式的末尾,以方便通过自下而上计算属性值。而在初始文法中不在末尾的语义动作就称为嵌入的语义动作。

首先我们要求嵌入的语义动作只涉及虚拟属性,也就是只能是函数调用,不能是对继承属性或综合属性的赋值等操作。

转换方法:

  • 加入新产生式 M → ε M\rightarrow \varepsilon Mε
  • 把嵌入在产生式中的每个语义动作用不同的非终结符 M M M 代替,并把这个动作放在产生式 M → ε M→\varepsilon Mε 的末尾

举例:

删除下面的翻译方案的嵌入语义动作。

【编译原理】语义分析_第7张图片

可以得到:

【编译原理】语义分析_第8张图片

可以看到,嵌入的语义动作是一个函数调用,仅涉及虚拟综合属性。

你可能感兴趣的:(【编译原理】,其他)