当前课程知识点:Compilers Techniques >  4 Syntax-Directed Translation >  4.4 Bottom-Up Parsing of L-Attributed Translation >  4.4.1 Bottom-Up Parsing of L-Attributed Translation

返回《Compilers Techniques》慕课在线视频课程列表

4.4.1 Bottom-Up Parsing of L-Attributed Translation在线视频

下一节:4.4.2 Simulate the Parsing of Inherited Properties

返回《Compilers Techniques》慕课在线视频列表

4.4.1 Bottom-Up Parsing of L-Attributed Translation课程教案、知识点、字幕

各位同学大家好

我们继续第4章的学习

在上一讲中

我们讲解的是

基于递归下降方法进行属性计算

我们可以实现

翻译动作的边分析边计算

然而由于我们的自上而下分析

仅能够处理LL(1)文法

LL(1) 文法相对于其它的

像 LR 文法

它有一定局限性

因此为了为更一般的文法计算L属性

我们需要研究如何实现

自下而上的属性计算方法

并且它应该是基于比LL(1)更复杂的LR文法

在本讲中

我们将系统介绍

L属性的自下而上计算

以及它的语法分析过程

我们说L属性的自下而上计算

和自上而下的语法分析是一致的

但是它的难点在于

我们如何分析栈上的继承属性

栈上的综合属性

我们在之前的讲解中已经介绍过

我们只需要在状态栈之外

增加一个属性栈

然而这个属性描述的是

每一个文法记号的综合属性

它并不能直接的描述继承属性

因此为了描述继承属性

我们需要使用模拟的方式

来完成我们对继承属性的计算

因此我们在本讲的后续内容中将详细讲解

如何在栈上实现 L属性计算

在自下而上的语法分析框架中

我们实现L属性方法

可以做到对任何一个

基于LL(1)文法它的L属性的定义

它的计算

以及可以实现许多基于LR文法的

L属性计算

当然要注意

这里并不是所有的LR文法

都可以使用这种方法

在最后我们会给一个反例

我们回顾一下

在我们之前的讲解中

我们曾经提到过

在自下而上分析的时候

我们可以通过加入一个属性栈的方式

来为每一个文法记号分配一个属性

但是这个属性对应的是综合属性

而不是继承属性

考虑这样一个翻译方案

A→XYZ

并且Z有一个继承属性 i

i 的值是由 X 的 x 属性来赋值

x 属性是X的一个综合属性

因此它可以出现在我们的属性栈上

但是在这个时候

我们分析 Z 之前

我们需要准备好 Z 的 i 属性

因为它是一个继承属性

然而在我们的栈上

并没有位置

可以存放我们的 i 属性

因此在我们的自下而上分析的时候

我们要解决的一个重点问题就是

如何在我们栈上消除继承属性

在这里

我们把我们的问题来描述一下

在自下而上分析中

我们的语义动作执行

是在产生式对句柄进行归约的时候来进行的

但是L属性定义的继承属性

它的计算需要嵌入在产生式右部不同的位置

这对我们的自下而上分析

或者移进归约分析的时候

无法在属性栈中加入我们的属性

因此是一个需要解决的问题

而我们解决的方法就是通过改写文法

使得所有嵌入在产生式中间的动作

变换成只在产生式最右端出现

也就是说

把它变成一个综合属性

因为所有的综合属性

可以在我们的属性栈中分配位置

这样一来,就可以消除

我们必须为继承属性

分配额外空间的这样一个问题

例如我们可以首先考虑一些特殊情况

然后我们把它推广到一个一般的情况

第1个例子中

我们考虑之前曾经介绍过的

中缀表达式转化成后缀表达式

它的文法以及对应的翻译方案

它的文法是这样的

E → TR

R → +TR1 | -TR1 | ε

T → num

我们的动作分别包括

针对T→num的一个打印字面值的一个动作

以及在分析完T以后

我们打印对应的操作

包括加号和减号两种类型

在这种情况下

我们说一个带副作用的操作

可以看成是一个虚拟综合属性

然而在 T之后的虚拟综合属性

它并不能在T的属性栈中

指定位置出现

因为T的属性栈位置

应该保存的是num.value

这时候就带来一个问题

我们如何在打印动作的时候

获得相应的操作值

这个时候

我们可以在文法中

加入一个产生 ε 的这样一个标记非终结符

它是一个非终结符

并且它是一个特殊的类型

它只能推出一个空串

我们把推出空串的这样一个非终结符

加入到

我们需要打印的一个位置

并且我们把打印动作

作为当前新加入的非终结符的

一个虚拟综合属性

例如我们文法变成这样

E → TR 不变

R → +TR1 | -TR1

我们在R1之前加入M和N两个非终结符

并且这两个非终结符

都只能推出 ε

这样它对整个文法所描述的语言

并没有影响

