第三章.词法分析
3.1 词法分析简介
- 编译器可以拆分成为两个“盒子”,一个前端,一个后端。
而前端又可以拆分成为三个盒子,分别是词法分析器,语法分析器,语义分析器。
- 词法分析器的任务:字符流到记号流
(1)字符流:和被编译的语言密切相关(ASCII, Unicode, or …)。
(2)记号流:编译器内部定义的数据结构,编码所识别出的词法单元。
- 例子:如图所示,把输入的C语言字符流转化成为特定的记号流。
记号的数据结构定义(也可以采用其他的数据结构):
3.2 词法分析器的手工构造
- 词法分析器的实现方法:
(1)手工编码实现法 :相对复杂、且容易出错。但是是目前非常流行的实现方法,如GCC, LLVM, …
(2)词法分析器的生成器:可快速原型、代码量较少,但较难控制细节 - 转移图
最开始输入时,我们先判断输入的是>,<还是=,如果是<则继续判断下一个输入是=,>还是其他符号,根据判断返回最终结果。
C语言代码实现:rollback()表示回滚到上一个状态
扩展内容:
#include<stdio> //头文件
fseek(FILE *stream,long offset,int fromwhere)
/*
功能:重新定位流上的文件指针。
具体:函数设置文件指针stream的位置,如果执行成功stream会指向以fromwhere为基准,
偏移offset个字节的位置(offset可负),如果失败则不改变stram指向的位置。
返回值:成功返回0,失败返回其他值
获取文件位置:
(1)SEEK_SET:文件开头
(2)SEEK_CUR:当前位置
(3)SEEK_END:文件结尾
*/
//例子:
FILE *fp;
fp=fopen(filename,"rb+");
fseek(fp,100L,SEEK_SET); //把fp指针移到离文件开头100字节处
fseek(fp,-1L*sizeof(S),SEEK_END); //offset为负数
- 标识符的转移图:
- 关键字
(1)从词法分析的角度看,关键字是标识符的一部分。
(2)以C语言为例:
标识符 | 关键字 |
---|---|
以字母或下划线开头,后跟零个或多个字母,下划线,或数字 | if,while,else |
(3)识别方法:切分转移图或关键字表算法
(4)切分转移图(以if为例子):
(5)关键字表算法
对给定语言中所有的关键字,构造关键字构成的哈希表H
对所有的标识符和关键字,先统一按标识符的转移图进行识别
识别完成后,进一步查表H看是否是关键字
通过合理的构造哈希表H(完美哈希),可以O(1)时间完成
3.3 正则表达式
- 手工 VS. 自动生成
- 正则表达式
(1)定义:
(2)例子:
- 语法糖:
3.4 有限状态自动机
- 有限状态自动机(FA)
- 自动机例子:
(1)确定状态有限自动机DFA:对任意的字符,最多有一个状态可以转移。
(2)非确定的有限状态自动机NFA:对任意的字符,有多于一个状态可以转移。
3.5 正则表达式转NFA
- DFA因为状态转移唯一,所以可以通过如图所示列表的方式进行实现。
但NFA情况复杂,所以实现会复杂一些。需要把NFA转化成为DFA再进行词法分析
- Thompson算法
例子:a (b | c)*
3.6 NFA转DFA
- 算法思想:消除非确定性——找出一个字符可能转移到的所有状态,将这些状态的集合做为一个状态。(读入这个字符去往的状态肯定属于该集合)
- 例子
- 子集构造算法伪代码
(1)NFA=(∑, S, q0, F, D),DFA=(∑, S*, q0*, F*, D*)
(2)算法不可能无限运行下去,因为该算法的时间复杂度在最坏的情况下为O(2^N),而最坏情况基本不会出现,因为并不是每个子集都出现。 - ε-闭包的计算的伪代码(深度优先)
3.7 DFA的最小化
- 状态压缩合并
- 伪代码
(1)将状态最开始分为两类N和A,A为终止状态。
3.8 DFA的代码表示
- DFA是一个有向图,其代码表示可为转移表,跳转表,哈希表等,选择哪一种表示方式取决于在实际现实中对时间空间的权衡。
- 转移表
(1)最短匹配驱动代码:如果识别的是if,则在读取字符串ifif时,读取到第一个if时直接返回。
(2)最长匹配驱动代码:最后返回ifif。
3. 跳转表