第一節、lex和yacc是什么?
lex 代表 lexical analyzar(詞法分析器),yacc 代表 yet another compiler compiler(編譯器代碼生成器)。lex和yacc在UNIX下分別叫flex和bison. 可以搜索到很多介紹flex&bison的文章,但這類文章對初學者來說不太容易看懂。
我們舉個簡單的例子來理解lex和yacc:在linux下,有很多系統配置文件,一些linux下的軟件也有配置文件,那么程序是如何讀取配置文件中的信息的呢?先用到lex詞法分析器,讀取配置文件中的關鍵詞(后面說到的token標記其實可看做關鍵詞);然后把關鍵詞遞交給yacc,yacc對一些關鍵詞進行匹配,看是否符合一定的語法邏輯,如果符合就進行相應動作。
上面舉的例子是分析配置文件內容的,當然可分析其他文件內容,或者制作編譯器等。
第二節、一個簡單的lex程序。
1、程序代碼。
來看一個簡單的lex程序,代碼見下面,這段lex程序的目的是:輸入幾行字符串,輸出行數,單詞數和字符的個數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
|
/******************************************* * Name : test.l * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : 一個簡單的lex例子,輸入幾行字符串, * 輸出行數,單詞數和字符的個數。 *******************************************/ /* 第一段 */ %{ int chars = 0; int words = 0; int lines = 0; %} /* 第二段 */ %% [a-zA-Z]+ { words++; chars += strlen(yytext); } \n { chars++; lines++; } . { chars++; } %% /* 第三段 */ main(int argc, char **argv) { yylex(); printf("%8d%8d%8d\n", lines, words, chars); }
|
程序中yytext是lex變量,匹配模式的文本存儲在這一變量中。yylex()這一函數開始分析,它由lex自動生成。關於lex變量和函數后續再介紹,這里只是通過簡單的lex程序來認識lex.
2、按照下面過程編譯運行。
#flex test.l
#gcc lex.yy.c –lfl
#./a.out
然后輸入一段文字,按ctrl+d結束輸入,則會輸出行數,單詞數和字符的個數。
見下圖:

3、分析上面的lex程序。
(1)%%把文件分為3段,第一段是c和lex的全局聲明,第二段是規則段,第三段是c代碼。
(2)第一段的c代碼要用%{和%}括起來,第三段的c代碼不用。
(3)第二段規則段,[a-zA-Z]+ \n . 是正則表達式,{}內的是c編寫的動作。
4、編譯時不加-lfl選項。
上面編譯時用gcc lex.yy.c –lfl,那么如何直接用gcc lex.yy.c進行編譯呢?答案是加上yywrap函數(具體原因見lex的庫和函數分析),代碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
|
/******************************************* * Name : test.l * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : 一個簡單的lex例子,輸入幾行字符串, * 輸出行數,單詞數和字符的個數。 * 加yywrap函數。 *******************************************/ %{ int chars = 0; int words = 0; int lines = 0; %} %% [a-zA-Z]+ { words++; chars += strlen(yytext); } \n { chars++; lines++; } . { chars++; } %% main(int argc, char **argv) { yylex(); printf("%8d%8d%8d\n", lines, words, chars); } int yywrap() { return 1; }
|
第三節、lex進階。
修改第二節程序,將正則表達式放在全局聲明中,使邏輯更清晰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
|
/******************************************* * Name : test.l * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : 一個簡單的lex例子,輸入幾行字符串, * 輸出行數,單詞數和字符的個數。 * 正則表達式放在全局聲明中。 *******************************************/ int chars = 0; int words = 0; int lines = 0; %} mywords [a-zA-Z]+ mylines \n mychars . %% {mywords} { words++; chars += strlen(yytext); } {mylines} { chars++; lines++; } {mychars} { chars++; } %% main(int argc, char **argv) { yylex(); printf("%8d%8d%8d\n", lines, words, chars); }
|
編譯運行同第二節。
第四節、lex再進階—循環掃描。
下面給出一個lex程序,這個程序在掃描到 + 或 - 時做一個特殊輸出。當調用yylex()函數時,若掃描到return對應的標記時,yylex返回,且值就為return后的值;若沒掃描到return對應的標記,yylex繼續執行,不返回。下次調用自動從前一次的掃描位置處開始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
|
/******************************************* * Name : test.l * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : lex進階,循環掃描。 *******************************************/ %{ enum yytokentype { ADD = 259, SUB = 260, }; %} myadd "+" mysub "-" myother . %% {myadd} { return ADD; } {mysub} { return SUB; } {myother} { printf("Mystery character\n"); } %% main(int argc, char **argv) { int tok; while (tok = yylex()) { if (tok == ADD || tok == SUB) { printf("meet + or -\n"); } else { printf("this else statement will not be printed, \ because if yylex return,the retrun value must be ADD or SUB."); } } }
|
編譯和運行見下圖:

