【编译原理笔记03】词法分析:正则表达式、有穷自动机(FA)、DFA与NFA及RE的相互转换、DFA识别单词、语法检测

本次笔记内容:
3-1 正则表达式
3-2 正则定义
3-3 有穷自动机
3-4 有穷自动机的分类
3-5 从正则表达式到有穷自动机
3-6 从NFA到DFA的转换
3-7 识别单词的DFA

词法分析:正则表达式

正则表达式可以更好地描述语言,非正则例子如下:

L = { a } { a , b } ( { ϵ } ( { . , _ } { a , b } { a , b } ) ) 语言L = \{ a \} \{ a,b \}^* (\{ \epsilon \} \cup ( \{ .,\_ \} \{a,b\} \{ a,b \}^* ))

  • 正则表达式(Regular Expression, RE)是一种用来描述正则语言的更紧凑的表示方法。
  • 例如,上式可以用正则表达式表示:
    r = a ( a b ) ( ϵ ( . ) ( a b ) ( a b ) ) r=a(a|b)^* (\epsilon | (.|_)(a|b)(a|b)^*)

句子的第一个符号是字母a;接下来连接一个任意长度的ab串;接下来连接一个空串,此时句子结束;除此之外,还可以连接一个.或者_,接下来再连接一个长度大于等于1的ab串。

  • 正则表达式可以由较小的正则表达式按照特定规则递归地构建。每个正则表达式r定义(表示)一个语言,记为 L ( r ) L(r) 。这个语言也是根据 r r 的子表达式所表示的语言递归定义的。

正则表达式的定义

  • ϵ \epsilon 是一个RE, L ( ϵ ) = { ϵ } L(\epsilon)=\{ \epsilon \}
  • 如果 a Σ a \in \Sigma ,则 a a 是一个RE, L ( a ) = { a } L(a)=\{ a \}
  • 假设 r r s s 都是RE,表示的语言分别是 L ( r ) L(r) L ( s ) L(s) ,则
    • r s r|s 是一个RE, L ( r s ) = L ( r ) L ( s ) L(r|s)=L(r) \cup L(s)
    • r s rs 是一个RE, L ( r s ) = L ( r ) L ( s ) L(rs)=L(r)L(s)
    • r r^* 是一个RE, L ( r ) = ( L ( r ) ) L(r^*)=(L(r))^*
    • ( r ) (r) 是一个RE, L ( ( r ) ) = L ( r ) = L ( r ) L((r))=L(r) = L(r)

运算的优先级为:*、连接、|。

正则表达式简单例子

Σ = { a , b } \Sigma = \{ a,b \} ,则

  • L ( a b ) = L ( a ) L ( b ) = { a } { b } = { a , b } L(a | b)=L(a) \cup L(b)=\{a\} \cup\{b\}=\{a, b\}
  • L ( ( a b ) ( a b ) ) = L ( a b ) L ( a b ) = { a , b } { a , b } = { a a , a b , b a , b b } L((a | b)(a | b))=L(a | b) L(a | b)=\{a, b\}\{a, b\}=\{a a, a b, b a, b b\}
  • L ( a ) = ( L ( a ) ) = { a } = { ε , a , a a , a a a , } L\left(a^{*}\right)=(L(a))^{*}=\{a\}^{*}=\{\varepsilon, a, a a, a a a, \ldots\}
  • L ( ( a b ) ) = ( L ( a b ) ) = { a , b } = { ε , a , b , a a , a b , b a , b b , a a a , } L\left((a | b)^{*}\right)=(L(a | b))^{*}=\{a, b\}^{*}=\{\varepsilon, a, b, a a, a b, b a, b b, a a a, \ldots\}
  • L ( a a b ) = { a , b , a b , a a b , a a a b , } L\left(a | a^{*} b\right)=\{a, b, a b, a a b, a a a b, \ldots\}

