编译原理学习笔记----
Thompson算法由正规式构造NFA
例如:求正规式 1(0|1)*101 的NFA
首先将正规式r=1(0|1)*101分解成r=r1,r2r3
将r2,r3展开得:
不确定有穷自动机(NFA)
确定有穷自动机(DFA)
一个确定的有穷自动机M是一个五元组:M=(K, ∑,f,S,Z)其中,
1)K是一个有穷集,他的每个元素称为一种状态。
2) ∑是一个有穷字母表,他的每个元素称为一个输入符号,所以∑称为输入符号表。
3)f是转换函数,是KX∑-->K 上的映像,例如f(ki,a)=kj这就意味着,当前状态为k,输入字符a后,将转换到下一状态kj,我们把kj称为ki的一个后继状态;
4)S属于K,是唯一的一个出态。
5)Z属于K,是一个终态,终态也称为可接受状态或结束状态。
例如:
这个DFA可以表示一个状态图:
也可以用状态矩阵显示,行表示状态,列表示输入符号,终态在表的右端标1,非终态在表的右边标0。
NFA相对于DFA的2点区别:
(NFA的不确定性)
1.当前状态下,对同一字符可能有多于一个的下一状态。
2.可能存在ε状态转移。
NFA转DFA
例如:给出NFA的图
求其最小DFA,求法如下:
(ps:因为集合C和D接收一个输入字符a或b时都得到同一个集合(即同一状态),所以C和D不可区分,可以去掉D这一行)。
由正规式的NFA构造CFG(上下文无关文法)
例如:从正规式r=(a|b)*abb的NFA构造CFG
正规式对应的NFA为:
一般方法:
A → HT
H →ε| aH | bH
T → abb
所以,可以改写为:
A0 → aA0|bA0|aA1
A1 → bA2
A2 → bA3
A3 → ε
将每一个状态看做一个非终结符,相当于照图写出状态经一步能达到的状态转移。
文法类型
若文法G=(N,T,P,S)的每个产生式α→β中,均有α∈(N∪T)*,且至少含有一个非终结符,β∈(N∪T)*,则称G为0型文法。
对0型文法施加以下第i条限制,即得到i型文法。
1.G的任何产生式α→β(S→ε除外)满足|α|≤|β|;
2.G的任何产生式形如A→β,其中A∈N,β∈(N∪T)*;
3.G的任何产生式形如A→a或者A→aB(或者A→Ba),其中A和B∈N,a∈T。
LL(1)文法:一种自上而下的分析方法
构造LL(1)文法:
第一步:计算first集合
算法:每次选取右部第一个符号,推导直至右部第一个符号是终结符,把其加入First集合中。
例如:计算以下文法的First集合
L →E;L|ε
E →TE'
E'→+TE'|-TE'|ε
T →FT'
T'→*FT'|/FT'|mod FT'|ε
F →(E)|id|num
计算First集合要自下而上分析。
因为(E)第一个符号为终结符,所以将(直接加入First集合,同理id,num也加入First集合。
再如:T->FT',右部第一个符号是非终结符F,将其推导至F->(E)|id|num,发现3个推导式中第一个符号都为终结符,所以将他们都加入First集合。
需要注意的是,若产生式中包含ε也要加入First集合。
由以上的分析方法可计算出:
FIRST(F/T/E)= {( id num}
FIRST(T') = {* / mod ε}
FIRST(E') = {+ - ε}
FIRST(L) = {ε ( id num}
第二步:计算Follow集合(Folow集合的算法稍微复杂一些,教科书上的解释很复杂,我自己总结了一下,通俗的描述出来)
算法:4条规则( 求Follow(U) )
1.U是文法开始符号,则直接加入 # 。
2.形如 ...Ua... ,加入 a 。
3.形如 S->...UP... ,加入First(P),并且,如果First(P)中包含 ε 符号时,要去掉 ε 并加入Follow(S)。
4.形如 P->...U ,加入Follow(P)。
例如:计算以下文法的Follow集合
L →E;L|ε
E →TE'
E'→+TE'|-TE'|ε
T →FT'
T'→*FT'|/FT'|mod FT'|ε
F →(E)|id|num
计算Follow集合要自上而下分析。
因为L是开始符号,所以Follow(L)={#}
求E的Follow集合时,先将跟E有关的式子列出
L →E;L|ε
F →(E)|id|num
形如...Ua...,所以将;和)加入Follow(E)集合。
由以上分析方法即可求出:
FOLLOW(L) = {#}
FOLL0W(E/E')= {) ;}
FOLLOW(T/T')= {+ - ; )}
FOLLOW(F) = {+ - * / mod ) ;}
第三步:构造预测分析表
首先观察一下预测分析表和咱们求出的First和Follow集合:
FIRST(F/T/E)= {( id num}
FIRST(T') = {* / mod ε}
FIRST(E') = {+ - ε}
FIRST(L) = {ε ( id num}
FOLLOW(L) = {#}
FOLL0W(E/E')= {) ;}
FOLLOW(T/T')= {+ - ; )}
FOLLOW(F) = {+ - * / mod ) ;}
比如,求L这行,发现First(L)集合中有ε ( id num这几个符号,因此在这行的这几个符号中填入L的产生式(ε产生式可以忽略,并且如果有多个产生式,就填相关的产生式),又因为First(L)中包含ε,因此将这行存在于Follow(L)集合中所有的终结符填入ε。
第四步:根据预测分析表以格局的形式写出对某输入序列的分析过程。
例如:
匹配过程就是匹配到栈内容中的非终结符时,根据当前输入中匹配的终结符查预测分析表,选取相应的产生式,将产生式倒过来写,替换栈中匹配的非终结符。
LL(1)文法的判别:
简单判断:看看文法有无左因子,有无左递归,有就不是。
定义判断:
假设文法中有A->B|C
1.若First(B)∩First(C)有终结符,则不是LL(1)文法。
2.若B=>*ε 而且 C=>*ε,则不是LL(1)文法。
3.若B=>*ε 且First(C)∩Follow(A)有终结符,则不是LL(1)文法。
LR(0)文法:
LR(0)项目集:产生式右部用.分隔。例如:E->T*F 的LR(0)项目有
E->.T*F
E->T.*F
E->T*.F
E->T*F.
识别活前缀的DFA:
例如:文法为
E→E*T|T
T→T+F|F
F→id
给出它的识别活前缀的DFA。
解答:
就是将LR(0)项目集进行推导,直至产生式推导完毕。但是,要注意的是,如果推导出的产生式的 . 后面有非终结符,要将非终结符的活前缀一起写出来。例如,I5状态中的 E->E.*T 经过 * 推导出 E->E*.T , T 是非终结符,所以,下一个状态中要加入
T->.T+F
T->.F
F->.id
项目集存在移进/归约冲突:某一状态即可归约(.在产生式最后,推导完毕)又可以经过某字符到达下一状态。
例如:某文法活前缀的DFA
分析可知:I2 , I11存在移进/归约冲突。
LR(0)文法判断:存在移进/归约或者归约/归约冲突冲突就不是LR(0),如果冲突可解决就是SLR(1)。
SLR(1)文法判断:
当LR(0)文法存在移进/归约冲突时即状态集中同时存在
A→β1.β2 和 B→β.
如果FIRST(β2)∩FOLLOW(B)=Φ,则冲突可解决,是SLR(1)文法。
存在归约/归约冲突时即同时存在
A→α. 和 B→β.
如果FOLLOW(A)∩FOLLOW(B)=Φ,则冲突可解决,是SLR(1)文法。
句型、短语、直接短语、句柄
短语:以非终结符为根子树中所有从左到右的叶子。
直接短语:子树中只有父子两代的子树的叶子节点。
句柄:子树中最左边的那棵只有父子两代的子树的所有叶子结点自左至右排列起来,就是该句型的句柄。
例如:有文法
E→E+T|T
T→T*F|F
F→id
和句型:id1+id2*id3。
画出其分析树:
句柄:因为最左部有子树F1-id1只有父子两代关系,所以句柄为id1。
后缀式、三地址码、三元式、四元式
后缀式计算方法:将最后计算的计算符号放到最后,把它的左操作数放到开始(ps:左操作数如果是一个表示达也要重复之前操作并将结果放到中间),右操作数如果是一个表达式,则重复之前的操作将结果放到中间。
例如:求 3+5*2/7 的后缀式
分析:最后运算的符号是+,左操作数是3,右操作数是表达式5*2/7,将3放到最前面,+放到最后面,接着计算5*2/7的后缀式,同样5放到最前面,*放到最后并加入到之前的后缀式中间,计算2/7的后缀式,2放到最前面,/放到最后面,右操作数7放到中间,并将其整体加入到之前的后缀式中间。
因此,得到
3 5 2 * 7 / +
三元式、四元式
例如:计算 x:=(a+b)*(a+b) 的三元式和四元式。
首先画出它的注释语法树:
由注释语法树从叶子节点写到根结点很容易可得三元式:
(1)(+, a, b )
(2)(+, a, b )
(3)(*,(1),(2))
(4)(:=,x, (3))
和四元式:
(1)(+, a, b, T1)
(2)(+, a, b, T2)
(3)(*, T1,T2,T3)
(4)(:=,x, T3,T4)
其实四元式也就是给三元式每一步生成的式子用一个变量表示。
三地址码计算方法
例如,求 x := a + b * c 的三地址码序列:
(*,b,c,T1)
(+,a,T1,T2)
(:=,x,T2,T3)
将最先运算的表达式用一个变量T1表示,依次用T2,T3...表示其他运算,三地址码序列就是将表达式树用四元式组表示。