文法分析概览

文法分析概览

利用BNF写出的文法规则,可以用来对输入的文本进行文法分析。一条BNF文法规则,左边是一个非终结符(Symbol或者non-terminal),右边则定义该非终结符是如何构成的,也称为产生式(Production),产生式中可能包含非终结符,也可能包含终结符(terminal),也可能二者都有。在所有文法规则中,必有一个开始的规则,该规则左边的部分叫做开始符号(start symbol)。一个规则的写法如下:

Symbol := Production

下面是一个BNF文法定义的例子。FN是fractional number的意思,DL是digit list的意思,S是start symbol。

S := '-' FN | FN

FN := DL | DL '.' DL

DL := D | D DL

D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

一个非终结符可能有多个产生式,相互间用竖线(|)隔开。

每一条BNF产生式,都有自己的启动集(start set)。启动集里的元素就是每个Production中的第一个部分,比如上例S规则的启动集就是{'-'}以及{FN}。

利用BNF文法来分析目标文本,其分析方法比较流行的有几种,下面作一概述[Garshol 03]。

LL(k)分析

LL分析又称为自顶向下的分析(top-down parsing),也有叫递归下降分析(recursive-descent parsing)。也是最简单的一种分析方式。它工作的方式类似于找出一个产生式可以从哪一个终结符开始。

当分析时,从起始符号开始,比较输入中的第一个终结符和启动集,看哪一个产生式规则被使用了。当然,两个启动集之间不能拥有同一个终结符。如果有的话,就没有办法决定选择哪个产生式规则了。

Ll文法通常用数字来分类,比如LL(1),LL(0)等。这个数字告诉你,在一个文法规则中的任何点可以允许一次察看的终结符的最大数量。LL(0)就不需要看任何终结符,分析器总是可以选择正确的产生式规则。它只适用于所有的非终结符都只有一个产生规则。只有一个产生规则意味着只有一个字符串。[不用看当前的终结符是什么就可以决定是哪一个产生规则,说明这个规则是为一个固定的字符串所写的。]这种文法是没有什么意义的。

最常见也是比较有用的事LL(1)文法。它只需要看一个终结符,然后就可以决定使用哪一个产生规则。而LL(2)则可以查看两个终结符,还有LL(k)文法等等。对于某个固定的k值,也存在着根本不是LL(k)的文法,而且还很普遍。

下面来分析一下本章开头给出的例子。首先看下面这条规则:

D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

上述规则有十个产生式,每个产生式的启动集是一个数字终结符构成的集合{'0'}、{'1'}、……、{'9'}。这是一个很好的LL(1)文法,因为我们只要看一个终结符,就可以选择一个正确的产生式。例如,如果看到一个终结符,其内容是3,那么就采用上面第四个产生式,即D := '3'。

接下来分析DL规则。

DL := D | D DL

上述规则有两个产生式,启动集是{D},{D}。很不幸,两个产生式的启动集相同。这就表示只看第一个输入中的第一个终结符不能选择正确的产生式。

然而可以通过欺骗来绕过这个问题:如果输入中第二个终结符不是一个数字,那么就选择第一个产生式,但如果两者都是数字就必须选择第二个产生式。换句话说,这意味着这是一条好的LL(2)文法规则。实际上这里有些东西被简化了。

再分析下FN规则吧。它的情况更糟糕。

FN := DL | DL '.' DL

它有两条产生式,而且启动集相同,均为{DL}。然而这次不像DL规则那么幸运了。咋一看,似乎通过LL(2)可以分辨应该使用哪一个产生式。但是很不幸,我们无法确定在读到终结符('.')之前,需要读多少个数字才算是DL符号的最后一个数字。[想想吧,分析器这么工作着:读入第一个终结符,一看是相同的DL符号,那么就读第二个终结符吧;读入第二个终结符,两者合起来一看,还是一样的DL符号;读入第三个终结符,前三个终结符合起来看,仍然是相同的DL符号。但是DL符号表指示数字表示没有长度限制的。]没有任何一个给定的k值,这都不符合LL(k)文法,因为数字表总能突破这个k的长度。

最后看看启动符号规则。有点意外,它产生规则的选择很简单。

S := '-' FN | FN

它有两个产生规则,两者的启动集是{'-'}和{FN}。因此,如果输入中第一个终结符是'-',那么就选择第一个产生式,否则选择第二个产生式。所以这是一个LL(1)文法。

从上述的LL分析看,只有FN和DL规则引起了问题。但是不必绝望。大部分的非LL(k)文法都可以容易地转换为LL(1)文法。下面以当前的这个例子来看看如何转换有问题的FN和DL。

对于FN符号来说,它的两个产生式都开始于DL,但是第二个产生式其后续的是一个小数点终结符('.'),以及另外一个数字表。那么这很容易解决:可以将FN改变为一个产生式,其以DL开始,后跟一个FP(fractional part)符号。而FP符号则定义成或者为空,或者为小数点后跟着一个数字表,如下所示:

FN := DL FP

FP := @ | '.' DL

