编译期中复习
编译期中的重点在两个方面,分别是词法分析 和 语法分析
正则表达式-> NFA -> DFA -> 化简DFA
提取左公因子,消除左递归,构造LL(1)分析表(无空串follow,有空串加入follow)
拓广加入开始并拆开,LR(0)分析表,SLR分析表
LR(1)分析表,LALR(1)分析表
词法分析
Token的基本格式为<ID,“a”>,<EQ,”=”>,诸如此类
正则表达式
字母表
字母表是符号的非空有穷集合。
比如:
机器语言字母表:
字母表:
符号串
用字母表中的符号所组成的任意有穷序列
通常约定:
用英文字母表比较靠前的小写英文字母表示符号,如
用小写希腊字母或字母表中靠后的英文字母表示符号串,如 等,其中 常用来表示空串
用大写英文表示符号串集合
相关术语
前缀,后缀,子串,子序列,逆转,长度…
运算
语言
符号串集合
运算
合并L M,连接 LM,方幂 闭包,正闭包 …
LM!=ML, {}M!=M{} ,{}M = M{}
正则表达式
定义:空集是正则表达式,表达的语言 ,字母表中的单字母都是正则表达式,归纳L(r) L(s),L(r)L(s),(L(r)) ,L(r)均为正则表达式
实例
C语言标识符:(A|B|C…Z|a|b|…z|)(A|B|C…Z|a|b|…z|) | (0|1…|9)
有符号整数:(+|-|)(0|1…|9)(0|1…|9)
性质
都是对于 合并运算 | 和 连接运算,满足交换,结合,分配律
正则定义
…
R为正则表达式,D为名字,R只能出现字母表与之前定义的正则表达式名字
例:
letter A|B|…Z|a|b…z|_
digit 0|1|…|9
id letter_(letter_| digit)
中心思想:把问题分解成多个小问题
扩展
一次或多次出现:+
0或1次出现: ?
字符类:【abc】表示a|b|c,【a-z】表示 a|b|…|z
双引号和转义字符\:取消特殊含义
补集字符:^
^匹配行开始,$匹配行结尾
状态转换
状态转换图
状态:在识别词素时可能出现的情况
看作是已处理部分的总结,接收状态或最终状态表示已找到词素,开始状态用开始边表示
边:从一个状态指向另一个状态
匹配规则
先匹配最长可能的串,然后排在前面的正则表达式优先匹配
有限自动机
确定的有限自动机(DFA)
定义:五元组,包含有限字母表,有限状态集合,开始状态,终止状态集合,以及转换函数(输入为a()时从一个状态转到下一个状态 ,可以用转移矩阵表示)

非确定的带有 转移的有限自动机(NFA)
其他定义与DFA一样,但是转移函数δ 是一个从 的映射(称为转换函数, 表示 Q 的幂集)
DFA是NFA的一种特例
正则表达式到有限自动机
步骤 正则表达式 -> NFA -> DFA
正则表达式转NFA
按照r的形式分类讨论

NFA转DFA
子集构造法:
定义::从状态集合S中任一状态处罚,仅沿弧到达的状态集合(包含S自身)称为S的闭包,记为
DFA中 M’的状态:每个状态是DFA M的状态集合,令是M的初始状态,M’的初始状态 ,包含M的任意终态的状态集合都是M’中的终止状态。
DFA M’的转换函数:
例: 

DFA的最小化
等价状态:对 设 若对任意当且仅当 ,则称p和q是等价状态

思路:找出等价的状态并合并它们
等价态的判定条件:
一致性条件:p和q必须同时为接收态,或者同时为非接收态
蔓延性条件:对任意符号, r和s必须等价 ,反过来,r和s不等价,则p和q不等价
等价类的划分方法
1. 把所有状态分为两个组:接受状态组和非接受状态组
2. 任意选定一个输入符号a,判断每个组中的各个状态对于a的转换,如果落入不同的组中则进行分割
3. 重复2,直到每个组中状态等价
例: 

