语法分析
语法分析的若干问题
语法分析的作用
根据词法分析提供的记号流,生成语法树
检查输入中的语法错误
语法错误的处理原则
可能出现的错误:语法错误和语义错误
语法错误:
- 词法错误:出现非法字符或关键字、标识符的拼写错误
- 语法错误:语法结构出错
语义错误:
- 静态语义错误:编译时检查出的错误
- 动态语义错误:运行时检查出的错误
错误的诊断和恢复集中在语法分析阶段:
原因一:大多数错误是语法错误
原因二:语法分析方法的准确性
语法错误的处理目标
清楚而准确地报告错误的出现,地点正确、不漏报、不错报也不多报
迅速从每个错误中恢复过来,以便分析继续进行(不是遇到第一个错误就停止)
不应使对语法正确源程序的分析速度降低太多
语法错误的基本恢复策略
- 紧急方式恢复:抛弃若干输入,直到遇到同步记号。
- 短语级恢复:采用串替换的方式对剩余输入进行局部纠正(抛弃+插入)。
- 出错产生式:用出错产生式捕捉错误(预测错误)。预置型的短语级恢复方式。
- 全局纠正:对错误输入序列x,找相近序列y,使得x变换成y所需的修改、插入、删除次数最少。
上下文无关文法
上下文无关文法是一个四元组:
G=(N,T,P,S)
N是非终结符的有限集合
T是终结符的有限集合
P是产生式的有限集合,产生式的形式:A→a。其中A∈N, α∈(N∪T)*
S是非终结符,被称为文法的开始符号
默认用大写表示非终结符
小写表示终结符
推导
推导:从文法的开始符号S开始,反复使用产生式,将产生式左部的非终结符换为右部的文法符号序列,直到得到一个终结符序列。
直接推导
零步或多步推导
至少一步推导(推导过程中至少使用一次产生式)
推导具有自反性和传递性
上下文无关语言(CFL)
左推导:在推导过程中,若每次直接推导均替换句型中最左边的非终结符,则称为最左推导。
左句型:由最左推导产生的句型
最右推导(规范推导)
推导、分析树与语法树
推导的过程可以用一棵树来表示,称为分析树
- 每一直接推导(或每个产生式)对应一棵仅有父子关系的子树,即产生式左部非终结符长出右部的孩子。
- 分析树的叶子从左到右构成G的一个句型。若叶子仅有终结符构成,则构成一个句子
分析树既反映了产生句型的推导过程,又反映了句型的结构
语法树:用树反映句型的结构实质,而忽略句型的推导过程。语法树是表示表达式结构的最好形式。
实质上,语法树与分析树的最根本区别在于它们的内部节点(包括根结点)
分析树的内部节点是非终结符
语法树的内部节点是操作符(运算符)
语法树省略了反应分析过程的非终结符
抽象语法树:语法树
具体语法树:分析树
二义性与二义性的消除
若文法G对同一个句子产生不止一棵分析树,则称G是二义的。
文法二义的实质是,在产生句子的过程中某些直接推导有多于一种选择,从而使得下一步分析不确定。
一个句型有多于一棵分析树,仅与文法和句型有关,与采用的推导方式无关。
造成二义性的原因,是文法中缺少对文法符号优先级和结合性的规定。
在任何一个程序设计语言中,若出现了二义性,则表示同一段程序在确定的、相同的环境下反复执行,会得到不同的结果,而在这种情况在程序设计中是不允许的。
任何一个程序设计语言都不应该存在二义性
二义性的消除
一个文法是二义的,并不意味着它所产生的语言一定是二义的,只有当产生一个语言的所有文法都是二义的,这个语言才被认为是二义的。
解决二义性问题:
- 改写二义文法为非二义文法
- 对二义文法施加限制,具体就是为文法符号规定优先级和结合性。
改写二义文法
基本思想:引入新的终结符,使原来分辨不清的结构受到约束,从而使得对任何一个句子,仅能构造一棵分析树。
关键步骤:
引入一个新的非终结符,增加一个子结构并提高一级优先级;
递归非终结符在终结符左边,运算具有左结合性,否则具有右结合性;
为文法符号规定优先级和结合性
二义文法的优点:
比非二义文法容易理解
分析效率高(分析树低,直接推导步骤少)
语言与文法简介
正规式和上下文无关文法
文法的重要作用:
给出精确、易于理解的语言结构说明;
以文法为基础的语言,便于加入新的、或修改、删除旧的语言结构;
有些类别的文法,可以自动生成高效的分析器。
正规式与上下文无关文法
正规式所描述的语言结构可以用CFG描述,反之不一定
为什么用正规式而不用CFG描述程序设计语言的词法
词法规则简单,用正规式描述已足够;
正规式的表示比CFG更直观、简洁、易于理解;
有限自动机的构造比下推自动机简单,且分析效率高;
区分词法和语法,为编译器前端的模块划分提供方便。
一般情况下,正规式适合描述线性结构,如标识符、关键字、注释等,CFG适合描述具有嵌套层次性质的非线性结构
上下文有关文法(CSG)
例如:变量的声明和引用,过程调用时形参和实参的一致性检查
形式语言和自动机
乔姆斯基
文法四类型:0型,1型,2型,3型
任何0型文法都是递归可枚举的,递归可枚举集也必定是一个0型语言
1型文法就是上下文有关文法,这种文法意味着对非终结符的替换必须考虑上下文,并且一般不允许换成ε串
2型文法就是上下文无关文法,非终结符的替换无需考虑上下文
3型文法等价于正规式
i 型文法比 i+1 型文法能力强
就描述与识别能力来讲,0型文法和图灵机最强,3型文法和有限自动机最弱
自上而下语法分析
基本思想:对于任何一个输入序列,从文法的开始符号开始,进行最左推导,直到得到一个合法句子或者发现一个非法结构
自上而下语法分析可能遇到问题:
由于存在左因子,会出现大量回溯
由于存在左递归现象,是分析无法继续进行。
对文法重写
消除左递归,避免陷入死循环
提取左因子,以避免回溯
消除左递归
左递归:若文法G中的非终结符A,对某个文法符号序列α存在推导
直接左递归:若G中有形如A→Aα的产生式,则称该产生式对A直接左递归。
消除文法的直接左递归
算法:
整理A产生式为如下形式:
A→Aα1|Aα2|···|Aαm|β1|β2|···|βn
其中,αi非空,βj均不以A开始,然后用下述产生式代替A产生式
A→β1A‘|β2A‘|····|βnA‘
A’→α1A‘|α2A‘|···|αmA‘|ε
消除文法的左递归
将非终结符合理排序:A1,A2,A3,···,An
for(i = 2; i <= n; i++)
{
for (j=1;j<=i-1;j++)
{
用Aj→δ1|δ2|···|δk的右部
替换每个形如Ai→Ajγ产生式中的Aj,
得到新产生式:Ai→δ1γ | δ2γ | ... | δkγ;
消除Ai产生式中的直接左递归;
}
}
核心思想:将不是直接左递归的非终结符右部展开到其他产生式中
该算法不能消除所有文法中的左递归,若文法G产生句子的过程中出现了A+=>A的推导则无法消除左递归
提取左因子
算法:
重排A产生式:A→αβ1|αβ2| ··· |αβn|γ;
并用 A→αA’|γ
和 A’→β1|β2| ··· |βn取代原A产生式
重复上述过程,直到所有A产生式候选项中不再有公共前缀
当一个文法中即有左递归又含左因子时,一般的做法是先消除左递归,因为左递归也是左因子的一种形式,当左递归消除后,同时也消除了部分左因子。
递归下降分析
递归下降分析是直接以程序的方式模拟产生式产生语言的过程
基本思想:为每一个非终结符构造一个子程序,每一个子程序的过程体中按该产生式的候选项分情况展开,遇到终结符直接匹配,而遇到非终结符就调用相应非终结符的子程序。
递归下降分析对文法的限制是:不能有公共左因子和左递归
构造递归下降子程序:
构造文法的状态转换图并化简
将转换图转化为EBNF表示
从EBNF构造子程序
文法的状态转换图
化简的策略:
- 标记为A的边可等价为标记ε的边转向A转换图的初态;
- 标记为ε边所连接的两个状态可以合并
- 标记相同的路径可以合并
- 不可区分的状态可以合并
文法的扩展BNF表示(EBNF)
① { }:重复0或若干次(while)
② [ ]:可选择内容(if或while)
③ |:括弧( )之内的或关系(case)
④ ( ):改变运算的优先级和结合性
递归下降子程序
EBNF已经可以看做是程序的抽象
lookahead是当前的下一个输入终结符
eof是输入的结束标志
match(t):用于进行终结符的匹配
再次强调递归下降的基本思想:
从文法的开始符号开始,根据当前输入序列中的终结符,按不同情况展开非终结符的右部,最终与输入序列完全匹配或报错。
预测分析器
预测分析器由:一张预测分析表,一个符号栈,一个驱动器 组成
消除了递归子程序调用,所以也称为非递归预测分析器或者表驱动的预测分析器
非递归预测分析器的工作模式
下推自动机:
由一个只读头、一个下推栈,一个有限状态转移控制组成
预测分析器是下推自动机的一个具体实现;
下推栈是符号栈,存放非终结符和终结符
有限状态转移控制由一个预测分析表和一个驱动器组成
格局:三元组(栈内容,当前剩余输入,改变格局的动作)
初始格局
接受格局:表明分析成功
出错格局:发现一个语法错误
有限状态转移控制根据当前下推栈中的内容和当前的剩余输入确定相应动作,改变下推栈和剩余输入的状态,从而进入下一个格局。
在预测分析器中,驱动器与预测分析表协同工作(在预测分析器中,驱动器和预测分析表其有限状态转移控制的作用),实现对格局的改变
预测分析表
M[A,a]:所有的非终结符构成分析表的行下标,所有的终结符构成分析表的列下标。其内容表示当前栈顶为非终结符A且当前输入为终结符a时,分析器要进行的动作。
非终结符构成行下标
终结符构成列下标
改变格局的动作:
①匹配终结符:若栈顶和当前输入终结符相等且不是结束标志,则分析器弹出栈顶符号,输入指针指向下一个终结符
②展开非终结符:栈顶符号是非终结符X,当前输入是终结符a,驱动器访问分析表M[X,a];若M[X,a]是X产生式的某候选项,则用此候选项取代栈顶的X
③报告分析成功:若栈顶和当前输入符号均为#,则分析成功并结束
④报告出错:其它情况,调用错误恢复例程。
驱动器的算法
x = top;
a = ip;
while (x!=#)
{
if (x belongs to T)
{
if (x == a) //匹配终结符
{
pop(x);
next(ip);
}
else
exit(error);
}
else
{
if (M[x, a] = X -> Y1 Y2 ··· Yk)
{
pop(X); //展开产生式
push(Yk Yk-1 ··· Y2 Y1);
}
else
exit(error);
}
}
构造预测分析表
预测分析方法的特征:
驱动器与文法无关,所有预测分析器的驱动器均是相同的,而唯一不同的是预测分析表中的内容。
构造预测分析表:构造FIRST和FOLLOW集合,根据两个集合构造分析表
FIRST集合:α的FIRST集合,就是从α开始可以推导出的所有以终结符开头的序列中的开头终结符。
FOLLOW集合:一个非终结符A的FOLLOW集合,即从文法开始符号可以推导出的所有含A序列中紧跟A之后的终结符
FIRST集合算法:
- 若X是终结符,则FIRST(X)=X;
- 若X是非终结符,且X→ε,则ε加入到FIRST(X)中
- 若X是非终结符,且X→Y1Y2···Yk,并令Y0=ε,Yk+1=ε,则从左到右所有 j (0<=j<=k),若a∈FIRST(Yj+1)且ε∈FIRST(Yj),则加入a到FIRST(X)中
FOLLOW算法:(非终结符才有FOLLOW集合)
- 加入#到FOLLOW(S)中,其中S是开始符号,#是输入结束标记
- 若有产生式A→αBβ,则除ε外,FIRST(β)的全体加入到FOLLOW(B)中
- 若有产生式A→αB或A→αBβ而ε∈FIRST(β),则FOLLOW(A)的全体加入到FOLLOW(B)中
FIRST集合的计算是自底向上的(从远离文法开始符号的非终结符开始)
FOLLOW集合的计算是自顶向下的(从文法开始符号开始)
构造预测分析表算法:
- 对文法的每个产生式A→α,执行2.和3.;
- 对FIRST(α)的每个终结符a,加入α到M[A,a];
- 若ε∈FIRST(α),则对FOLLOW(A)的每个终结符b(包括#)加入α到M[A,b];
- M中其它没有定义的条目均是error。
M[A,a]的作用:指导产生式产生句子
M[A,a]指导下一部动作:
- 若当前栈顶为A,当前输入为a,则规则2表示下一步动作是展开A→α,因为a∈FIRST(α),所以展开后下一次正好匹配a。
- 若当前栈顶为A,当前输入为b且b∈FOLLOW(A),则规则3表示下一步动作是展开A→ε,即栈顶弹出A,继续分析A之后的部分,因为b∈FOLLOW(A),所以弹出A后下一次正好匹配A的后继b。
LL(1)文法:
每个M[A,a]中最多有一个条目,因此对于每个栈顶符号和当前输入终结符对,有且仅有一个动作与之匹配(包括出错条目),从而分析的每一步均是确定的。
可是,也会在M[A,a]存在多个条目
一个文法G称为LL(1)文法,当且仅当它构造的预测分析表中不含多重定义的条目时。
第一个L表示从左到右扫描输入序列
第二个L表示产生最左推导
1表示在确定分析器的每一步动作时向前看一个终结符
任何二义文法都不是LL(1)文法
任何含有左递归和左因子的文法不是LL(1)文法
推论:
一个文法G是LL(1)的,当且仅当G的任何两个产生式A→α|β满足下面条件:
- 对任何终结符a,α和β不能同时推导出以a开始的串;
- α和β最多有一个可以推导出ε;
- 若β=*>ε,则α不能导出以FOLLOW(A)中终结符开始的任何串。
若条件1不满足,即存在终结符a,α和β同时推导出以a开始的串,则根据算法步骤2.,M[A,a]中有多重定义A→α和A→β;
若条件2不满足,即α和β均可推出ε串,则根据算法步骤3,任何属于FOLLOW(A)的终结符b(包括#),M[A,b]中有多重定义A→α和A→β ;
若条件3不满足,即存在终结符b,它既在FOLLOW(A)中,又在FIRST(α)中,则算法步骤2把条目A→α加入到M[A,b]中,而步骤3又把条目A→β加入到M[A,b]中,即M[A,b] 中有多重定义A→α和A→β。
我们一般不使用LL(1)文法,因为:
文法比较难写
LL(1)文法适应范围有限,对于有些语言,往往写不出它的LL(1)文法
改写LL(1)文法基本就是消除左递归和提取左因子
自下而上的语法分析
自上而下的分析采用的是推导,即从根到叶子构造分析树,也即从文法的开始符号产生出句子
自上而下的分析(最左推导)的方法是产生语言的自然过程。
对于源程序的分析过程来讲,自下而上分析的方法更自然,因为:语法分析处理的对象一开始都是终结符组成的串,而不是文法的开始符号
自下而上的分析采用的规约,即从叶子到根构造分析树,也即从句子开始归约出文法的开始符号。
自下而上的中最一般的方法:LR方法
缺点:分析表的构造比较复杂
LR方法的能力比自上而下的分析LL方法要强
还有一种改的自下而上的分析方法是:算符优先分析
适合于表达式结构的语法分析,便于手工构造
自下而上分析的基本方法
基本思想:从待分析的句子ω开始,从左到右扫描ω,反复用产生式的左部替换产生式的右部、谋求对ω的匹配,最终得到文法的开始符号,或者发现一个错误。
规范规约与“剪句柄”
定义:设αβδ是文法G的一个句型,若存在S =*>αAδ,A =+>β,则称β是句型αβδ相对于A的短语。特别的,若有A→β,则称β是句型αβδ相对于产生式A→β的直接短语。
一个句型的最左直接短语被称为句柄。
短语形成的两个要素:
从开始符号可以推导出某个非终结符,即S=*>αAδ;
从A开始经过至少一次推导得到短语β,即A=+>β。
直观上,句型是一个完整结构,短语是句型中相对某非终结符的局部(针对某非终结符)。S是一个句型而不是一个短语(树根不是短语)。
分析树中的叶子与短语、直接短语和句柄有以下关系:
短语:以非终结符为根的子树中所有从左到右的叶子;
直接短语:只有父子关系的树中所有从左到右排列的叶子(树高为2);
句柄:最左边父子关系树中所有从左到右排列的叶子(句柄是唯一的)。
如果要确定一个短语不是XX句型中相对于任何非终结符的短语,就是找:
从文法开始符号推导不出非终结符X和相应的句型
或者即,
在分析树中,找不到任何一个非终结符,它的子树的所有叶子构成该短语
上述定义,举个栗子:
最左归约的逆过程是最右推导
最右推导称为规范推导
最左归约称为规范归约
最左归约也称为规范归约
剪句柄:从一个句子假象的分析树开始,每次把句柄剪去(丢弃相应非终结符的孩子),从而暴露下一个句柄,直到露出根为止。
移进——归约分析器的工作模式
移进——归约方法:用一个栈“记住”将要归约句柄的前缀,并用一个分析表来确定何时栈顶已形成句柄,以及形成句柄后选择哪个产生式进行归约
移进——归约分析器的工作模式仍以格局变化来反映。
格局的形式(栈,剩余输入,动作)
分析从初始格局开始,经过一系列格局变化,最终达到接受格局,表明分析成功;到达出错格局,表明发现一个语法错误
开始格局中的剩余输入应该是全部输入序列,而接受格局中剩余输入应该是空,任何其他格局或者出错格局的剩余输入应该是全部输入序列的一个后缀。
格局中栈的内容一般是文法符号与状态
改变格局的四个形式:
移进:把当前输入的下一个终结符移进栈
归约:句柄在栈顶形成,用适当产生式左部代替句柄
接受:宣告分析成功
报错:发现语法错误,调用错误恢复例程。
从移进——归约的分析过程中可以看出:
句柄总是在栈顶形成
栈中保留的总是一个右句型的前缀(活前缀)
最左归约是逻辑上从下到上构造一个分析树或者是从下到上剪句柄。
LR分析
特点:
采用最一般的无回溯移进——归约方法
可分析的方法是LL文法的真超集
能够及时发现错误,及时从左到右扫描输入序列的最大可能
分析表较复杂,难以手工构造
LR分析器的核心是LR分析表和驱动器
LR分析表:
一部分是动作表(action)
一部分是转移表(goto)
action[s,a]:指示当前栈顶状态为s和输入终结符为a时应进行的下一动作
goto[s,A]:指示当前栈顶为s和非终结符A时的下一个状态转移
改变格局的四个动作:
① action[s,a]= si:(移进)
② ------------- = rj:用第j个产生式的左部替换栈中的句柄
③ ------------- = acc:接收
④ ------------- = blank:报错
⑤ goto[s,A] = s’:s状态下遇到A转移到状态s’。
提示:②和⑤共同完成归约。
LR文法:
文法中允许出现左递归,这是LL分析无法做到的
文法中减号即可用于二元运算,也可用于一元运算,这是算符优先分析不易做到的
LR分析算法:
输入:输入序列ω和文法G的LR分析表(action与goto)
输出:若ω属于L(G),得到ω的规范归约,否则指出一个错误
方法:初始格局为:(#0,ω#,驱动器的第一个动作),其中0是初态
令ip指向ω#中的第一个终结符,top指向栈顶初始状态;
loop s = top ^; a = ip ^;
case action[s,a] is
shift s': push(a); push(s'); next(ip) --移进,a与s'进栈且扫描下一个输入
reduce by A→β :
pop(2 * | β | ); --弹出句柄和相应状态
s' := top^; -- 暴露出当前栈顶状态s'
push(A); --产生式左部符号进栈
push(goto(s',A)); -- 新栈顶状态进栈
write(A→β); --完成归约,跟踪分析轨迹
accept: return; --成功返回
others : error; --出错处理
end case;
end loop;
每次文法符号与当前状态同时进栈或出栈
LR(k)文法
若为文法G构造的移进-归约分析表中不含多重定义的条目,则称G为LR(k)文法,分析器被称为是LR(k)分析器,它所识别的语言被称为LR(k)语言。
L表示从左到右扫描输入序列,R表示逆序的最右推导,k表示为确定下一动作向前看的终结符个数,一般情况下k<=1。当k=1时,简称LR。
LR分析器是一类分析器
根据分析表的构造,有LR(0)、SLR(1)、LALR(1)和LR(1)分析器。
它们功能的强弱和构造的难度依次递增。
当k>1后,分析器的构造趋于复杂,一般情况下并不构造k>1的LR(k)分析器。
我们仅构造SLR(1)分析器。
构造SLR(1)分析器
SLR(1) 分析器也称为SLR分析器
指:在分析器工作时,可以根据简单向前看一个终结符来确定下一步动作。
构造SLR分析器基本思想:首先构造一个可以识别文法G中所有活前缀的DFA,然后根据DFA和简单的向前看信息构造SLR分析表。
活前缀和LR(0)项目
活前缀:出现在移进-归约分析器栈中的右句型的前缀
活前缀的两要素:右句型的前缀;已在分析栈中。
活前缀加上若干个(可以是0个)终结符,即可得到一个右句型。
在移进-归约分析中,只要保证已扫描过的输入序列可以归约为一个活前缀,就意味着分析到目前为止没有错误。
LR分析的基本思想就是为文法G构造一个识别它的所有活前缀的DFA
为了表现NFA的状态
在产生式的右部加入一个点".",用它在右部的位置表示一个NFA状态。
一个LR(0)项目(简称项目)是这样一个产生式,在它的右部某个位置有一个点"."。对于A→
ε,它仅有一个项目A→ . 。
一个产生式右部有n个文法符号,那么该产生式就有n+1个项目。
每个产生式是一个识别活前缀的NFA;每个项目是NFA的一个状态
项目A→α.β显示了分析过程中看到(移进)了产生式的多少。
- β不为空的项目称为可移进项目
- β为空的项目称为可归约项目。
拓广文法与识别活前缀的DFA
拓广文法G’
G’ = G∪{S’→S}
其中:S’→.S是识别S的初态,S’→S.是识别S的终态。
目的是使最终构造的DFA状态集中具有唯一的初态和终态。
关于子集法:
其中,X可以是终结符也可以是非终结符
closure(I)
项目集I的闭包closure(I)是这样一个项目集
- I中的所有项目属于closure(I);
- 若A→α.Bβ属于closure(I),则所有形如B→.γ的项目属于closure(I);
- 其它任何项目不属于closure(I)。
goto(I,x)
对所有属于项目集I、且形如[A→α.Xβ]的项目(X∈N∪T),goto(I,X)是所有形如[A→αX.β]的项目。
goto是一个项目集。
goto(I,x)集合中的项目有一个共同特点:项目中的"."都不在产生式右部的最左边,即A→α.β中α不为空,因为至少有一个X
设J=goto(I,X),K=closure(J),K中项目A→α.β分为两类:
- J: α非空,因为至少有一个X。
- K-J: α=ε,即 "."在产生式右部最左边;
可由某个J计算而来(K-J=closure(J)-J)。
项目[S’→.S]和所有“.”不在产生式右部最左边的项目称为核心项目,其它“.”在产生式右部最左边的项目(不包括[S’→.S])称为非核心项目
核心项目:J=goto(I,X)加S’→.S(作为项目集的代表)
非核心项目:closure(J)-J(特点:可由某J计算得到)
识别活前缀的DFA的一个状态,是NFA的一个状态集合,称为LR(0)项目集
DFA的所有状态被称为LR(0)项目集族
计算文法G的LR(0)项目集的、识别活前缀的DFA
I = closure(S'→.S); # 初态
C += I(unsigned);
while (C not has unsigned I)
{
I is signed; # 标记I
for (x in I) #对当前状态下所有的x
{
if ((j = closure(goto(I, x))) != NULL) # 有下一个状态转移
{
Dstran(I, x) = j; # 记录状态转移
}
if (j !in C) # 如果j是一个新状态
{
C += j(unsigned);
}
}
}
步骤:
- 计算DFA的初态,I0=closure({E’→E})
- 计算初态下的每个可能的状态转移,即考察I0中的每个项目"."后边文法符号X,计算经X能到达的下一状态全体(closure(goto(I0,x)))
- 对所有未被标记且还有下一状态转移的状态Ii,反复计算closure(goto(Ii,X)),直到再没有新状态集加入。
若存在最右推导S’=*>αAω=>αβ1β2ω,则称项目[A→β1.β2] 对活前缀αβ1有效。
项目A→β1.β2对活前缀αβ1有效,具有两层含意:
从文法开始符号,经αβ1可到达该项目(项目所在状态);
在当前活前缀的情况下,该项目可指导下一步分析动作(αAω=>αβ1β2ω)。
活前缀和项目的关系:
-
一个项目可能对若干个活前缀有效
项目A→β1.β2对所有从初态出发可以到达此项目的路径上的标记均有效(一个路径标记是一个活前缀)。如果从初态到达I的路径上有环,则I项目集中的项目对无穷多的活前缀都有效。 -
若干个项目可能对同一个活前缀有效
若 I 中一项目对某活前缀有效,则 I 中其他任何项目对该活前缀也有效。
结论:
同一项目集中的所有项目,对此项目集的所有活前缀均有效
即,项目集中的每个项目均有同等权利指导下一步动作
有效项目的意义
1.到目前为止分析是正确的;
2.指导下一步的分析:
A→β1.β2(可移进项):移进β2中第一个终结符
B→β.(可归约项):按产生式B→β归约
冲突
SLR的构造
if DFA中有不能解决的移进 / 归约和归约 / 归约冲突
then error;
else for 每个状态转移Dtran[i, x] = j
loop if x∈T
then action[i, x]: = Sj;
else goto[i, x]: = j;
end if;
end loop;
for 状态i的每个可归约项A→α.
loop if S'→ S.
then action[i, #]: = acc;
else for 每个a∈FOLLOW(A)
loop action[i, a]: = Rk;
end loop;
end if;
end loop;
end if;