簡述
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