语法分析
处理token序列,构造语法分析树
根据文法规则,从源程序单词符号串中识别出语法成分,并进行语法检查
基本任务:识别符号串 SS 是否为某个合法的语法单元
种类:通用,自顶向下,自底向上
文法
定义:,其中,
是非空有穷的终结符号集合
是一个非空有穷的非终结符号集合,
P = { 且至少包含一个非终结符},称为产生式集合
称为开始符号,必须在某个产生式的左部至少出现一次
上下文无关文法
CFG:
所有产生式的左边只有一个非终结符号,即产生式的形式为 ,因此不需要任何上下文就可以对A进行推导。
关于文法的约定
通常不用将文法的四元组显式地表示出来,而只需要将产生式写出,一般约定
大写字母表示非终结符,小写字母表示终结符
第一条产生式地左部是开始符号
用尖括号括起来的是非终结符,否则是终结符
例子

推导/归约
直接推导/归约
直接推导/ 直接归约:若某个串 α 可以根据某条文法一步化为串 β,则称:
可以直接推导出
可以直接归约到
定义 令 ,若,且 ,则称 直接推导出
拓展定义 为:
要么(直接就是)
要么(经过几次推导)
这里类似正则表达式,在正则表达式中:+ 代表一次或者多次匹配* 代表零次或者多次匹配
最左推导与最右推导
最左推导:若,即 α 是一个由终结符组成的字符串。
最右推导:若,即 β 是一个由终结符组成的字符串。
最左推导每次替换最左边的非终结符,而最右推导每次替换最右边的非终结符。
句子/句型/语言
句型:如果S ⇒ ,那么是文法的句型.可能包含非终结符和终结符,也可以是空串
句子:不包含非终结符的句型
语言:所有句子的集合,记为L(G)
证明文法生成的语言:
1. 证明L(G) L文法生成的任意句子都属于语言L
2. 证明L L(G)语言的任意句子都可以用文法G生成
3. 一般可以使用数学归纳法L L(G)按符号化才能长度构造,L(G) L按照序列长度归纳。
语法分析树
推导的一种图形表现
根节点:文法的开始符号S
叶子节点:非终结符,终结符或者 ε
内部节点:非终结符,每个内部节点往下推,表示某个产生式的一次应用,内部节点的标签为产生式左部,该节点的子节点对应产生式右部
例子

