4 Some Simple Examples
首先,通过一些简单的示例来了解使用flex。
以下flex输入指定了一个扫描程序scanner,当它遇到字符串’username’将其替换为用户的登录名:
1 | %% |
默认情况下,任何与flex scanner不匹配的文本都将被复制到输出中,因此,该scanner的最终效果是仅将每次出现的用户名替换了。
在此输入中,只有一个规则(rule)。’username’是模式(pattern),而’print’就是action。’%%’符号标志着rules的开始。
另一个简单的例子
1 | int num_lines = 0, num_chars = 0; |
该scanner计算其输入中的字符数和行数。除了有关字符数和行数的最终报告外,它不产生任何输出。
第一行声明了两个全局变量,num_lines和num_chars,在第二个%%之后声明的yylex()的main()例程都可以访问它们。
有两个规则(rule),一个匹配换行符(‘\n’),并同时增加行数和字符数。
另一个匹配除了换行符之外的任何字符(通过.正则表达式)
看一个更复杂的例子。
1 | /* scanner for a toy Pascal-like language */ |
这是针对Pascal等语言的简单scanner的开始。它标识不同类型的token并报告所见内容。
以下部分将说明此示例的详细信息。
5 Format of the Input File
flex输入文件包括三部分,由%%分开。
1 | definitions |
5.1 Format of the Definitions Section
该定义部分包含了简单的Name definitions的声明,以及start conditions的声明,这将在后面的章节解释。
Name definitions有如下形式
1 | name definition |
name是一个以字母或下划线开头的单词,然后是零个或多个字母,数字,’_’,或者’-‘(破折号)。
definition从名称后的第一个非空白字符开始,一直到该行的末尾。该definition随后可以使用{definition},它将扩展为(definition)。例如,
1 | DIGIT [0-9] |
定义“数字“是与一位数字匹配的正则表达式,而’ID’是一个正则表达式,它匹配一个字母,后跟零个或多个字母及数字。
1 | {DIGIT}+"."{DIGIT}* |
等价于
1 | ([0-9])+"."([0-9])* |
其可以匹配一个或多个数字,后跟一个’.’,然后跟零个或多个数字。12.34
不缩进的注释(以/*
开头的行)逐字复制到输出,直到遇到下一个*/
。
任何缩进文本,或者包括在%{
和%}
之中的也将逐字复制到输出中(移除%{和%}
符号),%{和%}
符号本身必须在行上没有缩进。%top
块是类似于%{和}%
的,但它将块中的代码重定位到生成的文件的顶部(在所有flex定义之前),%top
在当您要定义某些预处理器宏或在生成的代码之前包含某些文件时,该块很有用。单个字符{
和}
用于分隔%top
块,如以下示例所示:
1 | %top{ |
%top
允许多个块,并保留其顺序。
5.2 Format of the Rules Section
Flex输入的 rules section 对以下表格有一系列的规则:
pattern | action |
---|---|
其中 pattern 必须是不缩进的,action 必须开始在同一行。 有关 patterns 和 actions 的进一步描述,请参见Patterns。
在rules section中,出现在第一个rule之前的任何缩进或%{ %}
括号内的文本都可用于声明scanning routine的局部变量和每次进入scanning routine时执行的(声明之后的)代码。
rules section中的其他缩进文本或%{ %}
文本仍然复制到输出中,但其含义没有良好定义,并且很可能导致编译时错误(这个特性是为了符合 POSIX 要求。查看 Lex 和 Posix,以获得其他此类特性)。
%{
和 %}
中包含的任何缩进文本或文本都会被逐字复制到输出中(删除了%{
和%}
符号)。 %{
和%}
符号本身必须在该行没有缩进。
5.3 Format of the User Code Section
用户代码部分仅逐字复制到lex.yy.c。它作为scanner的辅助函数使用。此部分的出现是可选的;如果不存在,则输入文件中的第二个”%%”可以被省略。
5.4 Comments in the Input
Flex支持C风格的注释,即:介于/ *
和* /
之间的任何内容都被认为是注释。Flex遇到注释时,会将整个注释逐字复制到生成的源代码中。注释可能出现在任何地方,但有以下例外情况:
- 在需要正则表达式的 flex 中,注释可能不会出现在 Rules 部分。 即:注释可能不会出现在一行的开头,或紧跟在scanner states列表之后。
- 注释不能出现在 Definitions 部分的
%option
行上。
如果您希望遵循一个简单的规则,那么始终在新行上开始注释,在开始的/*
之前使用一个或多个空格字符。 此规则适用于输入文件的任何位置。下面例子中的所有注释都是有效的:
1 | %{ |
6 patterns
输入里的patterns是根据一个正则表达式扩展集合来写的:
x
:匹配字符
x
.
:匹配任意字符(1字节),除了换行符
[xyz]
:单个字符类;在该case,表示匹配x或y或z
[abj-oZ]
:具有范围的字符类;在该case表示匹配a或b,或在j-o之中选择一个匹配,或者匹配一个Z
[^A-Z]
:否定字符类,即除该类之外的任何字符;在该case表示不匹配大写字母
[^A-Z\n]
:不匹配大写字母和回车
[a-z]{-}[aeiou]
:匹配除了元音字母之外的字符
r*
r是一个正则表达式, 匹配零次或多次
r+
匹配一次或多次。
r?
匹配零次或一次。
r{2,5}
{n,m}, m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。
r{2,}
{n,}, n 是一个非负整数。至少匹配n 次。
r{4}
{n}, n 是一个非负整数。匹配确定的 n 次。
{name}
name定义的扩展,查看format
‘“[xyz]"foo”’
the literal string: ‘[xyz]”foo’
‘\X’
if X is ‘a’, ‘b’, ‘f’, ‘n’, ‘r’, ‘t’, or ‘v’, then the ANSI-C interpretation of ‘\x’. Otherwise, a literal ‘X’ (used to escape operators such as ‘*’)
\0
匹配NULL(ascii code 0)
\123
匹配八进制值为123的字符
\x2a
匹配16进制值为2a的字符
(r)
match
r
;括号用于提高优先级(?r-s:pattern)
:之后的是使用的pattern,而使用r并略去s对pattern进行解释
r和s这两个参数可以为空或者i
s
x
i表示大小写不敏感 -i表示大小写不敏感
s表示通过.
匹配单字节的任意字符, -s表示.
匹配\n之外的任何字节(译者注:其实就是用s来指明.
的意思)
x会忽略注释和空白符,除非空格被反斜杠转义,或者包含在””中或者出现在前面所说的字符类里,否则将被忽略
以下表达式都是合法的1
2
3
4
5
6
7
8
9
10
11
12
13
14(?:foo) same as (foo)
(?i:ab7) same as ([aA][bB]7)
(?-i:ab) same as (ab)
(?s:.) same as [\x00-\xFF]
(?-s:.) same as [^\n]
(?ix-s: a . b) same as ([Aa][^\n][bB])
(?x:a b) same as ("ab")
(?x:a\ b) same as ("a b")
(?x:a" "b) same as ("a b")
(?x:a[ ]b) same as ("a b")
(?x:a
/* comment */
b
c) same as (abc)(?# comment )
忽略任何在()里的字符
rs
将r和s两个正则表达式相串联 (不是很懂)
r|s
用r或s去匹配,并联的意思。
r/s
匹配r,但是r之后必须有s,确定此规则为最长匹配项时包括s,但是在返回文本时只返回r。^r
只在一行的开头去匹配r(即,刚开始扫描时或在扫描到换行符之后)r$
在一行的结尾去匹配r<s>r
在start condition为s的时候才用r匹配;<s1,s2,s3>r
start condition为s1或s2或s3时才用r匹配;
<*>r
在任何start condition都可以用r匹配
<<EOF>>
匹配EOF(文件结束)
<s1,s2><<EOF>>
在start condition为s1和s2的时候,匹配EOF
注意一下在字符集合里,所有的正则表达式的operator丢失了他们的特殊意义,除了’\‘ ‘-‘ ‘]]’和在集合前面的’^’
上面所有提到的正则表达式都被根据优先级从最高到最低来组织,那些被分组到一起的是具有相同优先级的(在–posix 遵从POSIX标准的参数的文档中关于repeat operator ‘{}’优先级的特殊标记) 例如:
1 | foo|bar* |
等价于
1 | (foo)|(ba(r*)) |
因为*
操作符比串联有更高的优先级,串联比并联(|
)更高的优先级。
因此这个pattern匹配的是字符串”foo”或者”ba”后面跟着0或多个满足r的字符串
为了匹配’foo’或者0或多个”bar” 可以这么写
1 | foo|(bar)* |
或者为了匹配0个或多个”foo或bar”,可以写成这样
1 | (foo|bar)* |
{-}
这个是求差集的operator,他会计算两个character class的差集例如
[a-c]{-}[b-z]
会匹配在a-c里不在b-z里的字符{+}
是求并集的operator,他会计算两个character class的并集例如在“C”的运行环境中
[[:alpha:]]{-}[[:lower:]]{+}[q]
这个和[A-Zq]
是等价的一条规则最多可以包含一个尾随上下文实例(“/”运算符或“$”运算符)。起始条件“^”和“<
>”模式只能出现在模式的开头,并且不能与“/”和“$”一起放在括号内。规则开头没有出现的“^”或规则结尾没有出现的“$”将失去其特殊属性,并被视为普通字符。
以下表达式是不合法的:
第一句可以被写成这样foo/bar\n
1
2foo/bar$
<sc1>foo<sc2>bar以下情况$或者^会被忽略,当作一个普通字符
1 | foo|(bar$) |
如果你希望匹配的是’foo’或者’bar’后接一个新行,可以这么写,一个小trick将work。
1 | foo | |
7 How the Input Is Matched
生成的扫描程序(scanner)运行的时候,它会分析输入来寻找与模式(pattern)匹配的字符串。如果找到多个匹配字符串,它会匹配文本最多的那一个(for trailing context rules,包括trailing部分的长度)。如果找到多个长度相同的匹配字符串,则按照flex输入文件中最先列出的规则选择。
一旦确定匹配,就在全局字符指针yytext中提供与该匹配相对应的文本(称为token),并在全局int变量yyleng中提供长度。然后执行与匹配模式(pattern)相对应的操作(action),然后扫描剩余的输入寻找下一个匹配。
如果找不到匹配,则执行默认规则:下一个输入的字符将被视为匹配并复制到标准输出中。因此,最简单的有效flex输入是:
1 | %% |
其生成一个扫描程序(scanner)将输入(一次一个字符)简单地复制到输出。
注意yytext可以用两种方式定义:作为字符指针或者字符数组。你可以通过在你的flex输入的开头(定义)部分包含%pointer
或%array
中的一个来控制flex使用哪一个定义。
默认是%pointer
,除非使用'-l'
lex兼容性选项,在这种情况下yytext就是一个数组。使用%pointer
的好处是在匹配非常大的token时(除非您用尽了动态内存),扫描速度明显加快,并且没有缓冲区溢出,缺点是在修改yytext方面,你的action将受到限制(参考action),而且对unput()
函数的调用会破坏yytext的当前内容,这在不同lex版本之间移动的时候可能会有很大的麻烦。
使用%array
的好处在于你可以修改yytext为你想要的内容,而且调用unput()
不会破坏yytext。此外,现有的lex程序有时会使用以下形式的声明从外部访问yytext:
1 | extern char yytext[]; |
这个声明在使用%pointer
时是错误的,但在使用%array
时是正确的。
%array
声明将yytext定义为YYLMAX个字符的数组,YYLMAX默认为相当大的值,你可以在flex输入的第一部分中简单的#define YYLMAX为另一个值来更改它的大小。如上所述,使用%pointer
时yytext会动态地增长来容纳很大的token。即你的%pointer
扫描程序(scanner)可以容纳非常大的token(例如匹配整个注释块),但请记住每次扫描器必须重新调整yytext的大小时,还必须从头开始重新扫描整个token,因此匹配这种token可能会很慢。
如果调用unput()导致太多文本被push back,则yytext目前不会动态增长,而是会导致运行时错误。
另外注意,不能将%array
与C++扫描程序(scanner)类一起使用(参考Cxx)。
8 Actions
规则中的每个pattern都有一个相应的action, 这些action可以是任意的c语言.
Pattern以第一个非转义的空字符结束; 这一行剩下的部分就是它的action.
如果这个action是空的,那么当pattern进行匹配时, 它的input token就会被简单的丢弃.
例如, 下面是一个程序的rule, 它从输入中删除了所有出现的”zap me”:
1 | %% |
这个示例将输入中的所有其他字符复制到输出中, 因为这些其他字符将由默认规则匹配.
下面是一个程序, 它将多个空格和制表符(tabs)压缩到单个的空白(blank), 并丢弃行尾的空格:
1 | %% |
如果这个action包含"{"
, 那么该action将一直持续到找到"}"
, 并且这个操作可能会跨越几行.
flex明白C的strings和注释, 所以flex不会因为字符串和注释里面的大括号而上当. 并且flex允许action以'%{'
开头的操作,并将该action视为"%}"
之前的所有文本(而不管action中出现的普通大括号).
一个仅包括竖线"|"
的action表示与下一个action的规则(rule)相同。可见于下面的例证.
Actions可以包括任意的C代码,包括return
语句–将一个值返回给任何调用yylex()
的程序.每次调用yylex()
,它将从上次中断的地方继续处理token,直到文件的末尾或者执行返回.
Actions可以自由地修改yytext
,除了延长它的长度(向其末尾增加字符––这样会覆盖输入流中后面的字符). 但是,使用%array
的情况不适用于此(请参考Matching).在这种情况下,可以任意修改yytext
.
Actions可以自由地修改yyleng
,但是如果action还包括yymore()
的使用,则不应该修改yyleng
(见下文).
这里有许多特殊的指令(directives),这些指令可以被用于action中:
ECHO
拷贝yytext到scanner的输出BEGIN
紧随其后的是开始条件的名称,将scanner置于相应的开始条件中(见下文)yymore()
告诉scanner,下次它匹配规则时,应该将对应的token加到yytext
的当前值上,不是替换它.例如,给定输入为'mega-kludge'
,下面的输出将写入mega-mega-kludge’
:1
2
3%%
mega- ECHO; yymore();
kludge ECHO;这是关于
yymore()
的两个说明.
首先,yymore()
取决于yyleng
的值,它正确的反应了当前token的大小,所以如果使用yymore()
则不能修改yyleng
.
其次,scanner的action中存在yymore()
会对scanner的匹配速度造成轻微的性能损失.yyless(n)
除了当前token的前n个字符,将剩下的字符返回到输入流,当scanner查找下一个匹配时,这些字符将被重新查找.对于yytext
和yyleng
做了一些适当地调整(例如,yyleng
现在等于n).例如,在输入'foobar'
,下面将输出'foobarbar'
:1
2
3%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;将0作为参数传入
yyless()
将导致再次扫描当前输入的字符串.除非您已经改变了scanner处理输入的方式(例如使用了BEGIN
),否则这将导致一个死循环.
注意,yyless()
是一个宏,只能在flex输入文件中使用,而不能从其他源文件中使用.unput(c)
将字符c
放回输入流.这将是下一个字符扫描.下面的action将使用当前的token,进行重新扫描,并用括号闭合。
1 | { |
注意,由于每个unput()
都将给定的字符放回输入流的开头,所以必须由后向前插入字符串.
使用unput
时的一个重要的潜在问题是,如果使用%pointer
(默认),调用unput()
会破坏yytext
的内容,从其最右边的字符开始,并在每次调用时向左吞噬一个字符。如果你需要在调用unput()
后保留yytext
的值(如上面的示例所示),则必须先将它复制到其他地方,或者使用%array
构建scanner(参见Matching).
最后,请注意,不能将'EOF'
插入(push back)来尝试用文件结束来标记输入流.
- input()
从输入流读取下一个字符.例如,以下是清除C注释的一种方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25%%
"/*" {
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;
}
}
}
(请注意,如果scanner是使用c++编译的,那么input()
将被替换为yyinput()
,以避免产生与c++流名字冲突的情况.
YY_FLUSH_BUFFER
刷新scanner的内部缓冲区,以便下次scanner尝试匹配token,它将首先使用YY_INPUT()
填充缓冲区(参见Generated Scanner.这个action是比yy_flush_buffer
函数更通用的特例.如下所述(参见 Multiple Input Buffers)yyterminate()
可以用来代替action中的返回语句.它终止scanner并将0返回给scanner的调用者,指示”全部完成”.默认情况下,当遇到文件结束,也会调用yyterminate()
.它是一个宏,可以重新定义.
9 The Generated Scanner
flex的输出是lex.yy.c,包括扫描程序(scanning routine) yylex(),许多用于匹配token的表,一些辅助函数和宏定义。
默认yylex()函数被声明如下
1 | int yylex() |
但是我们可以通过一个宏定义来改变它。
1 | #define YY_DECL float lexscan( a, b ) float a, b; |
这代表另scanning routine名为lexscan,并包括两个float类型的参数和返回一个float类型的变量。
此外flex创建的程序遵循c99标准。
每次yylex调用,都会从全局输入yyin(默认为stdin)中顺序扫描token,直到到达文件末尾(此时返回0),或者遇到一个执行”return”语句的action。
如果scanner到达文件末尾,则后续调用是不确定的。
既可以将yyin指向新的输入文件(在这种情况下,扫描将从该文件继续进行),也可以调用yyrestart()函数。
yyrestart()接受一个参数,一个FILE *指针(这个指针可能是NULL,如果你已经设置了YY_INPUT宏从其他地方读取,而不是从yyin),然后它将初始化yyin,用于从这个文件(FILE *)继续扫描。
这两种方法之间基本上没有区别。
后者可以兼容早期版本的flex,因为它可以用于在扫描的过程中就切换输入文件;通过将yyin传递给yyrestart,调用这个函数,也可以用来丢弃当前的input buffer,但是最好还是使用YY_FLUSH_BUFFER。
请注意,yyrestart()不会将开始条件重置为INITIAL
如果yylex()由于在某个action上执行了return而停止扫描,则可以再次调用scanner,并且它将从中断处继续扫描。
默认情况下(为了提高效率),scanner使用块读取而不是简单的getc()调用来读取字符y,可以通过定义YY_INPUT宏来控制如何获取输入。
YY_INPUT() is YY_INPUT(buf,result,max_size),它用于在buf数组里放置最多max_size个字节,并且在整数变量result中,记录读取的字节数量或者YY_NULL(在Unix系统上值为0),YY_NULL是为了表示遇到EOF。
YY_INPUT默认从yyin中读取。
下面是一个简单的例子,在输入文件的define部分的示例定义。
1 | %{ |
此定义会将输入处理更改为一次读取一个字符。
当scanner从YY_INPUT接收到EOF时,它将使用yywrap()函数进行检查。如果yywrap()返回false(零),则假定该函数已进行设置y指向另一个输入文件,然后继续扫描。
如果返回true(非零),则scanner终止,并向其调用方返回0。
请注意,无论哪种情况,start condition均保持不变;它并没有恢复 INITIAL。
如果您没有提供自己的版本yywrap(),则必须使用%option noyywrap(在这种情况下,scanner的行为就像yywrap()返回1),或者您必须链接“-lfl’来获取默认的yywrap版本,该版本始终返回1。
关于从内存缓冲区扫描(例如 scanning string),在Scanning Strings和Multiple Input Buffers部分。
scanner将写入它的ECHO输出到yyout global(默认为stdout),用户只需将其分配给其他FILE指针即可重新定义。
10 Start Conditions
flex提供了有条件的激活规则机制,任何以<sc>
前缀的pattern,仅在scanner处于名为sc的开始状态时,才处于活动状态。
例如
1 | <STRING>[^"]* { /* eat up the string body ... */ |
这个pattern将被激活,仅当scanner处于STRING状态。
1 | <INITIAL,STRING,QUOTE>\. { /* handle an escape ... */ |
这个pattern将被激活,仅当scanner处于INITIAL,STRING或QUOTE状态
可以使用%s和%x来定义两种特殊的start condition。
一个开始条件被激活,通过BEGIN action。在执行下一个BEGIN action之前,具有给定开始条件的规则将处于活动状态,而具有其他开始条件的规则将处于非活动状态
包容性(inclusive)的启动条件
如果启动条件是inclusive的,则完全没有给出sc限定的规则也将处于活动状态。
排他性(exclusive)的启动条件
如果是排他性的,则只有符合开始条件的规则才是活动的。
一组基于相同排他开始条件的规则描述了一个扫描程序,该扫描程序独立于以下任何其他来自flex input的规则。
因此,排它的启动条件使指定”mini-scanners”变得容易,该”mini-scanners”将扫描输入中与其余语法(例如,注释)不同的部分。
如果上述描述有点模糊,考虑以下例子
1 | %s example |
等价于
1 | %x example |
如果没有<INITIAL,example>
限定符,则bar第二个示例中的pattern在启动条件处于example时,将不会处于活动状态(即无法匹配)。
但是,如果我们仅用<example>
限定条件bar,那么它将仅在处于example时被激活而不在处于INITIAL时被激活。
而在第一个示例中它同时在两个中都起作用。
(译者注:排他是在已经进入了某个sc时的排他,包容也是在某个sc里的包容)
还要注意,特殊的启动条件说明符<*>
匹配每个启动条件。因此,上面的示例也可以写成:
1 | %x example |
The default rule (to ECHO any unmatched character) remains active in start conditions. It is equivalent to:
1 | <*>.|\n ECHO; |
BEGIN(0)返回到没有开始条件被激活的初始状态。
此状态也可以称为INITIAL,因此BEGIN(INITIAL)等效于BEGIN(0)。(在开始条件名称周围的括号不是必需的,但是被认为是很好的样式)
BEGIN动作也可以在规则部分的开头以缩进代码的形式给出。例如,以下内容将导致scanner进入SPECIAL开始状态,当每次yylex()被调用且全局变量enter_special为true。
1 | int enter_special; |
为了说明开始条件的用法,下面是一个scanner,它提供了两种不同的字符串解释,例如’123.456’。
默认情况下,它将视为三个token,即整数’123’,点(‘.’)和整数’456’。
但是,如果该行字符串的前缀是’expect-floats’,它会将其视为单个令牌,即浮点数’123.456’:
1 | %{ |
下面是一个scanner,其可以在保持当前输入行计数的同时,识别并丢弃掉注释。
1 | %x comment |
请注意,起始条件名称实际上是以整数值存储。因此,上述内容可以通过以下方式扩展:
1 | %x comment foo |
此外,您可以使用整数值YY_START宏访问当前的start condition。例如,上面的分配comment_caller可以改为
1 | comment_caller = YY_START; |
Flex提供YYSTATE作为YY_START的别名(因为AT&T使用了它)。
最后,这是一个示例,说明如何使用排他的开始条件来匹配C样式的带引号的字符串,包括扩展的转义序列(但不包括检查过长的字符串):
1 | %x str |
通常,例如在上面的某些示例中,您最终要编写一堆规则,所有规则都以相同的开始条件开头。通过引入启动条件范围的概念,Flex使此操作变得更加轻松和简洁。起始条件范围始于:<SCs> {
其中<SCs>
是一个或多个开始条件的列表。在开始条件范围内,每个规则都会自动为其应用前缀<SCs>
,直到遇到匹配的“}“。因此,例如
1 | <ESC>{ |
等价于
1 | <ESC>"\\n" return '\n'; |
起始条件范围可以嵌套。
以下routines可用于操纵开始条件的堆栈:
void yy_push_state (int new_state)
将当前启动条件推送到启动条件堆栈的顶部,并切换到 new_state, 就好像您曾经使用过的一样 BEGIN new_state (请注意,启动条件名称也是整数)。void yy_pop_state ()
弹出堆栈的顶部,然后切换到堆栈的顶部BEGIN。int yy_top_state ()
返回堆栈的顶部而不更改堆栈的内容。
起始条件堆栈会动态增长,因此没有内置的大小限制。如果内存耗尽,程序将中止执行。
要使用开始条件堆栈,scanner必须包含一个%option stack指令(请参阅scanner选项)。
11 Multiple Input Buffers
一些scanner(例如支持“ include”文件的scanner)需要从多个输入流中读取。由于Flex扫描程序会进行大量缓冲,因此无法通过简单地重写对扫描上下文敏感的YY_INPUT()
来控制将从下一个输入读取的位置。YY_INPUT()
仅在扫描程序到达其缓冲区的末尾时才调用,这可能是在扫描诸如include语句之类的语句(会花费)很长的时间,(在这之后),该语句要求切换输入源。
为了解决这类问题,flex提供了一种创建多个输入缓冲区之间和切换的机制。输入缓冲区是通过使用以下命令创建的:
YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
参数是FILE指针和size,并创建与给定文件关联的缓冲区,缓冲区足够大以容纳size大小的字符(发生问题时,试试使用YY_BUF_SIZE
作为大小)。它返回一个YY_BUFFER_STATE
句柄,可以用来将其传递给其他例程(请参见下文)。YY_BUFFER_STATE
类型是指向opaque structure yy_buffer_state
结构的指针,因此您可以根据需要将YY_BUFFER_STATE
变量安全地初始化为((YY_BUFFER_STATE)0)
,还可以引用opaque structure以正确声明源文件中的输入缓冲区(而非源文件中的scanner)。请注意,调用yy_create_buffer
时的FILE指针仅用作YY_INPUT
看到的yyin
值。如果重新定义YY_INPUT()
使其不再使用yyin
,则可以安全地将NULL
FILE指针传递给yy_create_buffer
。
您可以使用以下方法选择要扫描的特定缓冲区:
void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer )
这个函数可切换扫描器的输入缓冲区,使得后续tokens将来自new_buffer。请注意,yywrap()
可以使用yy_switch_to_buffer()
来设置要继续扫描的内容,而不是打开新文件并用yyin
指向它。如果您正在寻找输入缓冲区的堆栈,那么您想使用yypush_buffer_state()
代替此函数。还要注意,通过yy_switch_to_buffer()
或yywrap()
切换输入源不会更改启动条件。
void yy_delete_buffer ( YY_BUFFER_STATE buffer )
用于回收与缓冲区关联的存储。 (缓冲区可以为NULL,在这种情况下例程不执行任何操作。)
您还可以使用以下方法清除缓冲区的当前内容:
void yypush_buffer_state( YY_BUFFER_STATE buffer )
该函数将新的缓冲区状态压入内部栈。压入的状态变为新的当前状态。栈由flex维护,并将根据需要增长。当您要更改状态时,应使用此函数代替yy_switch_to_buffer
,但保留当前状态以供以后使用。
void yypop_buffer_state ( )
此函数从栈顶部弹出当前状态,并通过调用yy_delete_buffer
删除它。堆栈中的下一个状态(如果有)将成为新的当前状态。
void yy_flush_buffer ( YY_BUFFER_STATE buffer )
此函数会丢弃缓冲区的内容,因此,下次扫描程序尝试从缓冲区中匹配token时,它将首先使用YY_INPUT()
重新填充缓冲区。
YY_BUFFER_STATE yy_new_buffer ( FILE *file, int size )
是yy_create_buffer()
的别名,用于兼容 C ++的new和delete用于创建和销毁动态对象。YY_CURRENT_BUFFER
宏将YY_BUFFER_STATE
句柄返回到当前缓冲区。不应将其用作左值。
这是使用这些功能编写扩展包含文件的scanner的两个示例(下面将讨论<< EOF >>功能)。
第一个示例使用yypush_buffer_state
和yypop_buffer_state
。 Flex在维护一个内部栈。
1 | /* the "incl" state is used for picking up the name |
下面的第二个示例执行与上一个示例相同的操作,但是手动管理其自己的输入缓冲区栈(而不是让flex进行操作)。
1 | /* the "incl" state is used for picking up the name |
以下例程可用于设置输入缓冲区以扫描内存中的字符串而不是文件。它们都创建了一个新的输入缓冲区来扫描字符串,并返回一个对应的YY_BUFFER_STATE
句柄(完成后应使用yy_delete_buffer()
删除)。还使用yy_switch_to_buffer()
切换到新缓冲区,因此对yylex()
的下一次调用将开始扫描这个字符串。
YY_BUFFER_STATE yy_scan_string ( const char *str )
扫描NULL结尾的字符串
YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len )
扫描len长度(包括NULL)的字符串
请注意,这两个函数都会创建并扫描字符串或字节的副本。 (这可能是可取的,因为yylex()
修改了它正在扫描的缓冲区的内容。)您可以使用以下方法避免复制:
YY_BUFFER_STATE yy_scan_buffer (char *base, yy_size_t size)
它将扫描从基址开始的缓冲区,该缓冲区由size大小字节组成,其最后两个字节必须为YY_END_OF_BUFFER_CHAR(ASCII NUL)
。最后两个字节不扫描。因此,扫描由base [0]到base [size-2]组成。
如果您无法以这种方式设置base(即忘记最后两个YY_END_OF_BUFFER_CHAR
字节),则yy_scan_buffer()
将返回NULL指针,而不是创建新的输入缓冲区。
Data type: yy_size_t
是整数类型,您可以将其转换为反映缓冲区大小的整数表达式。
12 End-of-File Rules
特殊规则<<EOF>>
指示遇到文件结尾符(end-of-file)和yywrap()
返回非零值时,要采取的action(即,表示没有其他要处理的文件)。该action必须通过执行以下任一action来完成:
- 分配 yyin 到新的输入文件(在的
flex
早期版本中 ,完成分配后,您必须调用特殊actionYY_NEW_FILE
。现在,这不再是必需的。) - 执行一条
return
语句; - 执行
yyterminate()
特殊action。 - 或者,如上例所示,用
yy_switch_to_buffer()
切换到新的缓冲区。
<<EOF>>
规则不得与其他pattern一起使用,他们可能只能用start condition进行限定。如果给出了未限定的<<EOF>>
规则,则该规则适用于它适用于尚未执行<<EOF>>
action的所有启动条件。。要只为初始开始条件指名<<EOF>>
规则,请使用:
1 | <INITIAL><<EOF>> |
这些规则对于捕获未封闭的注释(comments)等有用。例子如下:
1 | %x quote |
13 Miscellaneous Macros
YY_USER_ACTION
可以定义宏以提供始终在匹配规则的操作之前执行的操作。例如,可以使用#define’d
去调用一个routine以将yytext转换为小写,当YY_USER_ACTION
被调用,(规则编号从1开始)匹配的规则编号会保存在变量yy_act
中。如果你想知道每一个规则的匹配频率,请看下面
1 | #define YY_USER_ACTION ++ctr[yy_act] |
ctr
是一个数组,用于保存不同规则的计数结果。请注意,YY_NUM_RULES
宏命令给出了规则总数(包括默认规则),即使你使用 ‘-s)’,所以,正确的ctr
声明是:
1 | int ctr[YY_NUM_RULES]; |
YY_USER_INIT
可以定义宏以提供始终在第一次扫描之前(以及在完成扫描器的内部初始化之前)执行的操作。例如,它可以被用来调用一个例程(routine )来读入数据或者打开一个日志文件。
宏yy_set_interactive(is_interactive)
可以被用来控制当前缓冲区是否被认为是交互式的。一个交互式的缓冲区处理速度较慢,但是当扫描器的输入源是交互式时,必须使用交互式缓冲区,以避免由于等待填充缓冲区而引出的问题(请参阅 Scanner Options 一文中关于‘-I’ flag的讨论)。
宏调用中的非零值会将缓冲区标记为交互式,零值记为非交互式的。请注意,这个宏的使用将覆盖%option always-interactive
和 %option never-interactive
(参阅 Scanner Options)。`yy_set_interactive必须在开始扫描交互式(或者非交互式)缓冲区之前调用。
The macro yy_set_bol(at_bol) can be used to control whether the current buffer’s scanning context for the next token match is done as though at the beginning of a line. A non-zero macro argument makes rules anchored with ‘^’ active, while a zero argument makes ‘^’ rules inactive.
如果从当前缓冲区扫描的下一个token将启用“^”规则,则宏YY_AT_BOL()返回true,否则返回false。
在生成的扫描程序中,所有actions都收集在一个大的switch语句中,并使用YY_BREAK
分开,YY_BREAK
可以被重新定义。
默认情况下,它只是一个break
,用于将每个规则的action与后面规则的action分开。
允许对YY_BREAK
重新定义,例如,C++用户可以通过#define YY_BREAK 来让YY_BREAK不执行任何操作(要非常小心,每个规则都需要以一个break
或一个return
结尾!),以避免遇到提示编译warnning(unreachable statement),因为规则的action以return
的话,则YY_BREAK
无法访问到。
14 Values Available To the User
本节总结了在rule actions下,可供用户使用的一些值:
char *yytext
维护当前token的文本信息,它可以被修改,但是不能加长,即不能在末尾添加字符。
如果特殊的directive %array出现在scanner description的first section,那么
yytext
将被声明为char yytext[YYLMAX]
.YYLMAX
是一个宏定义,默认值为8KB
,你可以在first section重定义它的大小。 使用%array
会导致scanner的速度稍慢一些,但是yytext
的值不受unput()
调用的影响。当yytext
是字符指针时,unput()
可能会破坏其值。与%array相对的是%pointer,%pointer是默认设置。
生成c++ scanner(开启“-+”flag)时,不能使用%array。int yyleng
保存当前token的长度
FILE *yyin
是默认情况下flex读取的文件。它可以重新定义,但只有在开始扫描之前或遇到
EOF
之后才有意义。在扫描过程中更改它会产生意外结果,因为flex会缓存其输入。当由于遇到EOF
而终止扫描后,可以重新分配yyin
指向新的输入文件,然后再次调用scanner以继续扫描。void yyrestart( FILE *new_file )
可以将
yyin
指向新的输入文件。立即切换到新文件(任何先前缓存的输入都将丢失)。请注意,使用yyin
作为参数调用yyrestart()
会丢弃当前的输入缓冲区(input buffer),并继续扫描相同的输入文件。FILE *yyout
是执行ECHO操作的文件。可以由用户重新定义。
YY_CURRENT_BUFFER
返回
YY_BUFFER_STATE
句柄到当前的缓冲区YY_START
返回与当前开始条件相对应的整数值。随后,您可以将这个值与BEGIN一起使用以返回到该开始条件。
15 Interfacing with Yacc
flex的主要用途之一是与yacc解析器生成器一起使用。 yacc解析器应当调用yylex()
来查找下一个输入token。yylex应返回下一个token的类型,并将所有关联的值放入全局变量yylval
中。
要将flex与yacc一起使用,请为yacc指定“-d”选项,来生成文件y.tab.h,其中包含所有出现在yacc输入中的%token
的定义。
然后,将此文件包含在Flex scanner中。例如,如果token之一是TOK_NUMBER
,则scanner的一部分可能看起来像:
1 | %{ |
16 Scanner Options
//todo
17 Performance Considerations
//todo
18 Generating C++ Scanners
重要:当前的扫描类的形式是实验性的,并且在各主要版本中有较大的不同。
flex提供两种不同的方式来生成用于C++的scanner。第一种方式就是简单的编译一个由flex生成的scanner,scanner由C++编译而不是C编译。你应该不会遇到任何编译错误(有就查看错误报告)。你可以在rule actions中使用C++代码而不是C代码。注意scanner的默认输入源仍然是 yyin, 默认回显仍然是 yyout。这两者都是 FILE* 变量,而不是C++流。
你也可以使用flex去生成一个C++ scanner类,使用’-+’选项(或者,相等的,%option c++),如果flex可执行文件的名称以”+”结尾,则会自动指定,比如 flex++。
当使用此选项时,flex默认将扫描程序生成为文件lex.yy.cc 而不是 lex.yy.c。生成的扫描程序包括头文件 FlexLexer.h,该文件定义了两个C++类的接口。
在FlexLexer.h中的第一个类是FlexLexer,它提供定义基本扫描程序类接口的抽象基类。它提供以下成员函数:
const char* YYText()
返回最近匹配的token的文本,与yytext等效。
int YYLeng()
返回最近匹配的token的长度,与yyleng等效。
int lineno() const
返回当前输入行号(参考 %option yylineno),如果未使用 %option yylineno,则返回1。
void set_debug( int flag )
设置scanner的调试flag,等效于分配给 yy_flex_debug(参考Scanner Options),注意必须使用%option debug来构建扫描程序,才能在其中包含调试信息。
int debug() const
返回调试标志的当前设置。
还提供了等效于yy_switch_to_buffer(),yy_create_buffer()(尽管第一个参数是istream&对象的引用,而不是FILE *),yy_flush_buffer(),yy_delete_buffer()和yyrestart()(第一个参数依旧是istream&对象的引用)的成员函数。
在FlexLexer.h中的第二个类是yyFlexLexer,它是从FlexLexer派生的。它定义了以下附加成员函数:
yyFlexLexer( istream* arg_yyin = 0, ostream* arg_yyout = 0 )
yyFlexLexer( istream& arg_yyin, ostream& arg_yyout )
使用给定的输入和输出流构造yyFlexLexer对象。如果未指定,则流分别默认为cin和cout。yyFlexLexer不拥有其流参数的所有权。用户有责任确保所指向的流至少在yyFlexLexer实例中保持有效。
virtual int yylex()
yylex()和原始的flex scanner起着相同的作用:它会扫描输入流并消耗令牌(tokens),直到rule的action返回一个值。如果你从yyFlexLexer派生一个子类S并想要在yylex()里访问S的成员函数和变量,则需要使用%option yyclass =”S”通知flex您将使用该子类而不是yyFlexLexer。
在这种情况下,Flex不会生成yyFlexLexer::yylex(),而是会生成S::yylex()(并且还会生成一个dummy yyFlexLexer::yylex(),如果调用它,则会调用yyFlexLexer::LexerError。
virtual void switch_streams(istream* new_in = 0, ostream* new_out = 0)
virtual void switch_streams(istream& new_in, ostream& new_out)
重新分配yyin到new_in(如果非空),重新分配yyout到new_out(如果非空),如果重新分配yyin,则删除先前的输入缓冲区。
int yylex( istream* new_in, ostream* new_out = 0 )
int yylex( istream& new_in, ostream& new_out )
首先通过switch_streams(new_in,new_out)切换输入流,然后返回yylex()的值。
此外,yyFlexLexer定义了以下受保护的虚函数,您可以在派生类中重新定义它们以定制scanner:
virtual int LexerInput( char* buf, int max_size )
将最多max_size个字符读取到buf中,并返回读取的字符数。为了表示输入结束,返回0。
注意interactive scanner(参考”Scanner Options中的’ -B’和’-I’ flag)定义宏YY_INTERACTIVE。
如果重新定义LexerInput()并需要根据scanner是否正在扫描interactive input source而采取不同的操作,则可以通过#ifdef语句测试此名称的存在。
virtual void LexerOutput( const char* buf, int size )
从缓冲区buf中写出size个字符,如果scanner的rules可以匹配带有NUL的text,则该文本在NUL终止时也可能包含内部NUL。(?)
上句原句:writes outsize
characters from the bufferbuf
, which, whileNUL
-terminated, may also contain internalNUL
s if the scanner’s rules can match text withNUL
s in them.virtual void LexerError( const char* msg )
报告致命错误消息。该函数的默认版本将message写入流cerr并退出。
注意yyFlexLexer对象包含其整个扫描状态。因此,您可以使用此类对象创建可重入的scanner,但另请参考Reentrant。你可以实例化同一yyFlexLexer类的多个实例,并且你还可以使用上述的”-P”选项在同一程序中将多个C++ scanner类组合在一起。
最后,请注意%array功能不适用于C++扫描程序类,你必须使用%pointer(默认设置)。
这是一个简单的C++ scanner的示例:
1 | // An example of using the flex C++ scanner class. |
如果要创建多个(不同)词法分析器类,你可以使用”-P”标志(或者是prefix=选项), 将每个yyFlexLexer重命名为其他一些”xxFlexLexer”。然后你可以将<FlexLexer.h>
包含在你的其他每一个词法分析类(lexer class)源码中,首先按以下方式重命名yyFlexLexer:
1 | #undef yyFlexLexer |
例如,如果你为一台scanner使用了%option prefix =”xx”,而另一台则使用%option prefix =”zz”。
19 Reentrant C Scanners
//todo
20 Incompatibilities with Lex and Posix
//undo
21 Memory Management
//todo
22 Serialized Tables
//undo
23 Diagnostics
//todo
24 Limitations
//todo
25 Additional Reading
您可能希望阅读有关以下程序的更多信息:
- lex
- yacc
- sed
- awk
以下书籍可能包含感兴趣的材料:
John Levine,Tony Mason和Doug Brown的《Lex&Yacc》,O’Reilly和Associates。确保获得第二版。
ME Lesk和E.Schmidt的《LEX – Lexical Analyzer Generator》
Alfred Aho,Ravi Sethi和Jeffrey Ullman的《Compilers: Principles, Techniques and Tools》,Addison-Wesley(1986)描述flex(确定性有限自动机)使用的模式匹配技术。
Indices
http://westes.github.io/flex/manual/Indices.html#Indices
Reference
Lexical Analysis With Flex, for Flex 2.6.2
http://westes.github.io/flex/manual/