1.flex输入文件的格式:
definition
%%
rule
%%
user code
definition中的名称定义:name definition
rule的定义:pattern action
在definition部分中,任何缩进的语句或使用%{ %}包含的语句都被完全原样地复制到输出文件中,%{ %}必须顶格写。顶格写的/* */风格的注释也被原样地复制到输出文件中。在definition部分可以定义一些局部变量,或者包括一些头文件。
rule中的pattern可以使用正则表达式的扩展集合来编写。
2.输入文件的匹配
匹配规则:选择最大匹配的规则,匹配的文本及文本长度分别存入全局变量yytext和yyleng中。如果未找到匹配的规则,则执行默认的匹配规则,认为输入中的下一个字符被匹配,并执行输出操作。yytext的存储方式可以通过%pointer或%array来指定,默认使用%pointer,使用%array时可以通过定义 #define YYLMAX 来定义字符数组的最大长度。
匹配后执行的动作:可以是任意的C或C++语句(注意在definition部分中包含对应的头文件)。如果动作为空,则将该匹配文本丢弃。如果一个动作为一个竖线"|",表示该动作与下一个规则的动作相同。动作中可以包含return语句,返回到任何调用yylex()的函数中,每一次调用yylex()都将从输入文本的剩余部分开始继续执行词法分析。
flex中有一些预定义好的特殊指令。
(1)ECHO 将yytext复制到输出文件中
(2)BEGIN 后跟一个开始条件名将扫描器定位到对应的开始条件处
(3)REJECT 使扫描器针对当前的输入继续去批评次优的规则
(4)yymore() 告诉扫描器匹配下一次规则时将匹配的符号附加到此次匹配的yytext之后,而不是替换此次的yytext
(5)yyless(n) 将当前匹配符号中除前n个字符之外的其它字符重新放回输入流中
(6)unput(c) 将字符c重新放回输入流中
(7)input() 从输入流中读取下一个字符 (g++编译时使用yyinput())
(8)YY_FLUSH_BUFFER 清除扫描器内部的缓冲区,使得下一次当扫描器执行扫描之前会使用YY_INPUT重新填充缓冲区。
(9)yyterminate() 可以在一个动作中代替return语句使用,结束扫描并向扫描器的调用者返回0.
3.生成的扫描器
(1)默认的扫描器入口函数为 int yylex(void),可以通过修改宏定义YY_DECL来修改入口函数。
(2)当yylex()被调用的时候,它从全局输入文件yyin中读取符号,直到遇到文件结尾符或者某个扫描动作中有return语句。当扫描器到达文件结尾时,后续的调用是无效的,除非yyin指向了一个新的输入文件或者调用了yyrestart(FILE *)。
(3)默认情况下,扫描器会一块一块地读取yyin,可以通过修改宏定义YY_INPUT来重定义读取函数。YY_INPUT(buf,result,max_size)。当扫描器从YY_INPUT中获取文件结束的指示时,它会去检查yywrap()函数,如果yywrap()函数返回false则扫描器认为函数已经将yyin重新指向另一个输入文件,扫描继续进行;如果yywrap()函数返回true则扫描器结束,向调用者返回0。如果用户未提供自己的yywrap()定义则需要在定义中加入%option noyywrap或者在链接时加入-lfl选项来使用默认的yywrap()函数,该函数总是返回true。
(4)扫描器会将ECHO的结果写入到yyout文件中,用户可以修改该文件来重定义输出。
(5)除了文件之外,还有yy_scan_string() yy_scan_bytes() yy_scan_buffer()三个函数可以从内存中读取输入。
4.启动条件
flex提供了一种有条件地激活规则的机制。任何前缀为的规则只有在扫描器处于名为sc的条件下才会被激活。例如:
[^"]*
\.
(1)定义。在defintion部分使用顶格的%s或%x开头,后跟一个名称列表。%s定义的是容他的启动条件,%x定义的是排他的启动条件。
(2)激活。条件的激活使用BEGIN(condition name)语句,当一个条件被激活后直到下一个BEGIN语句被执行前,使用该条件的规则被激活,使用其它条件的规则则关闭。BEGIN(INITIAL)/BEGIN(0)返回到原始状态,只有无条件的规则才被激活。
(3)启动条件实际上是一些整型值,可以被存入int值中。用户可以通过宏YY_START(YYSTART)来访问当前启动的条件值。启动条件值实际是被存入一个动态扩展到栈结构中,void yy_push_state(int state) , void yy_pop_state(), int yy_top_state()三个函数可以用来操作栈。另外为了操作栈,需要加入%option statck选项。
(4)多个相同启动条件的规则的合并,{}。例如
{
"\\n" return '\n';
"\\t" return '\t';
"\\r" return '\r';
}
5.多输入流
有些扫描器需要从多个输入流中读取,例如有些支持include file的语言源文件。由于flex做了大量的缓存,用户不能够通过仅仅修改YY_INPUT函数来切换输入文件,因为YY_INPUT对上下文是敏感的。YY_INPUT只有在扫描读取到它当前缓存的结束时才会被调用。为了解决此问题,flex提供一种创建和切换上下文的机制。
(1)输入缓冲区的创建: YY_BUFFER_STATE yy_create_buffer(FILE *file,int size); YY_BUFFER_STATE是一个指向yy_state_buffer结构的指针。别名yy_new_buffer()
(2)缓冲区的切换:void yy_switch_to_buffer(YY_BUFFER_STATE new_buffer)。
(3)缓冲区的释放:void yy_delete_buffer(YY_BUFFER_STATE buffer)。
(4)缓冲区内容的清空:void yy_flush_buffer(YY_BUFFER_STATE buffer);函数清空当前缓存区中的内容,扫描器下一次执行扫描会首先调用YY_INPUT来填充缓冲区。
(5)YY_CURRENT_BUFFER返回一个指向当前缓冲区的YY_BUFFER_STATE。
(6)有三个函数可以用来从非文件中创建缓冲区,分别是
yy_scan_string() yy_scan_bytes() yy_scan_buffer()。
6.文件结束规则(end-of-file rules)
<>表示一个特殊的规则,其后跟随的动作在遇到文件结尾符以及yywrap()返回true的情况下会执行。该动作必须执行以下四种动作之一:
(1)将yyin赋值到一个新的文件句柄
(2)执行一个return语句
(3)执行yyterminate()动作
(4)使用yy_switch_to_buffer()切换到另一个缓存。
<>规则不能够和其它规则一起使用,它只能与启动条件一起使用。
7.各种宏定义
(1)YY_USRE_ACTION 定义一个在任何匹配规则的动作执行之前的操作
(2)变量yy_act 当前匹配的规则的编号,编号是从1开始的
(3)YY_NUM_RULES 规则的总数
(4)YY_USER_INIT 在第一次扫描之前执行的操作
(5)yy_set_interactive(is_interactive)
(6)yy_set_bol(at_bol) YY_AT_BOL : bol==begining of line
(7)YY_BREAK 在生成的扫描器中所有的动作都合并到一个switch语句中,并使用YY_BREAK分隔,YY_BREAK默认的操作为break。
更多的信息可以参考 http://tinf2.vub.ac.be/~dvermeir/courses/compilers/,本文基本上都是其中某些章节的翻译。