0%

编译原理——第2章

Chapter 2 文法和语言的基础知识

2.1 概述

语法:对语言结构的定义,即这条语句是由什么构成的
语义:描述语言的含义,即这条语句是干什么的
语用:从使用的角度去描述语言,即这个语句对应的一类语句有什么用

2.2 字母表和符号串的基本概念

文法:描述语言的语法结构的形式规则
字母表:元素的非空有穷集合,记为Σ\Sigma
符号(字符):字母表中每个元素称为字符
字符串(字):Σ\Sigma中的字符构成的一个有穷序列
空字:不包含任何字符的序列,记为ε\varepsilon
所有字的全体:包含空字,记为Σ\Sigma^*,实际上就是Σ\Sigma的闭包。

Σ={a,b}\Sigma=\{a, b\},那么Σ={ε,a,b,ab,ba,aa,ab,aaa,...}\Sigma^*=\{\varepsilon,a,b,ab,ba,aa,ab,aaa,...\}

连接:x和y两个字符串的连接xy。Σ\Sigma^*的子集的连接(乘积)定义为:AB={xyxA&yB}AB=\{xy|x\in A \& y\in B\}
符号串的幂运算:设x为符号串,则其幂运算定义为:x0=ε,x1=x,x2=xx,...x^0=\varepsilon,x^1=x,x^2=xx,...。集合的幂运算与之类似。
正闭包A+A^+和闭包AA^*A的正闭包和闭包定义为:A+=A1A2...An...,A={ε}A+A^+=A^1\cup A^2\cup ...\cup A^n...,A^*=\{\varepsilon\}\cup A^+

2.3 文法和语言的形式定义

2.3.1 形式语言

序列的集合称为形式语言。形式语言有两种表示方式。
一种方式是将语言所有可能以枚举的形式写到一个集合中去。
另一种方式针对无法枚举的情况,称为产生式。

2.3.2 文法的形式定义

规则

规则又称产生式,是一个符号与一个符号串的有序对(A,β)(A,\beta),通常写作AβA\rightarrow \beta,其中AA规则左部,是一个符号,β\beta规则右部,是一个符号串。\rightarrow表示“生成”、“定义为”。规则的作用是告诉我们如何使用规则中的符号生成语言中的序列,即一组规则定义了一个语言的语法结构。

文法

文法是规则的有穷集合,通常用四元组G=(VN,VT,P,S)G=(V_N,V_T,P,S)表示,其中:

  • VNV_N是规则中非终结符号的集合
  • VTV_T是规则中终结符号的集合,VNVT=V_N\cap V_T=\empty。通常使用VV表示VNVTV_N\cup V_T,称为文法中的词汇表
  • PP文法规则的集合
  • SS是一个非终结符号,称为文法的开始符号或文法的识别符号,至少要在一条规则中出现,由它开始识别定义的语言

文法是对语言结构的定义和描述。文法四大要素中,关键是规则的集合。

为书写方便,对于若干左部相同的规则可以缩写为Aα1α2...A\rightarrow \alpha_1|\alpha_2|...,其中每一个αi\alpha_i称为AA的一个候选式。约定第一条规则的左部符号为识别符号,对文法GG不使用四元式显式表示,而是只写出规则。

2.3.3 语言的形式定义

直接推导

GG为一文法,从xAyxAy直接推出xαyx\alpha y,即xAyxαyxAy\Rightarrow x\alpha y,仅AαA\rightarrow \alphaGG的一个规则且x,y(VNVT)x,y\in (V_N\cup V_T)^*。即这一次直接推导只使用了一次规则。注意推导是\Rightarrow而规则是\rightarrow,二者千万不要搞混。

推导

如果存在一个推导序列α0α1...αn\alpha_0\Rightarrow\alpha_1\Rightarrow...\Rightarrow\alpha_n,则称这个序列是一个从α0\alpha_0αn\alpha_n的长度为n的推导,记为α0+αn\alpha_0\stackrel{+}\Rightarrow\alpha_n,其表示从α0\alpha_0出发可以经过1步或若干步或使用若干次规则推导出αn\alpha_n

广义推导

