flex
此篇不會(huì)講述規(guī)則表達(dá)式,自從.net流行之后,大量的原本只是在unix才使用的規(guī)則表達(dá)式現(xiàn)在廣泛使用在各種系統(tǒng)中。略.
1.內(nèi)置變量
yy_create_buffer:見后面的緩沖管理
yy_delete_buffer:見后面的緩沖管理
yy_flex_debug:見后面的緩沖管理
yy_init_buffer:見后面的緩沖管理
yy_flush_buffer:見后面的緩沖管理
yy_load_buffer_state:見后面的緩沖管理
yy_switch_to_buffer:見后面的緩沖管理
yyin: 輸入緩沖流的文件指針,可以被替換以實(shí)現(xiàn)解析某個(gè)自定義的文件
yyleng:當(dāng)前匹配字串的長度
yylex: 解析函數(shù),接口
yylineno:當(dāng)前匹配的文件行號(hào)
yyout: 輸出流的指針
yyrestart: 手動(dòng)調(diào)用yyrestart.會(huì)重啟解析
yyrestart( yyin );一般是打開某個(gè)文件之后,yyrestart(yyin)再解析.
yytext: 當(dāng)前匹配的字串
yywrap: 解析一個(gè)文件完畢之后,會(huì)調(diào)用yywrap:返回1表示結(jié)束,0表示繼續(xù)(此時(shí)最好重新打開yyin或者重置yyin流)
2. 幾個(gè)重要函數(shù):
1). yymore(): yymore()的含義是,當(dāng)當(dāng)前匹配的字串之后,想把后面配置的字串附加到這個(gè)字串后面,組成新的token返回.
比如:
%%
mega- ECHO; yymore();
kludge ECHO;
如果:“mega-kludge" the following will write "mega-mega-kludge" to the output。
為什么呢? 首先遇到 mega-,接著被more了一下,因此就會(huì)把kludga附加到mega-后面,而后面的kludge的動(dòng)作又是打印,因此會(huì)打印出:mega-mega-kludge
2). yyless(): yyless()的含義是:當(dāng)當(dāng)前的匹配之后,我想只返回前面幾個(gè)字符,并且把后面回退到輸入
比如:
%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;
input "foobar" the following will write out "foobarbar":
為什么呢? foobar輸入之后,匹配foobar,ECHO打印出來,接著yyless(3),則輸入流變?yōu)?span lang="EN-US">bar了(yytext為foo).接著再匹配,于是匹配 到[a-z]+,因此再次打印出bar.
3).BEGIN: flex下一個(gè)起始解析狀態(tài)。見第3節(jié),flex的狀態(tài).
4).REJECT: 相當(dāng)于拒絕此匹配,讓系統(tǒng)重新找下一個(gè)匹配。
"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(): 讀取輸入流下一個(gè)字符
7).yyrestart(): 該函數(shù)迫使yylex重新解析。yyrestart有個(gè)函數(shù)指針流,可以再打開之后,重新使用yyrestart().
3. 解析源管理:
1). 默認(rèn)是從yyin獲取,而yyin則是stdout,也可以是其它文件。
if ( ! yyin )
yyin = stdin;
if ( ! yyout )
yyout = stdout;
2). 如果你打開了一個(gè)文件,并把yyin指向此文件,則從該文件中讀取.比如:
在main中:
FILE* fp = NULL;
fp = fopen("hell.txt", "r");
yyin = fp;
之后再使用 yylex()
則flex從hell.txt中讀取信息并解析.
3).從字符串中解析
先使用下列函數(shù),轉(zhuǎn)化緩沖,之后再使用 yylex()
a. yy_scan_string(char*).使用了yy_scan_string(char*)之后,flex會(huì)把char*放到yy的輸入緩沖中(會(huì)調(diào)用到yy_switch_to_buffer.)
b. yy_scan_bytes(const char *base, int len);
c. yy_scan_buffer(char *base, yy_size_t size)
這幾個(gè)函數(shù)內(nèi)部都使用的是緩沖切換的創(chuàng)建等函數(shù),見后面的章節(jié).
4).利用EOF內(nèi)置規(guī)則,重新打開多個(gè)文件輸入:
比如:
<<EOF>> {
if ( *++filelist )
yyin = fopen( *filelist, "r" );
else
yyterminate();
}
5).多緩沖問題:
a.此問題可以按上面的 <<EOF>>或者 yywrap解決。
b.另外一種形式,比如:#include <iostream>或者類似于這種,這種形式的話,則不能使用yywrap,<<EOF>>來解決了。
這就需要用到在flex動(dòng)作中手動(dòng)切換緩沖。flex對(duì)每個(gè)緩沖有個(gè)緩沖輸入流指針,指向當(dāng)前位置,各個(gè)被切換的緩沖互不相干擾,這恰好很好地解決了文件包含另外一個(gè)文件,而子文件也許要yylex的這種場(chǎng)合.
這就需要使用到flex底層的緩沖管理了.見下節(jié)
4. flex的緩沖管理:
flex本質(zhì)上都是對(duì)緩沖輸入流進(jìn)行yylex詞法分析. 緩沖是個(gè)結(jié)構(gòu)體,每個(gè)緩沖有個(gè)緩沖輸入流指針,指向當(dāng)前位置,各個(gè)被切換的緩沖互不相干擾,而相關(guān)yyin,yyrestart,yy_create_buffer,yy_scan_string系列函數(shù)都是操縱flex底層緩沖的.
flex緩沖是一個(gè)結(jié)構(gòu)體:
我們以下面的詞法規(guī)則為例子:(來自flex官方網(wǎng)站的注解)
/* 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就是一個(gè)緩沖。該lex文法使用到了<incl>,這個(gè)是狀態(tài),見4節(jié)的flex的狀態(tài)管理.目前只需要知道它是個(gè)狀態(tài)即可.在incl狀態(tài)下才進(jìn)行[ \t]*的規(guī)則匹配.
<<EOF>>見上面的描述,指某個(gè)輸入流到了末尾則到了這個(gè)狀態(tài).
BEGIN(INITIAL)類似于BEGIN(0),表示狀態(tài)從開頭解析(0表示不帶狀態(tài)的解析,也就說規(guī)則前沒有<>這個(gè)標(biāo)記的狀態(tài).
上面的文法可以知道:
a. 在遇到include之后,跳到incl狀態(tài)。
b. 在incl狀態(tài)中,跳過空白的字符,得到文件名(include file),先把當(dāng)前的flexBuffer保存到數(shù)組棧,然后打開新的文件,并把flex的當(dāng)前輸入流切換到剛打開的新文件的輸入流.
c. 切換到INITIAL狀態(tài)(沒有<>在規(guī)則前的默認(rèn)的狀態(tài))
d.這里用到了幾個(gè)宏或者函數(shù): yy_switch_to_buffer, yy_create_buffer,YY_CURRENT_BUFFER,yy_delete_buffer.
這些函數(shù)看名字就應(yīng)該知道其作用了。
4. flex的狀態(tài)(Start conditions).
上面的緩沖管理已經(jīng)涉及到狀態(tài)管理了。flex的狀態(tài)管理相當(dāng)于普通詞法的擴(kuò)展。通過flex的狀態(tài),大大擴(kuò)充了詞法分析本身的功能。
比如:
a. <STRING>[^"]* { /* eat up the string body ... */
...
}
表示在STRING狀態(tài)下才進(jìn)行 [^"]*匹配。
b. <INITIAL,STRING,QUOTE>\. { /* handle an escape ... */
...
}
表示在INITIAL,STRING,QUOTE才匹配。\.
flex的狀態(tài)怎么使用呢?
a. 首先定義:以%開頭(flex的申明本質(zhì)上所有的都是以%開頭)定義,有2種: %s,%x,其中%s = %x+INITIAL,也就說是%s的狀態(tài)為%x定義的+INITIAL狀態(tài)
b. 在規(guī)則域中,使用<狀態(tài)>規(guī)則,比如 comment是個(gè)狀態(tài),則有 <comment>.\,其中.\是個(gè)lex規(guī)則文法,comment則就是一個(gè)狀態(tài)了.
c.有幾個(gè)特殊內(nèi)置的狀態(tài)。INITIAL,*.比如: <*>規(guī)則,則表示這個(gè)規(guī)則在任何狀態(tài)下有效. <INITIAL>是個(gè)默認(rèn)狀態(tài)。
d.某個(gè)規(guī)則可以支持多個(gè)狀態(tài),使用“,”隔開。比如<INITIAL,STRING,QUOTE>規(guī)則.如果和<<EOF>>重用一個(gè)規(guī)則的話,則是<quote><<EOF>>。
e.flex還提供了一套相關(guān)函數(shù):
yy_push_state, yy_pop_state, yy_top_state, BEGIN()
可以說有了狀態(tài)的支持,flex的功能更加強(qiáng)大了,簡(jiǎn)單的文法分析甚至可以不借助于yacc/bison來做了。
一個(gè)完整的例子:
%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++的支持
編譯時(shí),使用flex -+ 文件,就可以得到.cc的文件,而且flex也會(huì)生成C++相關(guān)類,對(duì)應(yīng)的類和方法有:
FlexLexer:成員方法有:
a. yylex(), YYText(), YYLeng(),lineno(), set_debug(),debug(),
b. 構(gòu)造函數(shù)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;
}