lex 基礎(chǔ)

一、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ù)

yyinyyout :這是Lex中本身已定義的輸入和輸出文件指針。這兩個變量指明了lex生成的詞法分析器從哪里獲得輸入和輸出到哪里。默認:鍵盤輸入,屏幕輸出。
yytextyyleng :這也是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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 鏈接地址:https://www.tutorialspoint.com/compiler_design/compi...
    dannyvi閱讀 4,902評論 1 12
  • 一、背景 為了讀懂postgresql的語法分析和更好的使用 Lex ,所以繼續(xù)學習lex的進階部分。Lex & ...
    hemny閱讀 7,409評論 0 5
  • 源于編譯的實驗作業(yè)…… 參考鏈接:blog.csdn.net/dl88250/article/details/17...
    minlover閱讀 2,678評論 0 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,641評論 1 32
  • 簡介 如果你有Unix環(huán)境的編程經(jīng)驗,想必你肯定遇到過神秘的Lex和YACC工具,在GUN/Linux中,又分別稱...
    upupSue閱讀 4,739評論 0 8

友情鏈接更多精彩內(nèi)容