一、背景
構(gòu)建一個簡單計算器,識別輸入的計算表達(dá)式并計算結(jié)果。通過計算器程序 來說明 lex & yacc 的開發(fā)過程 和 Lex 的結(jié)構(gòu)規(guī)范。
二、環(huán)境
? 系統(tǒng): CentOS 7.5
? 編譯器: gcc - 4.8.5
? lex: flex 2.5.37
? Yacc: bison (GNU Bison) 3.0.4
安裝 flex 和 bison 。
yum install flex bison如果在編譯鏈接過程中出現(xiàn)以下錯誤:
/usr/bin/ld: cannot find -lfl
請重新安裝flex:
yum remove flex
yum install flex
三、簡單計算器實現(xiàn)
計算器實現(xiàn)整數(shù)的 +、-、*、/、% 五種簡單運(yùn)算。
詞法分析程序 cal.l
%{
#include "cal.tab.h"
extern int yylval;
%}
%%
[0-9]+ { yylval = atoi(yytext); return NUMBER; }
[ \t] ; /* ignore white space */
\n return 0; /* logical EOF */
. return yytext[0];
%%
代碼中定義了四條規(guī)則,前面的部分就是模式,處于一行的開始位置,后面部分是動作,也就是,輸入中匹配到了這個模式的時候,對應(yīng)進(jìn)行什么動作(就像機(jī)器人接受到了什么樣的指令,然后會執(zhí)行相應(yīng)的動作一樣)
第一個模式,匹配連續(xù)一個或者多個數(shù)字,匹配到之后就返回標(biāo)簽NUMBER。
第二個模式,匹配空格,沒有任何操作,忽略所有空格。
第三個模式,匹配一個換行符,匹配到之后結(jié)束匹配。
第四個模式,匹配出了\n之外的字符,返回該字符。
總體來說,匹配到連續(xù)數(shù)字,則返回NUMBER;忽略空格;換行結(jié)束;匹配到任何其他字符返回字符。
語法分析程序 cal.y
%{
#include <stdio.h>
%}
%token NAME NUMBER
%%
statement: NAME '=' expression
| expression { printf("= %d\n", $1); }
;
expression: expression '+' NUMBER { $$ = $1 + $3; }
| expression '-' NUMBER { $$ = $1 - $3; }
| expression '*' NUMBER { $$ = $1 * $3; }
| expression '/' NUMBER { $$ = $1 / $3; }
| expression '%' NUMBER { $$ = $1 % $3; }
| NUMBER { $$ = $1; }
;
%%
int main()
{
yyparse();
return 0;
}
int yyerror(char *s)
{
printf("%s/n",s);
return 0;
}
編譯運(yùn)行過程
[appusr@postgre cal]$ ls -l
total 8
-rw-rw-r--. 1 appusr appusr 200 Aug 16 11:27 cal.l
-rw-rw-r--. 1 appusr appusr 533 Aug 16 14:11 cal.y
/* 1. 編譯lex文件,生成lex.yy.c文件 */
[appusr@postgre cal]$ flex cal.l
[appusr@postgre cal]$ ls -l
total 52
-rw-rw-r--. 1 appusr appusr 200 Aug 16 11:27 cal.l
-rw-rw-r--. 1 appusr appusr 533 Aug 16 14:11 cal.y
-rw-rw-r--. 1 appusr appusr 44068 Aug 16 15:58 lex.yy.c
/* 2. 編譯yacc文件,生成cal.tab.h 與cal.tab.c文件 */
[appusr@postgre cal]$ bison -d cal.y
[appusr@postgre cal]$ ls -l
total 100
-rw-rw-r--. 1 appusr appusr 200 Aug 16 11:27 cal.l
-rw-rw-r--. 1 appusr appusr 44255 Aug 16 15:58 cal.tab.c
-rw-rw-r--. 1 appusr appusr 2063 Aug 16 15:58 cal.tab.h
-rw-rw-r--. 1 appusr appusr 533 Aug 16 14:11 cal.y
-rw-rw-r--. 1 appusr appusr 44068 Aug 16 15:58 lex.yy.c
/* 3. 鏈接生成的.c 文件,并生成相應(yīng)的可執(zhí)行文件 cal */
[appusr@postgre cal]$ gcc -o cal cal.tab.c lex.yy.c -lfl
[appusr@postgre cal]$ ls -l
total 128
-rwxrwxr-x. 1 appusr appusr 28632 Aug 16 15:58 cal
-rw-rw-r--. 1 appusr appusr 200 Aug 16 11:27 cal.l
-rw-rw-r--. 1 appusr appusr 44255 Aug 16 15:58 cal.tab.c
-rw-rw-r--. 1 appusr appusr 2063 Aug 16 15:58 cal.tab.h
-rw-rw-r--. 1 appusr appusr 533 Aug 16 14:11 cal.y
-rw-rw-r--. 1 appusr appusr 44068 Aug 16 15:58 lex.yy.c
/* 4. 運(yùn)行可執(zhí)行文件cal,計算簡單表達(dá)式 */
[appusr@postgre cal]$ ./cal
22*33
= 726
[appusr@postgre cal]$
四、Lex 的結(jié)構(gòu)規(guī)范
lex是用來生成詞法分析器的
lex源文件擴(kuò)展名.l
分為三個段:定義段、規(guī)則段、用戶子程序段
/* 定義段 */
%{
...
%}
...
%%
/* 規(guī)則段 */
...
%%
/* 用戶子程序段 */
...
三個段用 %% 進(jìn)行分隔:
1. 定義段
定義段包括文字塊、定義、內(nèi)部表聲明、起始條件和轉(zhuǎn)換。
C語言的注釋、頭文件包含等一般就放在%{%}之間,這一部分的內(nèi)容會被直接復(fù)制到輸出文件的開頭部分。
例如:
%{
#include "cal.tab.h"
extern int yylval;
%}
2. 規(guī)則段
規(guī)則段為一系列匹配模式和動作,模式一般使用正則表達(dá)式書寫,動作部分為C代碼:
模式1 {動作1 (C代碼)}
例如:
[0-9]+ { yylval = atoi(yytext); return NUMBER; }
????在輸入和模式1匹配的時候,執(zhí)行動作部分的代碼。
????C代碼被逐字拷貝到生成的C文件中。
當(dāng)lex掃描程序運(yùn)行時,它把輸入與規(guī)則段的模式進(jìn)行匹配。
? 每次發(fā)現(xiàn)一個匹配(被匹配的輸入稱為標(biāo)記(token))時就執(zhí)行與那種模式相關(guān)的C代碼。
? 如果模式后面跟著 | 符號,則該模式將使用與文件中下一個模式相同的C代碼。
? 當(dāng)輸入字符不匹配模式時,詞法分析程序的動作就好像它匹配上了代碼ECHO的模式,ECHO將標(biāo)記的拷貝寫到輸出。
3. 用戶子程序段
這里為C代碼,會被原樣復(fù)制到c文件中,一般這里定義一些輔助函數(shù)等,如動作代碼中使用到的輔助函數(shù)。
如果重新定義input()、unput()、output()、或者yywrap(),新的版本或者支持子程序,都可以放在這里。
詞法分析器所做的,就是在輸入中尋找字符的模式(pattern)。
在詞法分析器中,我們要給定我們需要識別的模式,因此需要使用一種方式來描述模式,這就是常用的正則表達(dá)式。
五、總結(jié)
本文通過親自動手構(gòu)建一個計算器實現(xiàn)來示范 lex & yacc 的開發(fā)過程 和 Lex 的結(jié)構(gòu)規(guī)范,加深 lex & yacc 的編程理解。