编译原理笔记2:词法分析基础与模式的形式化描述
词法分析,是词法分析器将源程序转化为线性记号流的过程。该过程中会对各种符号进行分类,比如将变量名换为标识符。
词法分析的含义:
- 规定词形成的规则,定义什么词是合法的;
- 根据规则识别输入的序列(词法分析),识别合法单词、指出非法的输入序列。
词法分析
模式、记号、单词
- 模式(Pattern): 产生和识别元素的规则。也就是定义的词法规则;
- 记号(Token): 按照某个模式(即,规则)识别出的(一组)元素。进行词法分析时,词法分析器将程序代码中的各个部分转为一个个记号的过程,就是根据规则得到一个记号流的过程;
- 单词(lexeme): 被识别出的元素自身的值(一个),也称为词值。可以理解为源程序中一个个的字符串。
上面三个词放一起理解:源程序里面是一个个单词,我们使用“模式”这个规则,对单词进行识别、分类,把它们放到相应的记号里面去。记号是一堆单词,Pascal 语言的记号举例如下:
记号的类别 | 单词举例 | 模式的非形式化描述 |
---|---|---|
const(01) | const | const |
if(03) | if | if |
relation(81) | <, <=, =, <>, >, >= | <, <=, =, … |
id(82) | pi, count, D2 | 字母打头的字母数字串 |
num(83) | 3.1416, 0, 6.02E23 | 任何数值常数 |
literal(84) | “core dumped” | 双引号间的任意字符串 |
comment | { x is an integer } | 花括号间的任意字符串 |
- id: 标识符记号。这里“字母”需要进行严格的形式化描述;
- literal:字面量;
- comment:注释
单词的基本分类
- 关键字 kw(keyword, reserved word)
- 标识符 id (identifier)
- 字面量 literal
- 特殊符号 ks (key symbol, special symbol)
例:
记号
记号 = 记号的类别 + 记号的属性
例如,mycount > 25,由三个记号组成。类别就是上表中对应的类别编号
词法分析器的作用与工作方式
编译器中只有该部分直接接触源代码,其他的部分都是通过使用之前的工作成果来间接接触源代码。词法分析器要进行的工作包括:
- 去掉注释、空格一类的无用部分;
- 处理和平台有关的输入,比如文件结束符的不同表示;
- 根据模式识别记号,交给语法分析器;(主要功能)
- 调用符号表管理器/出错处理器,进行相关处理。
工作方式:
- 词法分析器单独进行扫描,生成记号流。再将整个记号流交给语法分析器;
- 词法分析器作为语法分析器的子程序进行工作,语法分析器调用词法分析器去读源程序,得到词法分析器返回的记号就拿来构造语法树。然后用掉了这个记号就再去调用词法分析器读新的记号,如此重复;
- 词法、语法分析器并行工作。两者有一个共享的记号流,前者不停读程序、把记号放入记号流,后者不停取记号流来构造语法树。
模式的形式化描述
字符串与语言
从词法分析角度来看,语言是记号(Token)的集合。
语言 L 是有限字母表 Σ 上有限长度字符串的集合,字母表是字符的集合。
空集Ø是一个语言,仅含一个空符号串的集合{ε}也是一个语言。Ø和{ε}是不同的语言。
字母表中的字符能够组成字符串。
例:字母表 Σ={ a, b, c },则其上的语言 L = { ε, a, b, c, aa, ab, ac, ba, bb, bc, … } (ε为空串,长度为0)
字符串的基本概念
术语 | 示例 |
---|---|
|S| | |abc| = 3 |
ε | |ε| = 0 |
S1S2 | abc def = abcdef |
Sn | (abc)3 = abcabcabc |
S 的前缀 X | abc 的前缀有:ε, a, ab, abc |
S 的后缀 X | abc 的后缀有:ε, c, bc, abc |
S 的子串 X | abc 的子串有:ε, a, b, c, … |
S 的真前缀 | abc 的真前缀有:a, ab(去掉空和全) |
S 的真后缀 | (去掉空和全) |
S 的真子串 | (去掉空和全) |
S 的子序列 X | abdf 是 abcdef 的一个子序列(和原序列顺序相同,可去掉一些字母) |
术语 | 意义 |
---|---|
Φ | 空集合 |
{ ε } | 空串是唯一元素 |
X = L ∪ M | 并: X = { s| s∈L or S ∈M } |
X = L ∩ M | 交:X = { s | s∈L and S ∈M } |
X = LM | 连接: X = { st|s∈L and t ∈ M } |
X = L* | (星)闭包:X= L0∪L1∪L2∪… |
X = L+ | 正闭包:X= L1∪L2∪L3∪… |
若 L = {a, b}, M = {c, d}, 则 LM = {ac, bc, ad, bd}, L∪M = Φ
对任意符号串集合A,有{ε}A = A{ε} = A
L* = { ε, a, b, aa, bb, ab, ba, aaa, … }
L+ = { a, b, aa, bb, ab, ba, aaa, … }
A
0
A^0
A0 = {ε}
A
1
A^1
A1 = A
A
n
A^n
An = A
A
n
−
1
A^{n-1}
An−1(n>0)
eg:若A={0,1},则
A
0
A^0
A0 ={ε},
A
1
A^1
A1={0,1},
A
2
A^2
A2={00,01,10,11}。
正规式与正规集(文法和语言)
正规式是用来描述词法规则的,也就是描述:记号该长成什么样子、数字该长成什么样子之类。
正规式(Regular Expression,也叫正则表达式)是在字母表之上的集合——正规式表示集合。
正规式表示的集合叫做正规集,而正规集是语言,因此正规式表示语言。
比如有个正规式是字母 a,那么正规式 a 表示集合 {a},集合 {a} 就是语言 L(a)。
正规式表示正规集,正规集是与正规式对应的语言。
这两个概念是词法分析的基础。
正规式和正规集的递归定义
Σ 是有限字母表,则其上的正规式及其表示的集合(即正规集)递归定义如下:
- ε 是正规式,其表示集合(正规集) L(ε) = {ε}
- 若 a 是 Σ 上的字符,则 a 是正规式,它表示集合 L(a)={a}
- 若正规式 r 和 s 分别表示集合 L( r ) 和 L(s),则
- r|s 是正规式,表示集合 L( r) ∪ L(s)(“|”在正规式中表示“或”,也可以写作 r + s )
- rs 是正规式,表示集合 L( r)L(s)(直接将两个语言拼接起来,也可以写作 r·s)
- r* (正规式是一个星闭包)也是正规式,表示集合(L( r))*(L( r)这个语言进行星闭包)
- ( r) 是正规式,表示的集合仍然是 L( r)(也就是说正规式 r 外面括上括号得到的 ( r) 仍然是正规式,加括号可以用来改变运算次序)
语言是字母表上字符串的集合,而正规式是语言,因此正规式是字母表上字符串的集合。
可以用正规式描述的语言,就是正规语言或正规集
定义的扩展说明
-
运算有优先级和结合性
- 三种运算都有左结合的性质(左结合的意思是,当多个同优先级符号连写时是从左往右算。如果从右往左算就叫右结合)
- 优先级从高到低:闭包、连接、或
正规式中不必要的括号(去掉了也不影响运算顺序)是可以省略的。
例:正规式: a|b*c 表示的语言有以下两种情况:
- 表示一个串:a
- 表示另一个串:以 0 到多个 b 开头,以 c 结尾
-
正规式的等价
长得不同的正规式可以表示同一个正规集(就像加法中的1+3、2+2都可以表示4一样),即,同一个正规集可以对应多个正规式。
正规式等价
定义:若正规式 P、Q 表示了同一个正规集,则称 P、Q 是等价的,记为 P = Q。
【例】: 设字母表 Σ={a, b, c}, 则 Σ 上有:
正规式 | 正规集 |
---|---|
a, b, c | {a}, {b}, {c} |
a|b, b|a | {a}∪{b} = {a, b} |
a(a|b)* | {a, aa, ab, aba, abb, aab, …},以 a 为首的 ab 串 |
Σ* | {ε, a, b, c, aa, ab, ac, ba, bb, bc, …} |
【例】:令 L(x) = {a, b}, L(y) = {c, d}
则 L(x|y) = {a, b, c, d}
L(y, x) = {a, b, c, d}
判断等价性,可以根据定义,证明两个正规式是否能表达同一个集合;也可以使用正规式的代数性质进行运算比较:
公理 | 公理 |
---|---|
r|s = s|r | ( r s ) t = r( s t ) |
r|( s|t ) = ( r|s )|t | ε r = r , r ε = r |
r ( s|t ) = r s | r t | r* = ( r+ | ε ) |
( s|t ) r = s r | t r | r** = r* |
简言之,就是:
- |可交换、可结合;
- · 对 | 可分配;
- · 可结合;
- 幂等
记号的说明
先复读一遍模式、记号的概念:
- 模式(Pattern): 产生和识别元素的规则,就是定义的词法规则;
- 记号(Token): 按照某个模式(或规则)识别出的元素(一组)。进行词法分析时,将程序转为一个个的记号,就是根据规则得到一个记号流;
正规式可以用于严格地规定记号的模式。用正规式说明记号的公式:
记号=正规式 读作“记号定义为正规式” / “记号是正规式”。不引起混淆的情况下,可以直接把说明记号的公式叫做正规式/规则
e.g. id = a ( a | b )* 读作:“id定义为a(a|b)*”(这里的 id 就是一个标识符了。定义为a开头的ab串)
【例】记号 relation、id、num 分别是 Pascal 的关系运算符、标识符和无符号数,它们的正规式表示如下:
relation = < | <= | <> | > | >= | =
id = (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z)
(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9)*
num = (0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)*
(ε|.(0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)*)
(ε|E(+|-|ε)(0|1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)*)
其中,一些东西可以进行简化描述
简化描述
-
正闭包:r 表示 L( r) 的正规式,那么 r+ 就表示 (L( r))+的正规式。
即:r+ = rr* = r*r, r* = r+|ε
比如:(0|1…|9)(0|1…|9)* 可以化简为 (0|1…|9)+
-
可缺省:r? 表示 L®∪{ε} 的正规式。
即:r? = r|ε
比如: E(+|-|ε) 可改写为:E(+|-)?
上面这些运算中的 +、? 、* 的结合性、优先级相同。
-
字符组。字符组是正规式的一种形式:对于只由 | 构成的正规表达式 r,可以改写为[r’]:
- r’ 可以枚举:正规式 r = a|b|c 等价于 [abc]
- r’ 可以分段:[0123456789abcdefghijklmnopqrstuvwxyz]等价于[0-9a-z]
-
非字符组。这里的“非”指的是非运算。也就是去掉某部分:
若 [r] 是字符组形式的正规式,则 [^r] 表示 Σ -L( r) 的正规式,例如:
若 Σ={a, b, c, d, e, f, g},则 L(^abc)={d, e, f, g}
-
辅助定义。就是给已有的正规式起个名字,以后可以直接用这个名字指代。
(如果 digits 不加花括号,那 digits 就是“ digittttt… ”)
optional_fraction:可选的小数位。小数点如果不加双引号,在这里表示任意一个字符。
optional_exponent:可选指数
上面的是 id,下面的是 num
2.2.2 语言的形式化定义
- 直接推导与推导(直接推导是一步,推导是任意步)
- (句子是树叶,为终结符集,可以推导出来的则都为句型)句子一定是句型,句型不一定是句子
- 语言(语言是句子的集合,并且每个句子都能从开始符号推导得来,是终结符集的闭包的子集)
2.2.3 短语、直接短语、句柄
- 直接短语一定是一个产生式的右部,但是产生式的右部不一定是当前句型的直接短语(例如活水可能是为有源头活水来的直接短语,而不是当前句型的直接短语)
- 高人,民生,活水则都不是当前句型的直接短语
2.2.4规范推导和规范归约(最右推导,最左规约)
- 最左推导:对一个推导序列中的每一步直接推导α⇒β,都是对α中的最左非终结符进行替换。
- 最右推导(规范推导):对一个推导序列中的每一步直接推导α⇒β,都是对α中的最右非终结符进行替换。
- 规范句型:由规范推导得到的句型。
- 最左归约(规范归约):规范推导的逆过程。
2.3 语法分析树与文法的二义性
2.3.1 语法分析树
- 语法分析树:一个句型推导过程的树形表示称为语法分析树,简称语法树。
- 满足条件:设G=(
V
N
{V}^N
VN,
V
T
{V}^T
VT,P,Z)是一个上下文无关文法(CFG)。
1)根节点的标记为Z。
2)根节点外的每个节点也有一个标记,它是 V N {V}^N VN∪ V T {V}^T VT∪{ε}中的符号。
3)每一个内部节点(非终结符,所以标记在 V N {V}^N VN)的标记A必在VN中。
4)若某个内部节点标记为A,其子节点的标记从左到右分别为X1,X2,…,Xn,则A→X1X2…Xn必为P中的一条产生式规则。
5)若节点有标记ε,则该节点为叶子,且是它父亲唯一的孩子(子节点存在标记ε,则是其父节点的唯一子节点,即其无兄弟节点)。 - 构造步骤:已知文法G[Z],对于w,若Z ⇒* w,则
1)以开始符号Z为标记的根节点。
2)对每一步推导,根据使用的产生式规则生成一颗子树,直到所有叶子节点从左到右的标记符号连接为w为止。
若产生式规则为A→X1X2…Xn,则生成以A为根节点的子树,其孩子节点从左到右分别为X1,X2,…,Xn。
eg:设文法G[E]:
E→E+T|E-T|T
T→TF|T/F|F
F→(E)|i
推导句型T+i(F-i)的语法树。
2.3.2 文法的二义性
- 定义:若一个文法存在某个句子对应两棵不同的语法树,则称这个文法是二义的。
- 特点:为编译程序的执行带来不确定性。
2.3.3 二义性的消除
- 不改变文法:通过附加限制性条件(增加消歧规则,例如:每个if跟最近的尚未匹配的else匹配)消除二义性。
寻找充分不必要条件,当文法满足这些条件时可确保文法是无二义性的。(即满足就是二义性,不满足也不一定是无二义性)
2.改变文法:改写原有文法(增加 V N {V}^N VN或者空产生式),把排除二义性的规则合并到原文法消除二义性。
2.4 文法的化简
- 若一个非终结符不能推导出终结字符串,则该非终结符是无用的,删除所有包括该非终结符的产生式规则。
- 若一个符号不能出现在文法的任何句型中,则该符号是无用的,删除所有包括该符号的产生式规则。
2.5 语言的分类
二型识别程序设计语言,三型定义程序设计语言
- 0型文法(短语文法,上下文无关文法),每个产生式的左边α∈(
V
N
{V}^N
VN∪
V
T
{V}^T
VT)*且至少含有一个非终结符号
1)定义:若文法G[Z]=( V N {V}^N VN, V T {V}^T VT,P,Z)中的每个产生式规则的形式为:α→β,其中α∈( V N {V}^N VN∪ V T {V}^T VT) 且至少含有一个非终结符号,而β∈( V N {V}^N VN∪ V T {V}^T VT),则G[Z]为0型文法。
2)特点:0型文法的能力相当于图灵机,识别能力最强。 - 1型文法(上下文敏感文法)
1)定义:若文法G[Z]=( V N {V}^N VN, V T {V}^T VT,P,Z)中的每个产生式规则的形式为:αAβ→αvβ,其中α,β∈( V N {V}^N VN∪ V T {V}^T VT),A∈ V N {V}^N VN,v∈( V N {V}^N VN∪ V T {V}^T VT)+,则G[Z]为1型文法。 - 2型文法(上下文无关文法)(左边只是Vn,0型文法左边则(
V
N
{V}^N
VN∪
V
T
{V}^T
VT)*
1)定义:若文法G[Z]=( V N {V}^N VN, V T {V}^T VT,P,Z)中的每个产生式规则的形式为:A→v,其中A∈ V N {V}^N VN,v∈( V N {V}^N VN∪ V T {V}^T VT)*,则G[Z]为2型文法。
2)特点:语法结构上下文无关,一般用于识别程序设计语言的语法结构。 - 3型语言(正规文法)
1)种类:右线性文法、左线性文法
正则文法 左(右)线性文法
2)右线性文法:若文法G[Z]=( V N {V}^N VN, V T {V}^T VT,P,Z)中的每个产生式规则的形式为:A→αB或A→α,其中A,B∈ V N {V}^N VN,α∈( V N {V}^N VN∪ V T {V}^T VT),则G[Z]为右线性文法。(非终结部分永远在右部)(最右推导)
3)左线性文法:若文法G[Z]=( V N {V}^N VN, V T {V}^T VT,P,Z)中的每个产生式规则的形式为:A→Bα或A→α,其中A,B∈ V N {V}^N VN,α∈( V N {V}^N VN∪ V T {V}^T VT),则G[Z]为左线性文法。(非终结部分永远在左部)(最左推导)
4)特点:作为定义程序设计语言规则的文法
5)正规语言:3型文法定义的语言。