α0αn\alpha_0\stackrel{*}\Rightarrow\alpha_n表示从α0\alpha_0出发可以经过0步或若干步或使用若干次规则推导出αn\alpha_n。0步指的是α0=αn\alpha_0=\alpha_n的情况。

句型和句子

设有文法G[S]G[S],如果Sx,x(VNVT)S\stackrel{*}\Rightarrow x,x\in(V_N\cup V_T)^*,则称符号串x为文法G[S]G[S]句型Sx,x(VT)S\stackrel{*}\Rightarrow x,x\in(V_T)^*,称x为文法G[S]G[S]句子。即句子只含有终结符,是一个确定的字符串序列,而矩形可能含有非终结符,可能是一个字符串范式,表示一类字符串的格式。

语言

文法G[S]G[S]产生的所有句子的集合称为该文法GG所定义的语言,记作L(G[S])L(G[S])L(G[S])={xS+x&xVT}L(G[S])=\{x|S\stackrel{+}{\Rightarrow}x\&x\in V_T^*\}。当文法确定时,语言也就确定。注意L(G)L(G)VTV_T^*的一个子集,属于VTV_T^*的符号串xx不一定属于L(G)L(G),因为VTV_T^*中不是所有的符号串都满足文法规定的句型格式

2.3.4 规范推导和规范规约

最左(最右)推导:对于一个推导序列中的每一步直接推导αβ\alpha\Rightarrow\beta,都是对α\alpha中的最左(最右)非终结符进行替换。最右推导又称规范推导,由规范推导推导出的句型称为规范句型

规范规约:规范推导的逆过程,即从左向右进行规约,也称为最左规约。

2.4 短语、直接短语和句柄

2.4.1 短语和直接短语

GG为一个文法,SS为文法开始符号,假定αβδ\alpha\beta\delta是文法GG的一个句型,如果有SαAδS\stackrel{*}\Rightarrow\alpha A\deltaA+βA\stackrel{+}\Rightarrow\beta,则称β\beta是相对于非终结符AA的句型αβδ\alpha\beta\delta短语。特别地如果有SαAδS\stackrel{*}\Rightarrow\alpha A\deltaAβA\Rightarrow\beta,则称β\beta直接短语。一个句型的最左直接短语称为该句型的句柄

2.5 语法树与文法的二义性

2.5.1 推导和语法树

语法树:使用一张图表示一个句型的推导,称为语法树。一棵语法树是不同推导过程的共性抽象。
语法树的子树:由某一个非末端结点连同所有分支组成的部分。
简单子树:只有单层分支的子树。

可以根据语法树快速确定一个句子的短语、直接短语和句柄。方法如下:
短语——子树的末端结点形成的符号串是相对于子树根的短语。
直接短语——简单子树的末端结点形成的符号串是相对于简单子树根的短语。
句柄——最左简单子树的末端结点形成的符号串是句柄。

2.5.2 文法的二义性

当一个句型有多个最左(最右)推导时,表示该文法存在某个句子对应两棵不同的语法树,该文法具有二义性。一个语言是二义的,如果对其不存在没有二义性的文法。

消除二义性文法可以通过添加限制性规定、调整运算符的优先顺序和结合规则等方式实现。

2.6 文法和语言的分类

文法和语言分为4大类:0型~3型。

  • 0型:无限制文法。即每条规则的左式和右式都为(VNVT)(V_N\cup V_T)^*,且左式中至少含有1个非终结符。
  • 1型:上下文有关文法。每一条规则形式为αAβαuβ\alpha A\beta\rightarrow \alpha u\beta,其中AVN,α,β(VNVT),u(VNVT)+A\in V_N,\alpha,\beta\in(V_N\cup V_T)^*,u\in(V_N\cup V_T)^+,即在替换非终结符AA时需要考虑上下文。
  • 2型:上下文无关文法。每一条规则的形式为AβA\rightarrow\beta,其中AVN,β(VNVT)A\in V_N,\beta\in(V_N\cup V_T)^*。即使用该规则时无需考虑AA的上下文结构而可以直接将其转换。
  • 3型:正规文法。每一条规则的形式为AαBA\rightarrow \alpha BAαA\rightarrow \alpha,其中A,BVN,αVTA,B\in V_N,\alpha\in V_T^*。右线性文法和左线性文法都为3型文法。

