使用lex---01

(一)、寫在前面


lex是構建詞法分析程序的工具。詞法分析程序把隨機輸入流標記化,即將他拆分成詞法標記。然後,可以進一步處理這種被標記化的輸出,通常是由yacc來處理的,或者他就成爲“最終產品”。

當編寫lex規範的時候,可以創建lex匹配輸入所用的一套模式。每次匹配一個模式的時候,lex程序就調用我們提供的C代碼來處理匹配的文本。採用這種方式,lex程序將輸入拆分成成爲標記的字符串。lex本身不產生可執行程序。相反,他把lex規範轉化成包含C例程yylex()的文件。程序調用yylex()來運行詞法分析程序。

(二)、正則表達式


1:正則表達式字符

正則表達式被廣泛應用於UNIX環境,並且lex可以使用豐富的正則表達式語言。

正則表達式是一種使用”元(meta)”語言的模式描述。元語言用於描述特定模式。形成正則表達式的字符爲:

符號 含義
. 匹配除換行符(“\n”)以外的任何單個字符
* 匹配前面表達式的零個或多個拷貝
[] 匹配括號中任意字符的字符類
^ 作爲正則表達式的第一個字符匹配行的開頭,也用於方括號中的否定
$ 作爲正則表達式的最後一個字符匹配行的結尾
{} 當括號中包含一個或兩個數字的時候,指示前面的模式被允許匹配多少次
\ 用於轉義字符
+ 匹配前面的正則表達式的一次或多次出現
? 匹配前面的正則表達式的零次或一次出現
“…” 引號中的每個字符解釋爲字面意義
/ 只有在後面跟有指定的正則表達式的時候才匹配前面的正則表達式
() 將一系列正則表達式組合成一個新的正則表達式

2:lex的正則例子程序

下面我們編寫一個小數的lex規範:

首先我們先來看一下表示小數的正則表達式:

-?(([0-9]+)|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?)

現在我們來看一下lex的實現:

名稱:xs.lex

%%
[\n\t ] ;
-?(([0-9]+)|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) { printf("number\n"); }
. ECHO;
%%

main()
{
   yylex();
}

int yywrap()
{
    return 0;
}

我們使用下面的命令編譯程序:

lex xs.lex
gcc lex.yy.c -o xs
./xs     //運行程序

下面我們來看一下運行效果:

這裏寫圖片描述

(三)、實例:單詞計數程序

下面我們通過一個實例來進一步瞭解lex。

lex規範由二部分組成:定義段,規則段和用戶子例程段。第一部分處理lex用在詞法分析程序中的選項,並且一半建立詞法分析程序運行的執行環境。

單詞計數示例的定義段如下:

%{
unsigned int charCount = 0,wordCount = 0,lineCount = 0;
%}

word    [^ \t\n]+
eol     \n

由”%{“和”%}”括住的部分是C代碼,他們將被逐字地拷貝到詞法分析程序中。這些C代碼一開始就被放入到輸出代碼中。

最後的兩行是定義。lex提供了一種簡單的替換機制,從而使定義長的或複雜的模式變得很容易。我們這裏添加了兩個定義,第一個定義提供了單詞描述:除了空格,製表符和換行符以外的字符的非空組合。第二個定義描述行結束字符,即換行。

規則段包含指定詞法分析程序的模式和動作。下面是示例中的單詞計數的規則段:

%%
{word}  { wordCount++; charCount+=yyleng; }
{eol}   { charCount++; lineCount++; }
.       { charCount++; }

規則段以”%%”開始。在模式中,lex使用substitution代替大括號{}中的名字。在詞法分析程序識別了完整的單詞之後,我們的示例增加單詞和字符的數目。

大括號中封閉的多個語句組成的動作生成一個C語言複合語句。

值得重複的是,lex總是嘗試匹配了能最長的字符串。因此,詞法分析程序將把字符串”well-being”作爲一個單詞。

示例中也使用了lex的內部變量yyleng,他包含詞法分析程序識別的字符串長度。如果匹配了well-being,yyleng就爲10。

lex規範的第三部分和最後部分是用戶子例程段。他通過”%%”和前面的段分開。用戶子例程段包含任何有效的C代碼。他被逐字拷貝到生成的詞法分析程序中。

%%
int main()
{
    yylex();
    printf("%d %d %d",lineCount,wordCount,charCount);
    return 0;
}

int yywrap()
{
    return 0;
}

首先,它調用詞法分析程序的入口點yylex(),然後調用printf()打印這次運行的結果。

下面我們來看一下該示例的整體程序:

%{
unsigned int charCount = 0,wordCount = 0,lineCount = 0;
%}

word    [^ \t\n]+
eol     \n

%%
{word}  { wordCount++; charCount+=yyleng; }
{eol}   { charCount++; lineCount++; }
.       { charCount++; }

