编译原理实验2.
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第二章语法分析器构造
我们采用一种最为常用的语法分析器─LR分析器。LR分析器的构造主要是指上下文无关文法的自下而上分析程序的自动构造。从实践角度出发,我们采用手工方法来设计LR 分析器。LR系指“自左向右扫描和自下而上进行归约”。大多数用上下文无关文法描述的程序语言都可用LR分析器予以识别。LR分析法在自左至右扫描输入串时就能发现其中的任何错误,并能准确地指出出错地点。
一个LR分析器包括两部分,一个总控(驱动)程序和一张分析表。注意,所有LR分析器的总控程序都是一样的,只是分析表各有不同。因此,产生器的主要任务就是产生分析表。
2.1 LR分析器基本知识
我们知道,规范归约(最左归约,即最右推导的逆过程)的关键问题是寻找句柄。LR 方法的基本思想是:在规范归约过程中,一方面记住已移进和归约出的整个符号串,即记住“历史”;另一方面根据所用的产生式推测未来的可能碰到的输入符号,即对未来进行“展望”。当一串貌似句柄的符号串呈现于分析栈的顶端时,我们希望能够根据所记载的“历史”和“展望”以及“现实”的输入符号等三方面的材料,来确定栈顶的符号是否构成相对某一产生式的句柄。
一个LR分析器实质上是一个带先进后出存贮器(栈)的确定有限状态自动机。我们将把“历史”和“展望”材料综合地抽象成某些“状态”。分析栈(先进后出存贮器)用来存放状态。栈里的每个状态概括了从分析开始直到某一归约阶段的全部“历史”和“展望”资料。任何时候,栈顶的状态都代表了整个的历史和已推测出的展望。LR分析器的每一步工作都是由栈顶状态和现行输入符号所唯一决定的。为了有助于明确归约手续,我们把已归约出的文法符号串也同时放在栈里。于是,栈的结构可看成图2-1所示的结构。TOP
图2-1 分析栈示意
栈的每一项内容包括状态s和文法符号X两部分。(s O,#)为分析开始前预先放入栈里的初始状态和句子括号。栈顶状态为s m,符号串X1X2…X m是至今已移进归约出的文法符号串。
LR分析器的核心部分是一张分析表。这张分析表包括两部分:一是“动作”(ACTION)表,另一是“状态转换”(GOTO)表。它们都是二维数组。ACTION[s,a]规定了当状态s面临输入符号a时应采取什么动作。GOTO[s,X]规定了状态s面对文法符号X(终结符或非终结符)时下一状态是什么。显然,GOTO[s,X]定义了一个以文法符号为字母表的DFA(确定有限自动机)。
每一项ACTION[s,a]所规定的动作不外是下述四种可能之一:
(1)移进:把(s,a)的下一状态s′=ACTION[s,a]和输入符号a推进栈(对终结符a,GOTO[s,a]的值已放入ACTION[s,a]中),下一输入符号变成现行输入符号。
(2)归约:指用某一产生式A→β进行归约。假若β的长度为γ,归约的动作是去掉栈顶的γ个项,使状态s m-γ变成栈顶状态,然后把(s m-γ,A)的下一状态s′=GOTO[s m-γ,A] 和文法符号A推进栈。归约动作不改变现行输入符号。执行归约的动作意味着呈现于栈顶的符号串X m-γ+1…X m是一个相对于A的句柄。
(3)接受:宣布分析成功,停止分析器的工作。
(4)报错:报告发现源程序含有错误,调用出错处理程序。
LR分析器的总控程序本身的工作是非常简单的,它的任何一步只需按栈顶状态s和现行输入符号a执行ACTION[s,a]所规定的动作。不管什么分析表,总控程序都是一样地工作。
我们主要关心的问题是如何从文法构造LR分析表。对于一个文法,如果能够构造一张分析表,使得它的每个入口均是唯一确定的,则我们将把这个文法称为LR文法。对于一个LR文法,当分析器对输入串进行自左至右扫描时,一旦句柄呈现于栈顶,就能及时对它实行归约。
一个LR分析器有时需要“展望”和实际检查未来的K个输入符号才能决定应采取什么样的“移进-归约”决策。一般而言,一个文法如果能用一个每步顶多向前检查K个输入符号的LR分析器进行分析,则这个文法就称为LR(k)文法。
对于一个文法,如果它的任何“移进-归约”分析器都存在如下的情形:尽管栈的内容和下一个输入符号都已了解,但无法确定是“移进”还是“归约”;或者,无法从几种可能的归约中确定其一;那么这个文法就是非LR(1)的。注意,LR文法肯定是无二义的,一个二义文法决不会是LR的。但是,LR分析技术可修改为适用于分析一定的二义文法。
2.2 LR(0)分析表的构造
我们希望仅由一种只概括“历史”资料而不包含推测性“展望”材料的简单状态就能识别呈现在栈顶的某些句柄,而LR(0)项目集就是这样一种简单状态。
在讨论LR分析法时,需要定义一个重要概念,这就是文法的规范句型的“活前缀”。字的前缀是指该字的任意首部,例如字abc的前缀有ε、a、ab或abc。所谓活前缀是指规范句型的一个前缀,这种前缀不含句柄之后的任何符号。在LR分析工作过程中的任何时候,栈里的文法符号(自栈底而上)X1X2…X m应该构成活前缀,把输入串的剩余部分配上之后即应成为规范句型(如果整个输入串确实构成一个句子)。因此,只要输入串的已扫描部分保持可归约成一个活前缀,那么就意味着所扫描过的部分没有错误。
对于一个文法G,我们首先要构造一个NFA(非确定有限自动机),它能识别G的所有活前缀。这个NFA的每个状态就是一个“项目”。而文法G每一个产生式的右部添加一个
圆点称为G的一个LR(0)项目(简称项目)。例如产生式A→XYZ对应有四个项目: A→·X Y Z
A→ X·Y Z
A→ X Y·Z
A→ X Y Z·
但是产生式A→ε只对应一个项目A→·。一个项目指明了在分析过程的某时刻我们看到产生式多大一部分。我们可以使用这些项目状态构造一个NFA,用来识别一个文法的所有活前缀。使用子集方法把识别活前缀的NFA确定化,使之成为一个以项目集合为状态的DFA (确定有限自动机),这个DFA就是建立LR分析算法的基础。构成识别一个文法活前缀的DFA的项目集(状态)的全体称为这个文法的LR(0)项目集规范族。这个规范族提供了建立一类LR(0)和SLR(简单LR)分析器的基础。注意,凡园点在最右端的项目,如A →α·,称为一个“归约项目”;对文法的开始符号S′的归约项目,如S′→α·称为“接受”项目。形如A→α·aβ的项目,其中a为终结符,称为“移进”项目;形如A→α·Bβ的项目,其中B为非终结符,称为“待约”项目。
2.2.1 LR(0)项目集规范族的构造
我们采用ε_CLOSURE(闭包)的方法来构造一个文法G的LR(0)项目集规范族。
首先,为了使“接受”状态易于识别,总是将文法G进行拓广。假定文法G是一个以S为开始符号的文法,我们构造一个G′,它包含了整个G并引进了一个不出现在G中的非终结符S′,同时加进了一个新产生式S′→S,而这个S′是G′的开始符号,我们称G ′是G的拓广文法。由于会有一个仅含项目S′→S·的状态,这就是唯一的“接受”态。
假定I是文法G′的任一项目集,定义和构造I的闭包CLOSURE(I)的方法是:
(1)I的任何项目都属于CLOSURE(I);
(2)若A→α·Bβ属于CLOSURE(I),那么对任何关于B的产生式B→γ,项目B→·γ也属于CLOSURE(I)(设A→α·Bβ的状态为i,则i到所有含B→·γ的状态都有一条ε弧);
(3)重复执行上述两步骤直至CLOSURE(I)不再增大为止。
在构造CLOSURE(I)时,请注意一个重要的事实,那就是对任何非终结符B,若某个圆点在左边的项目B→·γ进入到CLOSURE(I),则B的所有其它圆点在左边的项目B→·β也将进入同一个CLOSURE集。
函数GO是一个状态转换函数。GO(I,X)的第一个变元I是一个项目集,第二个变元X 是一个文法符号。函数值GO(I,X)定义为:
GO(I,X)=CLOSURE(J)
其中,如果A→α·Xβ属于I,则J={任何形如A→αX·β的项目}。也即,如果由I项目集发出的字符为X的有向弧,则到达的状态即为CLOSURE(J)。直观上说,若I是对某个活前缀γ有效的项目集(状态),那么GO(I,X)便是对γX有效的项目集(状态)。
通过函数CLOSURE和GO很容易构造一个文法G的拓广文法G′的LR(0)项目集规范族。也就是说,如果我们已经求出了I的闭包CLOSURE(I),则用状态转换函数GO可以求出由项目集I到另一项目集状态必须满足的字符(即转换图有向弧上的字符)。然后,再求出有向弧到达的状态所含的项目集(即用GO(I,X)=CLOSURE(J)求出J,然后再对J求