第五節、yacc語法。
1、yacc語法規則部分和BNF類同,先來看BNF巴克斯范式。
(1)<> 內包含的內容為必選項;
(2)[] 內的包含的內容為可選項;
(3){ } 內包含的為可重復0至無數次的項;
(4) | 表示在其左右兩邊任選一項,相當於"OR"的意思;
(5)::= 是“被定義為”的意思;
(6)雙引號“”內的內容代表這些字符本身;而double _quote用來表示雙引號。
(7)BNF范式舉例,下面的例子用來定義java中的for語句:
FOR_STATEMENT ::=
"for" "(" ( variable_declaration |
( expression ";" ) | ";" )
[ expression ] ";"
[ expression ]
")" statement
2、yacc語法。
注:components是根據規則放在一起的終端和非終端符號,后面是{}括起來的執行的動作。
3、語法例子。
1 2 3 4 5
|
|
param : NAME EQ NAME { printf("\tName:%s\tValue(name):%s\n", $1,$3); } | NAME EQ VALUE { printf("\tName:%s\tValue(value):%s\n",$1,$3);} ;
|
yacc文件第一段中定義的token,lex文件對目標進行掃描並返回這些token。yacc文件對規則冒號右邊componets進行匹配,如果符合一定語法規則就執行相應動作。
1 2 3 4 5 6 7 8 9 10 11
|
|
simple_sentence: subject verb object | subject verb object prep_phrase ; subject: NOUN | PRONOUN | ADJECTIVE subject ; verb: VERB | ADVERB VERB | verb VERB ; object: NOUN | ADJECTIVE object ; prep_phrase: PREPOSITION NOUN ;
|
分析:|表示左右兩邊任選一項,如| subject verb object prep_phrase ;中|的左邊為空,所以該句表示匹配空或者subject verb object prep_phrase ;而上面還有一句subject verb object ,所以
simple_sentence: subject verb object
| subject verb object prep_phrase ;
的意思是匹配subject verb object 或 subject verb object prep_phrase ;
第六節、lex和yacc結合使用。
1、lex程序。
當匹配a b c not時分別返回相應的token.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
|
/******************************************* * Name : test.l * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc結合使用。 *******************************************/ %{ #include "test.tab.h" #include <stdio.h> #include <stdlib.h> %} %% a { return A_STATE; } b { return B_STATE; } c { return C_STATE; } not { return NOT; } %%
|
2、yacc程序。
當掃描到A_STATE B_STATE時打印1,當掃描到A_STATE B_STATE
c_state_not_token時打印2,當掃描到NOT時打印3.
其中,A_STATE B_STATE NOT是token,c_state_not_token
是非終端符號,此處定義為
c_state_not_token : C_STATE {}.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
|
/******************************************* * Name : test.y * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc結合使用。 *******************************************/ %{ #include <stdio.h> #include <stdlib.h> %} %token A_STATE B_STATE C_STATE NOT %% program : A_STATE B_STATE { printf("1"); } c_state_not_token { printf("2"); } | NOT { printf("3"); } c_state_not_token : C_STATE {} %% yyerror(const char *s) { fprintf(stderr, "error: %s\n", s); } int main() { yyparse(); return 0; }
|
3、編譯和運行。
lex和yacc在UNIX下分別叫flex和bison.

第七節、lex和yacc結合使用進階。
1、我們希望用lex和yacc結合完成下面文件解析。
我們對文本test.txt進行分析,test.txt中的內容如下:
ZhangSan=23
LiSi=34
WangWu=43
掃描test.txt文本后,我們希望輸出:
ZhangSan is 23 years old!!!
LiSi is 34 years old!!!
WangWu is 43 years old!!!
2、利用lex掃描test.txt文本,返回token.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
|
/******************************************* * Name : test.l * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc結合使用進階。 *******************************************/ %{ #include "test.tab.h" #include <stdio.h> #include <string.h> %} char [A-Za-z] num [0-9] eq [=] name {char}+ age {num}+ %% {name} { yylval = strdup(yytext); return NAME; } {eq} { return EQ; } {age} { yylval = strdup(yytext); return AGE; } %% int yywrap() { return 1; }
|
3、yacc根據lex返回的token,判斷這些token是否符合一定的語法,符合則進行相應動作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
|
/******************************************* * Name : test.y * Date : Mar. 11, 2014 * Blog : http://www.cnblogs.com/lucasysfeng/ * Description : lex和yacc結合使用進階。 *******************************************/ %{ #include <stdio.h> #include <stdlib.h> typedef char* string; #define YYSTYPE string %} %token NAME EQ AGE %% file : record file | record ; record : NAME EQ AGE { printf("%s is %s years old!!!\n", $1, $3); } ; %% int main() { extern FILE* yyin; if (!(yyin = fopen("test.txt", "r"))) { perror("cannot open parsefile:"); return -1; } yyparse(); fclose(yyin); return 0; } int yyerror(char *msg) { printf("Error encountered: %s \n", msg); }
|
4、編譯運行。

補充:lex變量和和函數。