二义性
如果一个文法中存在某个句子有两棵分析树, 那么该句子是二义性的。如果一个文法产生二义性的句子, 则称这个 文法是二义性的。
二义性的消除方法没有规律可循
自顶向下的语法分析
消除文法中的左递归
文法左递归:
直接左递归:经过一次推导就可以看出文法存在左递归,如
消除直接左递归(变换成右递归):将原始规则 转换为
间接左递归:多次推导才可以看出文法存在左递归,如
消除间接左递归:1. 先转换成直接左递归: 转换为 ,再消除左递归
消除所有左递归的算法
1. 将文法G的非终结符顺序整理为
2. 逐步消除间接左递归:
对每个i从1到n,对于每个j从1到i-1,将形如的规则替换为 , 是当前的所有产生式。最后,消除所有直接左递归
3.化简得到的文法
预测分析法
试图从开始符号推导出输入符号串
以开始符号S作为初始的当前句型
每次为最左边的非终结符号选择适当的产生式
通过查看下一个输入符号来选择这个产生式
有多个可能的产生式时预测分析法无能为力
问题:当两个产生式具有相同的前缀时无法预测
文法: stmt→∣if expr then stmt else stmt | if expr then stmt
处理方法:提取左公因子
新文法:stmt→if expr then stmt elsePart elsePart→else stmt ∣ ε
提取左公因子
含有左公因子的文法:
提取左公因子:
自顶向下的语法分析
自顶向下的语法分析时从文法的开始符号出发,试构造出一个最左推导,从左到右匹配输入的单词串
步骤:
1. 推导替换:当前被替换的非终结符号为A,当前从左至右读到的单词符号为a
2. 匹配产生式:如果A的产生式为 ,其中只有 推导出的第一个终结符号为a,则选择产生式构造最左推导
3. 策略:用 替换A,进行预测分析,如果匹配失败,则进行回溯尝试
例子:
特点:
带预测的,通过试探和回溯来构造句子结构,保证最左推导
如何保证没有回溯
在自顶向下的分析技术中,通常使用向前看几个符号来唯一地确定产生式(这里只假定只看一个符号)。
假设当前句型是 ,而输入是xa,那么选择产生式 的必要条件是1.α⇒∗ε 且 β 以 a 开头;(可以用更强的条件替代:在某个句型中 a 跟在 A 之后)2.α ⇒ * a…;
如果按照这两个条件选择时能够保证唯一性, 那么我们就可以避免回溯。
First 和 Follow
First集合
First:可以从某个符号串 α 推导出的串的首符号(终结符)的集合。
特别地,如果 α⇒∗ε,即 α 可以推导出空串 ε,那么我们规定 ε∈First(α)
简单来说,First 集合包含了 α 能够推导出的所有串的第一个终结符。
计算First集合
计算单个符号 X 的 First 集合:
终结符:如果 X 是终结符,那么 First(X)={X}
非终结符:
是产生式,
如果某个 a 在 First(Yi)中,并且 ε 在 中,那么 a 也在 First(X)中。(如果 ε 在这些 First(Yi) 中,那么就意味着 Yi⇒∗ε,也就可以忽略前面的,一顺由ε推过来)
如果 ε 在 中,那么 ε 也在 First(X)) 中。(理解:所有的子部分都可以推出空串,那么 X 也可以推出空串。)
如果 X 是非终结符,并且 X→ε 是一个产生式,那么 ε 在 First(X) 中。 计算产生式右部 X1X2⋯XnX1X2⋯Xn 的 First 集合
向集合中加入 First(X1)中所有非 ε 的符号。
如果 ε 在 First(X1) 中,再加入 First(X2) 中的所有非 ε 的符号;
依次类推,直到所有 Xi 被处理完。
如果 ε 在所有的 First(Xi) 中,则将 ε 加入 First(X1X2⋯Xn) 中。
Follow集合
Follow:可能在某些句型中紧跟在非终结符 A 右边的终结符的集合。
,S为开始符号
简单来说,Follow 集合包含了在某些推导过程中可能出现在 A 右边的终结符。
如果 A 是某个句型的最右符号时,那么 结束标记$也属于 Follow(A)。
计算Follow集合
1.将右端结束标记 $ 放到 Follow(S)中。
2.不间断迭代以下规则,直到所有的 Follow 集合都不再增长为止:
如果存在产生式 A→αBβ,那么 First(β) 中所有非 ε 的符号都在 Follow(B)中。 (此时即存在式子可以推导出 Bx…,x∈First(β))
如果存在产生式 A→αB,或者 A→αBβ 且 First(β)包含 ε,那么 Follow(A) 中的所有符号都加入到 Follow(B) 中。(此时即 Follow(A)⊂Follow(B),因为对于每个 A 出现的式子,我们都可以执行这个替换,从而使得原本接在 A 后面的字符接到 B 后面。)
LL(1) 文法
对于文法中任意两个不同的产生式
不存在终结符号 a 使得 α 和 β 都可以推导出以 a 开头的串。
α 和 β 最多只有一个可以推导出空串。
如果 β 可以推导出空串,那么 α 不能推导出以 Follow(A) 中任何终结符号开头的串。理解:(如果可以,那么产生了二义性:对于 A 推导为 β,然后再推导得到空串,接着后接 Follow(A)中的字符;对于 A 推导为 α,然后再推导得到 Follow(A)中的字符)
注:这里不一定只有 α 和 β 两个产生式,而是所有可能的产生式,这里只是简写了。
等价于 ;(条件1、2) 如果ε∈First(β),那么; 反之亦然。(条件3)
说明:LL(1)中的第一个“L”表示从左到右扫描输入,第二个“L”表示生成一个最左推导,“1”表示为做出分析动作的决定,每一步只需向前看1 个符号,输入串以$ 为结束标记。这相当于对文法作扩充, 即增加产生式S’→S $。所以Follow(S)一定包含 $
判别:计算First和Follow集合,
预测分析表的构造方法
输入: 文法G
输出: 预测分析表 M
方法:
对于文法G的每个产生式
对于 中的每一个终结符号a,将 加入到M[A,a]中;
如果 中,那么对于Follow(A)中的每个符号b,将 加入到M[A,b]中;
最后在所有空白条目中填入error
例子 
非LL(1)文法
二义性文法肯定不是LL(1)文法,例如

