1、语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 1第三部分 上下文无关语言和下推自动机前面介绍的有限自动机是计算的初级模型,它所接受的正规语言不太关心字符串自身的结构。上下文无关文法(CFL)是一种简单的描述语法规则的递归方法,语言中的字符串由这些规则产生。所有的正规语言都能用上下文无关文法描述,它也可以描述非正规语言。上下文无关文法描述的语法规则更复杂多变,可以在相当大的程度上,描述高级程序设计语言的语法和其他一些形式语言。类似正则语言对应的抽象机模型是有限自动机,CFL 也有对应的抽象机模型。 CFL 对应的计算模型是在有限自动机的基础上增加存储空间得到,并被设想成
2、无限空间(对应有限自动机的有限空间),采用了一种简单的管理模式,栈(stack),这种新的计算模型(或抽象机)称为下推自动机(pushdown automata),下推是栈最典型的操作。有必要在下推自动机中保留非确定性,确定型下推自动机不能接受所有的 CFL,但给定一个 CFG,容易构造一个相应的非确定型下推自动机,它在识别字符串过程中的移动模拟了文法的推导过程,这个过程称为分析(parse)。分析不是一定需要下推自动机来完成。CFL 仍然不够通用,不能包括所有有意义的、或有用的形式语言。采用类似第五章的技术,我们将给出一些不是 CFL 的简单例子,这些技术也用于解决与 CFL 相关的判定问题
3、。6 上下文无关文法6.1 上下文无关文法的定义为了描述我们在第二部分考察的各种语言,包括一些非正则语言,我们引入一种语言的递归定义方法,称为文法。文法与我们熟悉的语言的语法描述相近,是描述语言和分析语言的有力工具。问题:文法的形式化定义似乎可以模仿有限自动机,比如 5 元组或 6 元组之类。例子 6.1 正如我们在例子 2.16 中所见,字母表a, b上的回文语言 pal 可以用下面的递归方法描述:1. , a, bpal2. 对每个 Spal,aSa 和 bSb 也属于 pal3. pal 中不包含其他字符串如果将上面的符号 S 看成一个变量,代表了所有我们希望计算(比如某种递归算法)的p
4、al 的元素,那么上面的规则 1 和规则 2 可以非正式地重新表述如下:1. S 的值可以是, a, b2. 每个 S 可以写成 aSa 或 bSb 的形式如果我们用表示“可以取值为”,则可以写出下面的式子:SaSaabSbaabba=abba上面的产生过程可以总结成下面的两组产生式(或称规则):Sa | b | SaSa | bSb符号“|” 表示“或”的含义。上式的含义是 aSa 或 bSb,而不是 a 或 b,即连接运算的优先级高于“|” 。我们使用的这套术语中,小写字母 a 和 b 表示终结符,大写字母 S 表示非终结符,或称变量。总共有 5 条规则,或产生式(production)。
5、符号 S 是非终结符,也是起始语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 2终结符,即我们生成字符串的起始符号是 S,然后不断利用规则替换符号串中的非终结符,直到最终得到一个不含非终结符的符号串,就生成了规则所定义的语言的一个字符串。例子 2 中的产生式具有除起始符 S 外的多个非终结符,我们设想 S 表示了语言中任意的字符串,其他非终结符表示了其他辅助性的字符串类型,他们可用来方便地生成 S 表示的字符串。例子 6.2 我们要构造一个生成所有在字母表a, b上的非回文字符串的文法,那样的字符串可以描述如下:从字符串的两端开始比较,也许能够发现一些相同的字符对,但最
6、终能够发现一对不同的字符。对于前一种情况,我们可以借用回文语言的产生式:SaSa | bSb如果加入产生式Sa | b | 则这种左右匹配的形式将体现在整个字符串上,为了中断这种左右匹配的情况,即体现上面提到的第二种情况,我们引入新的非终结符,比如 D,表示那些左右两个端点上的字符不同的字符串。且所有符合 D 的字符串也符合 S,因此有 SD。非终结符的定义比较简单,它唯一的条件是左右两个端点的字符不相同,中间的字符串可以是任意的,我们用非终结符 A 表示任意的字符串,则有 DaAb | bAa。A 表示任意的字符串,因此 A 的产生式更简单了,不用添加新的非终结符来简化问题,它的产生式是,A
7、 | aA | bA。我们把上面三个非终结符的产生式写在一起,就得到了描述所规定语言的产生式集:SaSa | bSb | DDaAb | bAaA | aA | bA因此一个完整的非回文字符串“abbaaba”的产生过程是,SaSaabSbaabDbaabbAabaabbaAabaabbaabaabbaaba定义 6.1 上下文无关文法(context-free grammar, CFG)是一个 4 元组 G=(V, , S, P),其中,V 和是不相交的有限集,S V,P 是一组有限的产生式规则,形如 A,其中 AV,且 (V)*。V 的元素称为非终结符(或变量), 的元素称为终结符,S 是
8、一个特殊的非终结符,称为起始符,P 的元素称为语法规则,或产生式。设 G=(V, , S, P)是一个 CFG,我们将符号保留给 P 的产生式专用,符号 G 用于表示字符串的产生过程的每一步,如 G表示字符串能够通过替换 中的某些变量(根据 P 定义的产生式来替换)得到,即如果有=1A2 且 AP,则= 12。这里能够看到,我们命名为上下文无关(context-free)的含义,即我们在利用产生式规则,用一组符号替换某个非终结符时,与非终结符的上下文无关(此处,A 的替换与 1 和 2 无关),替换是无限制的。在没有多个文法出现,很清楚用到文法 G 是什么的情况下,推导符号 G 可以简写成。多
9、步的推导可以写成 *G,即如果存在一组符号串, =0、1、k=,每个后者都是前者的直接推导,则称为可以多步推导出,记为 *G, 简写成 *。定义 6.2 G=(V, , S, P)是一个 CFG,则 G 产生的语言是所有可由 G 产生的字符串组成的集合,即 L(G)=x* | S*Gx。一个语言 L 是上下文无关语言(context-free language, CFL),当且仅当存在一个 CFG G,使得 L=L(G)。语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 3此处可以把文法设想成类似自动机的抽象模型,则一个语言 L 是 CFL 当且仅当存在一个CFG G 接
10、受它(或识别它),类似前面正则语言与有限自动机的关系,接受(或识别)的含义是两方面的,一方面凡是 L 中的字符串都能由 G 产生,另一方面,凡是不属于 L 的字符串都不能由 G 产生。前面的例子,能够比较容易地说明这两方面的限制,下面的例子则不是很明显。例子 6.3 考虑语言 L=x0, 1* | n0(x)=n1(x),其中 ni(x)表示数字 i 在 x 中的出现次数(即含有相同数目 0 和 1 的语言)。写出生成 L 的 CFG。分析:既然 CFG 的本质是一个递归定义,那么类似例子 6.1,我们可以先发现归纳基础,然后找到归纳推理。显然 L,另外如果存在一个字符串 xL,那么得到更长的
11、属于 L 的字符串的两个方法是,0x1 和 1x0,即分别在两端添加相同数量的 0 和 1。(当然,还有很多生成方法,比如 x01,x10,或在 x 中插入相同数量的 0 和 1),这样得到三个产生式:S | 0S1 | 1S0显然,还遗漏了一些字符串,如 0110。我们注意到 L 中任意两个元素的连接仍然属于L,因此可以增加一个产生式,SSS。与前面的三个产生式合并,我们得到一个 CFG G 如下,S | 0S1 | 1S0 | SS显然 G 产生的字符串都满足 0 和 1 数目相等这个条件,即 L(G)L,现在只要证明 LL(G)。令 d(x)=n0(x)-n1(x)。则字符串 xL,当且
12、仅当 d(x)=0。因此只需证明每个满足 d(x)=0 的x,都有 xL(G)。我们用数学归纳法来证明 LL(G)。归纳对象是字符串的长度|x| 。1. 归纳基础,|x|=0 且 d(x)=0,则 xL(G),这是显然的,因为此时 x=,而可以由产生式 S得到。2. 归纳推理,设 k=0,每个满足|y|0 的 x 都能由G0 产生,对 x 的长度应用数学归纳法。1. 归纳基础,显然|x|=1 且 d(x)0 时,即 x=0,x 可由 S0 推导,属于 L(G0)。2. 归纳推理,设 k=0,对每个|x|0 的 x 都属于 L(G0),要证明每个|x|0 的 x 也属于 L(G0)。分情况讨论,
13、此处仅讨论 x=0y0 的情况(即以 0 开始和结尾的情况),a) x 仅由 0 组成,易证。b) x 至少含有一个 1。则 x=w1z,现在证明 d(w)0 且 d(z)0。设 x 含有 n 个1,n=1 ,对每个 1=0,则 d(wn)0,令 w=wn,z=zn,显然 d(z)0,得证;2)存在一个 wi,使得d(wi)0,且 d(wm-1)=10,令 w=wm-1,z=zm-1 ,易证 d(zm-1)0,因此得证。其他两种情况,x 以 1 开始,以 1 结尾证明略。类似方法能够得到生成 L1 的产生式,我们用 A 表示生成 L0 的产生式,B 表示生成 L1 的产生式,那么生成语言 L
14、的全部文法是:SA | BA0 | 0A | 1AA | AA1 | A1AB1 | 1B | 0BB | BB0 | B0B注意其中的第一个规则,它恰如其分地表示了合集的含义。6.2 更多地例子,包括一些熟悉的语言例子 6.5 我们前面已经提到了在计算机科学和其他领域应用很广泛的书写代数表达式的语言。一般地,我们允许任意的标识符和数字常数出现在表达式中,为了简化问题,我们规定只有一个标识符(字母 a)、4 种两项运算符(+、-、*、/)和左括号、右括号。我们用变量 A表示这个简单的表达式语言。它可以嵌入到复杂的表达式语言中。一个容易发现的递归现象是,如果存在两个表达式,那么可以利用运算符和括
15、号,连接它们构成新的表达式。显然,除了单个标识符 a,其他表达式都是通过这个递归过程构造的,因此我们得到下面的文法:SS+S | S-S | S*S | S/S | (S) | a表达式 a+(a*a)/a-a 的推导过程如下,SS-SS+S-Sa+S-Sa+S/S-Sa+(S)/S-Sa+(S*S)/S-Sa+(a*S)/S-S.a+(a*a)/a-a还存在其他一些推导过程,如SS/SS+S/Sa+S/Sa+(S)/Sa+(S*S)/Sa+(a*S)/Sa+(a*a)/Sa+(a*a)/S-Sa+(a*a)/a-Sa+(a*a)/a-a显然,第一种推导比第二种更自然,它符合了通常的运算符的优
16、先级和计算次序。比如,上述表达式通常的计算次序如下:1. 计算 a*a,记为 A2. 计算 A/a,记为 B3. 计算 a+B,记为 C4. 计算 C-a语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 5尽管第二种推导不符合通常的表达式结构,或表达式语义,但整个推导是符合文法规定的,是一个合法的推导。一个可能的结论是本例给出的 CFG 不是最合理的,它没有体现出运算符和括号的优先级,另外好的 CFG 对每个字符串仅仅提供一种推导过程(如果忽略次序不同的一些简单替换),在 6.4 节我们将回到这个问题,它称为 CFG 的歧义。例子 6.6 上面的例子类似例子 3.5 和
17、3.6,仅仅描述了程序设计语言(比如 C 和 Pascal)的某个部分,本例将显示,CFG 可用来描述程序设计语言的整个语法结构。C 语言有两大类语法结构, 和。包括条件声明()和循环声明( )等等,因此有,. | | | .if () for (; ; ) .可以类似构造多个声明连接而成的复合声明,以及等等。例子 6.7 高级程序设计语言的一个大的优点是写出的程序与英文陈述很接近,既然我们可以用 CFG 去描述高级程序设计语言,那么可不可以用来描述英语呢?对于一些简单的英语语法,容易找到它的 CFG,比如最基本、最典型的英语陈述句的结构是, ,因此可以构造出如下的产生式, 可以进一步构造生成
18、右部三个非终结符的产生式。写出一套合理的、具有广泛性、符合常用语法习惯的英语规则并不困难,且规则的数目也可以不是很多。困难的地方是防止产生不合语法,或合乎语法,然而不合语义,没有人会使用的英语句子。下面的产生式就是一个例子, John | Janereminded | himself | herself上面文法能够产生很多不“正确”的英语句子,如“John reminded herself”,“Jane reminded himself”。可以通过增加文法的复杂性(更多的非终结符和更多的产生式)来消除这种不正确的推导,比如修改产生式为, 而对于例如“Jane reminded Jane”还需要
19、其他消除方法,因为句子“Jane reminded John”是一个好的英语句子。要想区别这两个句子,需要上下文信息,因此本章讨论的 CFG 无法很深入、精细地刻画自然语言的一些细微特征。6.3 CFL 的合并、连接和 Kleene*运算例子 6.4 我们构造了一个 CFG,它生成的语言是另外两种语言的合集,而且找到了另外两种语言对应的 CFG。例子 6.4 揭示的方法和处理连接和 Kleene*运算的相应方法构成了下面定理的基础。定理 6.1 L1 和 L2 是两个 CFL,则语言 L1L2、L 1L2 和 L1*也是 CFL。证明:我们用构造法证明。假设生成 L1 和 L2 的文法分别是
20、G1=(V1, , S1, P1)和 G2=(V2, , S2, P2),以此为基础,分别构造生成上面三种新语言的 CFG。语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 6首先假设 V1V2=(否则可以通过改名来到达目的),设生成 L1L2 的文法 Gu=(Vu, , Su, Pu),其中,Su 是不属于 V1 和 V2 的新增非终结符,Vu=V1V2 Su,Pu=P1P2SuS1 | S2。现在证明 L(Gu)=L1L2。一方面,任取 xL1=L(G1),则 S1*x,又由于存在产生式SuS1,因此 Su*x。类似地,任取 xL1,也有 S*x。因此 L1L2L(G
21、u)。另一方面,任取 xL(Gu),则存在 S*x,则存在 S1*x 或 S2*x,因此 L(Gu)L1L2。得证。类似处理连接运算,文法 Gc=(Vc, , Sc, Pc),其中 Sc 是不属于 V1 和 V2 的新增非终结符。定义 Vc=V1V2Sc,Pc=P1P2ScS1S2 。任给 xL1L2,则x=x1x2,x1L1,x2L2。因此有 SS1S2*x1S2*x1x2=x,即 L1L2L(Gc)。任给 xL(Gc),即 S*x,则有 S1S2*x,由于 V1 和 V2 不相交,则存在 x1x2=x,满足S1*x1,S2*x2,因此 L(Gc)L1L2。文法 G*=(V, , S, P)
22、生成语言 L1*,其中,S 是不属于 V 的新增非终结符。语言 L1*所含字符串 x 的形式是 x=x1x2.xk,其中每个 xiL1,如果能够 S 连续地产生 k 个 S1,则 S 能够推导出 x,因此得到下面的产生式,SS 1S | ,而 P=P1SS1S | 。显然,L 1*L(G1*)。任给 xL(G1*),则存在 S*x,而 S 推导的第一步一定是 SS1k,因此 xL(G1)kL(G1)*。得证。下面的例子说明了证明的第一步消除 V1 和 V2 中的同名是很重要的。比如有两个 CFG 如下:S1XA, Xc, AaS2XB, Xd, Bb此处非终结符 X 出现在两个文法中,如果不改
23、名将带来混乱。如 SS1XAdAda,而事实上 S1 无法推导出 da。推论 6.1 每个正则语言都是 CFL。证明:根据正则语言的递归定义(定义 3.1) ,每个正则语言是以三种简单的字符(、 、a)为基础,多次施加三种运算(合并、连接、Kleeen*)得到。显然三种简单的字符组成的语言是 CFL,又根据定理 6.1,三种运算保持了 CFL 的性质,因此根据结构归纳法,命题得证。例子 6.8 语言 L 是正则表达式,(011+1)*(01)*,表示的正则语言,写出它对应的 CFG。分析:定理 6.1 的证明过程给出了发现一个 CFL 的 CFG 的方法。分别考虑(011+1)* 和(01)*
24、。而(011+1)*可进一步转化成考虑 (011+1),得到 A011 | 1,然后加上 Kleene*运算,得到,BAB | 类似地,对应正则表达式(01)*的产生式是,CDC | , D01最后应用连接运算,得到语言 L 对应的 CFG 是SBCBAB | A011 | 1CDC | D01最后引入的符号 S 是非终结符,构造过程中引入的符号是辅助非终结符。例子 6.9 语言 L=0i1j0k | ji+k的 CFG。分析:一个直观的观察是 L 的每个字符串都是三部分连接而成的:0 i、1 j、0 k。因此似乎可以分别构造三部分语言的 CFG,然后通过连接运算构造整个语言的 CFG。这种方
25、法是错误的,因为本例语言的三部分是相互关联的,而不是相互独立的,定理 6.1 揭示的方法仅仅用在语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 7相互独立的两个 CFG 之间的运算。可行的方法是,将本例中具有关系的参数 i、j 、k 转换成相互无关的参数。由于 ji+k,不妨令 j=i+k+m,m0。则 0i1j0k=0i1i1m1k0k。此处的三个参数 i、m、k 相互独立,因此语言L=L1L2L3,其中,L1=0i1i | i=0L2=1m | m0L3=1k0k | k=0先分别构造语言 L1、L2、L3 的 CFG,然后利用连接运算构造 L 的 CFG。L1 和
26、 L3 类似,L2 易于构造。发现 L1 的递归性质,L1,对每个 xL1,都有 0x1L1。因此得到 L1 的CFG,A 0A1 | 。类似地, L3 的 CFG 是 C1C0 | 。 L2 的 CFG 是 B1B | 1(注意,不是B,因为 L2 的字符串长度至少为 1) 。因此连接三部分,得到最终的 CFG G=(V, , S, P),其中,V=S, A, B, C, =0, 1,P=SABC, A0A1 | , B1B | 1, C1C0 | 。字符串 01402=(01)(1)(1202)的推导过程如下,SABC0A1BC01BC011C0111C001111C00011110001
27、111006.4 推导树和歧义对于一个自然语言,比如英语,理解它的句子从理解它的语法结构开始,即了解句子是怎样根据语言的句法规则产生的。给定一个 CFG 和一个它所生成的字符串 x,知道了 x 的推导过程有助于正确理解 x 的含义。一个展示推导过程(或推导结构)的自然的方法是画出推导树或分析树。树的根部是文法的起始非终结符,它是推导的起点。树的内部节点对应文法的一个非终结符,比如 A,A 的子节点对应形如 A的产生式的右部 的每个符号。对于形如 A的产生式,标记为 A 的内部节点的子节点只有一个,即 。以例子 6.5 文法为例,S S+S | S-S | S*S | S/S | (S) | a
28、,存在一个字符串的推导,SS-SS*S-Sa*S-Sa*a-Sa*a-a,它对应的推导树如图 6-1a 所示。另一个字符串的推导,SS-SS-S/S.a-a/a 的推导树如图 6-1b。这两个代数表达式常常用表达式树(expression trees)表示,表达式树是一个二叉树,它的叶节点是标识符或常量,内部节点是运算符(参见图 6-2)。表达式树显示了表达式的结构和推导过程,本节提出的推导树类似于这种表达式树。在一个 CFL 的字符串的完整推导树上,根节点对应文法的起始非终结符,叶节点(或称终端节点)对应终结符或。有时我们也用推导树表示起于某个普通非终结符的推导结构,或部分推导过程,这种推导
29、树的根节点不一定是起始非终结符,叶节点也不一定是终结符。推导的每一步都是用某个产生式的右部代替左部的非终结符,每个推导都由一组这样的替换按照一定顺序组成。替换的顺序很重要,因此推导 SS+Sa+Sa+a 和SS+SS+aa+a 是不同的推导。这种差异是在细节上,当得到 S+S 时,前者选取了最左非终结符,后者选取了最右非终结符。两种推导对应的推导树是完全一样的,因此可以认为推导过程中的细微的次序差异是不重要的。推导树完全反映了推导中用到的产生式,但不关心推导中用到的临时节点,或某些产生式的应用次序,这些次序也与字符串的语法结构无关,因此对应相同推导树的推导都认为是相同的推导。另一个比较两个推导
30、的方法是将推导的过程标准化,比如采用最左原则,即每次替换最左(或第一个)非终结符。如果两个遵循最左原则的推导是不同的,那么认为是“本质”不同的推导。实际上,最左原则和推导树原则是两个相当的判定标准。一方面,对应不同推导树的最左推导过程是显然不同的。另一方面,对应不同最左推导过程的推导树也是不同的。比如设下面的推导是第一次出现差异的情况,xAx1语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 8xAx2其中,x 是终结符组成的字符串,A 是非终结符,且 12,因此体现在推导树则一定不同。现在定义,一个字符串具有多个(超过 1 个)推导树当且仅当具有多个最左推导。注意我们也
31、可以采用最右原则,关键是消除一些细节上的差异。我们发现许多 CFG 生成的字符串具有多个“本质”不同的生成办法。定义 6.3 CFG G 是歧义的(ambiguous )当且仅当存在一个 xL(G)具有多个推导树(或最左推导过程)。显然,上面定义的歧义很接近我们日常应用的自然语言句子的歧义。比如一个记者用到的标题“Disabled Fly to See Carter”,如果它出现在美国第 39 任总统当政时期,对应的推导是:S .。但通常更多的解释是:S .。此处,正确理解一个新闻标题的关键是选择相应的语法推导。例子 6.10 回到例子 6.5 给出的代数表达式的 CFG,我们考察了字符串 a
32、+(a*a)/a-a 具有的本质不同的推导过程。分析:本例的 CFG 形如,S S+S | S-S | S*S | S/S | (s) | a,看一个简单的例子“a+a+a”,存在两个不同的最左推导:SS+Sa+Sa+S+Sa+a+Sa+a+aSS+SS+S+Sa+S+Sa+a+Sa+a+a它们对应的推导树分别见图 6-3a 和图 6-3b。如果结合具体的代数运算符含义,两者最终含义是相同的,前者等同于 a+(a+a),后者等同于(a+a)+a。可见括号具有消除歧义的作用。从例子 6.10 容易看到,凡是具有形如 AAA 的产生式的 CFG 都是有歧义的。然而有很多种引入歧义的方式,有时很难发
33、现并排除它们。例子 6.11 程序设计语言歧义的一个标准的例子是“dangling else”,考虑下面的产生式:if () | if () else |现在考虑声明:if (expr1) if (expr2) f(); else g();根据上面的产生式,可以有两个推导树,一种将 else 看成与第一个 if 相关,另一种将 else看成与第二个 if 相关。参见图 6-4a 和图 6-4b。在 C 语言中,为了消除歧义,常常引入括号,如,if (expr1) if (expr2) f(); else g();if (expr1) if (expr2) f(); else g();或者修改文
34、法,如 | if () else | if () |if () else 目前我们无法证明为什么新文法消除了歧义,但可以直观地解释。生成的都是 if-else匹配的情况,生成的是 if-else 不匹配的情况。而且出现在 else 之前的都是,因此不匹配的情况出现在 else 之后,即 else 总是与最接近的 if 匹配。程序设计语言 Modula-2 使用了类似括号的方法消除歧义,IF THEN END |IF THEN ELSE END |语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 9在上面文法下,图 6-4a 和图 6-4b 对应的字符串分别是IF A1 T
35、HEN IF A2 THEN S1 END ELSE S2 ENDIF A1 THEN IF A2 THEN S1 ELSE S2 END END6.5 一个无歧义的代数表达式尽管有些 CFL 是“内在”歧义的,即只能由有歧义的 CFG 生成,但通常意义的歧义是针对文法而言,而不是语言。如果一个 CFG 是歧义的,常常可能存在(也是我们希望发现的)一个与其相当的非歧义的 CFG,本节我们消除例子 6.5 给出的代数表达式的文法的歧义。为了简化问题,我们仅仅使用两个运算符“+”和“*”,得到产生式如下,SS+S | S*S | (S) | a如果消除了这两个运算符的歧义,能够类似地消除“-”和“
36、/”的歧义。正如前面讲到,产生式 SS+S 能够带来歧义,我们需要消除这种类型的产生式,同时保持各种运算符的优先级,比如*的优先级高于+,且位于前面的+优先级高于后面的+。新文法 G1 的产生式如下,SS+T | TTT*F | FF(S) | a需要证明两个方面:1)G1 与 G 相当;2)G1 没有歧义。为了证明方便,G1 中的起始符号改写成 S1。定理 6.2 文法 G 的产生式是 SS+S | S*S | (S) | a,文法 G1 的产生式是S1S1+T | TTT*F | FF(S1) | a则 L(G)=L(G1)。证明:首先证明 L(G1)L(G)。对属于 L(G1)的字符串
37、x 的长度使用数学归纳法。1. 归纳基础,x=a 时,显然 xL(G)2. 归纳推理,设 k=1,每个属于 L(G1)、长度=1,对每个yL(G)且|y|=1,任何一个从 S1、T、F 推导出来的长度=1,如果 AGnx,则 AG1*x。对 n 使用数学归纳法。1. 归纳基础,n=1,即 AG1x,则 P 中存在 Ax,x 是非空的,因此 P1 中也有 Ax,则 AG11x,A G1*x。2. 归纳推理,n=k 时所有|x|=1,如果 AG1nx,则 AG*x。类似可证。显然,消除文法的空产生式,需要添加大量新的产生式(很类似,消除 FA 的空转移需要添加大量新的转移) 。于是存在一个问题,添
38、加的新产生式是否带来了新的不好的性质。部分的回答是算法 6.1 不会带来歧义,即如果原来文法 G 是非歧义的,则处理后的文法 G1 也是非歧义的。证明并不困难,参见练习 6.38。单一产生式的消除类似空产生式的消除。比如要删除 AB(更普遍地,A*B) ,就要对所有 B,添加 A。为了简化讨论,我们消除单一产生式的工作基础是无空产生式的文法。下面先定义 A 可推导集(A-derivable) 。1. 如果存在产生式 AB,则 B 是 A 可推导的;2. 如果存在产生式 CB,且 C 是 A 可推导的,BA ,则 B 是 A 可推导的;3. 仅包含 1 和 2 产生的非终结符。算法 6.2 给定
39、一个没有空产生式的 CFG G=(V, , S, P),构造一个没有单一产生式的文法G1=(V, , S, P1)。1. 初始化 P1=P2. 对每个 AV,发现 A 的可推导集3. 对 A 的可推导集的每个元素 B,如果存在 B,则添加产生式 A到 P14. 删除 P1 中的所有单一产生式定理 6.5 设 G 是没有空产生式 CFG,G1 是算法 6.2 得到的文法,则 G1 没有单一产生式,且 L(G1)=L(G)。证明:略,参见练习 6.37。值得指出的是,如果文法 G 是无歧义的,则文法 G1 也是无歧义的。例子 6.13 G 是生成代数表达式的文法,它的产生式如下:SS+T | TT
40、T*F | FF(S) | a则 S 的可推导集是T, F,T 的可推导集是F。根据算法 6.2 的第 3 步,加入产生式ST*F | (S) | a 和 T(S) | a,然后删除单一产生式,最后的产生式集合如下,SS+T | T*F | (S) | aTT*F | (S) | a语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 13F(S) | a除了删除一些“不好”的产生式,如空产生式和单一产生式,对产生式的格式添加更多的限制也是有用的。已经提出了多种产生式的“规范形式”,本节介绍其中的一种,Chomsky 范式。定义 6.6 一个 CFG 的每个产生式符合下面两个
41、形式中的一种,则称为 Chomsky 范式(Chomsky normal form, CNF):A BC 和 Aa。其中, A、B、C 是非终结符,a 是终结符。将一个文法 G 转换成 CNF 需要三个步骤。第一步应用算法 6.1 和算法 6.2 得到没有空产生式和单一产生式的文法 G1,L(G1)=L(G)- ;第二步构造新的文法 G2,它的产生式只有下面两种形式:AB 1B2.Bk 和 Aa,其中,A、B i 是非终结符,a 是终结符,L(G2)=L(G1)。从G1 构造 G2 很简单,给每个终结符 a 引入一个相应的非终结符 Xa,添加产生式 Xaa,原产生式中的 a 都替换成 Xa(除
42、了 Aa 这类产生式) 。文法 G2 已经很接近 Chomsky 范式了,产生式的右部要么全是非终结符,要么是单个终结符。第三步,将 G2 中右部长度超过 2 的产生式用多个产生式替换。定理 6.6 对于每个 CFG G 都存在一个符合 Chomsky 范式的 CFG G,使得 L(G)=L(G)-。证明:略。例子 6.14 CFG G 的产生式如下:SAACDAaAb | CaC | aDaDa | bDb | 构造 G 对应的符合 Chomsky 范式的文法 G。分析:1. 删除空产生式,得到SAACD | ACD | AAC | CD | AC | CAaAb | abCaC | aDa
43、Da | bDb | aa | bb2. 删除单一产生式,增加 SaC | a,删除 SC。3. 增加产生式的限制,不允许非终结符和终结符在产生式右部混杂出现,得到SAACD | ACD | AAC | CD | AC | XaC | aAXaAXb | XbXbCXaC | aDXaDXa | XbDXb | XaXb | XbXbXaaXbb4. 转换成 CNF,得到SAT1 T1AT2 T2CDSAU1 U1CDSAV1 V1ACSCD | AC | XaC | aAXaW1 W1AxbAXaXb语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 14CXaC | a
44、DXaY1 Y1DxaDXbZ1 Z1DXbDXaXb | XbXaXaa Xbb-END-语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 15注意文法(grammar )的定义。定义 6.1 上下文无关文法(context-free grammar,CFG)是一个 4 元组 G=(V, , S, P),其中:V 是变量集,又称非终结符集是字母表,又称终结符集SV,称开始符P 是文法规则集(或称产生式),形如 A,AV,(V )*我们保留符号给 P 中的规则,而字符串的推导使用符号。如 ,表示中某些字符串根据 P 中某个规则被替换,从而得到。如:=1A2=12A是 P
45、中的一个规则。这里可以看到上下文无关的含义,即 P 中的规则在应用中与上下文无关,更确切地说,非终结符 A 在推导中,与上下文无关。*的定义:*,或者=,或者存在一个推导链 0 1 . k,i i+1,且0=,k=。定义 6.2 CFG G 产生的语言是:L(G)=x | S*x由 CFG 产生的语言就是 CFL。例子 6.3 d(0)=0 d(0y)=-1,因此必定存在一个点,使得前缀 d(z)=0。类似连续函数的中值定理。例子 6.4 CFL 的补集可能不是 CFL。S0 | S0 | 0S | 1SS | SS1 | S1S要证明两个语言相等,就要证明互为子集,因此是两个方向的证明。可以
46、用结构归纳法证明 S满足性质 n0(x)n1(x),反向证明较难。6.2 更多例子,包括一些熟悉的语言例子 6.5 代数表达式例子 6.6 C 语言语法例子 6.7 英语的部分语法6.3 CFL 的合并、连接和 Kleene*运算定理 6.1 如果 L1 和 L2 是上下文无关文法,则语言 L1L2、L1L2、L1*也是上下文无关文法。证明:用构造法。如果 G1=(V1, , S1, P1)和 G2=(V2, , S2, P2),设 V1V2=。L1L2: Gu=(Vu, , Su, Pu),Vu=V1 V2Su,Pu=P1P2SuS1|S2L1L2:Gc=(Vc, , Sc, Pc) ,Vu
47、=V1V2 Sc,Pu=P1P2 ScS1S2L1*:G*=V, , S, P ,V=V1S ,P=P1 SS1S|推论 6.1 每个正规语言都是上下文无关语言。根据正规语言的递归定义使用结构归纳法。定理 6.1 也给出了发现语言的 CFG 的方法,例子 6.8,(011+1)*(01)* ,逐步分解生成文法。例子 6.9 的巧妙分解,注意这里没有一种形式化的方法。6.4 推导树和歧义对于英语这样的自然语言,理解一个句子首先要理解它的语法结构,即句子是如何用语法规则推导出来的。一个自然的展示这个推导过程的方式是画推导树(derivation tree)或称分析树(parse tree)。树的根
48、是开始推导的起点,树中每个节点对应一个非终结符,它的儿节点就是相应规则的右部,表达式树。语言与计算理论导引 上下文无关文法陶晓鹏 Copyright 2003 16推导过程可以是多样的,因为同一时有多个非终结符,可以任意选择扩展非终结符的次序,而最终的结果一般是相同的。为了避免不必要的多样,可以规定每次选择最左的非终结符扩展,称为最左扩展。如果一个字符串也多个不同的最左扩展,则字符串在本质上具有不同的推导树。定义 6.3 如果存在一个字符串 xL(G),有多个不同的推导树,则称 CFG G 有歧义。例子 6.10 AAA 类型的歧义。例子 6.11 dangling else(悬挂)问题。括号在消除歧义中的作用。6.5 一个关于代数表达式的非歧义的 CFG尽管有些 C