例子:C语言无符号整数的RE

  • 十进制整数的RE: ( 1 9 ) ( 0 9 ) 0 (1|\ldots| 9)(0|\ldots| 9)^{*} | 0
  • 八进制整数的RE: 0 ( 0 1 2 3 4 5 6 7 ) ( 0 1 2 3 4 5 6 7 ) 0(0|1| 2|3| 4|5| 6 | 7)(0|1| 2|3| 4|5| 6 | 7)^{*}
  • 十六进制整数的RE: 0 x ( 0 1 9 a f A F ) ( 0 9 a f A F ) 0 x(0|1| \ldots|9| a|\ldots| f|A| \ldots | F)(0|\ldots| 9|a| \ldots|f| A|\ldots| F)^{*}

正则语言

可以用RE定义的语言叫做正则预言(regular language)或正则集合(regular set)。

RE的代数定律

  • r s = s r r|s=s| r ,|是可以交换的;
  • r ( s t ) = ( r s ) t r|(s | t)=(r | s)| t ,|是可以结合的;
  • r ( s t ) = ( r s ) t r(s t)=(r s) t ,连接是可以结合的;
  • r ( s t ) = r s r t r(s | t)=r s | r t ( s t ) r = s r t r (s | t) r=s r | t r ,连接对 | 是可分配的;
  • ϵ r = r ϵ = r \epsilon r=r \epsilon =r ϵ \epsilon 是连接的单元;
  • r = ( r ε ) r^{*}=(r | \varepsilon)^{*} ,闭包中一定包含 ϵ \epsilon
  • r = r r^{**}=r^{*} ,*具有幂等性。

正则文法与正则表达式等价

  • 对任何正则文法G,存在定义同一语言的正则表达式r;
  • 对任何正则表达式r,存在生成同一语言的正则文法G。

正则定义

正则定义(Regular Definition)

正则定义式具有如下形式的定义序列:
d 1 r 1 d_1 \to r_1
d 2 r 2 d_2 \to r_2
\ldots
d n r n d_n \to r_n

其中,

  • 每个 d i d_i 都是一个新符号,它们都不在字母表 Σ \Sigma 中,而且各不相同;
  • 每个 r i r_i 是字母表 Σ { d 1 , d 2 , . . . , d i 1 } \Sigma \cup \{ d_1,d_2,...,d_{i-1} \} 上面的正则表达式。

也就是说,正则定义是:给一些RE命名,并在之后的RE中像使用字母表中的符号一样使用这些名字。

例1:C语言中标识符的正则定义

  • d i g i t 0 1 2 . . . 9 digit \to 0|1|2|...|9
  • l e t t e r _ A B . . . Z a b . . . z _ letter \_ \to A|B|...|Z|a|b|...|z|\_
  • i d l e t t e r _ ( l e t t e r _ d i g i t ) id \to letter\_(letter\_ | digit)^*

digit表示0-9中的一个数字;letter_表示一个字母或者下划线;字母打头的字母数字串,即id。

例2:(整型或浮点型)无符号数的正则定义

  • d i g i t 0 1 2 . . . 9 digit \to 0|1|2|...|9
  • d i g i t d i g i t    d i g i t digit \to digit \; digit^*
  • o p t i o n a l F r a c t i o n . d i g i t s ϵ optionalFraction \to .digits | \epsilon
  • o p t i o n a l E x p o n e n t ( E ( + ϵ ) d i g i t s ) ϵ optionalExponent \to (E(+|-|\epsilon) digits) | \epsilon
  • n u m b e r d i g i t s    o p t i o n a l F r a c t i o n    o p t i o n a l E x p o n e n t number \to digits \; optionalFraction \; optionalExponent

optionalFraction表示.加上数或整体为空串,表示可选的分数部分;optionalExponent表示可选的指数部分;number则可为整数、小数、指数。

词法分析重大理论基础:有穷自动机(FA)