某些非二义性文法如左递归文法不是LL(1)文法,也不适合自顶向下分析
自底向上的语法分析
为一个输入串构造语法分析树的过程
从叶子(输入串中的终结符号,将位于分析树 的底端)开始,向上到达根结点
可以看作从串w归约为文法开始符号S的过程
归约步骤:一个与某产生式右部相匹配的特定子串被替换为该产生式左部的非终结符号
对输入从左到右扫描,并进行自底向上语法分 析,实际上可以反向构造出一个最右推导
句柄
最右句型中和某个产生式右部匹配的子串,对它的 归约代表了该最右句型的最右推导的最后一步
和某个产生式体匹配的最左子串不一定是句柄( 需要归约后能回到开始符号 )。
例子: 
移进-归约分析
移入 - 归约语法分析是一种 自底向上的语法分析技术,主要操作包括 移入和 归约。
主要操作
移入(shift):将下一个输入符号移到栈的顶部
归约(reduce):将栈顶符号串(右部)替换为相应的产生式左部
接受(accept):语法分析成功完成
报错(error):发现语法错误,并调用错误恢复工具
分析过程
开始时刻:栈中只包含$,而输入为 w $
成功结束时刻:栈中$S,而输入$
在分析过程中,不断地移进符号,并在识别到句柄时进行归约
句柄被识别时总是 出现在栈的顶部

