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