詞法分析器生成工具flex的簡單使用

分類: C/C++

1.FLEX簡介
   單詞的描述稱爲模式(Lexical Pattern),模式一般用正規表達式進行精確描述。FLEX通過讀取一個有規定格式的文本文件,輸出一個如下所示的C語言源程序。
   +------------+       +------------+       +----------------+ 
   | 輸入文件*.l |------>|flex工具     |------>|輸出文件lex.yy.c |
   +------------+       +------------+       +----------------+ 
   
    FLEX的輸入文件稱爲LEX源文件,它內含正規表達式和對相應模式處理的C語言代碼。LEX源文件的擴展名習慣上用.l表示。FLEX通過對源文件的掃描自動生成相應的詞法分析函數 int yylex(),並將之輸出到名規定爲lex.yy.c的文件中。實用時,可將其改名爲lexyy.c。該文件即爲LEX的輸出文件或輸出的詞法分析器。也可將 int yylex()加入自已的工程文件中使用。

2. LEX源文件的格式
LEX對源文件的格式要求非常嚴格,比如若將要求頂行書寫的語句變成非頂行書寫就會產生致命錯誤。而LEX本身的查錯能力很弱,所以書寫時一定要注意。
LEX的源文件由三個部份組成,每個部分之間用頂行的“%%”分割,其格式如下:

定義部份
%%
規則部份 
%%
用戶附加C語言部份


下面以統計單詞出現的次數的源程序count.l做說明,count.l的內容如下:

%{
   #include "stdio.h"
   #include "stdlib.h"
   int num_num=0,num_id=0;
%}
INTEGER [-+]?[1-9][0-9]*
ID [a-zA-Z][a-zA-Z_0-9]*
SPACE [ \n\t]
%%
{INTEGER} { num_num++;
  printf("(num=%d)\n",atoi(yytext));/*打印數字值*/
    /*數字數加一*/
}

{ID} { num_id++;
    printf("(id=%s)\n",yytext);
    }

{SPACE} |
. {
   /*什麼也不做,濾掉白字符和其它字符*/
   }
%%

int main()
{
   yylex();
   printf("num=%d,id=%d\n",num_num,num_id);
   return 0;
}

int yywrap()//此函數必須由用戶提供

{
    return 1;
}


2.1定義部分:

%{
   #include "stdio.h"
   #include "stdlib.h"
   int num_num=0,num_id=0;
%}
INTEGER [-+]?[1-9][0-9]*
ID [a-zA-Z][a-zA-Z_0-9]*
SPACE [ \n\t]

定義部份由C語言代碼、模式的宏定義、條件模式的開始條件說明三部份組成。
其中,C代碼部份由頂行的%{和}%引入,LEX掃描源文件時將%{和}%之間的部分原封不動的拷貝到輸出文件lex.yy.c中。上面的定義部分沒有條件模式的開始條件說明部分,只有C語言代碼、模式的宏定義。
模式宏定義是一個正則表達式的定義,如上面所示的
INTEGER [-+]?[1-9][0-9]*
正則表達式的匹配如下:
 模式  解 釋
 x  配置單個字母x
 .  匹配除換行符’\n’之外的任意字符
 [xyz]  匹配x、y或z 
 [abj-oz]  匹配a、b、z及j至o之間的字母 
 [^A-Z]  除大寫字母A-Z之外的其它字符
 [^A-Z\n]   除大寫字母A-Z和換行符之外的其它字符
 r*  匹配0個或多個r
 r+  匹配1個或多個r 
 r?  匹配0個或1個r 

2.2規則部分
   規則部份是LEX源文件的核心部份,它包括一組模式和在生成分析器識別相應模式後對相應模式進行處理的C語言動作(Action)。格式如下

C語言代碼
模式1 動作1
模式2 |
模式3 動作3