上面4种文法可以表示的句型范围从大到小,规范性从弱到强。一些句型可能无法使用高规范文法进行表示但可以用低规范性文法表示。对于现今程序设计语言,在编译程序中,仍然采用上下文无关文法来表述其语言结构。

2.7 有关文法的实用限制和变换

对文法的实用限制有两点:

  • 文法中不能包含如AAA\rightarrow A的规则,这被称为有害规则。
  • 文法中不能包含多余规则。即文法中出现以下两种规则:
    • 某条规则左部符号不在所属文法的任何规则右部出现,这样这个规则就永远不可能用得上。
    • 对文法中某个非终结符不能从其推导出任何终结符号串,这意味着如果使用了这个规则,那么它将一直推导下去永远无法到达终点,这显然是不允许的。

题型总结

1. 基本概念的理解

例-1:给出文法G[S]G[S]如下:
SBdS\rightarrow Bd
BAeB\rightarrow Ae
AAddA\rightarrow Ad|d
求句子ddeddded的句柄、所有短语和直接短语。
方法1:画出语法树。这是最为保险,也最不容易错的一种方法。此处语法树略。
方法2:进行推导。
SBdAedAdedddedS\Rightarrow Bd\Rightarrow Aed\Rightarrow Aded\Rightarrow dded。其中有B+dedB\stackrel{+}\Rightarrow ded,因此dedded是一个短语。同理第一个ddddddddeddded也是短语。
直接短语是直接推导而来,因此只有第一个dd是直接短语。
句柄是最左直接短语,即第一个dd

一定要搞清楚短语、直接短语和句柄的定义,不要混淆。

2. 根据字符串描述写出文法

例-2:使用文法表示下面描述的语言。该语言由0、1、左右括号组成,且由01开头,10结尾。中间的值可以为0、1、括号扩充的整体。由左右括号扩充的整体中,以00开头,11结尾,中间的值可以为0、1、括号扩充的整体。如01(00111)(0011011)011(0011)10是该语言的一个句子。

分析:求解写文法题时需要将问题分解,采用分治的思想逐个击破。如本题中可明显地将“括号扩充的整体”进行首先处理,然后再去处理其他的内容。将这部分定义为由非终结符TT生成,则有
T(00A11)T\rightarrow(00A11),其中AA应该表示的是0或1构成的序列:
AA0A101A\rightarrow A0|A1|0|1
外层的内容:
S01B10S\rightarrow 01B10,其中BB应该是TTAA构成的字符串
BBTBATAB\rightarrow BT|BA|T|A
所求文法:
S01B10S\rightarrow 01B10
BBTBATAB\rightarrow BT|BA|T|A
T(00A11)T\rightarrow (00A11)
AA0A101A\rightarrow A0|A1|0|1

技巧:对于一些常见的序列组合,有固定的模式可以套用——

  • 由终结符{a1,a2,...,an}\{a_1,a_2,...,a_n\}构成的所有字符串可以使用下面的文法规则(如不包含空串则删去最后的ε\varepsilon即可):
    • SSa1Sa2...Sana1a2...anεS\rightarrow Sa_1|Sa_2|...|Sa_n|a_1|a_2|...|a_n|\varepsilon
  • 运算符优先级控制,对于运算符{1,2,...,n}\{*_1,*_2,...,*_n\},下标越大优先级越高,则可以使用下面的文法规则表示(这只是一种表示,还有其他表示方式):
    • A1A11A2A2A_1\rightarrow A_1*_1A_2|A_2
    • A2A22A3A3A_2\rightarrow A_2*_2A_3|A_3
    • ......
    • AnAnnAn+1An+1A_n\rightarrow A_n*_nA_{n+1}|A_{n+1}
    • 若存在优先级相同的运算符,则作为一个规则的不同候选式。