移进-规约分析中的冲突
有时候,即使知道了栈中所有内容、以及下 面k个输入符号,仍然无法知道是否该进行 归约,或者不知道按照什么产生式进行归约
分别对应两种冲突情形: “移进-归约”冲突 和“归约-归约”冲突
移进-归约”冲突的例子: 考虑以下文法:E→E+E | id, 假设当前状态是:栈:E 剩余输入:+id,在这种情况下,分析器可以选择: 将 + 移入栈中。也可以根据 E→id,将 E 归约为 d。
“归约-归约”冲突:考虑以下文法:S→A,S→B, A→a,B→a假设当前状态是:栈:a剩余输入:空在这种情况下,分析器可以选择:根据 A→a 进行归约,根据 B→a 进行归约.
LR分析
L 指的是从左向右扫描输入符号串
R 指的是构造最右推导的逆过程(即规范归约)
k 指的是决定动作时向前看的符号个数,通常取0或1
LR (0) 项
项:一些状态,这些状态表示了语法分析过程中所处的位置。
一个文法 GG 的一个 LR (0) 项 是 GG 的一个产生式再加上一个位于它的右侧某处的点。
举例: A→XYZ A→⋅XYZ A→X⋅YZ A→XY⋅Z A→XYZ⋅
这里,⋅ 标记了当前读到的位置,⋅ 左边是已经读到的,⋅ 右边是尚未读到的。
项表明了语法分析过程的给定点,我们已经看到一个产生式的哪些部分。
比如,A→X⋅YZ 表明当前已经读到了 X,期望接下来在输入中看到一个从 YZ 推导得到的串(从而可以规约回 YZ,再读入 YZ 后即可规约回 A)。
LR (0) 项可分为四类:
移进项:A→α⋅aβ,a∈VT,表示当前可以读取符号 a 并进行移入操作
待归约项:A→α⋅Bβ,B∈VN,表示当前需要继续其他操作后(至少还要把 B 给规约出来),才可以归约到 A
归约项:A→α⋅,表示当前可以进行规约操作(已经把一个产生式体完全读入了),即将 α 规约为 A
接受项:S′→S⋅
对于产生式 A→ε 的唯一一项是 A→⋅,它是归约项。
项集:这些项的列表
规范 LR (0) 项集族的构造
为了构造一个文法的规范 LR (0) 项集族,我们定义了一个 增广文法和两个函数:Closure(I)(对应于DFA化算法的ε-CLOSURE) 和 Goto(I,X)(对应于DFA化算法的转换边)。
增广文法
如果 G 是一个以 S 为开始符号的文法,那么 G 的增广文法 G′ 就是在 G 中加上新开始符号 S′和产生式 S′→S而得到的文法。
当且仅当语法分析器要使用规则 S′→S 进行归约时(即 S′→S⋅),输入符号串被接受(即表明已经完全规约回到了原开始符号)。
引入这个新的开始产生式的目的是使得文法开始符号(S′)仅出现在一个产生式的左边,从而使得分析器只有一个接受状态。
补充:其实就是I0包含所有·在最左边的,然后依次按照·要经过的来分,如果右边是非终结符则把所有的列出来,其实是先得到dfa再得到项集,然后依次把边填入表中,有边填S,无边填r,最后找到结束符处填acc
计算closure
开始,将 I 中的各个项加入到 Closure(I) 中。
如果 A→α⋅Bβ在 Closure(I) 中,B→γ 是一个产生式,并且项 B→⋅γ 不在 Closure(I) 中,就将这个项加入其中。不断应用这个规则,直到没有新项可以加入到 Closure(I)为止。
Goto函数
Goto 函数形式为 Goto(I,X)其中:
I 是一个项集
X 是一个文法符号
Goto(I,X) 被定义为 I 中所有形如 【A→α⋅Xβ】的项所对应的项【A→αX⋅β】 的集合的闭包,即:Goto(I,X)=Closure({【A→αX⋅β】∣【A→α⋅Xβ】∈I})
直观地讲,Goto 函数用于定义一个文法的 LR (0) 自动机中的移入单个符号( 终结符号或者非终结符号都可以 )的步骤,也即一类 状态转换。
LR自动机的构造
规范LR(0)项集族中的项集可以作为LR(0)自动 机的状态
GOTO(I,X)=J,则从I到J有一个标号为X的转换
初始状态为CLOSURE({S’→•S})对应的项集
接受状态:包含形如A→α • 的项集对应的状态
LR语法分析表
语法分析表由两个部分组成: 一个语法分析动作函数 Action和一个转换函数 Goto
Action 表
Action 函数有两个参数:
状态 i
终结符号 a(或者是输入结束标记 $)。
Action【i,a】的取值可以有下列四种形式:
移入(Goto) Sj:j 表示一个状态,Sj 表示移进(Shift)到 j。语法分析器的动作是将输入符号 a 移入栈中,使用状态 j 来代表 a
归约(Reduce) rj:产生式 j=A→β语法分析器将栈顶的 β 根据这个产生式归约为产生式头 A
接受(Accept):语法分析器接受输入并完成语法分析过程
报错(Error):语法分析器在输入中发现错误并执行某个纠正动作
Goto 表
我们把定义在项集上的 Goto 函数扩展为定义在状态集上的函数:如果 Goto【Ii,A】=Ij那么 Goto 把状态 i 和一个非终结符号 A 映射到状态 j。
比如,对文法
假设输入字符串为id+id*id

