语法分析程序设计_编译工程6:语法分析(4)

接下来我们讨论自底向上的语法分析。自底向上的语法分析过程同样对应于为一个输入串构造语法分析树的过程,只不过是从叶子节点(底部)逐渐向上到达根节点(顶部)。

自底向上分析法的解析力量比自顶向下分析法要强大的多,和自顶向下分析法相比,它可以解析更为复杂、更为广泛的语法。但自底向上分析法的构造过程也复杂的多。分析器构造工具(如 bison )就是将用户定义的语法规则转化成一个自底向上分析器。

看一个自底向上例子:

对于以下文法:

S -> aABe 
A -> Abc | b
B -> d

对于输入abbcde是怎么样进行自底而上分析的呢?

9c69d51cca6d0ab26d154465511fea4f.png

规约是推导的逆过程,因此,自底向上分析的目标是反向构造一个推导过程,这个规约过程对应着以下的最右推导:

再使用下面的文法:

E -> E + T | T 
T -> T * F | F 
F -> ( E ) | id 
  

对字符串

进行自底向上分析:

64afd1f72c51c73b671d23accdbb6c27.png

自底向上的语法分析过程可以看成是一个将串

规约成文法开始符号的过程。在每个规约(reduction)步骤中,与产生式体相匹配的特定子串被替换为该产生式左部的非终结符号。

这个过程中,首先发现的是

的右部,进行规约;而
又是
的右部,再一步规约;接下来分析 *,此时*也好,
也好,都不是哪个产生式的右部,因此继续移入,发现
的右部,进行规约;此时
是产生式右部,进行规约。

上面的规约过程对应着下面的推导过程(最右推导):

这里引入一个概念“句柄“,句柄是和某个产生式体匹配的子串,一旦发现句柄,则立刻进行规约,因此形成了最左规约,也对应着最右推导。

f9549e6c7b5ce78bbc9c18de8d1fe8aa.png

这里的问题是何时进行规约;以及选用哪个产生式进行规约。在上面的例子中,虽然T是产生式E->T的体,但是符号T并不是最右句型

的句柄,因为如果T被替换为E,接下来不能进一步进行规约。因此,和某个产生式体匹配的最左子串并不一定是句柄。同时注意,句柄一定出现在栈的顶部,所以,可以使用栈帮助分析。【关于句柄为什么一定出现在栈的顶部,龙书中给出了分两种情况进行讨论的证明,在此不详述】

考虑:如果是

或者是
呢?

练习:

对于文法

,指出下面各个最右句型的句柄。
  1. 000111
  2. 00S11

再看一个例子,体会一下使用栈进行移入-规约的过程。

39985153d85dff0e9eab9b292d23aedb.png

在上面的例子中,处理输入串的时候都是用了移入和规约两个动作,一个移入-规约语法分析器可以采取以下四种可能的动作:

  1. 移入(shift):降下一个输入符号移到栈的顶端;
  2. 规约(reduce):被规约的符号串的右端必然是栈顶。语法分析器在栈中确定串的左端,并决定用哪个非终结符号来确定这个串。
  3. 接收(accept):宣布语法分析过程成功完成。
  4. 报错(error):发现语法错误,调用错误回复子例程。

通过以上的例子,对自下而上分析应该有一个直观了解。简而言之,对输入进行从左到右的扫描,并在扫描过程中对核某个产生式体相匹配的最右子串进行规约,就可以反向构造出一个最右推导。

这一部分中我们介绍的文法分析是LR,L就表示“从左至右“扫描,R就是反向构造出最右推导。直观地将,只要存在这样一个从左到右扫描的移入-规约语法分析器,总是能够在某文法的最右句型的句柄出现在栈顶时识别出这个句柄,那么这个文法就是LR的。


目前最流行的自底向上文法分析器都是基于LR(

)语法分析【Knuth于1965年提出】的概念。
表示在做出语法分析决定时向前看
个输入符号。只考虑
两种情况。当
过大时,产生的分析器过大,从而不具有实践意义。当省略
时,假设