并且我们的加号和减号的打印操作

可以作为M和N的虚拟综合属性

也就是说在 ε 归约成 M

或者 ε 归成 N 的时候

我们分别执行

打印加号和减号两个操作

在这种时候

我们就可以在我们的栈中保存我们需要的值

这就是我们的第1个类型

我们通过引入推出 ε 的非终结符

来保证我们可以在

正确的位置打印正确的值

接下来我们来看第2个特殊例子

我们考虑之前的变量声明的例子

例如给定一个int p, q, r

我们使用如下的文法来进行描述

D→TL

T→int | real

L是一个列表

可以推出L , id 或者 id

在这个文法中

我们加入动作以及动作执行的时机

把它变成一个翻译方案

在这个例子中

我们很明显知道

in 属性是一个继承属性

在分析 L 之前

我们需要使用T的 type 属性来进行赋值

并且把它传递给L

在L产生式中

我们要使用这个 in 属性

来为我们的 id 添加符号表项类型

来作为一个参数

在自下而上分析的时候

我们说继承属性

没有办法给它分配位置

在这个例子中

我们可以通过复写规则来

在指定的位置找到我们需要的值

也就是我们通过

重用属性栈中的数值

来为我们的继承属性赋值

我们可以考虑如下的例子

例如我们的状态栈、输入栈

和我们使用产生式如左图所示

我们的分析树在上边

D→TL

T是int类型

而L列表,我们有 p, q, r 三个值

我们的问题在于

我们需要在每一个参数列表

知道每一个类型

它如何赋值给 p, q, r 三个变量

也就是说

在使用 L → id 进行归约的时候

我们要为 addtype 准备一个参数

这个参数是一个继承属性

按理来说

这个属性

在我们的属性栈中是不存在的

然而由于文法的特殊性

我们总是能够从指定位置

取到这个值

例如我们可以考虑

这样的产生式和代码段

在出现 L.in 属性位置时候

我们其实总是能够从 L的上一个位置

取到它的类型

而L的上一个位置就是T

而T对应的属性栈的值

就是我们的 type 类型

在这个例子中就是int

也就是说属性 T.type

在我们的栈中的位置

相对于栈顶是事先知道的

因此我们可以使用栈中的属性值

T.type 来代替 L.in

也就是说每一次我们需要使用 L.in 的时候

我们总是可以从栈的指定位置

例如在L→ L1 , id 的时候

我们这个参数应该是L.in

而 L.in 总是能够从Top减3

当前我们的栈顶是 id

前面是逗号 L1

再往前一个就是T的类型

因此我们可以从Top减3的位置

取到我们的L.in

最后一个产生式类似的

L→ id 的时候

我们的栈顶是 id

而栈顶前一个位置事实上就是T

而 T 的 value

其实就是我们的替代 type 这个值

在这个例子中

不管哪种情况

我们的 L.in 的值

都可以从指定的位置来获得

并且这个位置

我们是能够事先知道的

因此我们在这种情况下

就不需要为继承属性

额外分配空间

只需要在指定的位置找到它

进行赋值即可

这就是我们的第2个例子

再接下来我们就来看

并不是所有的情况下

我们都是能够事先知道

我们需要的继承属性的位置

例如考虑如下的产生式

S→aAC

或者S→bABC

C→c

针对产生式

我们有如下的语义规则

在第1个产生式中 C.i 使用 A.s 来赋值

C.i 在第2个产生式中使用 A.s 来赋值

在第3个产生式中

C.s 这是一个综合属性

它是使用它的继承属性

使用 g 函数来计算,来进行获得

在前两个产生式中

C.i 是很典型的继承属性

它需要依赖产生式右部在它左侧出现的记号

因此它是可以进行分析的

然而在两个产生式中

A.s 的位置

相对于C来说是不固定的

在第1个产生式中

A和C是连接出现在栈中

而在第2个产生式中

ABC,A和C中间隔了一个B

因此当我们使用 C → c 这个产生式归约的时候

我们的 C.i 无法从指定的位置得到

考虑如下两个例子

第1个是 S → aAC

第2个例子是 S → bABC

我们 C.i 属性

在两个分析树上

是从不同的位置获得的

例如在前面一个例子

我们的栈可以是 aAC

我们可以从栈顶减1的位置

获得 A.i 属性

如果我们的栈顶是bABC的时候

我们的 C.i 应该从栈顶减2的位置获得

在我们使用 C → c 进行归约的时候

我们无法知道

到底应该是从第1种情况

还是第2种情况

获得我们的属性

因此在这种情况下

我们就无法预先知道

我们的继承属性从何而来

这就造成一个问题

而这个问题解决

需要我们考虑一般的情况

为了解决这个问题

我们就需要引入一个额外的记号

和前面特殊情况类似

我们可以在

第2个产生式C之前

