第三章 词法分析
图片的制作可能因为Markdown自身原因,箭头有一些都不见了
3.1 词法分析程序的设计
主要任务:
从左向右逐行扫描源程序字符,识别出各个单词,确定单词的类型,将识别出的单词转化成统一的机内表示–词法单元形式
token <种别码,属性值>
单词类型 | 种类 | 识别码 |
---|---|---|
关键字 | program,if,else,then… | 一词一码 |
识别符 | 变量名,数组名,记录名,过程名 | 多词一码 |
常量 | 整形,浮点型,字符型,布尔型 | 一型一码 |
运算符 | 算数(+,-,*,/)关系(> < == != >= <= ) 逻辑(& | ~) | 一词一码或一型一码 |
界限符 | ;() = { } | 一词一码 |
步骤:
- 组织源程序的输入
- 识别单词,转换成机内表示形式
- 删除注释行,空格及无用符号
- 查填符号表
- 检查词法错误
词类和属性:
程序语言单词的分类
1.关键字(保留字或基本字):while,if
2.标识符:用来表示各种名字
3.字面常数:256,3.14,true,‘abc’
4.运算符:+,-,*,/
5.分界符:逗号,分号,冒号
词法分析器的输出:
(词类编码,单词自身的属性值)
词类编码原则:
界符和运算符:一符一码
关键字可分为一类,也可以一个关键字分成一类:一字一码
常数可统归一类,也可以按类型(整型、实型、布尔型等),每个类型的常数划分成一类:一类型一码
所有的标识符分为一类:一类一码
对于关键字、界符、运算符来说,它们的词类编码就可以表示其完整的信息,故对于这类单词,其单词自身的属性值通常为空。
而对于标识符,词类编码所反映的信息不够充分,标识符的具体特性还要通过单词自身的属性进行互相区分。标识符的单词自身的属性常用其在符号表中的入口指针来表示。
对于常数, 其单词自身的属性常用其在常数表中的入口指针来表示
词法分析的设计形式:
1.设计成一个独立程序,完成词法分析的任务,结果以文件的形式组织,做为语法分析的输入
2.作为语法分析和语义分析的子程序
3.2 词法分析器的手工构造
为了构造词法分析器,要研究构词法,每种词类的结构模式以及识别它的数学模型——有穷自动机。
3.2.1 手工构造识别单词的DFA m
根椐DFA识别单词的定义,在研究给定程序语言单词结构的基础上,能直接构造出识别它的DFA m。
3.2.2 编写词法分析程序
根据画出的状态转换图(识别单词的)构造词法分析程序,每个状态对应一段程序,完成到达此状态的工作;
词法分析程序的控制程序模拟状态转换图的状态转换。
在识别标识符的过程中,首先要拼写出来,并和保留字区别开来;识别出的标识符要填入符号表中
在识别常数的过程中,要把它转换成机器表示以作为属性值,记录到常数表中。
3.3 词法分析程序的自动构造工具LEX简介
原理:
单词的结构用正规式描述
正规式
⟶
\longrightarrow
⟶NFA
⟶
\longrightarrow
⟶DFA
⟶
\longrightarrow
⟶min DFA
用LEX建立词法分析程序的过程:
lex源程序
- 声明
- 翻译规则
- 辅助过程
声明包括变量,符号常量和正规定义式。
翻译规则的形式为:
p1 { 动作1}
p2 { 动作2}
每个pi是正规定义式的名子,每个{动作i}是正规定义式pi识别某类单词时,词法分析器应执行动作的程序段。
**辅助过程**是动作需要的,这些过程用C书写,可以分别编译.例:LOOKUP()
3.4 正则表达式
**正则表达式:**是一种用来描述正则语言的更精凑的表示方法
例:r=a(a|b)*( ϵ \epsilon ϵ|(.| )(a|b)(a|b)*)
正则表达式可以由较小的正则表达式按照特定的递归地构建,每个正则表达式r定义(表示)一个语言,记为L(r)这个语言也是根据r的子表达式所表示的语言递归定义的
正则表达式的定义
ϵ \epsilon ϵ是一个RE,L( ϵ \epsilon ϵ)={ ϵ \epsilon ϵ}
如果a ∈ ∑ \in\sum ∈∑,则a是一个RE,L(a)={a}
假设r和s都是RE,表示的语言分别是L(r)和L(s),则
- r|s是一个RE,L(r|s)=L®UL(s)
- rs是一个RE,L(rs)=L®L(s)
- r ∗ r^* r∗是一个RE,L( r ∗ r^* r∗)= ( L ( r ) ) ∗ ) (L(r))^*) (L(r))∗)
- ®是一个RE,L(®)=L®
运算的优先级:*,连接,|
可以用RE定义的语言叫做正则语言或正则集合
正则文法与正则表达式等价
对任意正则文法G,存在定义同一个语言的正则表达式r
对任意正则表达式r,存在生成同一语言的正则文法G
3.5 正则定义
正则定义是具有如下形式的定义序列:
d
1
⟶
r
1
d_1\longrightarrow r_1
d1⟶r1
d 2 ⟶ r 2 d_2\longrightarrow r_2 d2⟶r2
d n ⟶ r n d_n\longrightarrow r_n dn⟶rn
其中:
每个 d i d_i di都是一个新符号,他们都不在字母表 ∑ \sum ∑中,而且各不相同
每个 r i r_i ri是字母表 ∑ \sum ∑U{ d 1 d 2 , . . . d i − 1 d_1d_2,...d_{i-1} d1d2,...di−1}上的正则表达式
例:
C语言中标识符的正则定义
d i g i t ⟶ 0 ∣ 1 ∣ 2 ∣ . . . ∣ 9 digit\longrightarrow0|1|2|...|9 digit⟶0∣1∣2∣...∣9
l e t t e r _ ⟶ A ∣ B ∣ C ∣ . . . ∣ Z ∣ a ∣ b ∣ z letter\_ \longrightarrow A|B|C|...|Z|a|b|z letter_⟶A∣B∣C∣...∣Z∣a∣b∣z
i d ⟶ l e t t e r _ ( l e t t e r _ ∣ d i g i t ) ∗ id\longrightarrow letter\_(letter\_|digit)^* id⟶letter_(letter_∣digit)∗
3.6 有穷自动机
有穷自动机是对一类处理系统建立的数学模型
这类系统具有一系列离散的输入输出信息和有穷数目的内部状态
系统只需要根据当前所处状态和当期面临的输入信息就可以决定系统的后继行为。每当系统处理了当前的输入后,系统的内部状态也将发生改变
FA模型
图
输入带:用来存放输入符号串
读头:从左向右逐个读取输入符号,不能修改(只读,不能往返运动)
有穷控制器:具有有穷个状态数,根据当前的状态和当前输入符号控制转入下一状态
FA的表示
转换图
结点:FA的状态
初始状态(开始状态):只有一个,由start箭头指向
终止状态(接收状态):可以有多个,用双圈表示
带标记的有向边:如果对于输入a,存在一个状态p到状态q的转换,就在p,q之间画一条有向边,并标记上a
FA定义(接收)的语句
给定输入串x,如果存在一个对应于串x的从初始状态到某个中止状态的转换序列,则称串x被该FA接收
由一个有穷自动机M接收的所有串构成的集合称为是该FA定义(或接收)的语言,记为L(M)
最长子串匹配原则
当输入串的多个前缀与一个或多个模式匹配时,总是选择最长的前缀进行匹配
到达某个终态之后,只要输入带上还有符号,FA就继续前进,以便寻找尽可能长的匹配
3.7 有穷自动机的分类
确定的FA(Deterministic finite automata,DFA)
非确定的FA(Nondeterministic finite autommata,NFA)
3.7.1 确定的有穷自动机(DFA)
M = ( S , ∑ , δ , s 0 , F ) M=(S,\sum,\delta,s_0,F) M=(S,∑,δ,s0,F)
S:有穷状态集
∑ \sum ∑:输入字母表,即输入符号集合。假设 ϵ \epsilon ϵ不是 ∑ \sum ∑中的元素
δ \delta δ:将S × ∑ \times\sum ×∑映射到S的转换函数。 ∀ s ∈ S , a i n ∑ , ϵ ( s , a ) \forall s\in S,a in \sum,\epsilon(s,a) ∀s∈S,ain∑,ϵ(s,a)表示从状态s出发,沿着标记为a的边所能达到的状态
s 0 s_0 s0:开始状态(或初始状态), s 0 ∈ S s_0 \in S s0∈S
F:接收状态(或终止状态)集合, F ⊂ S F \subset S F⊂S
一个DFA,可以用转换图来表示,也可以用转换表表示
3.7.2 非确定的有穷自动机(NFA)
M = ( S , ∑ , δ , s 0 , F ) M=(S,\sum,\delta,s_0,F) M=(S,∑,δ,s0,F)
S:有穷状态集
∑ \sum ∑:输入字母表,即输入符号集合。假设 ϵ \epsilon ϵ不是 ∑ \sum ∑中的元素
δ \delta δ:将S × ∑ \times\sum ×∑映射到 2 S 2^S 2S的转换函数。 ∀ s ∈ S , a i n ∑ , ϵ ( s , a ) \forall s\in S,a in \sum,\epsilon(s,a) ∀s∈S,ain∑,ϵ(s,a)表示从状态s出发,沿着标记为a的边所能达到的状态集合
s 0 s_0 s0:开始状态(或初始状态), s 0 ∈ S s_0 \in S s0∈S
F:接收状态(或终止状态)集合, F ⊂ S F \subset S F⊂S
一个NFA,可以用转换图来表示,也可以用转换表表示
如果转换函数没有给出对应于某个状态-输入对的信息,就把 ϕ \phi ϕ放入对应表项中
3.7.3 DFA和NFA的等价性
对任何NFA N,存在识别同一语言的DFA D
对任何DFA D,存在识别同一语言的NFA N
3.7.4 带有‘’ ϵ \epsilon ϵ-边 ‘’的NFA
M = ( S , ∑ , δ , s 0 , F ) M=(S,\sum,\delta,s_0,F) M=(S,∑,δ,s0,F)
S:有穷状态集
∑ \sum ∑:输入字母表,即输入符号集合。假设 ϵ \epsilon ϵ不是 ∑ \sum ∑中的元素
δ \delta δ:将S$\times(\sum U U U{\epsilon})$映射到 2 S 2^S 2S的转换函数。 ∀ s ∈ S , a i n ∑ , ϵ ( s , a ) \forall s\in S,a in \sum,\epsilon(s,a) ∀s∈S,ain∑,ϵ(s,a)表示从状态s出发,沿着标记为a的边所能达到的状态集合
s 0 s_0 s0:开始状态(或初始状态), s 0 ∈ S s_0 \in S s0∈S
F:接收状态(或终止状态)集合, F ⊂ S F \subset S F⊂S
3.7.5 DFA算法实现
**输入:**以文件结束符eof结尾的字符串x,DFA D的开始状态 s 0 s_0 s0,接收状态集F,转换函数move
**输出:**如果D接收x,则回答“yes”,否则回答“no“
**方法:**将下述算法应用于输入串x
s=s0;
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的边能达到的状态
3.7.6 从正则表达式到有穷自动机
根据RE构造NFA
构造NFA到DFA的转换:子集构造法