对于正规式
ε-closure(I) 是 I 中任何状态 S 经过任意条 ε 弧能到达的状态集合
move(I, a) 是 I 中任何状态经过一条 a 弧能到达的状态集合
子集构造算法
算法终止,得到 5 个子集:
那么由 NFA 构造的 DFA 如下
对于不含 的 NFA,可以利用状态转移矩阵变成 DFA
0 | 1 | |
---|---|---|
A | A | BC |
BC | AC | BC |
AC | AC | BC |
无用状态
从 a 到 b,消除了不可到达态 s4;从 b 到 c,消除了不可到达态 s6 和 s8
等价状态
分割子集法
可以利用转移矩阵逐步区分
1 | int state_index(char c) { |
对于如下有穷自动机
自动构造工具:是一类利用形式化描述如正规表达式自动生成程序的工具,也就是说,用户只需要用正规表达式描述单词模式,自动构造工具就可以自动生成用于词法分析的代码
lex:读入用户编写的一个 lex.l
的描述文件,生成一个名为 lex.yy.c
的 C 源程序文件,其中包含函数 yylex()
,用于读取源程序的字符流,并返回下一个 Token
正规表达式 | 匹配表达式 | 例子 | 注意 |
---|---|---|---|
x | 匹配字面量字符x | a匹配a,b匹配b | 区分大小写 |
. | 匹配除换行符之外的任意单个字符 | . 可匹配 x、3、# 等 | 换行符是\n |
[字符列表] | 匹配列表中任一字符 | [xyz] 匹配 x 或 y 或 z | 可用 - 表示范围,如 [1-4] 匹配 1,2,3,4 |
[^字符列表] | 匹配不在列表中的任一字符 | [^A-Z] 匹配除了大写字母之外的字符 | [^] 匹配任何字符(除换行符外) |
“字符串” | 匹配完全相同的字符串 | “if” 匹配单词 if | 字符串内的 " 和 \ 需转义 |
\转义字符 | 正规表达式中的特殊字符需要在前面加上斜杠 | \* 表示 *,\ 表示 \,\"表示 " | |
\数字 | 匹配八进制数对应的ASCII字符 | \101 匹配字符 A | |
\x数字 | 匹配十六进制数对应的ASCII字符 | \x41 匹配字符 A | |
r* | 匹配 r 的闭包 | a* 可匹配空串、a、aa、aaa 等 | 包括空串 |
r+ | 匹配 r 的正闭包 | a+ 可匹配 a、aa、aaa 等 | 不包括空串 |
r? | 匹配 r 零次或一次 | a? 匹配空串或 a | |
r{n} | 匹配 r 恰好出现 n 次 | a{3} 匹配 aaa | n 必须非负 |
r{m,n} | 匹配 r 至少 m 次,至多 n 次 | a{2,4} 匹配 aa、aaa 或 aaaa | m 必须小于等于 n |
r{m,} | 匹配 r 至少 m 次 | a{2,} 匹配 aa、aaa、aaaa 等 | |
® | 改变优先级,优先匹配 r | 捕获子串 (ab)+ 匹配 ab、abab 等 | |
rs | 匹配的连接 | ab 匹配字符串 ab | 顺序不能颠倒 |
r|s | 匹配 r 或 s | a|b 匹配 a 或 b | |
r/s | 匹配后面是 s 的 r | a/b 匹配 a | 不匹配 s,回退扫描 |
^r | 匹配以 r 开头的行 | ^abc 仅匹配行首的 abc | |
r$ | 匹配以 r 结尾的行 | xyz$ 匹配以 xyz 结尾的行 | |
<c>r | 匹配在特定条件下的 r | <COMMENT>r 表示在 COMMENT 状态下才能匹配 r | 用于区分不同上下文状态,INITIAL 是 Lex 默认的起始条件 |
{宏} | 匹配宏定义所代表的正规表达式 | 宏需要事先定义 |
常用的正则表达式
#D [0-9]
L [a-zA-Z]
{L}({L}|{D})*
[+-]?(0|[1-9][0-9]*)
-> 可选的正负号 + 0 或非 0 开头的数字\"(\\.|[^\\"\n])*\"
-> 双引号 + 转义字符或非反斜杠/非双引号/非换行符的闭包 + 双引号[ \t]+
-> 空格和制表符的闭包\/\/[^\n]*\n
-> 两个斜杠 + 非换行符的闭包 + 换行符lex 描述文件:描述各种单词的模式,并为每种模式指定对应的 C 语言动作代码,每个部分之间用 %%
分割
定义部分:设定词法分析器的全局环境和基础配置
#include <stdio.h>
int num_lines=0;
#define ADDCOL() Column += yyleng;
D [0-9]
%option noyywrap
表示不自动调用 yywrap()
%x COMMENT
规则部分:定义了词法分析器如何从输入中识别各类单词,同时应该作出什么反应,格式通常为 正则表达式 {动作代码}
"if"
或 [ \t\n]
{ return IF; }
或 { return ~YYEOF; }
辅助代码部分/用户子程序部分:实现了词法分析器的运行入口及相关功能,可以配合后续语法分析器使用
int main() { yylex(); return 0; }
yywrap()
或 yyerror()
~YYEOF
是一个特殊的返回值,用来表明匹配到了无效字符,从而让词法分析器忽略当前匹配,继续扫描下一个 token
lex 的正规表达式匹配原则
1 | %{ |
DIGIT 规则必须在 . 规则之上,否则将永远不会匹配到 DIGIT,num_numbers
永远不会更新
mylexer.l
$ lex mylexer.l
:生成 lex C 源程序文件 lex.yy.c
$ gcc lex.yy.c -ll -o mylexer
:链接 lex 库,生成可执行文件 mylexer
./mylexer < input.txt
:传递输入文件,执行可执行文件lex 描述文件
1 | %{ |
输入文本
1 | Hello world! |
输出结果
1 | Word: Hello |