引入一个特殊的非终结符

非终结符M它只能推出空串

因此它不会影响整个语义

并且可以看到

通过引入M以后

我们的M可以保存一个综合属性

这个综合属性可以是由A复制而来

在这种情况下

无论是第一个产生

还是第二个产生式

我们的 C.i 属性

都可以从栈顶减1 的位置得到

只不过在第1种产生式中

C.i 栈顶减1 是A

而在第2个产生式中

C.i 栈顶减1是M

并且M的属性

我们是可以从前面的A获得

而且M的属性和A它的位置是相对固定的

我们可以预先知道

M属性赋值方式

它总是从 M的位置向前减两个值

就可以得到A的属性值

因此我们通过引入一个中间记号

来保存 C.i 的属性值

并且 M的位置相对于C和前面的是一样的

在这种情况下

我们的C.i 的值

总是能够从栈顶减一的位置得到

无论是产生式 1 还是产生式 2

在这种情况下

我们就可以预先知道

C.i 属性是如何来计算的

例如在针对C→c 这个产生式

我们栈代码的话

就总是能够从栈顶减1 的位置取得

虽然说在第1个产生式和第2个产生式中

M的计算方式和A它是有区别的

但是我们总是能从这个位置

找到我们需要的值

这个就是我们解决的一个思路

通过标记非终结符M复写值的属性

我们就可以预先知道

值在栈中所处的位置

在这种情况下

我们总是能够通过

引入一个非终结符

并且保证通过引入非终结符

我们可以从栈指定位置

得到我们需要的信息

进而可以写出

我们需要的栈代码

我们把前几种情况做一个总结

我们的思路都是

希望能够从栈的指定位置

获得我们需要的信息

这个获得方式

都是通过引入

一个推出 ε 的这样一个非终结符

并且把属性计算

移动到当前新增标记的

归约时间来进行

这样在我们的产生式中

就可以能够从指定位置来得到这个信息

例如我们可以考虑

这样的一个一般的例子

继承属性可以由一个

综合属性的函数来获得

例如我们产生式

S→aAC C→c

在这个例子中

我们的 C.i 它不再是由一个属性

直接赋值而来

而是由 A.s 属性

通过一个函数计算来得到

在这种时候

我们也是可以考虑

使用类似的思路

例如如果我们不改写文法的话

我们的栈上是保存的是aAC

同时我们的属性栈分别表示

A.s 属性和 C.s 属性

我们无法同时保存 g(C.i) 和 f(A.s)

在这种情况下

我们的解决思路也是一样的

在需要继承属性的位置

我们可以引入一个

推出 ε 空串的一个非终结符

在我们的例子中

我们的非终结符是N

也就是说

原来的 S → aAC

变成了现在的 S → aANC

而N→ ε

在这种情况下

我们的属性计算

因为是一个函数

而不是直接赋值

我们就可以把这个函数计算

它的值保存在N的位置

也就是说,我们新增的虚拟非终结符

它对应的综合属性

保存的就是我们的对 N.i 做一个 f 计算

并且保存

而 N.i 是如何来的呢

N.i 是它的继承属性

可以从前一个产生式获得

在这种情况下

我们需要计算 C.i 属性的时候

我们就可以使用 N.s 属性

而 N.s 属性

总是能够从栈顶减 1 的位置

因为 N 总是出现在 C 前一个位置

它的传递方式就是

从A.s 属性先传到 N.i

然后 N.i 到 s 的时候

我们使用了一个函数计算

再接下来

N.s 可以传递给 C.i

我们的方式就是

栈点减 1 的位置得到 N.s

这就是一个一般的处理思路

我们都是通过引入一个

推出 ε 空串的一个非终结符

并且保证从我们的引入非终结符

到我们的指定位置

它是一个固定的值

我们就可以从预先知道的位置

取出我们需要的属性

在我们引入虚拟非终结符的时候

我们就可以避免了

必须在栈中出现

继承属性的这样一个问题

好,我们来做一个小结

针对LL(1)文法和我们的LR文法

我们的这种方式是有区别的

如果说我们的基础文法是LL(1)文法的话

我们所有的LL(1)文法

都是可以使用这种方式的

也就是它并不会产生影响

然而如果说我们的产生式是一个LR文法的话

我们就并不能保证

总是能够使用这种方式

例如我们可以考虑

如下的一个文法

L → Lb | a 这样一个文法

它本身是LR(1)文法

为了解决我们继承属性的问题

我们尝试在L前面加一个M属性

M是一个推出空串的一个虚拟非终结符

但是在这种情况下

并不能保证我们引入了虚拟非终结符以后

文法依然是LR(1)文法

这就是一个典型的例子

它并不是一个LR(1)文法

在这种情况下

我们虽然语义是一致的

但是它不能够使用我们的方法

来进行边分析并计算