接下来,介绍LR语法分析的基本概念,然后进入最简单的构造移入-规约语法分析器的方法,这个方法称为SLR(简单LR)。虽然现在可以使用Bison等工具自动生成语法分析器,但是理解它的工作原理仍然是有帮助的。在理解SLR之后,将继续介绍规范LR和LALR,它们被用于大多数的LR语法分析器中。

LR语法分析器和LL语法分析器一样,也是表格驱动的。如果我们能够为一个文法构造出语法分析表,那么这个文法就可以成为LR文法(LR Grammar)。直观地讲,只要存在这样一个从左到右扫描的从左到右扫描的移入-规约语法分析器,总是能够在某文法的最右句型的句柄出现时识别出这个句柄,这个语法就是LR的。

LR语法分析技术:

  • 几乎所有的程序设计语言构造,只要能够写出该构造的上下文无关文法,就能够构造出识别该构造的LR语法分析器。
  • LR语法分析方式是一致的最通用的无回溯移入-规约分析技术。
  • LR语法分析器可以在从左到右扫描时尽早检测到错误。
  • 可以使用LR方法进行分析的文法类是可以用预测方法或者LL方法进行语法分析的真超集。直观理解,LR(k)是当我们在最右句型中看到某个产生式的右部时,再朝前看k个符号来决定是否进行规约。对于LL(k)文法,只能看该产生式右部推导出的串的前k个符号,来决定是否使用该产生式。

LR方法的主要 缺点是为典型的语言手工构造LR分析器的工作量非常大。

为什么工作量很大呢?

譬如前面我们看到符号T并不是最右句型

的句柄,所以当
并不能继续规约,二必须将*进行移入。那么语法分析器怎么知道位于栈顶的
不是句柄,因此正确的动作是移入而不是将
规约到
呢?

我们还可以再看一个例子。对于文法

S -> var IDS : T
IDS -> i | IDS, i
T -> real | int

问题:如何分析串

var i1, i2 : real

上面这个文法就是变量串的声明,这个问题很简单。可以使用最右推导得出:

S => var IDS : T =>var IDS: real  => var IDS, i2 : real => var i1, i2 : real

相对应于上面的推导过程,一个自底向上的分析过程如下:

var i1                       IDS->i                    var IDS
var IDS, i2                  IDS -> IDS, i             var IDS
var IDS : T                  S -> var IDS : T          S

但是,我们在分析过程中也可能有这样的疑问:

var i1                       IDS->i                    var IDS
var IDS, i2                  IDS ->  i                 var IDS,IDS

这样将i2规约成IDS的话,后面的输入移入完成的时候就没办法规约了。

所以,语法分析器必须知道何时移入,何时规约。移入-规约语法分析器怎么知道何时进行移入,何时进行规约呢?LR分析器通过维护一些状态,这些状态来表明我们在语法分析中所处的位置,从而做出移入-规约决定。

状态是“项”的集合。直观上讲,项表示在分析过程中的给定点上,我们经看到了产生式的哪些部分。形式上来看,文法G的LR(0)项是G的一个产生式再加上一个位于它的体中的某处的点。因此,产生式

可以产生四个项:
  • A ->·XYZ
  • A ->X·YZ
  • A ->XY·Z
  • A ->XYZ·

产生式

只产生一个项 A ->·。

以上的项表示什么意思呢?

譬如A ->·XYZ就表示,接下来我们希望看到由XYZ推导得到的串;而A ->X·YZ表示我们已经看到了X推导得出的串,接下来我们希望看到由YZ推导得出的串;A ->XYZ·表示我们已经看到了XYZ推导得出的串,接下来可以进行规约了。

对于产生式

S -> BB

状态:

S -> ·BB     移入状态
S ->  B·B    待约状态
S ->  BB·    规约状态

使用称为规范LR(0)的项可以集构建一个确定有穷自动机,这个自动机可以做出语法分析决定。这个LR(0)的自动机的每一个状态代表了规范LR(0)集族中的一个项集。

bf9106e6245d756bccef8eab9b7fa409.png

