flex
此篇不会讲述规则表达式,自从.net流行之后,大量的原本只是在unix才使用的规则表达式现在广泛使用在各种系统中。略.
1.内置变量
yy_create_buffer:见后面的缓冲管理
yy_delete_buffer:见后面的缓冲管理
yy_flex_debug:见后面的缓冲管理
yy_init_buffer:见后面的缓冲管理
yy_flush_buffer:见后面的缓冲管理
yy_load_buffer_state:见后面的缓冲管理
yy_switch_to_buffer:见后面的缓冲管理
yyin: 输入缓冲流的文件指针,可以被替换以实现解析某个自定义的文件
yyleng:当前匹配字串的长度
yylex: 解析函数,接口
yylineno:当前匹配的文件行号
yyout: 输出流的指针
yyrestart: 手动调用yyrestart.会重启解析
yyrestart( yyin );一般是打开某个文件之后,yyrestart(yyin)再解析.
yytext: 当前匹配的字串
yywrap: 解析一个文件完毕之后,会调用yywrap:返回1表示结束,0表示继续(此时最好重新打开yyin或者重置yyin流)
2. 几个重要函数:
1). yymore(): yymore()的含义是,当当前匹配的字串之后,想把后面配置的字串附加到这个字串后面,组成新的token返回.
比如:
%%
mega- ECHO; yymore();
kludge ECHO;
如果:“mega-kludge" the following will write "mega-mega-kludge" to the output。
为什么呢? 首先遇到 mega-,接着被more了一下,因此就会把kludga附加到mega-后面,而后面的kludge的动作又是打印,因此会打印出:mega-mega-kludge
2). yyless(): yyless()的含义是:当当前的匹配之后,我想只返回前面几个字符,并且把后面回退到输入
比如:
%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;
input "foobar" the following will write out "foobarbar":
为什么呢? foobar输入之后,匹配foobar,ECHO打印出来,接着yyless(3),则输入流变为bar了(yytext为foo).接着再匹配,于是匹配 到[a-z]+,因此再次打印出bar.
3).BEGIN: flex下一个起始解析状态。见第3节,flex的状态.
4).REJECT: 相当于拒绝此匹配,让系统重新找下一个匹配。
"abcd", it
will write "abcdabcaba" to the output:
%%
a |
ab |
abc |
abcd ECHO; REJECT;
.|\n /* eat up any unmatched character */
5).unput(c): 把c重新放到输入流。
6).input(): 读取输入流下一个字符
7).yyrestart(): 该函数迫使yylex重新解析。yyrestart有个函数指针流,可以再打开之后,重新使用yyrestart().
3. 解析源管理:
1). 默认是从yyin获取,而yyin则是stdout,也可以是其它文件。
if ( ! yyin )
yyin = stdin;
if ( ! yyout )
yyout = stdout;
2). 如果你打开了一个文件,并把yyin指向此文件,则从该文件中读取.比如:
在main中:
FILE* fp = NULL;
fp = fopen("hell.txt", "r");
yyin = fp;
之后再使用 yylex()
则flex从hell.txt中读取信息并解析.
3).从字符串中解析
先使用下列函数,转化缓冲,之后再使用 yylex()
a. yy_scan_string(char*).使用了yy_scan_string(char*)之后,flex会把char*放到yy的输入缓冲中(会调用到yy_switch_to_buffer.)
b. yy_scan_bytes(const char *base, int len);
c. yy_scan_buffer(char *base, yy_size_t size)
这几个函数内部都使用的是缓冲切换的创建等函数,见后面的章节.
4).利用EOF内置规则,重新打开多个文件输入:
比如:
<<EOF>> {
if ( *++filelist )
yyin = fopen( *filelist, "r" );
else
yyterminate();
}
5).多缓冲问题:
a.此问题可以按上面的 <<EOF>>或者 yywrap解决。
b.另外一种形式,比如:#include <iostream>或者类似于这种,这种形式的话,则不能使用yywrap,<<EOF>>来解决了。
这就需要用到在flex动作中手动切换缓冲。flex对每个缓冲有个缓冲输入流指针,指向当前位置,各个被切换的缓冲互不相干扰,这恰好很好地解决了文件包含另外一个文件,而子文件也许要yylex的这种场合.
这就需要使用到flex底层的缓冲管理了.见下节
4. flex的缓冲管理:
flex本质上都是对缓冲输入流进行yylex词法分析. 缓冲是个结构体,每个缓冲有个缓冲输入流指针,指向当前位置,各个被切换的缓冲互不相干扰,而相关yyin,yyrestart,yy_create_buffer,yy_scan_string系列函数都是操纵flex底层缓冲的.
flex缓冲是一个结构体:
我们以下面的词法规则为例子:(来自flex官方网站的注解)
/* 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就是一个缓冲。该lex文法使用到了<incl>,这个是状态,见4节的flex的状态管理.目前只需要知道它是个状态即可.在incl状态下才进行[ \t]*的规则匹配.
<<EOF>>见上面的描述,指某个输入流到了末尾则到了这个状态.
BEGIN(INITIAL)类似于BEGIN(0),表示状态从开头解析(0表示不带状态的解析,也就说规则前没有<>这个标记的状态.
上面的文法可以知道:
a. 在遇到include之后,跳到incl状态。
b. 在incl状态中,跳过空白的字符,得到文件名(include file),先把当前的flexBuffer保存到数组栈,然后打开新的文件,并把flex的当前输入流切换到刚打开的新文件的输入流.
c. 切换到INITIAL状态(没有<>在规则前的默认的状态)
d.这里用到了几个宏或者函数: yy_switch_to_buffer, yy_create_buffer,YY_CURRENT_BUFFER,yy_delete_buffer.
这些函数看名字就应该知道其作用了。
4. flex的状态(Start conditions).
上面的缓冲管理已经涉及到状态管理了。flex的状态管理相当于普通词法的扩展。通过flex的状态,大大扩充了词法分析本身的功能。
比如:
a. <STRING>[^"]* { /* eat up the string body ... */
...
}
表示在STRING状态下才进行 [^"]*匹配。
b. <INITIAL,STRING,QUOTE>\. { /* handle an escape ... */
...
}
表示在INITIAL,STRING,QUOTE才匹配。\.
flex的状态怎么使用呢?
a. 首先定义:以%开头(flex的申明本质上所有的都是以%开头)定义,有2种: %s,%x,其中%s = %x+INITIAL,也就说是%s的状态为%x定义的+INITIAL状态
b. 在规则域中,使用<状态>规则,比如 comment是个状态,则有 <comment>.\,其中.\是个lex规则文法,comment则就是一个状态了.
c.有几个特殊内置的状态。INITIAL,*.比如: <*>规则,则表示这个规则在任何状态下有效. <INITIAL>是个默认状态。
d.某个规则可以支持多个状态,使用“,”隔开。比如<INITIAL,STRING,QUOTE>规则.如果和<<EOF>>重用一个规则的话,则是<quote><<EOF>>。
e.flex还提供了一套相关函数:
yy_push_state, yy_pop_state, yy_top_state, BEGIN()
可以说有了状态的支持,flex的功能更加强大了,简单的文法分析甚至可以不借助于yacc/bison来做了。
一个完整的例子:
%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++;
}
5. flex C++的支持
编译时,使用flex -+ 文件,就可以得到.cc的文件,而且flex也会生成C++相关类,对应的类和方法有:
FlexLexer:成员方法有:
a. yylex(), YYText(), YYLeng(),lineno(), set_debug(),debug(),
b. 构造函数yyFlexLexer( istream* arg_yyin = 0, ostream* arg_yyout = 0 )
c. 缓冲:switch_streams(istream* new_in = 0, ostream* new_out = 0),yylex( istream* new_in = 0, ostream* new_out = 0 )
等等。
例子:
// An example of using the flex C++ scanner class.
%{
int mylineno = 0;
%}
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';
%%
Version 2.5 December 1994
int main( int /* argc */, char** /* argv */ )
{
FlexLexer* lexer = new yyFlexLexer;
while(lexer->yylex() != 0)
;
return 0;
}