目录
第1章 引论
1.1 语言处理器
1.1.1 编译
翻译一种语言(源语言)的程序成为等价的另一种语言(目标语言)的程序,并能报告源语言中错误的一种程序。
1.1.2 解释
读入一个可执行的程序并产生执行该程序的结果的一种程序。
1.1.3 编译和解释的例子
C 是编译过程
Scheme 是解释过程
Java 结合了编译与解释过程
1.1.4 程序设计语言
用来编写计算机程序的语言。
1.1.5 一个语言处理系统
程序可能被分割成为多个模块,并存放于独立的文件中。把源程序聚合在一起的任务有时会由一个被称为预处理器(preprocessor)的程序独立完成。预处理器还负责把那些称为宏的缩写形式转换为源语言的语句。
然后,将经过预处理的源程序作为输入传递给一个编降器。编译器可能产生一个汇编语言程序作为其输出,因为汇编语言比较容易输出和调试。接着,这个汇编语言程序由称为汇编器(assembler)的程序进行处理,并生成可重定位的机器代码。
大部分程序经常被分为多个部分进行编译,一个文件中的代码可能指向另一个文件中的位置,而链接器(linker)能够解决外部内存地址的问题。最后,加载器(loader)把所有的可执行目标文件放到内存中执行。
1.2 一个编译器的结构
编译器可以分为两个部分:分析部分和综合部分。
分析部分
1.词法分析——线性的非递归的,仅识别各个“单词” ,它们是该语言的Tokens
2.语法分析-——为了识别表达式的构造需要递归
3.语义分析——决定该句子是否有且仅有一种无二义的解释
综合阶段
4.中间代码生成
5.代码优化
6.目标代码生成
1.3节的练习
1.3.1练习
C:强制式的、冯·诺依曼式的、第三代
C++:强制式的、冯·诺依曼式的、面向对象的、第三代
Cobol:强制式的、冯·诺依曼式的、第三代
Fortran:强制式的、冯·诺依曼式的、第三代
Java:强制式的、冯·诺依曼式的、面向对象的、第三代
Lisp:声明式的、函数式的、第三代
ML:强制式的、函数式的
Perl:强制式的、脚本语言
Python:强制式的、脚本语言
VB: 强制式的、面向对象的
1.6节的练习
1.6.1&1.6.2
a)w = 13 ,x = 11 ,y = 13 , z = 11
b)w = 9 ,x = 7 ,y = 13 ,z = 11
1.6.3
对于图1-14中的块结构代码,假设使用常见的声明的静态作用域规则,给出其中12个声明中的每一个的作用域。
w1——B1 - B3 - B4
x1——B1 - B2 - B4
y1——B1 - B5
z1——B1 - B2 - B5
x2——B2 - B3
z2——B2
w3——B3
x3——B3
w4——B4
x4——B4
y5——B5
Z5——B5
1.6.4
3
2
第二章 一个简单的语法制导翻译器
2.2 语法定义
2.2.1 文法定义(上下文无关文法)
1) 终结符号的集合
2)非终结符号的集合
3)产生式的集合
4)指定一个非终结符号为开始符号
2.2.2 推导
根据文法推导符号串时,我们首先从开始符号出发,不断将某个非终结符号替换为该非终结符号的某个产生式的体。
依据上面的文法,对产生串 9 - 5 + 2 进行推导
2.2.3 语法分析树
1)根结点的标号为文法的开始符号。
2)每个叶子结点的标号为一个终结符号或ε。
3)每个内部结点的标号为一个非终结符号。
4)如果非终结符号A是某个内部结点的标号,并且它的子结点的标号从左至右分别为X1,X2,…,Xn,那么必然存在产生式A→X1 X2 … Xn,其中X1,X2,…,Xn 既可以是终结符号,也可以是非终结符号。作为一个特殊情况,如果A→ε是一个产生式,那么一个标号为A的结点可以只有一个标号为ε的子结点。
推导可以经由分析树(Parse Tree)表示。
2.2.4 二义性
一个文法可能有多棵语法分析树能够生成同一个给定的终结符号串。这样的文法称为具有二义性(ambiguous)。要证明一个文法具有二义性,我们只需要找到一个终结符号串,说明它是两棵以上语法分析树的结果。
个人说明一个快速判断一个文法是否具有二义性:只要该文法的某一个产生式的右部的非终结符是对称的,那这个文法一般就是具有二义性的。 (充分条件)
例如:
文法如下
(其中产生式 string -> string + string 右部的非终结符是对称的,所以他是二义性文法。) —— 选择题可以用,不能作为证明或者简答
因为该文法可以推导产生串 9 - 5 + 2 有两棵分析树,所以该文法具有二义性。
2.2节练习
2.2.1 练习
1)
S -> S S *
-> S S + S *
-> a S + S *
-> a a + S *
-> a a + a *
2)
3)
用变量a进行乘和加操作的后缀表达式
2.2.2 练习
下面的各个文法生成什么语言?证明你的每一个答案
- S → 0 S 1 | 0 1
- S → + S S | - S S | a
- S → S ( S ) S | ε
- S → a S b S | b S a S | ε
- S → a | S + S | S S | S * | ( S )
1)生成至少1个0后面跟着至少1个1构成的表达式
2)生成用a进行加和减操作的前缀表达式
3)生成匹配的括号的表达式
4)生成任意个a和b构成的表达式
5)【不好描述,难搞!】生成以a+a,aa ,a* ,(a)组合或者嵌套的表达式。
2.2.3 练习
练习2.2.2中哪些文法具有二义性?
1)2)不是,3)4)5)是
第3章 词法分析
3.1 词法分析器
3.3.1 串和语言
字母表:有限符号的集合。
串:来自字母表符号的有限序列
串S的前缀:截取串S首字母开始,连续的,长度为n(0 <= n <= | S | ) 的串,串的前缀最多有 N + 1 个。( N = | S | )
串S的真前缀:串S的前缀的集合删除 ε 和 串S本身。
串S的后缀:删除串S字母开始,连续的,长度为n(0 <= n <= | S | ) 的串后的串,串的后缀最多有 N + 1 个。( N = | S | )
串S的真后缀:串S的后缀的集合删除 ε 和 串S本身。
串S的子串:截取串S中连续的,长度为n(0 <= n <= | S | ) 的串,串的子串最多有 N ( N + 1 ) / 2 + 1个。( N = | S | )
串S的真子串:串S的子串的集合删除 ε 和 串S本身。
串S的子序列:截取串S中长度为n(0 <= n <= | S | ) 的串,串的子序列最多有 2 N 个。( N = | S | )
3.3.2 语言上的运算
3.3.3 正则表达式
一个正则表达式是规则的集合,用于从一个字母表构造符号串的技术。
设A是个字母表,r 是个正则表达式,那么 L( r ) 是个由 r 的规则所刻画的语言。
归纳基础:
-
ε是一个正则表达式,L(ε) = lεI,即该语言只包含空串。
-
如果a是A上的一个符号,那么α是一个正则表达式,并且L(α)=l a l。也就是说,这个语言仅包含一个长度为1的符号串a。
归纳步骤:
由小的正则表达式构造较大的正则表达式的步骤有四个部分,假定r和s都是正则表达式,分别表示语言L( r )和L( s ),那么:
- ( r ) l ( s )是一个正则表达式,表示语言L( r ) U L (s)。
- ( r ) ( s ) 是一个正则表达式,表示语言L( r ) L ( s ) 。
- ( r ) * 是一个正则表达式,表示语言( L ( r ) ) *。
- ( r ) 是一个正则表达式,表示语言L ( r ) 。最后这个规则是说在表达式的两边加上括号并不影响表达式所表示的语言。
按照上面的定义,正则表达式经常会包含一些不必要的括号。如果我们采用如下的约定,就可以丢掉一些括号:
-
一元运算符*具有最高的优先级,并且是左结合的。
-
连接具有次高的优先级,它也是左结合的。
-
| 的优先级最低,并且也是左结合的。
正则表达式代数定律:
3.3.4 正则定义
将正则表达式与名字结合,形式为产生式,列如:letter -> A | B | … | Z | a | b | … | z | 。
3.3.5 正则表达式的扩展
-
一个或多个实例。单目后缀运算符 + 表示一个正则表达式及其语言的正闭包。运算符 + 和运算符*具有同样的优先级和结合性。两个有用的代数定律 r* = r+ l ε 和 r+ = r r* = r* r 说明了闭包*和正闭包之间的关系。
-
零个或一个实例。单目后缀运算符 ? 的意思是“零个或一个出现”。也就是说,r?等价于r l ε。运算符?与运算符+和运算符*具有同样的优先级和结合性。
-
字符类。一个正则表达式 a l a1 l … l an。(其中 ai 是字母表中的各个符号)可以缩写为[ a1a2…an.]。更重要的是,当a1,a2,…,,an形成一个逻辑上连续的序列时,比如连续的大写字母、小写字母或数位时,我们可以把它们表示成a1-an。也就是说,只写出第一个和最后一个符号,中间用连词符隔开。因此,[ abc ] 是 a l b l c 的缩写,[ a-z ]是 a l b l … I z 的缩写。
3.3.节练习
3.3.2
1)以字符a作为开始和结束,中间穿插任意个字符a或b的语言。
2)以任意个字符a和字符b构成的语言
3)以任意个字符a和字符b构成的,长度大于等于3且倒数第三个字符为a的语言
4)以任意个字符a和3个字符b构成的语言
5)以任意偶数个字符a和字符b构成的语言
3.3.3
试说明在一个长度为n的字符串中,分别有多少个
-
前缀——n + 1
-
后缀——n + 1
-
真前缀——n - 1
-
子串——n(n + 1) / 2 + 1
-
子序列—— 2n
3.3.5
1)other -> [ bcdfghjklmnpqrstvwxyz ]
other* a ( other | a )* e ( other | e )* i (other | i )* o (other | o )* u ( other | u )*
2)a* b* … z*
3.6 有穷自动机
- 有穷自动机是识别器(recognizer),它们只能对每个可能的输入串简单地回答“是”或“否”。
- 有穷自动机分为两类:
① 不确定的有穷自动机(Nondeterministic Finite Automata,NFA)对其边上的标号没有任何限制。一个符号标记离开同一状态的多条边,并且空串 ε 也可以作为标号。
② 对于每个状态及自动机输入字母表中的每个符号,确定的有穷自动机(Deterministic FiniteAutomata,DFA)有且只有一条离开该状态、以该符号为标号的边。
确定的和不确定的有穷自动机能识别的语言的集合是相同的。事实上,这些语言的集合正好是能够用正则表达式描述的语言的集合。这个集合中的语言称为正则语言。
3.6.1 不确定的有穷自动机(NFA)
一个不确定的有穷自动机(NFA)由以下几个部分组成:
- 一个有穷的状态集合S。
- 一个输人符号集合A,即输入字母表( input alphabet)。我们假设代表空串的不是公中的元素。
- 一个转换函数(transition function),它为每个状态和A U { ε }中的每个符号都给出了相应的后继状态(next state)的集合。
- S中的一个状态s被指定为开始状态,或者说初始状态。
- S的一个子集F被指定为接受状态(或者说终止状态的)集合。
不管是 NFA还是DFA,我们都可以将它表示为一张转换图( transition graph)。图中的结点是状态,带有标号的边表示自动机的转换函数。从状态 s 到状态 t 存在一条标号为 u 的边,当且仅当状态 t 是状态 s 在输入 a 上的后继状态之一。这个图与状态转换图十分相似,但是:
① 同一个符号可以标记从同一状态出发到达多个目标状态的多条边。
② 一条边的标号不仅可以是输入字母表中的符号,也可以是空符号串 ε 。
例如:( a | b )* abb 的NFA
3.6.2 转换表
我们也可以将一个 NFA表示为一张转换表( transition table),表的各行对应于状态,各列对应于输入符号和 ε。对应于一个给定状态和给定输入的条目是将 NFA的转换函数应用于这些参数后得到的值。如果转换函数没有给出对应于某个状态 – 输入对的信息,则填空集符号。
例如:( a | b )* abb 的转换表
3.6.4 确定的有穷自动机
确定的有穷自动机(简称DFA)是不确定有穷自动机的一个特例,其中:
- 没有输入 ε 之上的转换动作。
- 对每个状态 s 和每个输人符号 a,有且只有一条标号为 a 的边离开 s 。
3.6 节练习
为 a ( a | b )* a 设计一个DFA 和 NFA。
3.6.5
给出以下练习的NFA的转换表:
1)
2)
3)
3.7 从正则表达式到自动机
3.7.1 正则表达式到NFA
“语法制导”——按正规式的语法规则指引构造过程。
“语法制导”构造的特点:
- N( r ) 的状态数至多是 (字母个数 + 操作符数)*2。
- N( r ) 只有一个初态和一个终态。
- N( r ) 中的每个状态至多有一条标记为a ( a属于字母表 ) 的出边或者至多有两条标记为ε的出边。
- 需要仔细对所有状态进行命名
“语法制导”构造的构造步骤:
第一步 划分出正规式的子表达式
- ε
- 字母表中的字母
- r | s
- rs
- r*
第二步 为每个子表达式构造相应的NFA“ 片断 ”
- 对于正规式ε所构造的 NFA为:
- 对于正规式a所构造的 NFA为:
- 对正规式 s|t 所构造的NFA为:
- 对于正规式 st 所构造的NFA为:
- 对于正规式 s*所构造的NFA为:
第三步 将得到的NFA“片断”按一定规则拼接成NFA。
示例: ( ab*c ) | ( a ( b | c* ) )
3.7.2 NFA到DFA
定理:
设L为一个由不确定的有穷自动机接受的集合,则存在一个接受L的确定的有穷自动机。
算法:
子集法思想
算法步骤:
定义对状态集合I的有关运算:
-
状态集合I的ε-闭包,表示为ε-Closure(I)。
解释:集合I中的状态经过任意条弧ε能到达的状态的集合。 -
状态集合I的a弧转换,表示为move(I,a)。
解释:集合I中的状态只经过一条弧a能到达的状态的集合。
示例:( a | b )* abb 的NFA
该NFA的字母表:{ a,b }
第一步,计算ε-Closure( { 0 } ) 。
ε-Closure( { 0 } ) = { 0,1,2,4,7 } = A
第二步,对A
- 计算move( A,a ) 和 move( A,b ) 。
move( A,a ) = { 3,8 } = Aa
move( A,b ) = { 5 } = Ab - 计算ε-Closure( Aa ) 和 ε-Closure( Ab )
ε-Closure( Aa ) = { 1,2,3,4,6,7,8 } = B
ε-Closure( Ab ) = { 1,2,4,5,6,7 } = C
第三步,对B
- 计算move( B,a ) 和 move( B,b ) 。
move( B,a ) = { 3,8 } = Ba = Aa
move( B,b ) = { 5,9 } = Bb - 计算ε-Closure( Ba ) 和 ε-Closure( Bb )
ε-Closure( Ba ) = { 1,2,3,4,6,7,8 } = B
ε-Closure( Bb ) = { 1,2,4,5,6,7,9 } = D
第四步,对C
- 计算move( C,a ) 和 move( C,b ) 。
move( C,a ) = { 3,8 } = Ca = Aa
move( C,b ) = { 5 } = Cb = Ab - 计算ε-Closure( Ca ) 和 ε-Closure( Cb )
ε-Closure( Ca ) = { 1,2,3,4,6,7,8 } = B
ε-Closure( Cb ) = { 1,2,4,5,6,7 } = C
第五步,对D
- 计算move( D,a ) 和 move( D,b ) 。
move( D,a ) = { 3,8 } = Da = Aa
move( D,b ) = { 5,10 } = Db - 计算ε-Closure( Da ) 和 ε-Closure( Db )
ε-Closure( Da ) = { 1,2,3,4,6,7,8 } = B
ε-Closure( Db ) = { 1,2,4,5,6,7,10 } = E
第五步,对E
- 计算move( E,a ) 和 move( E,b ) 。
move( E,a ) = { 3,8 } = Ea = Aa
move( E,b ) = { 5 } = Eb = Ab - 计算ε-Closure( Da ) 和 ε-Closure( Db )
ε-Closure( Ea ) = { 1,2,3,4,6,7,8 } = B
ε-Closure( Eb ) = { 1,2,4,5,6,7 } = C
第六步,绘制表格
第七步,根据表格绘图
3.7.3 DFA最小化
一个有穷自动机可以通过消除多余状态和合并等价状态而转换成一个最小的与之等价的有穷自动机。
- 消除多余状态
- 合并等价状态
两个状态s和t等价的条件:
a.一致性条件
b.蔓延性条件
分割法思想:
把DFA分成一些不相交的子集,使得任何不同的两子集的状态都是可区别的,而同一子集中的任何两个状态都是等价的。
示例:
第一步,根据是否为结束状态将所有状态分为两个集合。(有时可能所有状态都是结束状态,所以只存在一个集合)
I1 = { A ,C,D }
I2 = { B,F }
第二步,考察集合的元素个数大于1个的集合
对I1:
move( A,a ) = D 属于 I1,move( A,b ) = D 属于 I1 。
move( C,a ) = F 属于 I2,move( C,b ) = D 属于 I1 。
move( D,a ) = B 属于 I2,move( D,b ) = D 属于 I1 。
发现A与CD经过一条弧到的的集合不同,将A分离单独一个集合,得到下面的集合:
I1 = { C,D }
I2 = { B,F }
I3 = { A }
对I2:
move( B,a ) = F 属于 I2,move( B,b ) = C 属于 I1 。
move( F,a ) = F 属于 I2,move( F,b ) = D 属于 I1 。
发现Bf经过一条弧到的的集合相同,不用分离。
考察结束,得到最小化的DFA。
第4章 语法分析
4.1 引论
4.1.3 语法错误的处理
错误类型的不同层次:
- 词法错误,包括标识符、关键字或运算符拼写错误和没有在字符串文本上正确地加上引号。
- 语法错误,包括分号放错地方、花括号,即“{”或“}”,多余或缺失。另一个C语言或Java语言中的语法错误的例子是一个case语句的外围没有相应的switch语句(然而,语法分析器通常允许这种情况出现,当编译器在之后要生成代码时才会发现这个错误)。
- 语义错误,包括运算符和运算分量之间的类型不匹配。例如,返回类型为void的某个Java方法中出现了一个返回某个值的return语句。
- 逻辑错误,可以是因程序员的错误推理而引起的任何错误。比如在一个C程序中应该使用比较运算符==的地方使用了赋值运算符=。这样的程序可能是良构的,但是却没有正确反映出程序员的意图。
错误处理:
- 清晰精确地报告出现的错误。
- 能很快地从各个错误中恢复,以继续检测后面的错误。
- 尽可能少地增加处理正确程序时的开销。
4.2 上下文无关法
4.2.1 定义
- 终结符号是组成串的基本符号。术语“词法单元名字”是“终结符号”的同义词。当我们讨论的显然是词法单元的名字时,我们经常使用“词法单元”这个词来指称终结符号。我们假设终结符号是词法分析器输出的词法单元的第一个分量。
- 非终结符号是表示串的集合的语法变量。非终结符号表示的串集合用于定义由文法生成的语言。非终结符号给出了语言的层次结构,而这种层次结构是语法分析和翻译的关键。
- 在一个文法中,某个非终结符号被指定为开始符号。这个符号表示的串集合就是这个文法生成的语言。按照惯例,首先列出开始符号的产生式。
- 一个文法的产生式描述了将终结符号和非终结符号组合成串的方法。每个产生式由下列元素组成:
- 一个被称为产生式头或左部的非终结符号。这个产生式定义了这个头所代表的串集合的一部分。
- 符号→。有时也使用::=来替代箭头。
- 一个由零个或多个 终结符号与非终结符号 组成的产生式体或右部。产生式体中的成分描述了产生式头上的非终结符号所对应的串的某种构造方法。
4.2.3 推导
- 在最左推导(leftmost derivation)中,总是选择每个句型的最左非终结符号。如果α=>β是一个推导步骤,且被替换的是α中的最左非终结符号,我们写作α=lm=>β。
- 在最右推导(rightmost derivation)中,总是选择最右边的非终结符号,此时我们写作α=rm=>β。
4.2.4 语法分析树和推导
语法分析树是推导的图形表示形式,它过滤掉了推导过程中对非终结符号应用产生式的顺序。语法分析树的每个内部结点表示一个产生式的应用。该内部结点的标号是此产生式头中的非终结符号A,这个结点的子结点的标号从左到右组成了在推导过程中替换这个A的产生式体。
一棵语法分析树的叶子结点的标号既可以是非终结符号,也可以是终结符号。从左到右排列这些符号就可以得到一个句型,它称为这棵树的结果(yield)或边缘(frontier)。
4.2.5 二义性
如果一个文法可以为某个句子生成多棵语法分析树,那么它就是二义性的(ambiguous)。换句话说,二义性文法就是对同一个句子有多个最左推导或多个最右推导的文法。
4.2.7上下文无关文法和正则表达式
文法是比正则表达式表达能力更强的表示方法。每个可以使用正则表达式描述的构造都可以使用文法来描述,但是反之不成立。换句话说,每个正则语言都是一个上下文无关语言,但是反之不成立。
例如,语言L = { anbn I n≥1 }是一个可以用文法描述但不能用正则表达式描述的语言的原型例子。
我们通俗地说“有穷自动机不能计数”,这意味着有穷自动机不能接受像{ anbn I n≥1 }这样的语言,因为它不能记录下在它看到第一个b之前读入的a的个数。类似地,“一个文法可以对两个个体进行计数,但是无法对三个个体计数”。
4.2.8 4.2练习
练习4.2.1 考虑上下文无关文法 S => S S + | S S * | a 以及串aa+a*
1)
2)
3)
4) 不是
5)进行加法和乘法的后缀表达式集合
练习4.2.2 对下面重复练习4.2.1
1)
2)
3)
4.3 设计文法
4.3.3 左递归的消除
把左递归的产生式A=>Aα | β替换为非左递归的产生式:
A => βA’
A’ => αA’ l ε
推广到一般式:
消除非立即的左递归:
将A中的S进行推导,
在对A进行立即左递归的消除,
4.3.4 提取左公因子
4.4 自顶向下的语法分析
4.4.2 First集和Follow集
First集:终结符的First集为本身。
对产生式A => αβ 进行分析,
- α为终结符,α属于First( A )
- α为非终结符,First( α ) 属于First( A )
- α => ε ,First( β ) 属于First( A )
- A => ε,ε 属于First( A )
Follow集:起始将 $ 放入 Follow( S ) 中,其中 S 为文法开始符号, $ 为结束标记
对产生式 A => αBβ 进行分析,
- β => ε 或者 β = ε ,Follow( A ) 属于 Follow( B )
- β为终结符,β 属于 Follow( B )
- β为非终结符,First( β ) - ε 属于 Follow( B )
4.4.3 LL(1)文法
一个文法G是LL(1)的,当且仅当G的任意两个不同的产生式 A→α l β 满足下面的条件:
- 不存在终结符号 a 使得α和β都能够推导出以 a 开头的串。
- α 和 β 中最多只有一个可以推导出空串。
- 如果β=*=>ε,那么α不能推导出任何以Follow( A )中某个终结符号开头的串。类似地,如果α=*=>ε,那么β不能推导出任何以Follow( A )中某个终结符号开头的串。
前两个条件等价于说First(α) 和 First(B)是不相交的集合。第三个条件等价于说如果 ε 在First( β )中,那么 First(α)和Follow( A )是不相交的集合,并且当 ε 在 FIRST( α )中时类似结论成立。
例如:
1)将文法中的“或”消去,即展开文法。
2)计算所有非终结符的First集和Follow集
计算First集和Follow集注意不要忘记该终结符可以推出 ε 的情况!!!
3)根据展开的文法和First集、Follow集 构造预测分析表
对每一个产生式 A => α 进行分析,
- First( α ) 的每个终结符号 a ,将产生式 A => α加入到Array[ A , a ]中
- First( α ) 中包括 ε ,对Follow( A ) 中的每个符号b(包括终结标记$) ,将产生式 A => α加入到Array[ A , b ]中。
另一种算法:Select( A => α )集的定义:
First( α ) 不包括ε,Select( A => α ) = First( α )
First( α ) 包括ε,Select( A => α ) = ( First( α ) - ε ) U Follow( A )
将每一个产生式A => α 填入对应的Array[ A , a ] ,其中a属于Select( A => α )
4.5 自底向上的语法分析
4.5.1 规约
规约就是最右推导的逆过程。
4.5.2 句柄剪枝
如果有S=*=> αAω => αBω ,那么紧跟 α 的产生式 A→B 是 αBω 的一个句柄( handle)。换句话说,最右句型γ的一个句柄是满足下述条件的产生式A→β及串β在y中出现的位置:将这个位置上的β替换为A之后得到的串是γ的某个最右推导序列中出现在位于γ之前的最右句型。
4.5.3 移入-归约语法分析技术
4.5.4 移入-归约语法分析中的冲突
有些上下文无关文法不能使用移入-归约语法分析技术。即使知道了栈中的所有内容以及接下来的 k 个输入符号,我们仍然无法判断应该进行移人还是归约操作(移入/归约冲突),或者无法在多个可能的归约方法中选择正确的归约动作(归约/归约冲突)。我们把它们称为非LR文法。LR( k )中的k表示在输人中向前看k个符号。在编译中使用的文法通常属于LR(1)文法类,即最多只需要向前看一个符号。
4.6 简单的LR技术
4.6.2 项和LR(0)自动机
一个文法G的一个 LR(O)项(简称为项)是G的一个产生式再加上一个位于它的体中某处的点。因此,产生式A→XYZ产生了四个项:
- A→·XYZ
- A→X·YZ
- A—XY·Z
- A→XYZ·
产生式A→ε 只生成一个项A→·。
如果G是一个以S为开始符号的文法,那么G的增广文法G’就是在G中加上新开始符号S’和产生式S’→S而得到的文法。引入这个新的开始产生式的目的是告诉语法分析器何时应该停止语法分析并宣称接受输入符号串。也就是说,当且仅当语法分析器要使用规则S’=>S进行归约时,输入符号串被接受。
构造LR(0)自动机
- 构造增广文法
- 计算开始状态I0,I0为Closure( S’ => · S )
- 对I0状态进行计算Goto( I0, X ),得到多个新的状态 Ii
- 对新的状态继续计算Goto( Ii, X ),直到不出现新的状态为止。
- 根据状态簇构造Action表和Goto表
- 根据Action表和Goto表,结合输入栈分析输入字符串
构造SLR语法分析表
1-4步与LR(0) 一样,在构造Action表和Goto表时多考虑了Follow集。在规约项只填Follow集的元素,在移进-规约冲突的项判断:
解决了LR(0)的移进-规约冲突。
构造LR(1)语法分析表
构造项集时比LR(0)多了向前搜索符,向前搜索符开始产生式为 $ ,其他产生式则:
构造Action表和Goto表是,类似SLR,只不过不适用Follow集而使用向前搜索符。
构造LALR(1)语法分析表
LALR(1)则就是合并同心集的LR(1),不过合并后如果产生规约规约冲突,则不能合并。
LR(0),SLR,LR(1),LALR(1)的区别
文法是LR(0),就一定是SLR。
第5章 语法制导翻译
5.1 语法制导定义
5.1.1 继承属性和综合属性
综合属性:结点N上的综合属性只能通过N的子结点或者N本身来定义。
继承属性:结点N上的继承属性只能由N的父结点、N的兄弟结点和N本身来定义。
终结符可以由综合属性,但不能由继承属性。
5.1.2 注释语法分析树
就是在语法分析树的继承上用语义规则来代替结点并计算好相应的属性值。
5.2 SDD求值顺序
5.2.1 依赖图
结合语法分析树,按照语义规则画好依赖。
5.2.3 S属性定义
如果一个SDD每个属性都是综合属性,那它就是S属性的。
对于S属性的SDD,可以按照语法分析树自底向上的顺序来计算它的各个结点的属性值。
5.2.4 L属性定义
如果一个SDD每个属性要么是综合属性,要么是有限制的继承属性,其中对继承属性的限制为,它的属性计算只能使用它的父结点,左边兄弟结点和自己本身结点。
对于L属性的SDD,适合自顶向下的语法分析顺序。
第6章 中间代码生成
6.1 语法树的变体
6.1.1 表达式的有向无环图(DAG)
将语法分析树的去掉非终结符,合并相同子树,即为有向无环图(DAG)
6.1.2 构造DAG的值编码
对DAG进行自底向上的顺序,按照< op , l , r >的格式书写,其中op为标号,l为左结点,r为右结点。变量和值特殊处理。
注意:左右结点的值不能随便选择,看表达式来确定!!!
6.1.3 6.1节练习
练习 6.1.1
解:
练习 6.1.2
1)
2)
3)
6.2 三地址代码
6.2.1 地址和指令
将指令:
do {
i = i + 1;
} while( a[ i ] < v );
翻译为三地址指令:
有点类似汇编!
6.2.2 四元式表示
格式为< op , arg1 , arg2 , result >
示例:
6.2.3 三元式表示
格式: < op , arg1 , arg2 >
间接三元式:
6.2.5 6.2节练习
6.2.1
6.2.2
1)
2)
3)