SLR(simple LR)分析表
依据 Follow集来选择是否进行归约。

如果按照上面的步骤填写的分析表中没有冲 突(即每个单元格中只包含一个动作),那么得到的就是合法的SLR分析表
其他同LR(0),但是r处先别写,求follow集,在E的follow集里填r
LR(1)
构造有效 LR (1) 项集族的方法实质上和构造规范 LR (0) 项集族的方法相同。
构建I0时 区别在于多了向前开始符。 首先要对拓广的开始符的follow集加入结束符 A = α⋅Bβ,a 如果为空,照抄上面的,不为空加入first(β) 新式子的向前搜索符重新算 r只给向前搜索符中的填
LR(1)有效项

Closure
设 I 是 G 的一个 LR (1) 项集,Closure(I) 是从 I 出发用以下三条规则构造的项集:
每一个 I 中的项都属于 Closure(I)。
若项 【A→α⋅Bβ,a】 属于 Closure(I)且 B→γ∈P则对任何 b∈First(βa),把 【B→⋅γ,b】 加到 Closure(I) 中。
重复执行 (2) 直到 Closure(I)不再增大为止。
Goto
设 I 是 G 的一个 LR (1) 项集,X 是一个文法符号,定义:Goto(I,X)=Closure(J)。其中 J={【A→αX⋅β,a】∣【A→α⋅Xβ,a】∈I}
构造LR(1)分析表
DFA 状态对应分析表行:
DFA 中的每个状态对应分析表中的一行。
DFA 状态转移:
对于 DFA 中的每一个从状态 i 到状态 j 的转移:
如果转移符号为终结符 a:在表项 M【i,a】中填写 移进动作 Sj
如果转移符号为非终结符 A:在表项 M【i,A】中填写 转移到状态 jj
包含归约项 【A→α⋅,a】的状态 i:
在表项 M【i,a】中填写归约动作 rk,其中 k 是产生式 A→α的编号
注意:如果每个单元格中只包含一个动作,则分析表合法
举例:


LALR(1)
合并同心集(合并搜索符串)后构造出的 LR 分析表。
合并同心项集不会产生移进 / 归约冲突,但是有可能产生归约 / 归约冲突。
合并表达式相同,向前搜索符不同的项集
高效构造
通过先构造 LR (1) 分析表再合并得到 LALR (1) 分析表的过程太慢了。
内核项表示:
使用内核项表示 LR (0) 或 LR (1) 项集。
内核项:【S′→⋅S】或 【S′→⋅S,$】,以及 ⋅⋅ 不在最左边的项(这些项代表对于已经读入的符号完全没有要求)。
传播和自发生成:
通过传播和自发生成,获得向前看符号,得到 LALR (1) 内核项。
传播 / 自发生成:向前看符号的传递过程。
对于某个项 】A→α⋅Bβ,a】 执行闭包:
传播:假设向前看符号是一个不在文法中的符号 #,即对 [A→α⋅Bβ,#][A→α⋅Bβ,#] 进行闭包,若得到的某些项的向前看符号 就是 #,那么就认为这些项的向前看符号是传播得到的,直接复制 a,就行;
自发生成:假设向前看符号是一个不在文法中的符号#,即对 【A→α⋅Bβ,#】 进行闭包,若有些项的向前看符号 不是 #,那么就认为这些项的向前看符号是传播得到的,不改动这些项的向前看符号;
Closure 函数:
使用 ClosureClosure 函数求出内核项的闭包,得到 LALR 分析表。
四种LR解析的对比
如果构造 LR (0) 的 DFA
没有归约冲突就是 LR (0) 文法
有冲突但可以通过 Follow 集合解决冲突就是 SLR 文法
否则不是 SLR 文法
如果构造 LR (1) 的 DFA
没有冲突就是 LR (1) 文法
如果合并同心集之后也没有冲突,那么就是 LALR (1) 文法
包含关系
LR(0)<SLR<LALR<LR(1)