Flex中文文档

4 Some Simple Examples

首先,通过一些简单的示例来了解使用flex。
以下flex输入指定了一个扫描程序scanner,当它遇到字符串’username’将其替换为用户的登录名:

1
2
%%
username printf( "%s", getlogin() );

默认情况下,任何与flex scanner不匹配的文本都将被复制到输出中,因此,该scanner的最终效果是仅将每次出现的用户名替换了。
在此输入中,只有一个规则(rule)。’username’是模式(pattern),而’print’就是action。’%%’符号标志着rules的开始。
另一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        int num_lines = 0, num_chars = 0;

%%
\n ++num_lines; ++num_chars;
. ++num_chars;

%%

int main()
{
yylex();
printf( "# of lines = %d, # of chars = %d\n",
num_lines, num_chars );
}

该scanner计算其输入中的字符数和行数。除了有关字符数和行数的最终报告外,它不产生任何输出。
第一行声明了两个全局变量,num_lines和num_chars,在第二个%%之后声明的yylex()的main()例程都可以访问它们。
有两个规则(rule),一个匹配换行符(‘\n’),并同时增加行数和字符数。
另一个匹配除了换行符之外的任何字符(通过.正则表达式)

看一个更复杂的例子。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* scanner for a toy Pascal-like language */

%{
/* need this for the call to atof() below */
#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,
atof( yytext ) );
}

if|then|begin|end|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 whitespace */

. printf( "Unrecognized character: %s\n", yytext );

%%

int main( int argc, char **argv )
{
++argv, --argc; /* skip over program name */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;

yylex();
}

这是针对Pascal等语言的简单scanner的开始。它标识不同类型的token并报告所见内容。
以下部分将说明此示例的详细信息。

5 Format of the Input File

flex输入文件包括三部分,由%%分开。

1
2
3
4
5
definitions
%%
rules
%%
user code

5.1 Format of the Definitions Section

该定义部分包含了简单的Name definitions的声明,以及start conditions的声明,这将在后面的章节解释。
Name definitions有如下形式

1
name definition

name是一个以字母或下划线开头的单词,然后是零个或多个字母,数字,’_’,或者’-‘(破折号)。
definition从名称后的第一个非空白字符开始,一直到该行的末尾。该definition随后可以使用{definition},它将扩展为(definition)。例如,

1
2
DIGIT    [0-9]
ID [a-z][a-z0-9]*

定义“数字“是与一位数字匹配的正则表达式,而’ID’是一个正则表达式,它匹配一个字母,后跟零个或多个字母及数字。

1
{DIGIT}+"."{DIGIT}*

等价于

1
([0-9])+"."([0-9])*

