生成C++的词法生成器open in new window

翻译自1open in new window 对原文格式有一点小修改

实验性

目前扫描类(scanning class)的形式是实验性的,在主要版本之间可能会有很大的变化。

scanner/lexer 词法生成器/lexer 认定为同一个东西

flex 提供了两种不同的方式去生成C++代码的lexer。第一种方法是简单地使用 C++ 编译器而不是 C 编译器编译flex生成的lexer。你应该不会遇到任何编译错误(见报告错误open in new window。然后你可以在你的规则动作中使用C++代码而不是C代码。注意,你的scanner的默认输入源仍然是yyin,默认的echo仍然是对yyout进行的。这两个仍然是 FILE * 变量,而不是C++流(streams)。

你也可以使用flex生成一个C++scanner类,使用-+选项(或者,使用%option c++),如果flex可执行文件的名称以'+'结尾,例如flex++,则自动指定该选项。当使用该选项时,flex默认生成的scanner为lex.yy.cc文件,而不是lex.yy.c。生成的scanner包括头文件FlexLexer.h,它定义了两个C++类的接口。

FlexLexer.h

FlexLexer.h

  • FlexLexer: 提供了一个定义一般scanner类接口的抽象基类。
  • yyFlexLexer,派生于FlexLexer。定义了额外的成员函数

FlexLexer

提供了以下成员函数。

  • const char* YYText(): 返回最近匹配的标记的文本,相当于yytext。
  • int YYLeng(): 返回最近匹配的标记的长度,相当于yyleng。
  • int lineno() const: 返回当前的输入行数(见%option yylineno),如果没有使用%option yylineno,则返回1。
  • void set_debug( int flag ): 设置scanner的调试标志,相当于分配给yy_flex_debug(见scanner选项open in new window)。注意,你必须使用%option debug来构建scanner,以便在其中包含调试信息。
  • int debug() const: 返回当前调试标志的设置。

还提供了相当于yy_switch_to_buffer()yy_create_buffer()(尽管第一个参数是一个istream&对象引用,而不是一个FILE*)、yy_flush_buffer()yy_delete_buffer()yyrestart()(同样,第一个参数是一个istream&对象引用)的成员函数。

yyFlexLexer

定义了以下额外的成员函数。

yyFlexLexer( istream* arg_yyin = 0, ostream* arg_yyout = 0 );
yyFlexLexer( istream& arg_yyin, ostream& arg_yyout );
1
2

virtual int yylex() 执行与yylex()对普通flexscanner相同的作用:它扫描输入流,消耗token,直到规则的动作返回一个值。如果你从yyFlexLexer派生出一个子类S,并想在yylex()中访问S的成员函数和变量,那么你需要使用%option yyclass="S"来通知flex,你将使用该子类而不是yyFlexLexer。在这种情况下,flex不会生成yyFlexLexer::yylex(),而是生成S::yylex()

同时也会生成一个假的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);
1
2

重新分配yyinnew_in(如果非空)和yyoutnew_out(如果非空),如果yyin被重新分配,则删除之前的输入缓冲区。

int yylex( istream* new_in, ostream* new_out = 0 );
int yylex( istream& new_in, ostream& new_out );
1
2

首先通过switch_streams(new_in,new_out)切换输入流,然后返回yylex()的值。

:::detail yylex(new_int,new_out)

// 可能的实现
int yylex( istream* new_in, ostream* new_out = 0 ){
  switch_streams(new_in,new_out);
  return yylex();
}
int yylex( istream& new_in, ostream& new_out ){
  switch_streams(new_in,new_out);
  return yylex();
}
1
2
3
4
5
6
7
8
9

:::

此外,yyFlexLexer定义了以下受保护的虚函数,你可以在派生类中重新定义这些函数,以定制scanner。

protected

virtual int LexerInput( char* buf, int max_size );
1

向buf中读取最多max_size的字符,并返回读取的字符数。为了表示输入结束,返回0个字符。注意,交互式scanner(见scanner选项open in new window中的-B-I标志)定义了宏YY_INTERACTIVE。如果你重新定义了LexerInput(),并且需要根据scanner是否可能正在扫描一个交互式输入源而采取不同的行动,你可以通过#ifdef语句测试这个名字的存在。

virtual void LexerOutput( const char* buf, int size );
1

从缓冲区buf中写出size的字符,虽然是NUL结尾的,但如果scanner的规则可以匹配其中有NUL的文本,那么它也可能包含内部的NUL。

virtual void LexerError( const char* msg );
1

报告一个致命的错误信息。这个函数的默认版本将信息写入流cerr并退出。

TIP

yyFlexLexer对象包含其整个扫描状态。

因此你可以使用这样的对象来创建可重入的scanner,但也请看可重入(Reentrant)open in new window

你可以实例化同一个yyFlexLexer类的多个实例,你也可以在同一个程序中使用上面讨论的 -P 选项将多个C++ scanner类组合在一起。

最后,请注意,%array功能对 C++ scanner类是不可用的;你必须使用%pointer(这是默认的)。

example

下面是一个简单的C++scanner的例子。

// 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;
}
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

如果你想创建多个(不同的)scanner类,你可以使用-P标志(或者prefix=选项)将每个yyFlexLexer重命名为其他'xxFlexLexer'。然后,你可以在你的其他源中包含<FlexLexer.h>,每个lexer类一次,首先对yyFlexLexer进行重命名,如下所示。

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

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

例如,如果你对你的一个scanner使用%option prefix="xx",对另一个使用%option prefix="zz"