基本术语: 记号、模式、词素
术语 | 含义 |
---|---|
记号 | 词素的规则名称,譬如所有变量其实都可以用记号:标识符 代替,标识符就是所有变量的记号 |
模式 | 表示该名词所属的规则的描述信息,譬如标识符的命名规则 |
词素 | 记号对应符号的特定实例,譬如记号标识符的某个词素就是一个变量名 |
下面是一个实际例子:
记号 | 词素 | 模式(非形式描述) |
---|---|---|
const | const | const |
relation | <, <=, ==, >, >= | < 或 <= 或 == 或 > 或 >= |
id | pi, a, count, d2 | 字符开头的字母数字串 |
在多数程序设计语言中, 下面这些结构被当作记号: 关键字、操作符、标识符、常量、字符串、标点符号
1. 记号属性:
当一个记号的模式描述可以匹配多个词素时,需要词法分析器为这个记号提供附加的关于匹配的特殊词素的信息。
一个记号和其属性值可以用一个二元组来表示:
<id, 指向符号表中变量A相关表项>
<assign_op, >
<num, 整数2>
<exp_op, >
2. 输入缓冲:双缓冲输入方案
1. 词法分析器实现的三种方案:
- 使用词法分析器生成器,从基于正则表达式的说明,自动产生一个词法分析器。(此时由生成器提供输入流的读取和缓冲)
- 使用传统的程序设计语言编写词法分析器,并使用该语言提供的I/O功能对输入进行读取
- 使用汇编语言编写词法分析器,并显式地控制输入流的读取
2. 双缓冲区:(龙书: P61)
在一个词素被一个模式匹配之前,通常需要向前扫描该词素后面的若干字符
在缓冲区中,存在两个指针,一个指针指向匹配的开始词素,一个指针指向匹配结束位置的词素。两个指针的移动可以抽象地表示读取和回退,效率更高。在后续地学习中,需要关注匹配结束位置对应地指针。
输入的源程序读取结束时,还会读入一个EOF
符号,表示整个源程序读取完毕。
3. *记号描述: (龙书: P62)
术语 | 描述 |
---|---|
字母表(字符类) | 表示有限符号集合 |
串(字符串, 句子, 字) | 字母表上的串时该字母表符号的有穷序列, 串s的长度是s中符号的个数,记为|s|, 空串记为 ϵ \epsilon ϵ |
语言 | 给定字母表上的串的集合 |
串的相关术语 | 描述 |
---|---|
s的前缀 | 去掉s尾部0个或者多个符号后得到的串,如:ban是banana的前缀 |
s的后缀 | 去掉s头部的0个或者多个符号后得到的符号串,如:nana是banana的后缀 |
s的子串 | 去掉s个一个前缀和一个后缀后得到的串 |
s的真前缀、真后缀、真子串 | 如果非空串x是s的前缀、后缀、子串,则称x为s的真前缀、真后缀、真子串 |
s的子序列 | 从串s中删除0个或多个符号后得到的串 |
语言的运算 | 描述 |
---|---|
L和M的并:记为L ∪ \cup ∪ M | L ∪ \cup ∪ M={s|s属于L或s属于M} |
L和M的连接:记为LM | LM={st|s属于L且t属于M} |
L的克林(Kleene)闭包:记作L* | L*= ∪ i = 0 ∞ L i \cup_{i=0}^{\infin} L^i ∪i=0∞Li , 表示0或多个 L的连接 |
L的正闭包:记作 L + L^+ L+ | L*= ∪ i = 1 ∞ L i \cup_{i=1}^{\infin} L^i ∪i=1∞Li , 表示1或多个 L的连接 |
1. 正规表达式与正规定义
正规表达式 和 我们常用的正则表达式并无二致,且此处只有基本的正则表达式语法, | + *
1. 正规定义:
d 1 → r 1 d 2 → r 2 d 3 → r 3 … d n → r n d_1 \rightarrow r_1 \\ d_2 \rightarrow r_2 \\ d_3 \rightarrow r_3 \\ \dots \\ d_n \rightarrow r_n d1→r1d2→r2d3→r3…dn→rn
上面是正规定义的一般形式, r i r_i ri 中的字符为 d j d_j dj或者符号表中的字符。
如果 r i r_i ri用到了 d j d_j dj, 且 j ≥ i j\ge i j≥i, 则 r i r_i ri 是递归定义的。
为了区分名字和符号,通常用黑体表示正规定义中的名字。
例:
letter → \rightarrow → A | B | ⋯ \cdots ⋯ | Z | a | b | ⋯ \cdots ⋯ | z
digit → \rightarrow → 0 | 1 | ⋯ \cdots ⋯ | 9
id → \rightarrow → letter (letter | digit) *
2. 缩写表示法:
- 一个或多个实例: ( r ) + (r)^+ (r)+
- 零个或一个实例: ( r ) ? (r)? (r)?
- 字符类: [ a b c ] [abc] [abc] 表示 a | b | c
3. 非正规集:
有些语言不可以用正规表达式描述,譬如带有括号匹配的编程语言, 但是在后面我们可以使用
上下文无关文法
来描述
2. *状态转换图 (龙书 P68)
transition diagram
术语 | 描述 |
---|---|
状态 | 用圆圈表示 |
边 | 状态间由箭头连接, 称之为边. 边上标识的字符(也称为标记字符)表示使状态s转换到状态r的输入字符 |
从一个状态出发, 如果每条边上的标记字符都不相同, 则称这个状态转换图是
确定的
如果输入一个字符, 可以让状态s转移到状态t, 也可以转移到状态r, 则称这个状态转换图是
不确定的
符号 ϵ \epsilon ϵ 表示空串(即不读入字符)
4. *有穷自动机: 词法分析章节的核心内容
确定的有穷自动机和不确定的又穷自动机, 都只能识别正规集: 即它们只能识别
正规表达式
所表示的语言
1. 不确定有穷自动机(NFA): (龙书 P77)
不确定的有穷自动机
是由以下几部分组成的数学模型:
- 一个状态的又穷集合 S S S
- 一个输入符号集合 Σ \Sigma Σ , 即输入符号表
- 一个转换函数 m o v e move move, 把由状态和符号组成的二元组映射到状态集合
- 状态 s 0 s_0 s0 是唯一的开始或初始状态
- 状态集合 F F F 是接受(或终止)状态集合
2. 确定有穷自动机(DFA)
确定的又穷自动机(简称DFA)是不确定的有穷自动机的特例, 满足以下条件:
- 没有一个状态具有 ϵ \epsilon ϵ 转换
- 对于每个状态 s s s 和输入符号 a a a , 最多只有一条标记为 a a a 的边离开 s s s
3. 从NFA到DFA的转换: 子集构造法
子集构造法
: 从NFA 构造 DFA从NFA上构造所有 ϵ \epsilon ϵ- c l o s u r e ( s ) closure(s) closure(s) , 每个闭包集合将成为输出的DFA中的一个状态
NFA上构造的闭包集合之间的转化将成为DFA中的边
算法元素 | 描述 |
---|---|
输入 | 一个NFA N |
输出 | 一个接受同样语言的DFA D |
方法 | 为D构造转换表(Tran ), DFA的每个状态是NFA的状态集,D将并行地模拟N对输入串的所有可能的移动 |
操作 | 描述 |
---|---|
ϵ \epsilon ϵ- c l o s u r e ( s ) closure(s) closure(s) | 从NFA中的状态s出发, 只经过 ϵ \epsilon ϵ 转换可以到达的NFA状态集合 |
ϵ \epsilon ϵ- c l o s u r e ( T ) closure(T) closure(T) | 从T中的状态只经过 ϵ \epsilon ϵ 转换可以到达的NFA状态集合 |
m o v e ( T , a ) move(T, a) move(T,a) | 从T中的状态s经过输入符号a上的转换可以到达的NFA状态集 |
4. 从正规表达式到NFA
算法元素 | 描述 |
---|---|
输入 | 字母表 Σ \Sigma Σ上的一个表达式r |
输出 | 接受 L ( r ) L(r) L(r)的NFA N N N |
- 对于 ϵ \epsilon ϵ , 构造出NFA:
- 对于 Σ \Sigma Σ中每个符号a, 构造NFA:
- 对于正规表达式 s ∣ t s|t s∣t ,可构造复合的NFA N ( s ∣ t ) N(s|t) N(s∣t)如下:
- 对于正规表达式 s t st st, 可构造复合的NFA N ( s t ) N(st) N(st)如下:
- 对于正规表达式 s ∗ s^* s∗, 可构造复合的NFA N ( s ∗ ) N(s^*) N(s∗)如下:
5. 从正规表达式到DFA
- 构造扩展的正规表达式 ( r ) # (r)\# (r)# 的语法树 T T T, 其中 # \# # 是附加在 ( r ) (r) (r) 后面的唯一结束标志
- 通过对 T T T进行深度优先遍历计算函数 n u l l a b l e nullable nullable、 f i r s t p o s firstpos firstpos、 l a s t p o s lastpos lastpos、 f o l l o w p o s followpos followpos的值
- 根据下面的伪代码构造 D s t a t e s Dstates Dstates (DFA D的状态集)、生成D的状态转换表Dtrans。D的开始状态是 f i r s t p o s ( r o o t ) firstpos(root) firstpos(root),接受状态是包含与结束符#相关位置的状态
初始时, Dstates中唯一未标记的节点是firstpos(root). root是(r)#语法树的根节点;
while Dstates 中存在一个未标记的状态T do begin
标记T;
for 每个输入符号a do begin
令U是followpos(p)中的位置的集合,p是T中的某个位置,位置p的符号为a
if U非空而且不在Dstates中 then
将U作为一个未标记的状态加入到Dstates中
Dtran[T, a] := U
end
end
节点n | nullable(n) | firstpos(n) |
---|---|---|
n是一个标记为 ϵ \epsilon ϵ 的叶节点 | true | ϕ \phi ϕ |
n是一个标记为位置i的节点 | false | {i} |
![]() | nullable( c 1 c_1 c1) or nullable( c 2 c_2 c2) | firstpos( c 1 c_1 c1) ∪ \cup ∪ firstpos( c 2 c_2 c2) |
![]() | nullable( c 1 c_1 c1) and nullable( c 2 c_2 c2) | if nullable( c 1 c_1 c1) then firstpos( c 1 c_1 c1) ∪ \cup ∪ firstpos( c 2 c_2 c2) else firstpos( c 1 c_1 c1) |
![]() | true | firstpos( c 1 c_1 c1) |
followpos函数描述了紧跟在 i i i后面的字符集合,对于
followpos
函数, 计算规则如下:
- 如果 n 是cat ( $\cdot ) 节 点 , 它 具 有 左 子 节 点 ) 节点, 它具有左子节点 )节点,它具有左子节点c_1$ 和右子节点 c 2 c_2 c2 , 并且i是lastpos( c 1 c_1 c1)中的一个位置, 则firstpos( c 2 c_2 c2)中所有位置都在followpos(i)中
- 如果n是star( ∗ * ∗ )节点, 并且i是lastpos(n)中的一个位置, 则所有firstpos(n) 中的位置都在followpos(i) 中
当每个节点的firstpos和lastpos都计算完毕时, 通过对语法树进行深度优先遍历就可以计算出每一个位置的followpos
6. 最小化DFA的状态数
算法元素 | 描述 |
---|---|
输入 | DFA M M M(其状态集合为 S S S),输入符号集为 Σ \Sigma Σ, 转换函数为 f f f: S × Σ → S S \times \Sigma \rightarrow S S×Σ→S , 开始状态为 s 0 s_0 s0, 接受状态集为 F 0 F_0 F0 |
输出 | 一个DFA M ′ M' M′ ,它接收和 M M M相同的语言, 且状态数最少 |
简化方法是:
- 构造具有两个组的状态集合初始划分 Π \Pi Π: 接受状态组 F F F, 非接受状态组 S − F S-F S−F
- 对 Π \Pi Π 执行下述伪代码的流程, 构造新的划分 Π n e w \Pi_{new} Πnew
- 如果 Π n e w \Pi_{new} Πnew= Π \Pi Π, 令 Π f i n a l \Pi_{final} Πfinal = Π \Pi Π, 执行步骤4. 否则, 令 Π \Pi Π:= Π n e w \Pi_{new} Πnew, 重复步骤2
- 在划分 Π f i n a l \Pi_{final} Πfinal的每个状态组中选一个状态作为该组的代表,这些代表构成了简化后的DFA M ′ M' M′的状态。令 s s s是一个代表状态,而且假设:在DFA M M M中,所有输入a上有从s到t的转换。令t所在的组的代表是r(r可以是t),在 M ′ M' M′中有一个从 s s s到 r r r的 a a a上的转换。令包含 s 0 s_0 s0的状态组的代表是 M ′ M' M′的开始状态,并令 M ′ M' M′的接受状态是那些属于 F F F集的状态所在组的代表。 Π f i n a l \Pi_{final} Πfinal的每个组中或者仅包含F中的状态或者不含F中的状态
- 如果 M ′ M' M′中有
死状态
(即一个对所有输入符号都有到自身的转换的非接受状态d),则从 M ′ M' M′中删掉它;删除从开始状态不可到达的状态;取消从任何其他状态到死状态的转换定义。
for Π \Pi Π中的每个组G do begin
当且仅当对任意输入符号a,状态s和t在a上的转换到达\Pi的同一组状态时, 才把G划分称小组,以便G的两个状态s和t在同一个小组中 /* 最坏情况下,一个状态就可能成为一个组 */ 用所有新形成的小组集代替\Pi_{new}中的G;
end