这个分析表比LL分析表更复杂一点,其中,主体部分是ACTION动作表,其中s表示shift,移入;r表示reduce规约。s5表示移入,并且进入状态5;r3表示使用第三个产生式进行规约。和LL不同,这里的非终结符成为了GOTO表的一部分,也即,当状态遇到非终结符时应该进入到什么状态。有了上面的分析表,我们可以对输入串进行分析,譬如对于输入串bb

栈             符号         输入           动作
0              $            bb$            移入到状态4
04             $b           b$             使用3 B->b进行规约
02             $B           b$             移入到状态4
024            $Bb          $              使用3 B->b进行规约
025            $BB          $              使用1 S ->BB进行规约
01             $S           $              接受

这里要注意,在进行规约的时候,要注意将符号和栈中的状态一起弹出;譬如第一次使用B->b进行规约的时候,栈中的状态是04,当b规约成B的时候,4也弹出了;此时相当于栈顶的状态是0,遇到B的时候,进入了状态2。这个结合上面的状态转换图也容易理解。

下面可以再做一个例子aabb。首先可以看一下aabb的最右推导过程。

S->BB->Bb->aBb->aaBb->aabb

我们可以看一下,进行LR(0)分析的过程是不是恰好对应着最右推导的逆过程。

bf9106e6245d756bccef8eab9b7fa409.png

具体过程如下:

栈             符号         输入             动作
0              $            aabb$            移入到状态3
03             $a           abb$             移入到状态3
033            $aa          bb$              移入到状态4
0334           $aab         b$               使用3 B->b进行规约;弹出状态4;进入状态6
0336           $aaB         b$               使用2 B->aB进行规约;弹出状态36;进入状态6
036            $aB          b$               使用2 B->aB进行规约;弹出状态36;进入状态2
02             $B           b$               移入到状态4
024            $Bb          $                使用3 B->b进行规约;弹出状态4;进入状态5
025            $BB          $                使用1 S ->BB进行规约
01             $S           $                接受

LR语法分析算法描述如下:

afcf7a38a6b2bcfe06b186cb6bd6f29e.png

另外,在构建以上自动机的时候,首先定义了一个增广文法G'。增广文法G'就是在文法G中增加了新的开始符号S'和产生式S'->S而得到的文法。引入这个新的产生式的目的是使得文法开始符号仅出现在一个产生式的左边,从而使得分析器只有一个接受状态。从而可以明确地告诉语法分析器何时停止语法分析并执行accept动作。也即,仅当使用规则S'->S进行规约时,输入符号串被接受。

在上图中,也即在状态I1中,当接受$时,可以进入accept动作。整个自动机中,也仅有这一个accept动作。

项集的闭包

接下来考虑下自动机的构造。自动机中的每个状态是项的集合,具体来讲,是等价的项目组成的项目集。

可以想象,产生式右部长度为

的话,就会有
个项;如果对每个产生式都产生出这么多项的话,整个自动状态机会非常大。因此,要把等价的项目组成项目集。等价的项目组成一个项目集,每个项目集闭包对应着自动机的一个状态。

在计算每个动作的时候,需要使用Closure函数,也即闭包函数。什么是项集的闭包呢?

4ab6a15dc9418d5c75514f1fb12ab061.png

也即,首先将I状态中的核心项【除

外,所有的
不在最左端的项】加入;然后对每个项,如果有新的产生式,那么就把新的产生式加入进去。直观上讲,譬如
在状态
中,表明在在这个状态中,我们认为接下来可能会看到一个能有
推导得到的子串。而这个由
推导得出的子串必然使用到
的产生式,所以,我们需要把
的产生式如
加入进来。表明,接下来我们期望看到

GOTO函数

返回项目集

对应于文法符号
的后继项目集闭包

算法描述如下:

fdf2c694136e89f3021daf6945ea503c.png

直观上将,GOTO函数用于定义一个文法的LR(0)自动机中的转换。这个自动机的状态对应于项集,而GOTO(I,X)描述了当输入为X时离开I的转换。

譬如

是有两项的集合{[
],[
]},那么
包含如下的项:

4b93a9b1f889291a0396bc0d9c5532c1.png

构造LR(0)自动机的状态集

规范LR(0)项集族(Canonical LR(0) collection)