上述@符号表示为空。现在FN文法没有任何问题了,因为它现在只有一个产生式。而FP也不会有问题,因为它的两个产生式的启动集是不同的:前者是输入的尾端,后者是小数点终结符。

DL符号就不是好啃的核桃了,原因在于其递归和至少需要一个D符号。解决方案就是,给DL一个产生式,由一个D后跟一个DR(digit rest)构成;而DR则有两个产生式,一个是D DR(表示更多的数字),一个是@(表示没有更多的数字)。最后本章开头的例子被转换成下面的一个完全的LL(1)文法了:

S := '-' FN | FN

FN := DL FP

FP := @ | '.' DL

DL := D DR

DR := @ | D DR

D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

LR分析

Lr分析也叫自底向上的分析(bottom-up parsing),或者叫移进-归约分析(shift-reduce parsing),相比LL分析难度更大些。它的基本原理是,首先收集输入,直到它发现可以据此利用一个符号对收集到的输入序列进行归约。可以与数学里面解方程式时的消元法进行类比。这听起来似乎很难。下面还是以一个例子来澄清。例子中将分析字符串3.14,看看是怎样从文法产生出来的。

S := '-' FN | FN

FN := DL | DL '.' DL

DL := D | D DL

D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

首先从输入中读入3。

3

然后看看是否可以将其归约为一个符号(Symbol,即非终结符)。实际上可以归约,就是说用D符号的产生式可以得到当前读入的字符串(这也是成为产生式的原因)。

很快发现,从DL符号可以产生符号D,于是又可以归约成DL。(实际上还可以进一步地归约成FN,于是这里就产生了歧义,到底应该归约成哪一个呢?这表明这个文法定义是二义性的,我们在这里就忽略这个问题,直接选择DL作为归约结果吧。)接着从输入中读入一个小数点,并试图进行归约:

D ==> 规约到DL

DL ==> 读入下一个字符

DL . ==> 规约到?

但是这次的归约尝试失败了,因为没有任何符号的定义可以产生这种形式的字符串。也就是说,这种形式不能规约到任何符号。

所以接着我们读入下一个字符1。这次可以将数字1归约到D符号。接着再读入一个字符4。4可以归约到D,继续归约到DL。这两次的读入和规约形成了D Dl这个序列,而这个序列可以归约到DL。

DL . ==> 读入下一个字符1

DL . 1 ==> 1归约到D

DL . D ==> 读入下一个字符4

DL . D 4 ==> 4归约到D

DL . D D ==> 4继续归约到DL

DL . D DL ==> D DL 归约到DL

察看文法我们可以很快地注意到,FN能产生DL . Dl这种形式的序列,所以可以做一个归约。然后注意到FN可以从S符号产生,所以可以归约到S,然后停止,整个分析结束。

DL . DL ==> 归约到FN

FN ==> 规约到S

S ==> 分析结束

可能你已经注意到,我们经常可以选择是否现在就做归约,还是等到读入更多的符号后再作不同的归约。移进-归约分析算法有很多不同的变种,按照复杂度和能力递增的顺序是:LR(0), SLR, LALR和LR(1)。LR(1)通常需要一个巨大的分析表,在实践上不具有实用性,因此LALR是最常使用的算法。SLR和LR(0)对于大部分的程序语言来说还不够强大。

实验2 文法的读入、判定和处理 一、实验目的 熟悉文法的结构,了解文法在计算机内的表示方法。 二、实验内容 1、 设计一个表示文法的数据结构; 2、 从文本文件中读入文法,利用定义的数据结构存放文法,并输出; 3、 本实验结果将来还有用。 三、实验要求 1、 了解文法定义的4个部分: G(Vn, Vt, S, P) Vn 文法的非终结符号集合,在实验中用大写的英文字母表示; Vt 文法的终结符号集合,在实验中用小写的英文字母表示; S 开始符号,在实验中是Vn集合中的一个元素; P 产生式,分左部和右部,左部为非终结符号中的一个,右部为终结符号或非终结符号组成的字符串,如S->ab|c 2、 根据文法各个部分的性质,设计一个合理的数据结构用来表示文法, 1) 若使用C语言编写,则文法可以设计成结构体形式,结构体中应包含上述的4部分, 2) 若使用C++语言或java语言编写,则文法可以设计成文法类形式,类中至少含有4个数据成员,分别表示上述4个部分 文法数据结构的具体设计由学生根据自己想法完成,并使用C或C++语言或Java实现设计的数据结构。 3、 利用完成的数据结构完成以下功能: 1) 从文本文件中读入文法文法事先应写入文本文件); 2) 根据文法产生式的结构,分析文法的4个部分,分别写入定义好的文法数据结构的相应部分; 3) 整理文法的结构,判断该文法文法类型,是否为0型,1型,2型或3型文法,并输出判断结果; 4) 在计算机屏幕或者文本框中输出文法文法输出按照一个非终结符号一行,开始符号引出的产生式写在第一行,同一个非终结符号的候选式用“|”分隔的方式输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值