起源与定义

  • 有穷自动机(Finite Automata,FA)由两位神经物理学家MeCuloch和Pitts于1948年首先提出,是对一类处理系统建立的数学模型;
  • 这类系统具有一系列离散的输入输出信息和有穷数目的内部状态(状态:概括了对过去输入信息处理的状况);
  • 系统只需要根据当前所处的状态和当前面临的输入信息就可以决定系统的后继行为。每当系统处理了当前的输入后,系统的内部状态也将发生改变。

FA的典型例子(电梯控制装置)

  • 输入:顾客的乘梯需求(所要到达的层号)
  • 状态:电梯所处的层数 + 运动方向
  • 电梯控制装置并不需要记住先前全部的服务要求,只需要知道电梯当前所处的状态以及还没有满足的所有服务请求

FA模型

FA模型

在这里插入图片描述

如上图为FA模型。

  • 输入带(input tape):用来存放输入符号串;
  • 读头(head):从左向右逐个读取输入符号,不能修改(只读)、不能往返移动;
  • 有穷控制器(finite control):具有有穷个状态数,根据当前的状态和当前输入符号控制转入下一状态。
转换图

在这里插入图片描述

上图为转换图(Transition Graph)。

  • 结点:FA的状态
    • 初始状态(开始状态):只有一个,由start箭头指向;
    • 终止状态(接收状态):可以有多个,用双圈表示;
    • 带标记的有向边:如果对于输入a,存在一个从状态p到状态q的转换,就在p、q之间画一条有向边,并标记上a。
FA定义(接收)的语言

给定输入串x,如果存在一个对应于串x的从初始状态某个终止状态的转换序列,则称 x被该 FA接收

由一个有穷自动机M接收的所有串构成的集合称为是该FA定义(或接收)的语言,记为L(M),其中,M表示的是Machine。

在这里插入图片描述

如上图,这个转换图模型能否接收“abbabb”?

  • 是接收“abbabb”可以的,这个转换图;
  • L ( M ) = a b b a , b L(M)=所有以abb结尾的字母表{a, b}上的串的集合

最长子串匹配原则(Longest String Matching Principle)

  • 当输入串的多个前缀与一个或多个模式匹配时,总是选择最长的前缀进行匹配;
  • 在到达某个终态之后,只要输入带上还有符号,DFA就继续前进,以便寻找尽可能长的匹配。

在这里插入图片描述

如上图,在这个自动机中,1、2、3、4都是一个终态。2表示,匹配的字符串是"<="。如果遇到了"<",到达终态1,此时输入带后还有符号,则继续前进(万一之后是"=“呢,不能把”<="错过了)。

FA分类

  • 确定的FA(Deterministic finite automata, DFA)
  • 非确定的FA(Nondeterministic finit automata, NFA)
确定的有穷自动机(DFA)

DFA被定义为一个五元组。

M = ( S , Σ , δ , s 0 , F ) M=(S,\Sigma , \delta , s_0 , F)

  • S:有穷状态集;
  • Σ:输入字母表,即输入符号集合。假设ε不是Σ中的元素;
  • δ:将S×Σ映射到S的转换函数。 s S , a Σ , δ ( s , a ) \forall s\in S, a\in \Sigma , \delta (s,a) 表示从状态s出发,沿着标记为a的边所能到达的状态(即状态S对应于输入符号的后继状态);
  • s 0 s_0 :开始状态(或初始状态),s0∈S;
  • F:接收状态(或终止状态)集合,F⊆S。
例:一个DFA

在这里插入图片描述

如上图,可以用转换表表示DFA。转换表和转换图功能是等价的。

非确定的有穷自动机(NFA)

M = ( S , Σ , δ , s 0 , F ) M=(S,\Sigma , \delta , s_0 , F)

  • S:有穷状态集;
  • Σ:输入字母表,即输入符号集合。假设ε不是Σ中的元素;
  • δ:将S×Σ映射到 2 S 2^S 的转换函数。 s S , a Σ , δ ( s , a ) \forall s\in S, a\in \Sigma , \delta (s,a) 表示从状态s出发,沿着标记为a的边所能到达的状态的集合
  • s 0 s_0 :开始状态(或初始状态), s 0 s_0 ∈S;
  • F:接收状态(或终止状态)集合,F⊆S。