其可以匹配一个或多个数字,后跟一个’.’,然后跟零个或多个数字。12.34
不缩进的注释(以/*开头的行)逐字复制到输出,直到遇到下一个*/
任何缩进文本,或者包括在%{%}之中的也将逐字复制到输出中(移除%{和%}符号),%{和%}符号本身必须在行上没有缩进。
%top块是类似于%{和}%的,但它将块中的代码重定位到生成的文件的顶部(在所有flex定义之前),%top在当您要定义某些预处理器宏或在生成的代码之前包含某些文件时,该块很有用。单个字符{}用于分隔%top块,如以下示例所示:

1
2
3
4
5
%top{
/* This code goes at the "top" of the generated file. */
#include <stdint.h>
#include <inttypes.h>
}

%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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
%{
/* code block */
%}

/* Definitions Section */
%x STATE_X

%%
/* Rules Section */
ruleA /* after regex */ { /* code block */ } /* after code block */
/* Rules Section (indented) */
<STATE_X>{
ruleC ECHO;
ruleD ECHO;
%{
/* code block */
%}
}
%%
/* User Code Section */

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
    2
    foo/bar$
    <sc1>foo<sc2>bar
  • 以下情况$或者^会被忽略,当作一个普通字符

1
2
foo|(bar$)
foo|^bar

如果你希望匹配的是’foo’或者’bar’后接一个新行,可以这么写,一个小trick将work。

1
2
foo      |
bar$ /* action goes here */

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
2
%%
"zap me"

这个示例将输入中的所有其他字符复制到输出中, 因为这些其他字符将由默认规则匹配.

下面是一个程序, 它将多个空格和制表符(tabs)压缩到单个的空白(blank), 并丢弃行尾的空格:

1
2
3
%%
[ \t]+ putchar( ' ' );
[ \t]+$ /* ignore this token */

如果这个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查找下一个匹配时,这些字符将被重新查找.对于yytextyyleng做了一些适当地调整(例如,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
2
3
4
5
6
7
8
9
10
{
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 );
}

注意,由于每个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
2
3
4
int yylex()
{
... various definitions and the actions in here ...
}

但是我们可以通过一个宏定义来改变它。

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
2
3
4
5
6
7
%{
#define YY_INPUT(buf,result,max_size) \
{ \
int c = getchar(); \
result = (c == EOF) ? YY_NULL : (buf[0] = c, 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
2
3
<STRING>[^"]*        { /* eat up the string body ... */
...
}

这个pattern将被激活,仅当scanner处于STRING状态。

1
2
3
<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
2
3
4
5
6
%s example
%%

<example>foo do_something();

bar something_else();

等价于

1
2
3
4
5
6
%x example
%%

<example>foo do_something();

<INITIAL,example>bar something_else();

如果没有<INITIAL,example>限定符,则bar第二个示例中的pattern在启动条件处于example时,将不会处于活动状态(即无法匹配)。
但是,如果我们仅用<example>限定条件bar,那么它将仅在处于example时被激活而不在处于INITIAL时被激活。
而在第一个示例中它同时在两个中都起作用。
(译者注:排他是在已经进入了某个sc时的排他,包容也是在某个sc里的包容)

还要注意,特殊的启动条件说明符<*>匹配每个启动条件。因此,上面的示例也可以写成:

1
2
3
4
5
6
%x example
%%

<example> foo do_something();

<*> bar something_else();

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
2
3
4
5
6
7
8
9
        int enter_special;

%x SPECIAL
%%
if ( enter_special )
BEGIN(SPECIAL);

<SPECIAL>blahblahblah
...more rules follow...

为了说明开始条件的用法,下面是一个scanner,它提供了两种不同的字符串解释,例如’123.456’。
默认情况下,它将视为三个token,即整数’123’,点(‘.’)和整数’456’。
但是,如果该行字符串的前缀是’expect-floats’,它会将其视为单个令牌,即浮点数’123.456’:

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
26
27
%{
#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" );

下面是一个scanner,其可以在保持当前输入行计数的同时,识别并丢弃掉注释。

1
2
3
4
5
6
7
8
9
10
%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);

请注意,起始条件名称实际上是以整数值存储。因此,上述内容可以通过以下方式扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%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宏访问当前的start condition。例如,上面的分配comment_caller可以改为

1
comment_caller = YY_START;

Flex提供YYSTATE作为YY_START的别名(因为AT&T使用了它)。

最后,这是一个示例,说明如何使用排他的开始条件来匹配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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
%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> {
其中<SCs>是一个或多个开始条件的列表。在开始条件范围内,每个规则都会自动为其应用前缀<SCs>,直到遇到匹配的“}“。因此,例如

1
2
3
4
5
6
<ESC>{
"\\n" return '\n';
"\\r" return '\r';
"\\f" return '\f';
"\\0" return '\0';
}

等价于

1
2
3
4
<ESC>"\\n"  return '\n';
<ESC>"\\r" return '\r';
<ESC>"\\f" return '\f';
<ESC>"\\0" return '\0';

起始条件范围可以嵌套。
以下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_stateyypop_buffer_state。 Flex在维护一个内部栈。

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
26
27
28
29
30
/* the "incl" state is used for picking up the name
* of an include file
*/
%x incl
%%
include BEGIN(incl);

[a-z]+ ECHO;
[^a-z\n]*\n? ECHO;

<incl>[ \t]* /* eat the whitespace */
<incl>[^ \t\n]+ { /* got the include file name */
yyin = fopen( yytext, "r" );

if ( ! yyin )
error( ... );

yypush_buffer_state(yy_create_buffer( yyin, YY_BUF_SIZE ));

BEGIN(INITIAL);
}

<<EOF>> {
yypop_buffer_state();

if ( !YY_CURRENT_BUFFER )
{
yyterminate();
}
}

下面的第二个示例执行与上一个示例相同的操作,但是手动管理其自己的输入缓冲区栈(而不是让flex进行操作)。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* the "incl" state is used for picking up the name
* of an include file
*/
%x incl

%{
#define MAX_INCLUDE_DEPTH 10
YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
%}

%%
include BEGIN(incl);

[a-z]+ ECHO;
[^a-z\n]*\n? ECHO;

<incl>[ \t]* /* eat the whitespace */
<incl>[^ \t\n]+ { /* got the include file name */
if ( include_stack_ptr >= MAX_INCLUDE_DEPTH )
{
fprintf( stderr, "Includes nested too deeply" );
exit( 1 );
}

include_stack[include_stack_ptr++] =
YY_CURRENT_BUFFER;

yyin = fopen( yytext, "r" );

if ( ! yyin )
error( ... );

yy_switch_to_buffer(
yy_create_buffer( yyin, YY_BUF_SIZE ) );

BEGIN(INITIAL);
}

<<EOF>> {
if ( --include_stack_ptr 0 )
{
yyterminate();
}

else
{
yy_delete_buffer( YY_CURRENT_BUFFER );
yy_switch_to_buffer(
include_stack[include_stack_ptr] );
}
}

以下例程可用于设置输入缓冲区以扫描内存中的字符串而不是文件。它们都创建了一个新的输入缓冲区来扫描字符串,并返回一个对应的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 %x quote
%%

...other rules for dealing with quotes...

<quote><<EOF>> {
error( "unterminated quote" );
yyterminate();
}
<<EOF>> {
if ( *++filelist )
yyin = fopen( *filelist, "r" );
else
yyterminate();
}

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
2
3
4
5
6
7
%{
#include "y.tab.h"
%}

%%

[0-9]+ yylval = atoi( yytext ); return TOK_NUMBER;

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 out size characters from the buffer buf, which, while NUL-terminated, may also contain internal NULs if the scanner’s rules can match text with NULs in them.

  • virtual void LexerError( const char* msg )
    报告致命错误消息。该函数的默认版本将message写入流cerr并退出。

注意yyFlexLexer对象包含其整个扫描状态。因此,您可以使用此类对象创建可重入的scanner,但另请参考Reentrant。你可以实例化同一yyFlexLexer类的多个实例,并且你还可以使用上述的”-P”选项在同一程序中将多个C++ scanner类组合在一起。

最后,请注意%array功能不适用于C++扫描程序类,你必须使用%pointer(默认设置)。
这是一个简单的C++ scanner的示例:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// An example of using the flex C++ scanner class.

%{
#include <iostream>
using namespace std;
int mylineno = 0;
%}

%option noyywrap c++

string \"[^\n"]+\"

ws [ \t]+

alpha [A-Za-z]
dig [0-9]
name ({alpha}|{dig}|\$)({alpha}|{dig}|[_.\-/$])*
num1 [-+]?{dig}+\.?([eE][-+]?{dig}+)?
num2 [-+]?{dig}*\.{dig}+([eE][-+]?{dig}+)?
number {num1}|{num2}

%%

{ws} /* skip blanks and tabs */

"/*" {
int c;

while((c = yyinput()) != 0)
{
if(c == '\n')
++mylineno;

else if(c == '*')
{
if((c = yyinput()) == '/')
break;
else
unput(c);
}
}
}

{number} cout << "number " << YYText() << '\n';

\n mylineno++;

{name} cout << "name " << YYText() << '\n';

{string} cout << "string " << YYText() << '\n';

%%

// This include is required if main() is an another source file.
//#include <FlexLexer.h>

int main( int /* argc */, char** /* argv */ )
{
FlexLexer* lexer = new yyFlexLexer;
while(lexer->yylex() != 0)
;
return 0;
}

如果要创建多个(不同)词法分析器类,你可以使用”-P”标志(或者是prefix=选项), 将每个yyFlexLexer重命名为其他一些”xxFlexLexer”。然后你可以将<FlexLexer.h>包含在你的其他每一个词法分析类(lexer class)源码中,首先按以下方式重命名yyFlexLexer:

1
2
3
4
5
6
7
#undef yyFlexLexer
#define yyFlexLexer xxFlexLexer
#include <FlexLexer.h>

#undef yyFlexLexer
#define yyFlexLexer zzFlexLexer
#include <FlexLexer.h>

例如,如果你为一台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/