FLEX的輸入文件稱為LEX源文件,它內(nèi)含正規(guī)表達(dá)式和對(duì)相應(yīng)模式處理的C語言代碼。LEX源文件的擴(kuò)展名習(xí)慣上用.l表示。FLEX通過對(duì)源文件的掃描自動(dòng)生成相應(yīng)的詞法分析函數(shù) int yylex(),并將之輸出到名規(guī)定為lex.yy.c的文件中。實(shí)用時(shí),可將其改名為lexyy.c。該文件即為LEX的輸出文件或輸出的詞法分析器。也可將 int yylex()加入自已的工程文件中使用。 2. 模式簡(jiǎn)介 LEX的模式的格式(也稱為規(guī)則)是機(jī)器可讀的正規(guī)表達(dá)式,正規(guī)表達(dá)工是用連接、并、閉包運(yùn)算遞歸生成的。為了方便處理,LEX在此基礎(chǔ)上增加了一些運(yùn)算。下列是按運(yùn)算優(yōu)先級(jí)由高往低排列的LEX的正規(guī)表達(dá)式的運(yùn)算符。 “\[]^-?.*+|()/${}%<> 關(guān)于LEX的模式定義,可參見下頁附表1.1 3.LEX源文件格式 LEX對(duì)源文件的格式要求非常嚴(yán)格,比如若將要求頂行書寫的語句變成非頂行書寫就會(huì)產(chǎn)生致命錯(cuò)誤。而LEX本身的查錯(cuò)能力很弱,所以書寫時(shí)一定要注意。 LEX的源文件由三個(gè)部份組成,每個(gè)部分之間用頂行的“%%”分割,其格式如下: 定義部份 %% 規(guī)則部份 %% 用戶附加C語言部份 3.1定義部份 定義部份由C語言代碼、模式的宏定義、條件模式的開始條件說明三部份組成。 其中,C代碼部份由頂行的%{和}%引入,LEX掃描源文件時(shí)將%{和}%之間的部分原封不動(dòng)的拷貝到輸出文件lex.yy.c中. 附表1.1 LEX 的模式定義 模 式 | 解 釋 | X | 匹配單個(gè)字符x。(也可將模式寫為”x”) | . | 匹配除換行符’\n’之外的任意字符 | [xyz] | 匹配x、y或z | [abj-oZ] | 匹配字符集:a、b、Z以及j到o之間的字母(包括j和o) | [^A-Z] | 匹配字符集A到Z之間字符集的補(bǔ)集。即除大寫字母的其它字符 | [^A-Z\n] | 匹配除大寫字母和換行符之外的其它字符 | r* | R是正規(guī)表達(dá)式,r*匹配0個(gè)或多個(gè)r | r+ | R是正規(guī)表達(dá)式,r+匹配1個(gè)或多個(gè)r | r? | R是正規(guī)表達(dá)式,r?匹配0個(gè)或1個(gè)r | r{2,5} | R是正規(guī)表達(dá)式,r{2,5}匹配2個(gè)到5個(gè)r | r{2,} | R是正規(guī)表達(dá)式,r{2,}匹配2個(gè)或以上r | r{4} | R是正規(guī)表達(dá)式,r{4}匹配4個(gè)r | {name} | name是在定義部份出現(xiàn)的模式宏名,在規(guī)則部份將之替換為模式 | “[xyz]\”foo” | 匹配字符串[xyz]”foo | \x | 如x是’a’、’b’、’f’、’n’、’r’或’t’,\x為轉(zhuǎn)義字符,定義同ANSI C,否則,匹配字符x.此方法用于匹配正規(guī)表達(dá)式的運(yùn)算符 | \123 | 匹配八進(jìn)制ASCII碼為123的字符 | \x2a | 匹配十六進(jìn)制ASCII碼為2a的字符 | (r) | 匹配r,優(yōu)先運(yùn)算正規(guī)式r | Rs | 匹配正規(guī)式r和s的連接 | r|s | 匹配正規(guī)式r或s | r/s | 匹配正規(guī)式r,但是,r之后一定要出現(xiàn)正規(guī)式s。稱s為r的尾部條件 | ^r | 匹配正規(guī)式r,但是,r一定要出現(xiàn)在行首 | r$ | 匹配正規(guī)式r,但是,r一定要出現(xiàn)在行尾 | <s>r | 匹配正規(guī)表達(dá)式r,但是一定要在開始條件s激活之后 | <<EOF>> | 匹配文件結(jié)束標(biāo)志 | 模式的宏定義部份如同C語言中的宏定義,通過宏名定義一個(gè)模式,這樣,可以簡(jiǎn)化在源文件中多次出現(xiàn)的正規(guī)表達(dá)式的書寫。格式為: 宏名1 宏定義1 宏名2 宏定義2 …… 例如: DIGIT [0-9] ID [A-Za-z][A-Za-z0-9_]* 宏名是以字母和下劃線”_”開始,以字母、數(shù)字和下劃線組成的字符串,且大小寫敏感。宏名必須頂行寫,宏名和宏定義必須寫在同一行上。宏名和宏定義之間以不包括換行符的白字符(空格符、TAB符、換行符)隔開。 條件模式的開始條件說明格式如下: %start s1 s2 s3 其中,s1、s2、s3為條件名。必須為大小寫敏感的標(biāo)識(shí)符。關(guān)于條件模式的使用,我們將在后面作說明。 3.2 規(guī)則部份 規(guī)則部份是LEX源文件的核心部份,它包括一組模式和在生成分析器識(shí)別相應(yīng)模式后對(duì)相應(yīng)模式進(jìn)行處理的C語言動(dòng)作(Action)。格式如下 C語言代碼 模式1 動(dòng)作1 模式2 | 模式3 動(dòng)作3 …… 同定義部分一樣,C語言代碼必須出現(xiàn)在第一個(gè)模式之前,包括在%{和}%之中,且%{必須頂行書寫。%{和}%之間的代碼部份可用來定義yylex()用到的局部變量。 模式必須頂行書寫。模式可為正規(guī)式或用{}括起且在定義部份定義過的宏名。動(dòng)作為用{}括起的C代碼。且開始括號(hào){與模式之間用白字符隔開,且須和模式在同一行上。注意,在模式后加一|表示模式2和3采用同一動(dòng)作3.|和模式2以白字符隔開。 3.3用戶附加C語言部份 LEX對(duì)此部份不作任何處理,僅僅將之直接拷貝到輸出文件lex.yy.c的尾部。在些部份,可定義對(duì)模式進(jìn)行處理的C語言函數(shù)、主函數(shù)和yylex要調(diào)用的函數(shù)yywrap()等。如果用戶在其它C模塊中提供這些函數(shù),用戶代碼部份可以省略。 3.4 源文件格式小結(jié) 綜上所述,LEX源文件詳細(xì)格式如下: %{ /*此模塊為定義模塊中C語言代碼部份,在下面填入相應(yīng)C代碼*/ }% 模式宏名1 模式1 模式宏名2 模式2 …… %start s1 s2 s3 %% %{ /*此模塊為規(guī)則模塊中C語言代碼部份,在下面填入相應(yīng)C代碼*/ }% 模式1 動(dòng)作1 模式2 動(dòng)作2 …… %% /*此模塊為用戶附加C語言部份,在下面填入相應(yīng)C代碼*/ 注意:以上三部份及其中任何一子部份,均可省去。且如無第三部分,第二個(gè)%%也可省去,但第一個(gè)%%決不可省。 4.LEX的工作原理 LEX通過對(duì)源文件的掃描,經(jīng)過宏替換后,將規(guī)則部份的正規(guī)表達(dá)式轉(zhuǎn)換成與之相應(yīng)的DFA,并由之產(chǎn)生一個(gè)名為int yylex()的詞法分析函數(shù),將之拷貝到輸出文件lex.yy.c中。由于考慮到C代碼的可移植性和運(yùn)行效率問題,lex.yy.c中大量使用了宏定義,且文件較大(30-50kb)。因此,幾乎是不可讀的。但是,其可移植性相當(dāng)好。 lex.yy.c中定義了很多用戶可定義的全局變量,以及在LEX源文件的動(dòng)作中可調(diào)用的函數(shù)和宏。但是,由于lex.yy.c太過復(fù)雜,建議初學(xué)者不要隨意修改它。用戶在了解其的前提下,可在其它C模塊中引用之。 5.二義性問題的解決 yylex()函數(shù)被調(diào)用之后,它首先檢查全局文件指針變量yyin是否有定義,如有,則將之設(shè)置為將要掃描的文件指針。如無,則設(shè)置為標(biāo)準(zhǔn)輸入文件stdin.同理,如全局文件指針變量yyout無定義,則將之設(shè)置為標(biāo)準(zhǔn)輸出文件stdout。 若有多個(gè)模式與被掃描文件中的字符串相匹配,則yylex()執(zhí)行能匹配最長(zhǎng)字符串的模式,稱為“最長(zhǎng)匹配原則”;若還有多個(gè)模式匹配長(zhǎng)度相同的字符串,則yylex()選擇在LEX源文件中排列最前面的模式進(jìn)行匹配,稱為“最先匹配原則”。yylex()常通過超前搜索一個(gè)字符來實(shí)現(xiàn)這樣的原則,如果使用超前搜索匹配了某一模式,則yylex()在進(jìn)行下一次分析前,將回退一個(gè)字符。見下例: %% 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”時(shí),有模式一和三可以匹配,但根據(jù)最長(zhǎng)搜索原則,發(fā)現(xiàn)在繼續(xù)讀入輸入串時(shí),還可匹配模式三。這樣,將輸出”identifier:programming!”。如輸入串為”program”,則按最先匹配原則,模式一與之匹配,輸出”keyword:program!”。注意,若將模式一和模式三在源文件中次序弄反,則模式一永遠(yuǎn)也得不到匹配。若無模式可匹配輸入串,則使用缺省規(guī)則,即將輸入串原樣拷貝至輸出文件yyout中。 6.常用全局變量和宏 lex.yy.c中常用全局變量、函數(shù)和宏很多,在此僅指出一些最常用的,若需要更詳細(xì)信息,請(qǐng)閱讀源文件。 (1) FILE *yyin,*yyout:為指向字符輸入和結(jié)果輸出文件的指針。如用戶未對(duì)其定義,則設(shè)為標(biāo)準(zhǔn)輸入文件stdin和stdout。 (2) int yylex():為詞法分析程序,它自動(dòng)移動(dòng)文件指針yyin和yyout。在定義模式動(dòng)作時(shí),用戶可用return語句結(jié)束yylex(),return 必須返回一整數(shù)。由于yylex()的運(yùn)行環(huán)境都是以全局變量的方式保存,因此,在下一次調(diào)用yylex()時(shí),yylex()可從上次掃描的斷點(diǎn)處繼續(xù)掃描,在語法分析時(shí),可利用這一特性。若用戶未定義相應(yīng)的return語句,則yylex()繼續(xù)分析被掃描的文件,直到碰到文件結(jié)束標(biāo)志EOF。在讀到EOF時(shí),yylex()調(diào)用int yywrap()函數(shù)(該函數(shù)用戶必須提供),若該函數(shù)返回非0值,則yylex()返回0而結(jié)束。否則,yylex()繼續(xù)對(duì)yyin指向的文件掃描。 (3) char *yytext:存放當(dāng)前被識(shí)別的詞形。 (4) int yyleng:存放字符串yytext的長(zhǎng)度。 (5) int yywrap():參見(2) (6) yymore():將當(dāng)前識(shí)別的詞形保留在yytext中,分析器下次掃描時(shí)的詞形將加追加在yytext中。例模式定義如下 …… hello {printf(“%s!”,yytext);yymore();} world {printf(“%s!”,yytext);} …… 當(dāng)輸入串為”helloworld”時(shí),將輸出”hello!helloworld!” (7) yyless(int n):回退當(dāng)前識(shí)別的詞形中n個(gè)字符到輸入中 (8) unput(char c):回退字符c到輸入,它將作為下一次掃描的開始字符 (9) input():讓分析器從輸入緩沖區(qū)中讀取當(dāng)前字符,并將yyin指向下一字符 (10)yyterminate():中斷對(duì)當(dāng)前文件的分析,將yyin指向EOF。 (11)yyrestart(FILE * file):重新設(shè)置分析器的掃描文件為file (12)ECHO:將當(dāng)前識(shí)別的字符串拷貝到yyout (13)BEGIN:激活開始條件對(duì)應(yīng)的模式 (14)REJECT:放棄當(dāng)前匹配的字符串和當(dāng)前的模式,讓分析器重新掃描當(dāng)前的字符串,并選擇另一個(gè)最佳的模式再次進(jìn)行匹配。 7.條件模式 LEX提供控制模式在一定狀態(tài)下使用的功能,稱為條件模式。LEX首先在定義部份通過%start來定義條件句。在規(guī)則部份可通過宏 BEGIN 條件名 來激活條件。BEGIN INITIAL或BEGIN 0將休眠所有的條件模式,使分析器回到開始狀態(tài)。 例:將輸入文件中的單詞”magic” 作如下處理:識(shí)別”magic”時(shí),如”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(chǎn)’:printf(“first”);break; case ‘b’:printf(“second”);break; default :ECHO;break; } } %% 如使用條件模式,則上述源文件可簡(jiǎn)化為 %start AA BB CC %% ^a {ECHO;BEGIN AA;} ^b {ECHO;BEGIN BB;} \n {ECHO;BEGIN 0;} <AA>magic {printf(“first”);} <BB>magic {printf(“second”);} %% 8. 示例 例一:編制LEX源程序,分別統(tǒng)計(jì)文本文件a.txt中出現(xiàn)的標(biāo)識(shí)符和整數(shù)個(gè)數(shù),并顯示之。標(biāo)識(shí)符定義為字母開頭,后跟若干個(gè)字母,數(shù)字或下劃線。整數(shù)可以帶+或-號(hào),也可不帶,且不以0開頭。非單詞和非整數(shù)則忽略不記,將之濾掉不顯示。 設(shè)LEX源文件名為count.l.文件內(nèi)容如下 %{ #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)",atoi(yytext));//打印數(shù)字值 /*數(shù)字?jǐn)?shù)加一*/ } {ID} { num_id++; printf("(id=%s)",yytext); } {SPACE} | . { //什么也不做,濾掉白字符和其它字符 } %% void main() { yylex(); printf("num=%d,id=%d",num_num,num_id); } int yywrap()//此函數(shù)必須由用戶提供 {return 1;} 設(shè)count.l所在目錄為c:\test,且已用path命令指定flex.exe所在目錄。則調(diào)用命令 c:\test> flex count.l后可在c:\test目錄下得到一文件lex.yy.c,打開C環(huán)境,新建工程文件my.prj(TURBOC 或BORLAND C下后綴為.prj,VC下后綴為.dsw),將lex.yy.c加入工程文件中,編譯運(yùn)行可得可執(zhí)行文件my.exe.若需分析從標(biāo)準(zhǔn)輸入中輸入的字符串,運(yùn)行my.exe即可.若需分析放在其它文件中的串,如設(shè)在文件hello.txt中,則運(yùn)行my.exe<hello.txt即可. 例2:編制一LEX源程序,分別求出文件hh.c中字母,數(shù)字,回車符的個(gè)數(shù).源程序如下: %{ #include "stdio.h" #include "stdlib.h" int num_digit=0,num_letter=0,num_enter=0; %} DIGIT [0-9] LETTER [A-Za-z] %% {DIGIT} {num_digit++;} {LETTER} {num_letter++;} \n {num_enter++;} . {/*其它字符不作處理*/} %% void main() { yyin=fopen(”hh.c”,r); yylex(); printf("num=%d,letter=%d,enter=%d", num_digit,num_letter,num_enter); } int yywrap()//此函數(shù)必須由用戶提供 { return 1; } |