所以说我们在使用这种方式

来解决的时候

需要考虑特殊情况

好,最后我们做一个小结

在本节中我们介绍的是

L属性定义

以及对应它的翻译方案

针对自上而下分析和自下而上分析

我们都可以实现通过基于推导

和基于归约的方式

来实现边分析边计算

特别的我们强调了

在移进归约分析中

如果我们需要做边分析边计算的话

我们可以通过引入一个

虚拟非终结符的方式

来消除继承属性

虚拟非终结符

是推出空串的这样一个非终结符

通过这种方式

我们就可以消除

必须在栈中使用继承属性

这样一个问题

好,这就是本讲的内容

谢谢大家

Compilers Techniques课程列表:

1 Overview of Compilers Techniques

-1.1 Overview of Compilers Techniques

--Chapter 1 Overview of Compilers Techniques

--Overview of Compilers Techniques

2 Lexical Analysis

-2.1  Lexical Tokens, Strings and Language

--2.1  Lexical Tokens, Strings and Language

--2.1 Lexical Tokens, Strings and Language

-2.2  Regular form

--2.2 Regular form

--2.2 Regular form

-2.3  Finite automata

--2.3 Finite automata

--2.3 Finite automata

-2.4  DFA construction, Subset construction, Simpleset DFA

--2.4  DFA construction, Subset construction, Simpleset DFA

-2.5 Lex

--2.5 Lex

3 Syntax Analysis

-3.1 Context-free Grammars

--3.1.1 The Role of the Parser

--3.1.2 The Formal Definition of a Context-free Grammar

--3.1.3 Derivations

--3.1.4 Ambiguity

-3.2 Writing a Grammar

--3.2.1 Elimination of Left Recursion

--3.2.2 Left Factoring

--3.2 Top-Down Parsing

-3.3 Languages and Grammars

--3.3 Languages and Grammars

--3.3 Language and Grammars

-3.4 Top-Down Parsing

--3.4.1 First and Follow

--3.4.2 LL(1) Grammers

--3.4.3 Recursive Descent Analysis

--3.4.4 Nonrecursive Descent Analysis

-3.5 Bottom-up Parsing

--3.5.1 Reductions and Handle

--3.5.2 Shift- Reduce Parsing

--Bottom-up Parsing

-3.6 LR Parsing

--3.6.1 LR Parsing

--3.6.2 Viable Prefixes

--3.6.3 Simple LR

--3.6.4 LR(1)

--3.6.5 LALR

--3.6.6 Characteristics of LR Parsing

--3.6.7 Non Ambiguous and Not LR Context-Free Grammars

4 Syntax-Directed Translation

-4.1 Syntax-Directed Definitions

--4.1.1 Attribute Grammars

--4.1.2 Attribute Dependency Graphs and Ordering the Evaluation of Attributes

--Syntax-Directed Definitions

-4.2 Bottom-Up Calculation of S Attribute

--4.2.1 Bottom-Up Calculation of S-Attributed

--4.2.2 Stack Code

-4.3 L-Attributed Definitions

--4.3.1 L-Attributed Definitions

--4.3.2 Translation Schemes

--4.3.3 Design of Predictive Translator

--L-Attributed Definitions

-4.4 Bottom-Up Parsing of L-Attributed Translation

--4.4.1 Bottom-Up Parsing of L-Attributed Translation

--4.4.2 Simulate the Parsing of Inherited Properties

--Bottom-Up Parsing of L-Attributed Translation

5 Organization and Management of Run-Time Storage Space

-5.1 Overview

--5.1 Overview

--Overview

-5.2 Global Stack Storage

--5.2 Global Stack Storage

-5.3 Calling Sequences

--5.3 Calling Sequences

-5.4 Non Local Names

--5.4 Non Local Names and dynamic scope

--Non Local Name

6 Intermediate Code Generation

-6.1 Overview of Intermediate Code Generation

--6.1 Overview of Intermediate Code Generation

-6.2 Declaration Statements

--6.2 Declaration Statements

7 Code Generation

-7.1 Issues in the Design of Code Generator

--7.1 Issues in the Design of Code Generator

-7.2 Target Machine

--7.2 Target Machine

--Target Machine

-7.3 Basic Blocks and Flow Graphs

--7.3 Basic Blocks and Flow Graphs

-7.4 A Simple Code Generator

--7.4 A Simple Code Generator

8 Design and Implementation of a Simple Compiler

-8.1 Demonstration of Compiler Framework based on Python

--8.1 Demonstration of Compiler Framework based on Python

-8.2 Basic

--8.2.1 Scanner

--8.2.2 Parser -1LRItem

--8.2.3 Parser-2ActionGoto

--8.2.4 SA

-8.3 SimpleJava

--8.3 SimpleJava

4.4.1 Bottom-Up Parsing of L-Attributed Translation笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。