同定義部分一樣,C語言代碼必須出現在第一個模式之前,包括在%{和}%之中,且%{必須頂行書寫。%{和}%之間的代碼部份可用來定義yylex()用到的局部變量。
模式必須頂行書寫。模式可爲正規式或用{}括起且在定義部份定義過的宏名。動作爲用{}括起的C代碼。且開始括號{與模式之間用白字符隔開,且須和模式在同一行上。注意,在模式後加一|表示模式2和3採用同一動作3.|和模式2以白字符隔開。

2.3 用戶附加C語言部份
   LEX對此部份不作任何處理,僅僅將之直接拷貝到輸出文件lex.yy.c的尾部。在些部份,可定義對模式進行處理的C語言函數、主函數和yylex要調用的函數yywrap()等。如果用戶在其它C模塊中提供這些函數,用戶代碼部份可以省略。

3.生成源代碼和編譯運行
flex count.l
gcc -g lex.yy.c -o count
運行:
osdba@osdba-laptop:~/tmp$ ./count <
aaa bbb ccc 999
EOF

(id=aaa)
(id=bbb)
(id=ccc)
(num=999)
num=1,id=3
osdba@osdba-laptop:~/tmp$ 

4. 模式匹配說明
   yylex()函數被調用之後,它首先檢查全局文件指針變量yyin是否有定義,如有,則將之設置爲將要掃描的文件指針。如無,則設置爲標準輸入文件stdin.同理,如全局文件指針變量yyout無定義,則將之設置爲標準輸出文件stdout。
   若有多個模式與被掃描文件中的字符串相匹配,則yylex()執行能匹配最長字符串的模式,稱爲“最長匹配原則”;若還有多個模式匹配長度相同的字符串,則yylex()選擇在LEX源文件中排列最前面的模式進行匹配,稱爲“最先匹配原則”。yylex()常通過超前搜索一個字符來實現這樣的原則,如果使用超前搜索匹配了某一模式,則yylex()在進行下一次分析前,將回退一個字符。見下例:
   %%
   program {printf(“keyword:%s!\n”,yytext); /*模式一*/}
   procedure {printf(“keyword:%s!\n”,yytext); /*模式二*/}
   [a-z][a-z0-9]* {printf(“identifier:%s!\n”,yytext); /*模式三*/}
   %%
   如輸入串爲”programming”,yylex()分析到子串”program”時,有模式一和三可以匹配,但根據最長搜索原則,發現在繼續讀入輸入串時,還可匹配模式三。這樣,將輸出”identifier:programming!”。如輸入串爲”program”,則按最先匹配原則,模式一與之匹配,輸出”keyword:program!”。注意,若將模式一和模式三在源文件中次序弄反,則模式一永遠也得不到匹配。若無模式可匹配輸入串,則使用缺省規則,即將輸入串原樣拷貝至輸出文件yyout中。

5. 常用全局變量和宏
   lex.yy.c中常用全局變量、函數和宏很多,在此僅指出一些最常用的,若需要更詳細信息,請閱讀源文件。
   (1) FILE *yyin,*yyout:爲指向字符輸入和結果輸出文件的指針。如用戶未對其定義,則設爲標準輸入文件stdin和stdout。
   (2) int yylex():爲詞法分析程序,它自動移動文件指針yyin和yyout。在定義模式動作時,用戶可用return語句結束yylex(),return 必須返回一整數。由於yylex()的運行環境都是以全局變量的方式保存,因此,在下一次調用yylex()時,yylex()可從上次掃描的斷點處繼續掃描,在語法分析時,可利用這一特性。若用戶未定義相應的return語句,則yylex()繼續分析被掃描的文件,直到碰到文件結束標誌EOF。在讀到EOF時,yylex()調用int yywrap()函數(該函數用戶必須提供),若該函數返回非0值,則yylex()返回0而結束。否則,yylex()繼續對yyin指向的文件掃描。
   (3) char *yytext:存放當前被識別的詞形。
   (4) int yyleng:存放字符串yytext的長度。
   (5) int yywrap():參見(2)
   (6) yymore():將當前識別的詞形保留在yytext中,分析器下次掃描時的詞形將加追加在yytext中。例模式定義如下
   ……
   hello {printf(“%s!”,yytext);yymore();}
   world {printf(“%s!”,yytext);}
   ……
   當輸入串爲”helloworld”時,將輸出”hello!helloworld!”
   (7) yyless(int n):回退當前識別的詞形中n個字符到輸入中
   (8) unput(char c):回退字符c到輸入,它將作爲下一次掃描的開始字符
   (9) input():讓分析器從輸入緩衝區中讀取當前字符,並將yyin指向下一字符
   (10)yyterminate():中斷對當前文件的分析,將yyin指向EOF。
   (11)yyrestart(FILE * file):重新設置分析器的掃描文件爲file
   (12)ECHO:將當前識別的字符串拷貝到yyout
   (13)BEGIN:激活開始條件對應的模式
   (14)REJECT:放棄當前匹配的字符串和當前的模式,讓分析器重新掃描當前的字符串,並選擇另一個最佳的模式再次進行匹配。

6. 條件模式
   LEX提供控制模式在一定狀態下使用的功能,稱爲條件模式。LEX首先在定義部份通過%start來定義條件句。在規則部份可通過宏
   BEGIN 條件名 來激活條件。BEGIN INITIAL或BEGIN 0將休眠所有的條件模式,使分析器回到開始狀態。
   例:將輸入文件中的單詞”magic” 作如下處理:識別”magic”時,如”magic”所在行行首爲字符’a’,則輸出”first”;若爲’b’,則輸出”second”;否則,輸出”magic”。如不用條件模式,LEX源文件可這樣寫:

%{int flag;}%
%%
^a {flag=’a’;ECHO;}
^b {flag=’b’;ECHO;}
\n {flag=0;ECHO;}
magic {
switch(flag)
{
case ‘a’:printf(“first”);break;
case ‘b’:printf(“second”);break;
default :ECHO;break;
}
}
%%

   
   如使用條件模式,則上述源文件可簡化爲

%start AA BB CC
%%
^a {ECHO;BEGIN AA;}
^b {ECHO;BEGIN BB;}
\n {ECHO;BEGIN 0;}
<AA>magic {printf(“first”);}
<BB>magic {printf(“second”);}
%%

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章