陈颂光
全栈工程师,承接从编译器到网站的各类软件开发与咨询,也可以聊历史哲学。
关注我的 GitHub

Flex分词程序生成器概览

flex是一个分词程序生成器,我们只用指定各种标记满足的模式和碰到它们时应执行的C代码,flex就能生成对应C语言的记法分析程序。

用法

Flex命令用法是常规的:

flex [OPTIONS] [FILE]...

如不用-o选项指定输出文件,默认为lex.yy.c

flex语法

flex文件的总体结构如:

定义
%%
规则
%%
用户代码

定义部分

名字定义形如:

名字 值

其中名字由字母或下划线开始然后是若干个字母、数字或下划线,值从名字后首个非空字符到行末。后面可用{名字}引用(值)

除非在%option行,可以用C风格从/**/的注释,会复制到输出。

缩进了或被%{%}行包围的也会原样复制到输出(不含%{%}。被%top{%}行包围的也会原样复制到输出(不含%top{%}),但会放到flex的定义前。

%s 状态

定义了一个状态,处于初始状态也视为处于此状态。

%x 状态

定义了一个状态,处于初始状态不视为处于此状态。

规则部分

每条规则如:

模式   动作

其中模式是不缩进的正则表达式,前面可加上以尖括号包围的用逗号分隔的状态列表,表示仅当当前状态在列表中时才可用此规则。由于经常有多个模式有相同的开始条件,可以设立开始条件作用域,如:

<ESC>{
"\\n"   return '\n';
"\\r"   return '\r';
"\\f"   return '\f';
"\\0"   return '\0';
}

相当于

<ESC>"\\n"  return '\n';
<ESC>"\\r"  return '\r';
<ESC>"\\f"  return '\f';
<ESC>"\\0"  return '\0';

一种特别模式是可选的开始条件后接<<EOF>>而非正则表达式,它匹配文件结束,通常用于设置yyin以便从其它文件继续。

动作从模式后首个非空字符到行末(或者花括号块结束),可以是任何C语句或者用|表示与下一条规则的动作同。动作中可用以下宏或函数:

宏或函数 用途
ECHO 把yytext输出
BEGIN(状态); 进入状态
REJECT 回退并寻找次优匹配
yymore() 下一迭代中把匹配值附加到当前yytext后而非取代之
yyless(n) 把yytext除前n个外的字符送加输入流
unput(c) 把字符c送回输入流
input() 读入一个字符
YY_FLUSH_BUFFER; 清空内部缓冲
yyterminate() 结束扫描
yy_push_state(new_state) 把当前状态推入栈并把状态设为new_state
yy_pop_state () 把栈顶弹出成当前状态
yy_top_state () 返回栈顶

除非在预期正则表达式的地方,可以用C风格从/**/的注释,会复制到输出。

缩进了或被%{%}行包围的也会原样复制到输出(不含%{%}。里面定义的变量局部于扫描过程。

用户代码

用户代码会被原样复制到输出,通常是调用扫描器或被扫描器调用的过程。如果没有,可省略第二个%%

flex扫描器的工作方式

扫描器yylex()会确定满足其中某个模式的最长字符串并决定对应规则(若有多于一个则选最先声明的),然后让指针yytext指向该字符串开始,让yyleng的值为长度,再执行相应动作。接着对余下文本重复这过程。

如果没有匹配,则执行默认规则,匹配下一个字符并写到输出。也就是说,flex文件%%会生成一个复制器。

yytext可定义为字符数组或字符指针(默认),可以在定义部分用导言%array%pointer设置。数组的大小由宏YYLMAX控制。

状态YYSTART初始时为INITIAL。

输入和输出文件用yyin的yyout变量设置,默认为标准输入和输出。在碰到文件结束后可通过设置yyin或调用yyrestart(FILE *),再调用yylex()继续从其它文件读取输入。

更准确地,扫描器用宏YY_INPUT(buf,result,max_size)读取输入(默认从yyin读),它展开为已读字符数,0表示文件结束。当扫描器收到YY_INPUT指示文件结束,它会调用yywrap(),若它返回0则扫描器继续,否则扫描器结束。如果不手动定义yywrap(),则要用导言%option noyywrap指示它总返回1或者在链接时加选项-lfl

关键词 unix flex lex