词法分析
编译器的执行流程
编译器执行可以分为8个阶段:词法分析、语法分析、语意分析、中间代码生成、指令选择、代码优化、寄存器分配、代码生成。其中前4个阶段构成编译器的前端frontend,剩余阶段组成了编译器的后端backend。如下图所示:
编译器的前端与机器无关,后端执行的结果是特定系统架构下的汇编代码。为了生成可执行程序,汇编代码还要经过汇编器、链接器。整个程序的执行可以用下面这张图来说明:
词法分析的作用
词法分析和NLP里的命名实体识别技术类似,都是把输入字符映射到有意义的代表物(Token)上,和人类语言相比,编程语言的规则语法过于简单,所以我们用基于规则的字符匹配算法就可以搞定这一步,并不需要模型算法技术。当然,在词法分析中,我们还可以滤除冗余信息(空格,换行符等),所以词法分析可以看作是从原始信息中提取关键信息的一步。对于source code中的词法结构,我们应该如何描述呢?正则表达式是一种工具,有描述工具之后就需要实现它,对应的实现工具就是有限自动机(finite automata)。
有限自动机
定义
有限自动机是一个五元组:
- 是一个有限状态集合,包含一个错误状态
- 是一个有限字母集合
- 叫做转移函数,对于 ,状态转移可以写为
- 叫做起始状态
- 是接受状态的集合
有限自动机也可以用图来表示,假设我们要识别new,not,while这三个关键词,程序伪代码如下:
c = NextChar()
if (c == 'n'):
c = NextChar()
if(c == 'e')
c = NextChar()
if(c=='t'):
return 'accept state'
else if (c == 'o'):
c = NextChar()
if (c == 't'):
return 'accept state'
else if(c=='w'):
c = NextChar()
if(c == 'h'):
c = NextChar()
if(c =='i'):
c = NextChar()
if(c=='l'):
c = NextChar()
if(c=='e'):
return 'accept state'
return 'unaccept state'
用状态图表示:
我们说状态机 接受字符串当且仅当如下条件成立:
有没有一种等价的方式来描述上述的状态图呢?答案是有!正则表达式regular expression
正则表达式
定义
正则表达式在给定字母表上描述了字符集合,包括空字符。这样的字符集合称之为language。对于给定的正则表达式r,language表示为L(r).正则表达式定义了三种运算:
- Union,
- Concatenation,
- Closure,
特别地,,[0…9]=(0|1|2|3|4|5|6|7|8|9)
例子
-
标识符identifier:
-
非负整数:or
-
非负实数:
-
字符串:“ ^“|\ ”
-
单行注释:\\(^\n)\n
-
多行注释:\*(^*)**/
确定性有限自动机DFA
对于任意输入字母c,的输出是唯一确定的
非确定性有限自动机NFA
对于任意输入字母c,的输出可以有多个,并且包含空跳(不消费字母即可进行状态跳转)
DFA与NFA的等价性
对于有N个状态的NFA,我们总可以用至多个状态节点构建DFA;对于DFA,本身就是NFA的一种特例,所以二者等价。
正则表达式到NFA
Thompson Construction
由于正则表达式在前面定义的三种运算上是封闭的,所以我们可以定义NFA的这三种等价运算:
例子
构建单独的DFA
构建
构建
构建
DFA的等价表示
对比DFA和NFA我们可以发现:NFA包含很多冗余的状态节点及状态转移,因此利用NFA构建DFA是必要的。
NFA到DFA
Subset Construction算法
变量解释:
-
:当前状态的所有空跳后继状态集合
-
:start state
-
:迭代状态变量
-
:DFA state set
算法描述
算法分析:
对于NFA的N个状态,路径排列是 ,在while循环中注意到,Q是单调递增的,且worklist中t一定是不同的(相同地不会加入Q和worklist),当Q中元素数量达到最大时,add操作不会执行,所以算法一定会终止,不会陷入死循环。