例:一个NFA

在这里插入图片描述

如上图,如果转换函数没有给出对应于某个状态-输入对的信息,就把Ø放入相应的表项中。

DFA和NFA的等价性
  • 对任何非确定的有穷自动机N ,存在定义同一语言的确定的有穷自动机D;
  • 对任何确定的有穷自动机D ,存在定义同一语言的非确定的有穷自动机N。

在这里插入图片描述

如上图,DFA和NFA可以识别相同的语言。上图中,两个FA都可以用这个正则表达式表示:

r = ( a b ) a b b r=(a|b)^* abb

正则文法⇔正则表达式⇔FA

带有" ϵ \epsilon - 边"的NFA

M = ( S , Σ , δ , s 0 , F ) M=(S,\Sigma , \delta , s_0 , F)

  • S:有穷状态集;
  • Σ:输入字母表,即输入符号集合。假设ε不是Σ中的元素;
  • δ:将S×( Σ { ϵ } \Sigma \cup \{ \epsilon \} )映射到 2 S 2^S 的转换函数。 s S , a ( Σ { ϵ } ) , δ ( s , a ) \forall s\in S, a\in (\Sigma \cup \{ \epsilon \}) , \delta (s,a) 表示从状态s出发,沿着标记为a的边所能到达的状态的集合
  • s 0 s_0 :开始状态(或初始状态), s 0 s_0 ∈S;
  • F:接收状态(或终止状态)集合,F⊆S。

在这里插入图片描述

如上图,空串用于连接。

带有和不带有" ϵ \epsilon - 边"的NFA的等价性

在这里插入图片描述

如上图,注意终结结点的安排与NFA转移状态的规律(A有(0;0,1;0,1,2);B有(1;1,2);C有(2))。

DFA的算法实现
  • 输入:以文件结束符eof结尾的字符串x。DFAD的开始状态 s 0 s_0 ,接收状态集F,转换函数move;
  • 输出:如果D接收x,则回答“yes”,否则回答“no”;
  • 方法:将下述算法应用于输入串x。
s = s_0;
c = nextChar();
while(c! = eof) {
	s = move(s,c);
	c = nextChar();
}
if (s在F中) return "yes";
else return "no";

其中,函数nextChar()返回输入串x的下一个符号;函数move(s, c)表示从状态s出发,沿着标记为c的边所能到达的状态。

从正则表达式到有穷自动机

在这里插入图片描述

如上图,从RE构造DFA比较困难,因此增加NFA进行过渡。

根据RE构造NFA

  • ϵ \epsilon 对应的NFA如下图

在这里插入图片描述

  • 字母表 Σ \Sigma 中符号a对应的NFA如下图

  • r = r 1 r 2 r=r_1 r_2 对应的NFA如下图

在这里插入图片描述

  • r = r 1 r 2 r=r_1 | r_2 对应的NFA如下图

在这里插入图片描述

  • r = ( r 1 ) r=(r_1)^* 对应的NFA如下图

在这里插入图片描述

例: r = ( a b ) a b b r=(a|b)^* abb 对应的NFA

在这里插入图片描述

RE转换为NFA的过程如上图。首先设置初始状态与终止状态,再边上标出正则表达式。之后对表达式进行分解。

从NFA到DFA的转换

例子1:DFA一个状态代表NFA多个状态

在这里插入图片描述

如上图,DFA的每个状态都是一个由NFA中的状态构成的集合,即NFA状态集合的一个子集。

例子2:从带有ε-边的NFA到DFA的转换

在这里插入图片描述

如上图,转换表很好地辅助转换。

子集构造法
  • 输入:NFA N
  • 输出:接受同样语言的DFA D
  • 方法如下。