%%
int main()
{
    yylex();
    printf("%d %d %d",lineCount,wordCount,charCount);
    return 0;
}

int yywrap()
{
    return 0;
}

要注意,我們的示例沒有任何花哨的操作:他既不接受命令行參數,也不打開任何文件,指示使用lex默認讀取標準輸入。當然,我們可以爲lex 重新連接輸入流。

我們來看一下:

%%
int main(argc,argv)
int argc;
char **argv;
{
    if(argc > 1){
        FILE *file;
        file = fopen(argv[1],"r");
        if(!file){
            fprintf(stderr,"could not open %s\n",argv[1]);
            exit(1);
        }

        yyin = file;
    }

    yylex();
    printf("%d %d %d\n",lineCount,wordCount,charCount);
    return 0;
}

int yywrap()
{
    // 0 - 輸入未完成 1 - 輸入已完成
    return 1;
}

lex詞法分析程序從標準IO文件yyin中讀取輸入,所以當需要的時候,只需要改變yyin。yyin的默認值是stdin。

編譯, 運行一下我們的程序,我們來看一下運行效果:

這裏寫圖片描述

當yylex()到達輸入文件的尾端的時候,它調用yywrap(),該函數返回數值0或1.如果值爲1,那麼程序完成而且沒有輸入。換句話說,如果值爲0,那麼詞法分析程序假設yywrap()已經打開了他要讀取的另一個文件,而且繼續讀取yyin。默認的yywrap()總是返回1 。通常是自定義一個yywrap()函數。

下面我們來實現一個處理多個文件的lex程序:

%{
/*
 * 多文件的單詞計數程序
 *
 */

 unsigned long charCount = 0,wordCount = 0,lineCount = 0;
 #undef yywrap   /* 默認情況下有時是一個宏 */

%}

word [^ \t\n]+
eol  \n

%%
{word} { wordCount++; charCount += yyleng; }
{eol}  { charCount++; lineCount++; }
.      charCount++;

%%

char **fileList;
unsigned currentFile = 0;
unsigned nFiles;
unsigned long totalCC = 0;
unsigned long totalWC = 0;
unsigned long totalLC = 0;

int main(int argc,char *argv[])
{
    FILE *file;

    fileList = argv + 1;
    nFiles = argc - 1;

    if(argc == 2){
        /*
         * 因爲不需要打印摘要行,所以處理單個文件的情況
         * 與處理多個文件的情況不同
         *
         */
         currentFile = 1;
         file = fopen(argv[1],"r");
         if(!file){
            fprintf(stderr,"could not open %s\n",argv[1]);
            exit(1);
         }
         yyin = file;
    }

    if(argc > 2)
        yywrap(); /* 打開第一個文件 */

    yylex();
    /*
     * 處理零個或一個文件與處理多個文件的又一個不同之處
     */
    if(argc > 2){
        printf("%8lu %8lu %8lu %s\n",lineCount,wordCount,charCount,fileList[currentFile-1]);
        totalCC += charCount;
        totalWC += wordCount;
        totalLC += lineCount;
        printf("%8lu %8lu %8lu total\n",totalLC,totalWC,totalCC);
    }else{
        printf("%8lu %8lu %8lu\n",lineCount,wordCount,charCount);
    }

    return 0;
}

/*
 * 詞法分析程序調用yywrap處理EOF。(比如,在本例中
 * 我們連接到一個新文件)
 */

int yywrap()
{
    FILE *file;

    if((currentFile != 0) && (nFiles > 1) && (currentFile < nFiles))
    {
        /*
         * 打印出前一個文件的統計信息
         */
         printf("%8lu %8lu %8lu %s\n",lineCount,wordCount,charCount,fileList[currentFile-1]);
         totalCC += charCount;
         totalWC += wordCount;
         totalLC += lineCount;
         charCount = wordCount = lineCount = 0;
         fclose(yyin); /* 處理完這個文件 */
    }

    while(fileList[currentFile] != (char *)0){
        file = fopen(fileList[currentFile++],"r");
        if(file != NULL){
            yyin = file;
            break;
        }

        fprintf(stderr,"could not open %s\n",fileList[currentFile-1]);
    }

    return (file ? 0 : 1); /* 0表示還有更多的輸入 */
}

示例使用yywrap()執行連續的處理。每次詞法分析程序調用yywrap()的時候,都嘗試從命令行中打開下一個文件名並將打開的文件賦給yyin,如果存在另一個文件就返回0,如果沒有就返回1.

下面我們來看一下運行的效果:

這裏寫圖片描述

(四):寫在後面

後面我們將繼續進行lex的學習,將會有更多的例子程序來使我們更加深入的理解和使用lex。加油。

代碼下載

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