实际上有了上面这两条规则模板,已经可以解决很多的问题了,但有时还可以通过一些更加巧妙的组合来简化文法的书写:

  • 长度相等子串对:在句子中包含有类似于αnβn(n0)\alpha^n\beta^n(n\ge 0)的形式,如果采用上面的方式需要两个规则,但实际上可以简化为一条规则:
    • SαSβαβεS\rightarrow\alpha S\beta|\alpha\beta|\varepsilon
  • 长度相关子串对:两个重复子串的长度不再相等,而是具有一定的关系:
    • 大小关系:αnβm(n>morn<m,m,n0)\alpha^n\beta^m(n>m\operatorname{or}n<m,m,n\ge 0),以n>mn>m为例,可以写为一条规则:
      • SαSβαSαβS\rightarrow\alpha S\beta|\alpha S|\alpha|\beta(此时可以不加ε\varepsilon,因为此时mmnn必有一个不为0,因此串不可能为空串)
    • 倍数关系:αanβn\alpha^{an}\beta^n,可以将αa\alpha^a看做一个整体:
      • SαaSβαaβεS\rightarrow \alpha^aS\beta|\alpha^a\beta|\varepsilon
    • 差数关系:两串长度的差值固定,将差的那一部分分离出来看做前后缀即可。
  • 注意:形如anbncna^nb^nc^n的句子无法使用2型文法规则定义,因为bb子串长度取决于其左右aa子串和cc子串的长度。换句话说,我们无法对这个句子用分治法进行规则解释。但a2nb2nc2na^{2n}b^{2n}c^{2n}可以解释,其文法为:
    • SASBABεS\rightarrow ASB|AB|\varepsilon
    • Aa2Aba2bA\rightarrow a^2Ab|a^2b
    • BbBc2bc2B\rightarrow bBc^2|bc^2
    • 这是将这个句子拆分成了a2nbna^{2n}b^nbnc2nb^nc^{2n}两份求解的,这两份的nn的关系由第一条规则可以定义。但anbncna^nb^nc^n不能这样分解是因为nn无法确定是否是偶数。当nn为偶数或奇数时相应的2型文法都可以写出来,但二者合并是无法实现的。(思考:当nn为奇数时,文法应该如何书写?)
  • 长度相关子串对,之间夹有其他字符
    • SS的第二个候选式αβ\alpha\beta变换为αTβ\alpha T\beta,其他内容由TT规则负责解释。

3. 根据文法写出语言描述

也即第2种题型的逆推。

例-3:根据下列文法说出其定义的语言
S0SSDDS\rightarrow0S|SD|D
DaSbD\rightarrow aS|b

分析:其定义的语言可以通过画树状图的方式来寻找规律。每一次对候选式进行分支,可以据此获取所有该语言的句子。如上述文法定义的语言的最短的几个句子应该是:bb0b0babab0ab0ab,可以发现所有这些句子的最后一个字符都是bb。有推导:SSD0SDS\Rightarrow SD\Rightarrow0^*SDSSDDDnanSnanDnanbnS\Rightarrow SD^*D\Rightarrow D^n\Rightarrow a^nS^n\Rightarrow a^nD^n\Rightarrow a^nb^n。再往下分析,就比较难了,因为没有对这个文法进行化简。

对该文法尝试进行化简:
S0SSaSSbaSbS\rightarrow 0S|SaS|Sb|aS|b
注意到其中有S0SaSS\rightarrow0S|aS,因此前缀应是0和aa组成的任意长度字符串,而后缀只能是bb组成的字符串。中间有一个规则是SSaSS\rightarrow SaS,表示两个SS中间有一个aa分隔。去掉这条规则后,就只剩下对前缀后缀的规则定义了,因此这个文法描述的语言是:

一个类型的字符串A以一种方式连接起来形成的总字符串。字符串A由两个部分组成,前面是以0或a构成的任意长度字符串(含空串),后面是以b构成的任意长度字符串(不含空串)。总字符串中有1个或多个这样的字符串,这些字符串中间以a连接。

4. 二义性检查与反例

检查文法的二义性主要有判断性问题和举反例问题,多出现在运算符优先级设置不合理的文法中。二义性的直接影响是在文法分析过程中会产生移进-规约冲突和规约-规约冲突,前者出现于一个右部串是另一个右部串的子串时,后者出现在一个右部串对应多个左部串。有时对于移进-规约冲突的情形,需要进行推导才能发现右部符号串有包含情况。