一、Lex(Lexical Analyzar) 描述文件的結(jié)構(gòu)介紹
Lex工具是一種詞法分析程序生成器,它可以根據(jù)詞法規(guī) 則說明書的要求來生成單詞識 別程序,由該程序識別輸入文本中的各個單詞。
其文件可為:
(1)定義
(2)規(guī)則
(3)用戶子程序
其中規(guī)則部分是必須的,定義和用戶子程序部分是任選的。
1、定義
(1)%{ 符號定義:
定義部分起始于 %{ 符號,終止符 %} 符號,可以是:文件包含,宏定義,常數(shù)定義,全局變量及外部變量定義,函數(shù)聲明等。
%{
#include <Foundation/Foundation.h>
#include <stdio.h>
int linenum;
#define smallerFunction(a, b) ((a) < (b) ? (a) : (b))
%}
注意:特殊括號%{和 %}都必須頂著行首寫。
(2)正規(guī)定義
示例:
letter [A-Za-z]
digit [0-9]
id {letter}({letter}|{digit})*
注意:上面正規(guī)定義中出現(xiàn)的小括號表示分組,而不是被匹配的字符。而大括號括起的部分表示正規(guī)定義名。
(3)狀態(tài)定義
詞法分析器在匹配正規(guī)式時,可以在不同狀態(tài)(或環(huán)境)下進行。我們可以規(guī)定在不同的狀態(tài)下有不同的匹配方式。每個詞法分析器都至少有一個狀態(tài),這個狀態(tài)叫做初始狀態(tài),可以用INITIAL或0來表示,如果還需要使用其他狀態(tài),可以在定義段用%s 來定義。
注意: %s也要頂行首寫。
示例1:定義了一個名為COMMENT的狀態(tài)和一個名為STRING_LETTER_STATE的狀態(tài),狀態(tài)名之間用空白分隔;
%s COMMENT STRING_LETTER_STATE
示例2:使用狀態(tài)時,可以用如下方式寫詞法規(guī)則:
<state1, state2> p0 {action0;}
<state1> p1 {action1;}
這兩行詞法規(guī)則表示:在狀態(tài)state1和state2下,匹配正規(guī)式p0后執(zhí)行動作action0,而只有在狀態(tài)state1下,才可以匹配正規(guī)式p1后執(zhí)行動作action1。如果不指明狀態(tài),默認情況下處于初始狀態(tài)INITIAL。
要想進入某個特定狀態(tài),可以在動作中寫上這樣一句: BEGINstate; 執(zhí)行這個動作后,就進入狀態(tài)state。
下面是一段處理C語言注釋的例子,里面用到了狀態(tài)的轉(zhuǎn)換,在這個例子里,使用不同的狀態(tài),可以讓詞法分析器在處于注釋中和處于注釋外時使用不同的匹配規(guī)則:
%s c_comment
%%
<INITIAL>“/*” {BEGIN c_comment;}
<c_comment>“*/” {BEGIN 0;}
<c_comment>. {;}
2、規(guī)則
規(guī)則,起始于%%符號,終止于%%符號,其規(guī)則是詞法規(guī)則。詞法規(guī)則由模板公式和動作兩部分組成。
模板公式部分可以由任意的正則表達式組成,動作部分是由開發(fā)者想用的開發(fā)語言(C語言、OC、JavaScript 等等)的語句組成,這些語句用來對所匹配的模式進行相應(yīng)的處理。
需要注意的是,lex將識別出來的單詞存放在 yytext[]字符數(shù)組中,因此該數(shù)組的內(nèi)容就代表了所識別出來的單詞的內(nèi)容。
(1)Lex源程序中詞法規(guī)則(即正規(guī)式)的相關(guān)規(guī)定:
(a)正文字符:除元字符以外的其他字符,這些字符在正規(guī)式中可以被匹配。
若單個正文字符c作為正規(guī)式,則可與字符c匹配,元字符無法被匹配,如果元字符想要被匹配,則需要通過“轉(zhuǎn)義”的方式,即用” ”包括住元字符,或在元字符前加 \ 。例如 ”+” 和 \+ 都表示加號。
C語言中的一些轉(zhuǎn)義字符也可以出現(xiàn)在正規(guī)式中,例如 \t \n \b 等。
(b)元字符:元字符是lex語言中作特殊用途的一些字符,包括:* + ? | { } [ ] ( ). ^ $ “ \ - / < >。
部分元字符在lex語言中的特殊含義:
(c)^ : 表示補集:[^…]表示補集,即匹配除 ^ 之后所列字符以外的任何字符。如 [^0-9] 表示匹配除數(shù)字字符 0-9 以外的任意字符。
除 ^ - \ 以外,任何元字符在方括號內(nèi)失去其特殊含義。
如果要在方括號內(nèi)表示負號 - ,則要將其至于方括號內(nèi)的第一個字符位置或者最后一個字符位置,例如[-+0-9][+0-9-]都匹配數(shù)字及+ - 號。
(d) . ^$ /:
點運算符 . 匹配除換行之外的任何字符,一般可作為最后一條翻譯規(guī)則。
(e)^ 匹配行首字符。如:^begin匹配出現(xiàn)在行首的begin
(f) $ 匹配行末字符。如:end$ 匹配出現(xiàn)在行末的end
(g)R1/R2(R1和R2是正規(guī)式)表示超前搜索:若要匹配R1,則必須先看緊跟其后的超前搜索部分是否與R2匹配。
如:DO/{alnum}*={alnum}*,表示如果想匹配DO,則必須先在DO后面找到形式為{alnum}*={alnum}*的串,才能確定匹配DO。
·········
(2)詞法規(guī)則段列出的是詞法分析器需要匹配的正規(guī)式,以及匹配該正規(guī)式后需要進行的相關(guān)動作。
示例:
while {return (WHILE);}
do {return (DO);}
{id} {yylval = installID (); return (ID);}
每行都是一條規(guī)則,該規(guī)則的前一部分是正規(guī)式,需要頂行首寫,后一部分是匹配該正規(guī)式后需要進行的動作,這個動作是用C語法來寫的,被包裹在 {}之內(nèi),被Lex翻譯器翻譯后會被直接拷貝進lex.yy.c。正規(guī)式和語義動作之間要有空白隔開。其中用{}擴住的正規(guī)式表示正規(guī)定義的名字。
也可以若干個正規(guī)式匹配同一條語義動作,此時正規(guī)式之間要用| 分隔。
3、輔助函數(shù)段
輔助函數(shù)段用C語言語法來寫,輔助函數(shù)一般是在詞法規(guī)則段中用到的函數(shù)。這一部分一般會被直接拷貝到lex.yy.c中。
4、Lex源程序中常用到的變量及函數(shù)
yyin和yyout:這是Lex中本身已定義的輸入和輸出文件指針。這兩個變量指明了lex生成的詞法分析器從哪里獲得輸入和輸出到哪里。默認:鍵盤輸入,屏幕輸出。
yytext和yyleng:這也是lex中已定義的變量,直接用就可以了。
yytext:指向當前識別的詞法單元(詞文)的指針
yyleng:當前詞法單元的長度。
ECHO:Lex中預(yù)定義的宏,可以出現(xiàn)在動作中,相當于fprintf(yyout, “%s”,yytext),即輸出當前匹配的詞法單元。
yylex():詞法分析器驅(qū)動程序,用Lex翻譯器生成的lex.yy.c內(nèi)必然含有這個函數(shù)。
yywrap():詞法分析器遇到文件結(jié)尾時會調(diào)用yywrap()來決定下一步怎么做;若yywrap()返回0,則繼續(xù)掃描;若返回1,則返回報告文件結(jié)尾的0標記。
備注 : 由于詞法分析器總會調(diào)用yywrap,因此輔助函數(shù)中最好提供yywrap,如果不提供,則在用C編譯器編譯lex.yy.c時,需要鏈接相應(yīng)的庫,庫中會給出標準的yywrap函數(shù)(標準函數(shù)返回1)。
5 、 .l文件的書寫:
/* 這是注釋的形式,與C中的/*...* /注釋相同。 */
/* 第一部分是定義、聲明部分。這部分內(nèi)容可以為空。*/
%{
/* 寫在 %{...%}這對特殊括號內(nèi)的內(nèi)容會被直接拷貝到C文件中。
*
* 這部分通常進行一些頭文件聲明,變量(全局,外部)、常量
* 的定義,用C語法。
*
* %{和%}兩個符號都必須位于行首
*/
/* 下面定義了需要識別的記號名,如果和yacc聯(lián)合使用,這些記號名 都應(yīng)該在yacc中定義 */
#include <stdio.h>
#define LT 1
#define LE 2
#define GT 3
#define GE 4
#define EQ 5
#define NE 6
#define WHILE 18
#define DO 19
#define ID 20
#define NUMBER 21
#define RELOP 22
#define NEWLINE 23
#define ERRORCHAR 24
int yylval;
/* yylval 是yacc中定義的變量,用來保存記號的屬性值,默認是int類型。
* 在用lex實現(xiàn)的詞法分析器中可以使用這個變量將記號的屬性傳遞給用
* yacc實現(xiàn)的語法分析器。
*
* 注意:該變量只有在聯(lián)合使用lex和yacc編寫詞法和語法分析器時才可在lex
* 中使用,此時該變量不需要定義即可使用。
* 單獨使用lex時,編譯器找不到這個變量。這里定義該變量為了“欺騙”編譯器。
*/
%}
/* 這里進行正規(guī)定義和狀態(tài)定義。
* 下面就是正規(guī)定義,注意,正規(guī)定義和狀態(tài)定義都要頂著行首寫。
*/
delim [ \t \n]
/* \用來表示轉(zhuǎn)義,例如\t表示制表符,\n表示換行符。*/
ws {delim}+
letter [A-Za-z_]
digit [0-9]
id {letter}({letter}|{digit})*
/* 注意:上面正規(guī)定義中出現(xiàn)的小括號表示分組,而不是被匹配的字符。
* 而大括號括起的部分表示正規(guī)定義名。
*/
number {digit}+(\.{digit}+)?(E[+-]?{digit}+)?
/* %%作為lex文件三個部分的分割符,必須位于行首 */
/* 下面這個%%不能省略 */
%%
/* 第二部分是翻譯規(guī)則部分。 */
/* 寫在這一部分的注釋要有前導空格,否則lex編譯出錯。*/
/* 翻譯規(guī)則的形式是:正規(guī)式 {動作}
* 其中,正規(guī)式要頂行首寫,動作要以C語法寫(動作會被拷貝到y(tǒng)ylex()函數(shù)中,),\
* 正規(guī)式和動作之間要用空白分割。
*/
{ws} {;/* 此時詞法分析器沒有動作,也不返回,而是繼續(xù)分析。 */}
/* 正規(guī)式部分用大括號擴住的表示正規(guī)定義名,例如{ws}。
* 沒有擴住的直接表示正規(guī)式本身。
* 一些元字符沒辦法表示它本身,此時可以用轉(zhuǎn)義字符或
* 用雙引號括起來,例如"<"
*/
while {return (WHILE);}
do {return (DO);}
{id} {yylval = installID (); return (ID);}
{number} {yylval = installNum (); return (NUMBER);}
"<" {yylval = LT; return (RELOP);}
"<=" {yylval = LE; return (RELOP);}
"=" {yylval = EQ; return (RELOP);}
"<>" {yylval = NE; return (RELOP);}
">" {yylval = GT; return (RELOP);}
">=" {yylval = GE; return (RELOP);}
. {yylval = ERRORCHAR; return ERRORCHAR;}
/*.匹配除換行之外的任何字符,一般可作為最后一條翻譯規(guī)則。*/
%%
/* 第三部分是輔助函數(shù)部分,這部分內(nèi)容以及前面的%%都可以省略 */
/* 輔助函數(shù)可以定義“動作”中使用的一些函數(shù)。這些函數(shù)
* 使用C語言編寫,并會直接被拷貝到lex.yy.c中。
*/
int installID () {
/* 把詞法單元裝入符號表并返回指針。*/
return ID;
}
int installNum () {
/* 類似上面的過程,但詞法單元不是標識符而是數(shù) */
return NUMBER;
}
/* yywrap這個輔助函數(shù)是詞法分析器遇到輸入文件結(jié)尾時會調(diào)用的,用來決定下一步怎么做:
* 若yywrap返回0,則繼續(xù)掃描;返回1,則詞法分析器返回報告文件已結(jié)束的0。
* lex庫中的標準yywrap程序就是返回1,你也可以定義自己的yywrap。
*/
int yywrap (){
return 1;
}
void writeout(int c){
switch(c){
case ERRORCHAR: fprintf(yyout, "(ERRORCHAR, \"%s\") ", yytext);break;
case RELOP: fprintf(yyout, "(RELOP, \"%s\") ", yytext);break;
case WHILE: fprintf(yyout, "(WHILE, \"%s\") ", yytext);break;
case DO: fprintf(yyout, "(DO, \"%s\") ", yytext);break;
case NUMBER: fprintf(yyout, "(NUM, \"%s\") ", yytext);break;
case ID: fprintf(yyout, "(ID, \"%s\") ", yytext);break;
case NEWLINE: fprintf(yyout, "\n");break;
default:break;
}
return;
}
/* 輔助函數(shù)里可以使用yytext和yyleng這些外部定義的變量。
* yytext指向輸入緩沖區(qū)當前詞法單元(lexeme)的第一個字符,
* yyleng給出該詞法單元的長度 */
/* 如果你的詞法分析器并不是作為語法分析器的子程序,
* 而是有自己的輸入輸出,你可以在這里定義你的詞法
* 分析器的main函數(shù),main函數(shù)里可以調(diào)用yylex()
*/
int main (int argc, char ** argv){
int c,j=0;
if (argc>=2){
if ((yyin = fopen(argv[1], "r")) == NULL){
printf("Can't open file %s\n", argv[1]);
return 1;
}
if (argc>=3){
yyout=fopen(argv[2], "w");
}
}
/* yyin和yyout是lex中定義的輸入輸出文件指針,它們指明了
* lex生成的詞法分析器從哪里獲得輸入和輸出到哪里。
* 默認:鍵盤輸入,屏幕輸出。
*/
while (c = yylex()){
writeout(c);
j++;
if (j%5 == 0) writeout(NEWLINE);
}
if(argc>=2){
fclose(yyin);
if (argc>=3) fclose(yyout);
}
return 0;
}
6、使用終端生成.l 文件
(1)
flex filename.l
(2)用gcc編譯器編譯lex翻譯器生成的c源程序(lex翻譯器生成的c源程序名固定為lex.yy.c):
gcc [-o outfile] lex.yy.c –lfl
其中,-lfl是鏈接flex的庫函數(shù)的,庫函數(shù)中可能包含類似yywrap一類的標準函數(shù)。-o outfile是可選編譯選項,該選項可將編譯生成的可執(zhí)行程序命名為outfile,如果不寫該編譯選項,默認情況下生成的可執(zhí)行程序名為a.exe(linux下實際為a.out)。
參考:
一個Lex/Yacc完整的示例:https://blog.csdn.net/libinbin_1014/article/details/78833172
Lex編程:https://blog.csdn.net/zhu867564473/article/details/79131381