算法如下:

8028b80e8cec8d7404f5527fc3c032dc.png

LR(0)分析表构造算法

3f08086c93c9c1460e690ebb5fb3b3d8.png

从上面的过程中,我们其实可以看到,之所以上面的分析方法叫LR(0),是因为处理输入的过程中它不需要向前看输入符号。LR(0)所做出的所有判断都是基于文法本身的。所以,它相对比较简单,也因此它能够处理的语言很有限,能够使用LR(0)方法分析的文法是LR(0)文法。LR(0)是构造其他LR分析器的基础。

LR(0)的不足是什么呢?下面看一下表达式文法

E -> E + T | T 
T -> T * F | F 
F -> ( E ) | id 

的LR(0)自动机。

214bfc288e47dfef9e775de20238ea21.png

按照LR(0)的算法,在状态

和状态
中都存在移入/规约冲突。对于状态

要求对于所有的终结符和$进行规约;而

则要求对 * 进行移入。

可以分析一下,在下一个输入字符是

时,对于
采用哪一种动作比较合适?

这里,采用移入的动作更合适。为什么呢?

因为如果采用了归约动作,归约成

,那么此时就意味着
后面的紧跟着
;也即意味着
应该是
的后继符号。但是从文法分析中可以看到,
的后继并没有

也即,在LR(0)中,一旦

出现在产生式的最右位置,就决定对所有的终结符进行规约是不够准确的。那么可以怎么样改进呢?

一种思路便是,如果

出现在产生式的最右位置,那么仅当下一个输入符号是产生式左部的非终结符的FOLLOW集中的终结符号时,才进行规约。这就是SLR分析方法。

构造SLR分析表的算法如下:

751f13171e2b0e81e7cc2005c6d2fe1f.png

譬如,上面表达式文法

E -> E + T | T 
T -> T * F | F 
F -> ( E ) | id 

的后继符号是{$, +, )};T的后继符号 {$, +, *, )};F的后继符号 {$, +, *, )},因此,所构造的SLR语法分析表如下:

126346425db39b6ac31d61f4ffbf817c.png

使用这个表就可以对输入进行处理。

2b859e10bfe788665845105a89a9e6e1.png

再看一个例子。

81112f592a17947244e954b0edee7379.png

综合上面的过程可以看出,SLR是在LR(0)基础上的一个改进;移入动作仅仅根据产生式便可以得到;规约动作是根据可能的后继符号产生的。【问题在于,一个非终结符号的后继符号可能是由多个不同的产生式产生的;这时相当于是对于该终结符号的所有后继符号,只用一个产生式来进行规约;因此,SLR仍然是一种比较粗糙的做法。】


练习。

为下面的文法的增广文法构造SLR项集,计算这些项集的GOTO函数,给出这个文法的语法分析表。这个文法是SLR文法吗?

首先,构造增广文法:

S’->S
S->SS+
S->SS*
S->a

然后构造LR(0)项集:

b05f4e305e6fbfd02f40bdbb13f04394.png

以及GOTO函数:

GOTO(I0,S)=I1    GOTO(I0,a)=I2
GOTO(I1,S)=I3    GOTO(I1,a)=I2       GOTO(I1,$)=acc
GOTO(I3,S)=I3    GOTO(I3,+)=I4       GOTO(I3,*)=I5        GOTO(I3,a)=I2

考虑到FOLLOW(S)={+,*,a,$},在状态2、4、5中包含有产生式体结束的项,所以相对应地,在分析表中有对应的规约项。

所以构造语法分析表如下:

b7d28d706dccbfa57356d60e36151133.png

因为在表中不存在冲突,所以,此文法是SLR文法。

使用上述分析表,分析一下输入aa*a+的处理过程。

练习:

  1. 说明下面的文法
S -> A a A b | B b B a
A -> ε
B -> ε

是LL(1)的,但不是SLR(1)的。

【通过这个例子可以体验,SLR不是根据向前看的符号,而是根据FOLLOW集来进行规约,所以引起冲突】

2. 说明下面的文法

S -> S A | A
A -> a

是SLR(1)的,但不是LL(1)的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值