一开始,ε-closure (s_0) 是Dstates中的唯一状态,且它未加标记;
while(Dstates中有一个未标记状态T){
	给T加上标记;
	for (每个输入符号a){
		U = ε-closure(move(T,a));
		if(U不在Dstates中)
			将U加入到Dstates中,且不加标记;
		Dtran[T,a] = U;
	}
}

上述代码中,操作说明如下表:

操作 描述
ε-closure(s) 能够从NFA的状态s开始只通过ε转换到达的NFA状态集合
ε-closure(T) 能够从T 中的某个NFA状态s开始只通过ε转换到达的NFA状态集合,即 U s T ϵ c l o s u r e ( s ) U_{s \in T} \epsilon - closure(s)
move(T,a) 能够从T中的某个状态s出发通过标号为a的转换到达的NFA状态的集合

计算ε-closure (T)的操作如下:

将T的所有状态压入stack中;
将ε-closure (T)初始化为T;
while(stack非空) {
	将栈顶元素t 给弹出栈中;
	for(每个满足如下条件的u: 从t出发有一个标号为ε的转换到达状态u)
		if( u不在ε-closure (T)) {
			将u加入到ε-closure (T)中;
			将u压入栈中;
		}
}

识别单词的DFA

识别标识符的DFA

标识符的正则定义为:

  • dig i t 0 1 2 9 \operatorname{dig} i t \rightarrow \mathbf{0}|\mathbf{1}| \mathbf{2}|\ldots| \mathbf{9}
  • letter A B Z a b z _{-} \rightarrow A|B| \ldots|Z| a|b| \ldots|z|
  • i d letter (letter  digit ) \left.i d \rightarrow \text {letter}_{-} \text {(letter }_{-} | \text {digit}\right)^{*}

其DFA如下图。

在这里插入图片描述

识别无符号数的DFA

number的正则表达式为:

  • digit 0 1 2 9 \operatorname{digit} \rightarrow \mathbf{0}|\mathbf{1}| \mathbf{2}|\ldots| \mathbf{9}
  • digits \rightarrow digit digit ^{*}
  • optionalFraction . \rightarrow . digits ε | \varepsilon
  • optionalExponent ( E ( + ε )  digits  ) ε \rightarrow(E(+|-| \varepsilon) \text { digits }) | \varepsilon
  • number \rightarrow digits optionalFraction optionalExponent

其NFA如下图。

在这里插入图片描述

得到其DFA如下图。

在这里插入图片描述

如何手算转换?按照算法,在DFA中,一个结点(状态)一个结点(状态)的生成,一个一个遍历,查看其可伸出的边。

识别各进制无符号整数的DFA

在这里插入图片描述

如上图。左边分别为识别十进制、八进制、十六进制的DFA,右边为集合了三者的DFA。

识别注释的DFA

在这里插入图片描述

如上图。

识别Token的DFA

在这里插入图片描述

如上图,此为识别各类标识符的DFA。如果是一个关键字,则根据关键字映射表返回其相应值。

词法分析阶段的错误处理

词法分析阶段可检测错误的类型:

  • 单词拼写错误,例如:int i=0X3G; 或float j=1.05e;
  • 非法字符,例如:~@

词法错误检测机制:

如果当前状态与当前输入符号在转换表对应项中的信息为空,而当前状态又不是终止状态,则调用错误处理程序。

词法分析的错误处理:

查找已扫描字符串中最后一个对应于某终态的字符:

  • 如果找到了,将该字符与其前面的字符识别成一个单词。然后将输入指针退回到该字符,扫描器重新回到初始状态,继续识别下一个单词;
  • 如果没找到,则确定出错,采用错误恢复策略。

错误恢复策略:

  • 最简单的错误恢复策略有“恐慌模式(panic mode)”恢复;
  • 从剩余的输入中不断删除字符,直到词法分析器能够在剩余输入的开头发现一个正确的字符为止。
发布了132 篇原创文章 · 获赞 36 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_42815609/article/details/103078632