原文网址在这里,一些我认为不重要的部分就略了。大部分是google翻译(毕竟一句一句慢慢看好再翻还是很累的好吧TAT),绝不代表本人的水平(滑稽.jpg)。不恰当的地方欢迎指正,也欢迎补充剩下的“略”。
————————————————————————(我是萌萌哒昏割线)
名称
flex - fast lexical analyzer generator(快速词法分析生成工具)。
概要
flex [-bcdfhilnpstvwBFILTV78+? -C[aefFmr] -ooutput -Pprefix -Sskeleton] [--help --version] [filename ...]
使用手册目录
略。
描述
flex是一种生成扫描器(scanner)的工具,扫描器指的是识别文本中词法模式的程序。flex可以读入给定的输入文件(如果没有输入文件则使用自己的标准输入)并生成符合某个描述的扫描器。描述由许多对正则表达式和C代码组成,每个正则表达式和它对应的C代码被叫做一个规则(rules)。flex会生成名为'lex.yy.c'的C源文件,该源文件中定义了一个叫'yylex()'的函数,文件编译后与'-lfl'库连接,产生可执行程序。程序运行时,它会分析输入中是否含有相应的正则表达式,如果匹配,则执行对应规则。
一些简单例子
首先通过一些简单的例子来了解怎么使用flex。下面的flex输入指定了一个扫描器,只要遇到字符串“username”就会用用户的登录名替换它:
%%
username printf( "%s", getlogin() );
默认情况下,任何与flex扫描器不匹配的文本都将被复制到输出中,因此该扫描器的实际效果是将其输入文件随同“username”复制到其输出。在这个输入中只有一条规则。 “用户名”是模式(pattern),“printf”是行为(action)。 “%%”标志着规则的开始。
另一个简单例子:
int num_lines = 0, num_chars = 0;
%%
\n ++num_lines; ++num_chars;
. ++num_chars;
%%
main()
{
yylex();
printf( "# of lines = %d, # of chars = %d\n",
num_lines, num_chars );
}
这个扫描器计算输入字符的数量和行数(除了计数的最终报告外,它不产生任何输出)。第一行声明了两个全局变量“num_lines”和“num_chars”,这两个全局变量既可以在yylex()内部访问,也可以在第二个“%%”之后声明的main()中访问。这里有两条规则:一条匹配换行符(“\ n”)并增加行数和字符数,另一条匹配换行符以外的任何字符(用正则表达式“.”表示)。
一个复杂点的例子:
%{
#include<math.h>
%}
DIGIT [0-9]
ID [a-z][a-z0-9]*
%%
{DIGIT}+ {
printf("An integer:%s (%d)\n",yytext,atoi(yytext));
}
{DIGIT}+"."+{DIGIT}+ {
printf("A float:%s (%g)\n",yytext,atoi(yytext));
}
if|then|begin|procedure|function {
printf("A keyword:%s\n",yytext);
}
{ID} {
printf("An identifier:%s\n",yytext);
}
"+"|"-"|"*"|"/" {
printf("An operator:%s\n",yytext);
}
"{"[^}\n]*"}" /*eat up one-line comments*/
[ \t\n]+ /*eat up white space
ATTENTION: HERE IS A SPACE!!!
*/
. {
printf("Unrecognized character: %s\n",yytext);
}
%%
int main(int argc,char* argv[]){
++argv, --argc;
if(argc>0)
yyin=fopen(argv[0],"r");
else
yyin=stdin;
yylex();
}
这是一个Pascal等语言的简单扫描仪,它识别不同类型的token并报告它所看到的内容。这个例子的细节将在下面的章节中解释。
输入文件格式
flex输入文件由三部分组成,每个部分用%%分开:
definitions
%%
rules
%%
user code
定义(definations)部分包含简单名称定义的声明,以简化扫描器规范以及开始条件的声明,这些将在后面的章节中解释。名称定义的形式如下:
name definition
“名称”是以字母或下划线('_')开头,后跟零个或多个字母,数字,'_'或' - '(短划线)的单词。该定义从名称后面的第一个非空白字符开始,并延续到行尾。随后可以使用“{name}”来引用该定义,该定义将扩展为“(definations)”。例如,
DIGIT [0-9]
ID [a-z][a-z0-9]*
将“DIGIT”定义为与单个数字相匹配的正则表达式,而“ID”则是一个字母后跟零个或多个字母或数字的正则表达式。另一个例子:
{DIGIT}+"."{DIGIT}*
和
([0-9])+"."([0-9])*
等价,匹配的是一个数字连接上小数点再连接上0到9中的任意个数字的字符串。
规则(rules)部分的形式如下:
pattern action
其中,模式(pattern)必须是没有缩进的,并且要和行为(action)处在同一行。
可以参看下面阅读更多关于模式和行为的知识。
最终,用户代码部分(user code section)被逐字逐句地拷贝到'lex.yy.c',它可能会被其它调用扫描器程序或被扫描器程序调用的程序使用。用户代码部分是可选的(optional),如果没有,第二个%%也可以省略。
定义和规则部分中的任何缩进的内容或者被'%{'和'%}'括起来的内容都会被逐字逐句地拷贝到'lex.yy.c'('%{'和'%}'会被删掉)。'%{'和'%}'必须是没有缩进的(即%和括号要紧挨在一起,中间不能空开)。
在规则部分,任何出现在第一条规则之前的缩进的或者有%{}的内容可以用来定义扫描函数的局部变量,其他缩进的或者有%{}的内容会被拷贝到'lex.yy.c'中,因为它们意义不明确,所以这样可能引起错误。
定义部分里的注释也会被拷贝到lex.yy.c中。
模式(Patterns)
模式使用正则表达式来描述:
x | 匹配字符x |
. | 匹配除换行(newline)外的任意字符 |
[xyz] | x或y或z |
[abj-oZ] | 匹配a或者b或者j到o的某个字符或者Z |
[^A-Z] | 匹配除A到Z的字符 |
[^A-Z\n] | 除A-Z和换行 |
r* | 0个或多个r |
r+ | 1个或多个r |
r? | 0个或1个r |
r{2,5} | rr或者rrr或者rrrr或者rrrrr(即2到5个r) |
r{2,} | 2个或者2个以上r |
r{4} | rrrr |
{name} | 由name定义的扩展 |
"[xyz]\"foo" | [xyz]\foo |
\x | 若x是a、b、f、n、r、t、v则按照ANSI-C的规则转义,否则是x本身 |
\0 | NUL |
\123 | 十进制值123 |
\x2a | 16进制值2a |
(r) | 被括号改变优先级的r |
rs | 连接 |
r|s | 或 |
r/s | 当且仅当r后面有s时才匹配 |
^r | 只有当r在行首才匹配 |
r$ | 只有当r在行末尾才匹配 |
<s>r | r,但是只满足初始情形s(start condition) |
<*>r | 满足任何初始情形的r |
<<EOF>> | 文件结束符 |
这些正则表达式按照优先级从高到低排序,如果要改变优先级,可以使用()(这里例子就略了,大家都懂)。
由[::]括起来的东西相当于C语言中的'isXXX'函数,例如:
[:alnum:] [:alpha:] [:blank:]
[:cntrl:] [:digit:] [:graph:]
[:lower:] [:print:] [:punct:]
[:space:] [:upper:] [:xdigit:]
下面例子中的表达式都是等价的:
[[:alnum:]]
[[:alpha:][:digit:]
[[:alpha:]0-9]
[a-zA-Z0-9]
如果你的扫描器不是大小写敏感的,那么[:upper:][:lower:]和[:alpha:]等价。
注意事项:
· [^A-Z]可以匹配换行,除非写成[^A-Z\n]。这和很多其他处理正则表达式的工具不同,这样的不一致主要是历史原因造成的。可以匹配换行符\n意味着像[^"]*这样的模式(pattern)可以匹配整个输入,除非在输入里有另一个引号。
· 一条规则最多只能有一个尾部上下文(trailing context)的实例(尾部上下文指的是'/'或者'$')。初始条件(start condition),'^'和<<EOF>>只能出现在模式(pattern)的最前面。'/'和'$'不能被放在圆括号里。不在开头的取反符'^'和不在末尾的'$'将失去它们的特殊含义,按照普通字符处理。以下是一些例子:
foo/bar$ /*错误,有两个尾部上下文*/
<sc1>foo<sc2>bar /*错误,有两个初始条件*/
注意第一个可以写成:
"foo/bar\n"
又如:
foo|(bar$)
foo|^bar
将会导致'$'和'^'被看作普通字符。
如果想要的只是foo或者bar后面跟着换行符,可以用('|'的用法将在下面介绍):
foo |
bar$ /* action goes here */
同样的技巧可以用来匹配foo或者位于行首的bar。
如何匹配输入
当运行生成的扫描程序时,它会分析其输入以查找与其任何模式(pattern)匹配的字符串。如果它找到多个匹配项,则它将采用匹配最多文本的匹配项(对于尾随上下文规则,这包括尾部部分的长度,即使它将返回到输入)。如果找到两个或更多相同长度的匹配项,则首先在flex输入文件中列出的规则被选中。
一旦匹配被确定,对应于匹配的文本(称为token,文中多处直接写了token,没有中文翻译)就可以在全局字符指针yytext中使用,并且其长度保存在全局整数yyleng中。然后执行对应于匹配模式的行为(action,后文有更详细的关于动作的描述),然后扫描剩余的输入以进行另一次匹配。
如果未找到匹配项,则执行默认规则:输入中的下一个字符被视为匹配并复制到标准输出。因此,最简单的合法输入是:
%%
它会生成一个扫描器,将其输入(一次一个字符)复制到输出。
请注意,yytext可以用两种不同的方式定义:作为字符指针或作为字符数组。您可以通过在flex输入的第一个部分(也就是定义部分)中包含特殊指令`%pointer'或`%array'来控制flex使用哪个定义。缺省值是`%pointer',除非使用`-l'lex兼容性选项,在这种情况下,yytext将是一个数组。使用'%pointer'的优点是在匹配非常大的token时,扫描速度更快,并且不会发生缓冲区溢出。缺点是你的操作限制了你的动作如何修改yytext(参见下一节),并且调用'unput()'函数会破坏yytext的当前内容,当在不同的lex之间移动时,这可能是一个相当令人头疼的问题。
'%array'的优点是你可以按自己的想法修改yytext的内容,调用'unput()'不会破坏yytext(见下文)。此外,现有的lex程序有时使用如下形式的声明,以便从外部访问yytext:
extern char yytext [];
这个定义在与'%pointer'一起使用时是错误的,但对'%array'是正确的。
`%array'将yytext定义为一个YYLMAX字符数组,默认为相当大的值。您可以通过在flex输入的第一部分中将#YYLMAX定义为不同的值来更改大小。如上所述,使用'%pointer',yytext动态增长以适应大的token。虽然这意味着您的'%pointer'扫描器可以容纳非常大的token(例如匹配整个注释块),但请记住,每次扫描器必须调整yytext的大小时,它也必须从头开始重新扫描整个token,匹配这样的token是很缓慢的。如果调用`unput()'导致太多文本被重新送回来处理,yytext便不再动态增长了;相反,此时会发生运行时错误。
另外请注意,您不能在C ++扫描程序类中使用`%array'(关于c ++选项;请参见下文)。
行为(Actions)
规则中的每个模式都有相应的操作,可以是任意的C语句。 该模式结束于第一个非转义空白字符; 该行的其余部分是其行为。 如果行为为空,那么当模式匹配时,只会简单地丢弃输入的token。 例如,下面的例子从输入中删除所有出现的“zap me”:
%%
"zap me"
(它会将输入中的所有其他字符复制到输出中,因为它们将与默认规则相匹配。)
下面的程序将多个空格和制表符压缩到一个空白处,并将在行尾找到的空白字符丢弃:
%%
[ \t]+ putchar( ' ' );
[ \t]+$ /* ignore this token */
如果行为包含'{',那么它会跨越文段找到配对的'}',并可能会跨越多行。 flex知道C字符串和注释,并且不会被它们内部的大括号所迷惑,而且还允许以'%{'开始的行为,并将行为视为下一个'%}'的所有文本(不管行为中的普通括号)。
仅由竖线('|')组成的动作表示“与下一个规则的动作相同”。详细用法请继续读下去。
操作可以包含任意C代码,包括返回语句以将值返回给任何名为`yylex()'的例程。 每次调用yylex()时,它都会从最后一次返回处的token开始工作,直到它到达文件末尾或执行返回。
行为可以自由修改yytext,除了延长它(向其末尾添加字符——这些字符将覆盖输入流中后面的字符)。 然而这在使用'%array'时不适用(见上文); 在这种情况下,yytext可以以任何方式自由修改。
动作可以自由修改yyleng,除非如果动作还包括使用'yymore()'(见下文)。
有一些指令(directive)可以被当作行为:
· `ECHO'将yytext复制到扫描仪的输出。
· BEGIN后跟开始条件的名称将扫描仪置于相应的开始条件(见下文)。
· REJECT指示扫描仪继续执行与输入(或输入的前缀)匹配的“次优”规则。 该规则的选择如上面“如何匹配输入”中所述,yytext和yyleng也会被重新设置妥当。 这个次优规则可能会像最初的规则一样匹配尽可能多的文本,较最初的规则而言处于flex输入文件中靠后的位置,或者,这个次优规则也有可能匹配较少的文本。 例如,以下内容将统计输入中的单词,并在每次看到“frob”时调用例程special():
int word_count = 0;
%%
frob special(); REJECT;
[^ \t\n]+ ++word_count;
如果没有REJECT,输入中的任何“frob”都不会被视为单词,因为扫描器通常每个标识(token)只执行一个动作。 多个REJECT是允许的,每个找到当前活动规则的下一个最佳选择。 例如,当以下扫描器扫描标记“abcd”时,它会将“abcdabcaba”写入输出:
%%
a |
ab |
abc |
abcd ECHO; REJECT;
.|\n /* eat up any unmatched character */
(前三条规则分享了第四条动作,因为它们使用特殊的'|'动作。)
在扫描仪性能方面,REJECT是一项特别昂贵的功能;如果在任何扫描仪的操作中使用它,则会减慢所有扫描仪的匹配。 此外,REJECT不能与`-Cf'或`-CF'选项一起使用(见下文)。 还要注意的是,与其他特殊行为不同,REJECT是一个分支;紧随其后的代码将不会被执行。
(译者注:所以这个REJECT的作用到底是什么?其实就是:放弃当前的字符串和模式,让扫描器重新扫描这个字符串,寻找一个次优的模式来匹配,上面扫描abcd的例子是这样工作的:首先发现abcd匹配后,执行ECHO,将abcd写到输出,然后执行REJECT,于是重新扫描,这时,相对于a |和ab |,abc |就是次优规则,然后将abc写到输出,又重新扫描,然后是ab,最后是a。)
· `yymore()'告诉扫描器下一次匹配规则时,相应的token应该附加到yytext的当前值上,而不是替换它。 例如,考虑到输入“mega-kludge”,以下将把“mega-kludge”写成输出:
%%
mega- ECHO; yymore();
kludge ECHO;
首先“mega-”匹配并回显到输出。 然后“kludge”匹配,但之前的“mega-”仍然在yytext的开始处悬停,因此“kludge”规则的“ECHO”实际上会写入“mega-kludge”。
(译者注:意思就是扫完以后不要丢弃,将处理过的这个字符串放到yytext最前面。所以上面例子得到的是mega-mega-kludge)
关于使用'yymore()'的两个注意事项。 首先,`yymore()'的正确性取决于yyleng能正确反映当前token的大小,因此如果使用`yymore()',则不得修改yyleng。 其次,在扫描仪的动作中出现'yymore()'会在扫描仪的匹配速度上造成较小的性能损失。
· `yyless(n)'将当前标记的前n个字符全部返回给输入流,当扫描器查找下一个匹配时,它们将被重新扫描。 yytext和yyleng会被适当调整(例如,yyleng现在等于n)。 例如,在输入“foobar”时,以下内容将写出“foobarbar”:
%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;
0到yyless的参数将导致整个当前输入字符串被再次扫描。 除非您改变了扫描仪随后处理其输入的方式(例如使用BEGIN),否则将导致无限循环。 请注意,yyless是一个宏,只能用于flex输入文件,而不能用于其他源文件。
· `unput(c)'将字符c放回输入流。 这将是下一个扫描的字符。 以下操作将采用当前标记并使其重新扫描,并用圆括号括起来。
{
int i;
/* Copy yytext because unput() trashes yytext */
char *yycopy = strdup( yytext );
unput( ')' );
for ( i = yyleng - 1; i >= 0; --i )
unput( yycopy[i] );
unput( '(' );
free( yycopy );
}
(译者注:程序是这样工作的:例如yytext保存了abc,那么strdup函数复制了这个字符串并把指针交给yycopy,unput在输入流中放了一个),然后倒着遍历yycopy,因为unput每次都把字符添加到输入流最前面,所以这个过程是:) -> c) -> bc) -> abc) -> (abc),这样就得到了圆括号括起来的abc)
请注意,由于每个`unput()'都将给定的字符放回到输入流的开始位置,所以推回字符串必须在后面执行。 当使用'unput()'时,一个重要的潜在问题是如果你使用`%pointer'(缺省值),调用`unput()'会破坏yytext的内容,从最右边的字符开始,每次调用吞噬一个左边的字符。 如果需要在调用`unput()'(如上例)之后保留yytext的值,则必须首先将其复制到其他位置,或者使用`%array'构建扫描器(请参阅“如何匹配输入”部分)。 最后,请注意,您不能放回EOF以尝试使用文件结束标记输入流。
· `input()'从输入流中读取下一个字符。 例如,以下是吃掉C注释的一种方法:
%%
"/*" {
register int c;
for ( ; ; )
{
while ( (c = input()) != '*' &&
c != EOF )
; /* eat up text of comment */
if ( c == '*' )
{
while ( (c = input()) == '*' )
;
if ( c == '/' )
break; /* found the end */
}
if ( c == EOF )
{
error( "EOF in comment" );
break;
}
}
}
(注意,如果扫描器是使用`C ++'编译的,那么`input()'被改为`yyinput()',以避免输入名称与'C ++'流冲突。)
(译者注:C语言register关键字表示希望编译器将这个变量存到寄存器里,这预示着该变量会被多次修改,例如这里用来保存输入流单个字符的c显然一直都在被修改。)
· YY_FLUSH_BUFFER刷新扫描程序的内部缓冲区,下次扫描程序尝试匹配token时,它将首先使用YY_INPUT重新填充缓冲区(请参阅下面的“生成扫描器”)。 这个动作是更通用的`yy_flush_buffer()'函数的一个特殊情况,在下面的“多输入缓冲”部分描述。
· `yyterminate()'可以用来代替动作中的返回语句。 它终止扫描器并向扫描器的调用者返回0,表示“全部完成”。 默认情况下,当遇到文件结尾时,也会调用`yyterminate()'。 这是一个宏,可能会被重新定义。
生成扫描器
暂时略
初始条件
flex为有条件地激活规则提供了一种机制。 只有当扫描器处于名为“sc”的开始条件时,其模式前缀为“<sc>”的任何规则才会处于激活状态。 例如,
<STRING>[^"]* { /* eat up the string body ... */
...
}
只有当扫描仪处于“STRING”开始状态时才会激活,又比如
<INITIAL,STRING,QUOTE> \. {/ *handle an escape... * /
...
}
只有在当前启动条件是“INITIAL”,“STRING”或“QUOTE”时才会激活。
开始条件在输入的定义(第一个)部分中以'%s'或'%x'开头,后跟一个名称列表的不带缩进的行来声明。 前者声明的是包含性的(inclusive)开始条件,后者是独占性的(exclusive)开始条件。 开始条件使用BEGIN操作激活。 在执行下一个BEGIN操作之前,具有给定开始条件的规则将处于激活状态,而具有其他开始条件的规则将处于非激活状态。 如果起始条件是包含的(inclusive),那么根本没有开始条件的规则也将被激活。 如果它是独占的(exclusive),则只有符合启动条件的规则才会生效。 根据相同独占开始条件的一组规则描述了独立于flex输入中的任何其他规则的扫描器。 正因为如此,排他的开始条件可以很容易地指定扫描输入中与其余部分在语法上不同的部分(例如注释)的“小型扫描仪”。
如果包容性和排他性启动条件之间的区别仍然有点模糊,以下是一个简单的例子,说明两者之间的联系。 规则集:
%s example
%%
<example>foo do_something();
bar something_else();
等价于:
%x example
%%
<example>foo do_something();
<INITIAL,example>bar something_else();
如果没有`<INITIAL,example>'限定符,则第二个示例中的“bar”模式在启动条件“example”时不会处于活动状态(即,不匹配)。 但是,如果我们只是使用`<example>'来限定`bar',那么它只会在`example'中而不是在INITIAL中有效,而在第一个例子中它在两个中都是有效的,因为在第一个例子中, 开始条件是包容性(`%s')开始条件。
另请注意,特殊的开始条件说明符“<*>”匹配每个开始条件。 因此,上面的例子也可以写成:
%x example
%%
<example>foo do_something();
<*>bar something_else();
默认规则('ECHO'任何不匹配的字符)在启动条件下保持有效。 它相当于:
<*>.|\\n ECHO;
`BEGIN(0)'返回到只有没有启动条件的规则处于活动状态的原始状态。 这个状态也可以被称为开始条件“INITIAL”,所以`BEGIN(INITIAL)'等同于'BEGIN(0)'。 (开始条件名称周围的圆括号不是必需的,但被认为是很好的风格。)
BEGIN操作也可以在规则部分的开头以缩进代码形式给出。 例如,如果调用“yylex()”且全局变量enter_special为true,则以下操作将导致扫描器输入“SPECIAL”开始条件:
int enter_special;
%x SPECIAL
%%
if ( enter_special )
BEGIN(SPECIAL);
<SPECIAL>blahblahblah
...more rules follow...
为了说明启动条件的用法,下面是一个扫描器,它提供了两种不同的字符串解释,例如“123.456”。 默认情况下,它会将它视为三个标识(token),整数“123”,点('。')和整数“456”。 但是如果字符串在字符串“expect-floats”的前面出现,它会将其视为单个标记,即浮点数123.456:
%{
#include <math.h>
%}
%s expect
%%
expect-floats BEGIN(expect);
<expect>[0-9]+"."[0-9]+ {
printf( "found a float, = %f\n",
atof( yytext ) );
}
<expect>\n {
/* that's the end of the line, so
* we need another "expect-number"
* before we'll recognize any more
* numbers
*/
BEGIN(INITIAL);
}
[0-9]+ {
printf( "found an integer, = %d\n",
atoi( yytext ) );
}
"." printf( "found a dot\n" );
这是一个扫描仪,可以识别(并丢弃)C注释,同时保持当前输入行的计数值:
%x comment
%%
int line_num = 1;
"/*" BEGIN(comment);
<comment>[^*\n]* /* eat anything that's not a '*' */
<comment>"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */
<comment>\n ++line_num;
<comment>"*"+"/" BEGIN(INITIAL);
为了尽可能多地与每个规则匹配文本这个扫描器会遇到一些麻烦。 一般来说,当试图编写高速扫描器时,尽量在每条规则上尽可能匹配,因为这是一个巨大的胜利。
请注意,开始条件名称实际上是整数值,可以像整数一样存储。 因此,上述程序可以按以下方式进行扩展:
%x comment foo
%%
int line_num = 1;
int comment_caller;
"/*" {
comment_caller = INITIAL;
BEGIN(comment);
}
...
<foo>"/*" {
comment_caller = foo;
BEGIN(comment);
}
<comment>[^*\n]* /* eat anything that's not a '*' */
<comment>"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */
<comment>\n ++line_num;
<comment>"*"+"/" BEGIN(comment_caller);
此外,您可以使用整数值YY_START宏访问当前的启动条件。 例如,上述的comment_caller分配可以被写入:
comment_caller = YY_START;
Flex提供YYSTATE作为YY_START的别名(因为这是AT&T lex所使用的)。
请注意,开始条件没有自己的名称空间; %s和%x的声明方式与#define相同。
最后,下面是如何使用独占开始条件匹配C风格代"号(quoted)字符串的示例,其中包括扩展转义序列(但不包括检查字符串是否太长):
%x str
%%
char string_buf[MAX_STR_CONST];
char *string_buf_ptr;
\" string_buf_ptr = string_buf; BEGIN(str);
<str>\" { /* saw closing quote - all done */
BEGIN(INITIAL);
*string_buf_ptr = '\0';
/* return string constant token type and
* value to parser
*/
}
<str>\n {
/* error - unterminated string constant */
/* generate error message */
}
<str>\\[0-7]{1,3} {
/* octal escape sequence */
int result;
(void) sscanf( yytext + 1, "%o", &result );
if ( result > 0xff )
/* error, constant is out-of-bounds */
*string_buf_ptr++ = result;
}
<str>\\[0-9]+ {
/* generate error - bad escape sequence; something
* like '\48' or '\0777777'
*/
}
<str>\\n *string_buf_ptr++ = '\n';
<str>\\t *string_buf_ptr++ = '\t';
<str>\\r *string_buf_ptr++ = '\r';
<str>\\b *string_buf_ptr++ = '\b';
<str>\\f *string_buf_ptr++ = '\f';
<str>\\(.|\n) *string_buf_ptr++ = yytext[1];
<str>[^\\\n\"]+ {
char *yptr = yytext;
while ( *yptr )
*string_buf_ptr++ = *yptr++;
}
通常,比如在上面的一些例子中,你写了一大堆规则,所有的规则之前都有相同的开始条件。 通过引入启动条件范围的概念,Flex使这一点变得更简单和更清晰。 开始条件范围开始于:
<SCs>{
其中SC是一个或多个起始条件的列表。 在起始条件范围内,每个规则都会自动应用前缀'<SCs>',直到与最初的'{'匹配的'}'为止。 所以,例如,
<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';
开始条件范围可以嵌套。
有三个例程可用于操作堆栈的启动条件:
`void yy_push_state(int new_state)'
将当前的开始条件推到开始条件堆栈的顶部,并切换到new_state,就像您使用过`BEGIN new_state'(回想起始条件名称也是整数)一样。
`void yy_pop_state()'
弹出堆栈顶部并通过BEGIN切换到它。
`int yy_top_state()'
返回堆栈的顶部而不更改堆栈的内容。
开始条件堆栈动态增长,因此没有内置的大小限制。 如果内存耗尽,程序执行将中止。
要使用开始条件堆栈,您的扫描器必须包含一个'%option stack'指令(请参阅下面的选项)。
多输入缓冲
暂时略
文件结束规则
特殊规则“<< EOF >>”表示当遇到文件结束并且yywrap()返回非零(即,指示没有要处理的进一步文件)时要采取的动作。 该行动必须完成以下四件事之一:
· 将yyin分配给一个新的输入文件(在之前的flex版本中,在完成分配之后,您必须调用特殊操作YY_NEW_FILE;这不再需要);
· 执行返回语句;
· 执行特殊的`yyterminate()'动作;
· 或者,使用`yy_switch_to_buffer()'切换到新的缓冲区,如上例所示。
“EOF”规则可能不适用于其他模式; 他们可能只能通过开始条件的列表进行限定。 如果给出了非限定的<< EOF >>规则,它适用于所有尚未具有<< EOF >>操作的开始条件。 要仅为初始启动条件指定<< EOF >>规则,请使用
<INITIAL><<EOF>>
这些规则对于捕捉未封闭的注释等内容非常有用。 一个例子:
%x quote
%%
...other rules for dealing with quotes...
<quote><<EOF>> {
error( "unterminated quote" );
yyterminate();
}
<<EOF>> {
if ( *++filelist )
yyin = fopen( *filelist, "r" );
else
yyterminate();
}
各种各样的宏
宏YY_USER_ACTION可以被定义为提供一个总是在匹配规则的动作之前执行的动作。 例如,它可以#define调用例程来将yytext转换为小写。 当调用YY_USER_ACTION时,变量yy_act给出匹配规则的编号(规则从1开始编号)。 假设你想描述你的每个规则匹配的频率,可以这样:
#define YY_USER_ACTION ++ctr[yy_act]
其中ctr是一个数组,用于保存不同规则的计数。 请注意,宏YY_NUM_RULES给出了规则的总数(包括默认规则,即使使用'-s'也是如此,所以对于ctr的正确声明是:
int ctr[YY_NUM_RULES];
可以定义宏YY_USER_INIT以提供在第一次扫描之前(并且在扫描器的内部初始化完成之前)总是执行的动作。例如,它可以用来调用例程来读取数据表或打开日志文件。
宏`yy_set_interactive(is_interactive)'可用于控制当前缓冲区是否被认为是交互式的。交互式缓冲区处理速度较慢,但必须在扫描仪的输入源确实是交互式时使用,以避免因等待填充缓冲区而产生问题(请参阅下面对“-I”标志的讨论)。宏调用中的非零值将缓冲区标记为交互式,即非交互式的零值。请注意,使用此宏将覆盖`%option always-interactive'或'%option never-interactive'(请参阅下面的选项)。必须在开始扫描缓冲区之前调用`yy_set_interactive()',否则缓冲区将被视为交互式。
宏`yy_set_bol(at_bol)'可用于控制当前缓冲区的下一个符号匹配的扫描上下文是否像在行首一样完成。一个非零的宏观参数使规则与锚定在一起。
如果从当前缓冲区扫描的下一个标记将具有活动的'^'规则,则宏'YY_AT_BOL()'返回true,否则返回false。
在生成的扫描程序中,这些操作都收集在一个大的switch语句中,并使用YY_BREAK进行分隔,这可能会被重新定义。默认情况下,它只是一个“休息”,将每个规则的操作与以下规则分开。例如,重新定义YY_BREAK允许C ++用户#定义YY_BREAK什么都不做(尽管非常小心每个规则以“break”或“return”结尾!),以避免由于规则的行为结束而导致无法访问的语句警告“返回”时,YY_BREAK无法访问。
用户可用的值
本节总结了规则操作中用户可用的各种值。
· `char * yytext'保存当前token的文本。它可能会被修改但不能被延长(您不能将字符追加到最后)。如果特殊指令'%array'出现在扫描器描述的第一部分,那么yytext被声明为'char yytext [YYLMAX]',其中YYLMAX是一个宏定义,如果你没有,你可以在第一部分重新定义像默认值(一般为8KB)。使用`%array'会导致扫描仪速度稍慢,但yytext的值不会受到对input()和unput()的调用的影响,当yytext是字符指针时,它可能会破坏它的值。 `%array'的对面是'%pointer',这是默认值。生成C ++扫描程序类(` - +'标志)时,不能使用`%array'。
· `int yyleng'保存当前token的长度。
· `FILE * yyin'是默认flex从中读取的文件。它可能被重新定义,但这样做只有在扫描开始之前或在遇到EOF之后才有意义。由于flex会缓冲其输入,所以在扫描过程中更改它将会产生意想不到的结果;改用`yyrestart()'。一旦扫描终止,因为已经看到文件结束,您可以在新输入文件中指定yyin,然后再次调用扫描仪以继续扫描。
· 可以调用`void yyrestart(FILE * new_file)'来将yyin指向新的输入文件。切换到新文件是立即的(任何以前缓冲的输入都会丢失)。请注意,将yyin作为参数调用`yyrestart()'会丢弃当前输入缓冲区,并继续扫描相同的输入文件。
· `FILE * yyout'是'ECHO'动作完成的文件。它可以由用户重新分配。
· YY_CURRENT_BUFFER返回当前缓冲区的YY_BUFFER_STATE句柄。
· YY_START返回一个对应于当前开始条件的整数值。随后可以使用此值与BEGIN返回到该起始条件。
和yacc的交互
flex的主要用途之一是作为yacc parser生成器的伴侣。 yacc的parser希望调用一个名为`yylex()'的例程来查找下一个输入的token。 该例程应该返回下一个token的类型并将任何关联值放到全局的yylval中。 为了在yacc中使用flex,我们为yacc指定了'-d'选项来指示它生成文件'y.tab.h',其中包含出现在yacc输入中的所有`%tokens'的定义。 这个文件被包含在flex扫描仪中。 例如,如果其中一个token是“TOK_NUMBER”,则扫描程序的一部分可能如下所示:
%{
#include "y.tab.h"
%}
%%
[0-9]+ yylval = atoi( yytext ); return TOK_NUMBER;
可选项
略
关于性能的考量
略
生成C++扫描器
暂时略
和lex以及POSIX不兼容的地方
略
诊断
这个部分列出的是编译时可能出现的错误,略。
文件
`-lfl”
与扫描仪必须链接的库。
`lex.yy.c”
生成的扫描仪(在某些系统中称为“lexyy.c”)。
`lex.yy.cc”
生成C ++扫描器类,当使用' - +'时。
`<FlexLexer.h>”
头文件定义了C ++扫描器基类FlexLexer及其派生类yyFlexLexer。
`flex.skl”
骨架扫描仪。 该文件仅在构建flex时使用,而不是在执行flex时使用。
`lex.backup”
“-b”标志的备份信息(在某些系统中称为“lex.bck”)。
Bugs
略。
另外可参见
略。
作者
对作者们表示感谢,但是这里也略了。