【Flex學習筆記】1:生成簡易的詞法分析程序

簡述

Flex是重寫Lex誕生的快速詞法分析生成器,在編譯前端(詞法分析->語法分析->語義分析)中處在最靠前的位置,它可以用來生成特定的詞法分析程序。

安裝Flex:

apt-get install flex

沒有專用於Flex的IDE,可以在VSCode安裝Lex Flex Yacc Bison插件,可以讓Flex語法高亮。

Flex使用示例

Flex程序通常寫成.l文件,其中由兩個%%分成上中下三部分,第一部分是聲明和選項設置(編譯後被原樣寫入生成的詞法分析程序最頂端),第二部分是正則表達式模式和相應Action,第三部分主程序中是一些與Action相關的例程(也會被照抄)。

第一部分如果不需要就不用寫。第三部分如果不寫,Flex會提供一個最小的調用詞法分析器的主程序。

核心就在第二部分,因爲一個正則表達式可以對應一個有限自動機,Flex就是將正則表達式翻譯成DFA,然後在DFA轉移時執行相應的Action完成處理。

如課本上的統計行數、單詞數、字符數的Flex程序:

%{
    int chars = 0; //用於記錄字符數
    int words = 0; //用於記錄單詞數
    int lines = 0; //用於記錄行數
%}

%% //分割第一部分和第二部分

[^ \t\n\r\f\v]+ { words++; chars+=strlen(yytext); } //匹配到單詞(沒有空白符的連續串)時,單詞數+1,字符數+=單詞長度
\n              { chars++; lines++; } //匹配到換行符時,字符數+1,行數+1
.               { chars++; } //匹配到其它任何字符,只增加字符數

%% //分割第二部分和第三部分

//主函數
int main(int argc, char **argv)
{
    yylex(); //調用Flex提供的詞法分析例程yylex()
    printf("%8d%8d%8d\n", lines, words, chars); //輸出統計結果
}

使用flex命令對其翻譯:

flex WordCount.l

在同一目錄下生成了近1800的C語言程序lex.yy.c,這就是Flex生成的程序,只不過這個例子不是詞法分析目的。將這個程序用gcc(或者Unix的cc)編譯:

gcc lex.yy.c -lfl

其中-lfl參數用於鏈接flex的庫函數。編譯後在目錄下生成了a.out,直接執行就可以使用這個程序了,輸入可換行的文本並最終Ctrl+D(這是Unix/Linux下的換行符)結束輸入:

lzh@DESKTOP-HCSIG2E:/mnt/e/Compiler/flex$ ./a.out
I am sb lzh.
I like cute cat, and i wanna eat foods.
That's all, bye!
       3      16      70

這表示,輸入的文本有3行,16個單詞("That’s"整個視爲一個單詞),70個字符。

Flex生成詞法分析程序

這裏按照課本上的案例,做一個整數的四則運算器。

Flex在這裏生成詞法分析程序,詞法分析程序會識別輸入的單詞,然後將分析結果輸出。

%%

"+"     { printf("PLUS\n"); }
"-"     { printf("MINUS\n"); }
"*"     { printf("TIMES\n"); }
"/"     { printf("DIVIDE\n"); }
"|"     { printf("ABS\n"); }
[0-9]+  { printf("NUMBER %s\n", yytext); } //數字
\n      { printf("NEWLINE\n"); }
[ \t]   { } //忽略空白符
.       { printf("Mystery charactor %s\n", yytext); } //其它字符是不合法的,提示錯誤

%%

翻譯,編譯,運行:

lzh@DESKTOP-HCSIG2E:/mnt/e/Compiler/flex$ ./a.out
2019+11-30/2
NUMBER 2019
PLUS
NUMBER 11
MINUS
NUMBER 30
DIVIDE
NUMBER 2
NEWLINE
77 + 8 8 | abc
NUMBER 77
PLUS
NUMBER 8
NUMBER 8
ABS
Mystery charactor a
Mystery charactor b
Mystery charactor c
NEWLINE

改進的詞法分析程序

一般爲模式匹配的Action中設置返回值(而不是像前面那樣直接print),這樣yylex()每次識別到相應的模式,如果有返回值,就立即返回,然後繼續調用yylex()識別下一個模式;如果是沒有返回值的模式,就會繼續向後識別。

另外,還可以設置記號編號記號值。記號編號用於記錄詞的類別,而記號的值則用於記錄此類別的某個具體值。例如記號編號可以是NUMBER,然後記號值取10,就表示了整型常量10。

Bison創建的語法分析器自動從258開始指派記號編號,這是爲了防止和文字字符衝突。Flex裏爲了和Bison統一,也不妨從258開始編號。

記號的值爲了能給不同的類型使用,通常使用union類型。不過這個例子裏因爲只有數字需要有記號值,所以直接就用int類型。

%{
    //記號編號
    enum yytokentype{
        NUMBER = 258,
        ADD = 259,
        SUB = 260,
        MUL = 261,
        DIV = 262,
        ABS = 263,
        EOL = 264
    };
    //存儲記號值
    int yylval;
%}

%%

"+"     { return ADD; }
"-"     { return SUB; }
"*"     { return MUL; }
"/"     { return DIV; }
"|"     { return ABS; }
[0-9]+  { yylval = atoi(yytext); return NUMBER; } //匹配到數字時,將其轉爲int寫入記號值的變量中
\n      { return EOL; }
[ \t]   { } //忽略空白符
.       { printf("Mystery charactor %c\n", *yytext); } //其它字符是不合法的,提示錯誤

%%

int main(int argc, char **argv) {
    int tok;
    while(tok=yylex()) { //每次從記號流中匹配出一個記號編號
        printf("%d", tok);
        if(tok==NUMBER) //如果是數字,還需要輸出記號值
            printf(" = %d\n", yylval);
        else
            printf("\n");
    }
    return 0;
}

翻譯,編譯,運行:

lzh@DESKTOP-HCSIG2E:/mnt/e/Compiler/flex$ ./a.out
2019+11-30/2
258 = 2019
259
258 = 11
260
258 = 30
262
258 = 2
264
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章