編譯原理_計算器_flex、bison實現(詳細輔助理解)
- P.S. 這篇文章只能助你理解並修改程序,博主也只理解了部分, 而且很久沒用了, 其他的都忘了. 附贈安裝配置環境的教程(畢竟很多人卡在第一步嘛( ̄▽ ̄)/)
- 一 環境配置:Windows/Ubuntu+flex、bison
- 二 一個簡單的示例代碼calcSimple下載
- 三 把 calcSimple 修改成 完整版的計算器 全攻略
- 四 非常重要的 兩個學長學姐的 示例程序。
- 五 理解 `.l` 文件和 `.y` 文件
- 六 詞法分析&語法分析
- 七 編譯、運行的時候,常見錯誤以及對策
- 八 源代碼
目標:參考範例程序, 用 Flex 和 Bison 實現一個功能更爲強大的計算器,包含以下運算:
a) 加、減、乘、除運算
b) 乘方、開方運算
c) 位運算
– 與 & 、或 |、非 ~…
d) 階乘運算 !
e)sin cos tan
sin(SIN*pi/180.0)//把角度變成弧度即把180度變成π
如果要寫實驗報告的話,請先看報告書的要求,一邊截圖一邊編程,免得寫報告的時候,浪費時間。
P.S. 這篇文章只能助你理解並修改程序,博主也只理解了部分, 而且很久沒用了, 其他的都忘了. 附贈安裝配置環境的教程(畢竟很多人卡在第一步嘛( ̄▽ ̄)/)
一 環境配置:Windows/Ubuntu+flex、bison
\1 使用Windows+CodeBlocks+flex、bison的環境(有兩種方式)
方式1 藉助codeblcoks編譯、運行。
flex_bison 下載 百度雲密碼:usk6
flex_bison 備用下載鏈接
1)下載百度雲裏的flex和bison。放到windows環境下。
2)把.l文件和.y文件複製到該文件下
3)在2)文件夾的地址欄(也就是下圖畫紅圈的地方),輸入cmd
4)在cmd裏輸入
flex -ocalc.c calc.l
bison -ocalc.tab.h calc.y //注意-o後面沒有空格
這樣,會生成兩個文件,calc.tab.h 和 calc.c
然後,把生成的.c文件(calc.c),丟進 codeblocks裏,編譯,運行。
方式2 配置MinGW直接在cmd下編譯、運行。
flex_bison 下載 百度雲密碼:usk6
flex_bison 備用下載鏈接
1)下載百度雲裏的flex和bison。放到windows環境下。
2)把.l文件和.y文件複製到該文件下
3)把%codeblocks%\MinGW\bin添加到 電腦\屬性\高級系統設置\環境變量\PATH(即把codeblocks的編譯器的路徑放到環境變量PATH裏)
4)在2)文件夾的地址欄(也就是下圖畫紅圈的地方),輸入cmd
flex calc.l
bison -o calc.tab.h calc.y //注意-o後面有沒有空格都可以 ==
gcc -o aa lex.yy.c calc.tab.h //編譯
aa //運行aa.exe
這種方式,會生成兩個文件,calc.tab.h 、calc.tab.c 和 calc.c
這樣,就直接在cmd界面,運行程序,而不要通過codeblocks。
\2 使用Ubuntu+flex、bison的環境,來編譯、運行。
vm12+ubuntukylin16.04 虛擬機安裝ヾ(o◕∀◕)ノヾ (❁´︶`❁)
然後,在ubuntu安裝flex、bison並完成編譯
老版本的ubuntu可能這樣安裝不了,這種情況,我只能說。。。。。重裝一下ubuntu?(逃
ubuntu下打開終端,安裝flex、bison:
sudo apt-get install flex bison //安裝flex和bison
flex -h
bison -h //如果有提示信息表示安裝成功
編譯和運行:
cd ........./calcSimple //移動到程序的當前目錄
bison -d calc.y
flex calc.l
/*
-lm在提示pow未定義引用時添加。
編譯lex.yy.c calc.tab.c 用-o輸出到calc
*/
gcc -o calc lex.yy.c calc.tab.c -lm
./calc //運行calc
如果有 正確的 Makefile文件 的話,直接輸入:
sudo make
./calc
二 一個簡單的示例代碼calcSimple下載
鏈接:http://pan.baidu.com/s/1slc9aPn 密碼:uyyi
calc.l
%option noyywrap
%{
/*
* 一個簡單計算器的Lex詞法文件
*/
void yyerror(char*);
#include "calc.tab.h"
%}
%%
/* a-z爲變量 */
[a-z] {
yylval = *yytext - 'a';
return VARIABLE;
}
/* 整數 */
[0-9]+ {
yylval = atoi(yytext);
return INTEGER;
}
/* 運算符 */
[-+()=/*\n] {return *yytext;}
/* 空白被忽略 */
[ \t] ;
/* 其他字符都是非法的 */
. yyerror("無效的輸入字符");
%%
##calc.y
%token INTEGER VARIABLE
%left '+' '-'
%left '*' '/'
%{
/*for Visual studio */
/* #define __STDC__ 0 */
#include <stdio.h>
void yyerror(char*);
int yylex(void);
int sym[26];
%}
%%
program:
program statement '\n'
|
;
statement:
expr {printf("%d\n", $1);}
|VARIABLE '=' expr {sym[$1] = $3;}
;
expr:
INTEGER
|VARIABLE{$$ = sym[$1];}
|expr '+' expr {$$ = $1 + $3;}
|expr '-' expr {$$ = $1 - $3;}
|expr '*' expr {$$ = $1 * $3;}
|expr '/' expr {$$ = $1 / $3;}
|'('expr')' {$$ = $2;}
;
%%
void yyerror(char* s)
{
fprintf(stderr, "%s\n", s);
}
int main(void)
{
printf("A simple calculator.\n");
yyparse();
return 0;
}
三 把 calcSimple 修改成 完整版的計算器 全攻略
此小節簡略說明一下.l文件和.y文件,如果想更多的瞭解這個程序的意義,請看下文
在calcSimple的基礎上,
在.l文件裏添加:(注意中/英文的標點符號不一樣)
/* 運算符 /
[-+()=/!\n] {return *yytext;}
在.y文件裏添加文法部分:
|expr ‘!’ {int i,s=1;for(int i=1;i<=$2;i++)s*=i;$$=s;}
這裏用到了c語言,所以要在.y程序第二部分即%{}%裏面添加#include<stdio.h>
然後在.y文件開頭添加 %right ‘!’
這裏表示左/右結合性,以及運算符優先級,越是在下面優先級越高
仿照以下兩個程序,把運算符都添加進去,就完成了基本完整的計算器。
http://blog.csdn.net/xiaofeige567/article/details/28301877
http://blog.csdn.net/ly_624/article/details/51125482
四 非常重要的 兩個學長學姐的 示例程序。
兩個學長學姐,寫的很清晰易懂,但不能直接使用,不清楚爲什麼,僅供參考。==
兩個代碼差別挺大的。
http://blog.csdn.net/xiaofeige567/article/details/28301877
http://blog.csdn.net/ly_624/article/details/51125482
五 理解 .l
文件和 .y
文件
\1 查閱龍書(編譯原理)中文第二版(P86和P170 )
(lex和yacc是Unix的軟件,而flex和bison是其在ubantu(linux下)的兼容版本)
P86 詳細解釋了flex(lex)軟件的 代碼。也就是calc.l文件的詳細解釋
P170 詳細解釋了bison(yacc)軟件的 代碼。也就是calc.y文件的詳細解釋
\2 老師課件 上的解釋
鏈接: 老師課件
2-詞法分析-RE-Lex.pptx
YACC.pptx
實驗-補充-LEX.pdf
六 詞法分析&語法分析
\1 詞法分析
首先來看flex的使用:
簡單來說分爲兩步:
1 先定義一個flex的輸入文件,描述詞法。
2 用flex程序處理這個文件,生成對應的C語言源代碼文件。
(一般flex的輸入文件以.l文件結尾, 比如這個文件calc.l)
文件分成三個部分
第一部分是從 %{ 到 }% 標記的部分。
這個部分會原封不動的複製到flex的生成代碼中。
文件開頭定義了一個YYSTYPE宏。
每個TOKEN可以有一個lval值屬性,
YYSTYPE定義類型就是token的lval的類型。
_EasyTData是我們的web服務層和web頁面層公用的通用數據結構。
然後就是一些要include的頭文件,第一部分就完了。
lex的輸入文件的第二部分,是從 % } 到 % % 之間的部分,
這部分用正則表達式定義了一些數據類型。
比如int num string ignore_char identifier等。
注意這裏使用的正則表達式的形式是ERE而不是BRE。
ERE與BRE比較明顯的區別就是,
ERE使用+表示字符重複一次以上,*表示字符重複0次以上。
BRE使用{1,}這種方式表示字符重a
文件的第三部分,是% % 到% % 的部分。
這裏定義了詞法分析器在解析的處理動作。
yytext是一個flex內部的標識符,表示匹配到的字符串。
上文介紹了,lval也是一個內部標識符,表示TOKEN的值。
json2tdata_是標識符的前綴, 在執行flex的時候,用-P指定。
flex輸入文件寫完之後,使用下面這條命令,
就可以把flex的輸入文件轉換爲C語言的源代碼了。
flex calc.l//生成lex.yy.c
\2 語法分析
語法分析是使用bison工具。
使用bison工具也是分爲兩步,
第一步寫bison的輸入文件,第二步用bison程序生成C語言源碼。
(bison的輸入文件一般用.y作爲後綴名。)
和flex的詞法分析輸入文件類似,bison的輸入文件也是分成3部分。
第一部分% {和% }之間,是原封不動拷貝到輸出的C語言源文件中的。
json2tdata_lex這個函數是flex生成的。
json2tdata_error是用來處理錯誤信息的函數。
通過定義和實現這個函數你可以把錯誤信息寫到任何地方。
與flex類似,json2tdata也是自定義的前綴。
第二部分是%token INT NUM STRING R_BRACKET COLON
SEMICOLON COMMA IDENTIFIER TRUE FALSE NIL這一行,
這一行的作用就是聲明在flex中定義的那些TOKEN。
第三部分是% % % %包圍的部分。
這部分就是語法的推導過程。
可以比較輕鬆的看出,這部分主要就是採用BNF對語法進行描述。
比如Array, 它有兩種形式。
第一種是 L_BRACKET ELEMENTS R_BRACKET,
第二種則是L_BRACKET R_BRACKET,
這表示一個空的Array。
Bison能夠完全支持LR(1)文法。
這種文法的特點是隻要多向前看一個TOKEN,
就能夠決定如何解析。
因此如果bison告訴你語法ambiguous的時候,
可以想一想如何把自己的文法改成LR(1)型文法。
另外,每一條規則的後面可以用{}來定義解析的動作
bison用$$表示規則左邊的對象,
用$1 $2 $3 等依次表示規則右邊的對象。
七 編譯、運行的時候,常見錯誤以及對策
1) …shift/reduce conflict…
最常見的情況是:在.l 和.y文件中沒有添加相應 符號,或者沒有寫優先級
2) 在原來的只能用整數的示例程序裏添加 小數 的功能
在 .l 和 .y 文件裏添加 #define YYSTYPE double
在.l文件裏 atoi(yytext)改爲 atof(yytext)
//一般會有錯誤提示,按照錯誤提示一個個改就好了。
爲所有用到整數型的地方,添加強制類型轉換 (int)
3) pow的未定義引用
兩種可能
.y文件裏沒有添加math.h頭文件
gcc -o calc lex.yy.c calc.tab.c -lm //沒有添加-lm
轉自:
http://blog.csdn.net/li740207611/article/details/51072111
原因:Linux下用math.h庫的pow()函數,
gcc編譯的時候報錯返回:對‘pow’未定義的引用
查了下資料,需要在gcc編譯的時候加上-lm參數才能正常編譯。
這是爲什麼呢?再查了下資料:
使用math.h中聲明的庫函數還有一點特殊之處,
gcc命令行必須加-lm選項,因爲數學函數位於libm.so庫文件中
(這些庫文件通常位於/lib目錄下),-lm選項告訴編譯器,
我們程序中用到的數學函數要到這個庫文件裏找。
本書用到的大部分庫函數(例如printf)位於libc.so庫文件中,
使用libc.so中的庫函數在編譯時不需要加-lc選項,
當然加了也不算錯,因爲這個選項是gcc的默認選項。
以上,如有疏漏,敬請指正。
八 源代碼
a.l 文件
%{
/*
* 一個簡單計算器的Lex詞法文件
*/
int yywrap();
#define YYSTYPE double
void yyerror(char*);
#include "a.tab.h"
%}
%%
/* a-z爲變量 */
[a-z] {
yylval = *yytext - 'a';
return VARIABLE;
}
/*16進制數*/
0x\.?[a-f0-9]+|0x[a-f0-9]+\.[a-f0-9]* {
yylval=atof(yytext);
return HEXADECIMAL;
}
/* 整數或者小數 */
\.?[0-9]+|[0-9]+\.[0-9]* {
yylval = atof(yytext);
return INTEGER;
}
/* 運算符 */
[-+()=/*&|~!^@\n] {return *yytext;}
/* 三角函數 */
sin {
return SIN;
}
cos {
return COS;
}
tan {
return TAN;
}
/* 空白被忽略 */
[ \t] ;
/* 其他字符都是非法的 */
. yyerror("無效的輸入字符");
%%
int yywrap()
{return 1;}
a.y 文件
%token HEXADECIMAL INTEGER VARIABLE SIN COS TAN
%left '+' '-'
%left '*' '/'
%left '&'
%left '|'
%left '^'
%right '@''~'
%left '!'
%{
/*for Visual studio */
/* #define __STDC__ 0 */
#include <stdio.h>
#include <math.h>
#define YYSTYPE double
#define pi 3.1415926
void yyerror(char*);
int yylex(void);
double sym[26];
%}
%%
program:
program statement '\n'
|
;
statement:
expr {printf("%lf\n", $1);}
|VARIABLE '=' expr {sym[(int)$1] = $3;}
;
expr:
INTEGER
|HEXADECIMAL
|VARIABLE{$$ = sym[(int)$1];}
|expr '+' expr {$$ = $1 + $3;}
|expr '-' expr {$$ = $1 - $3;}
|expr '*' expr {$$ = $1 * $3;}
|expr '/' expr {$$ = $1 / $3;}
|expr '&' expr {$$ = (int)$1 & (int)$3;}
|expr '|' expr {$$ = (int)$1 | (int)$3;}
|'~' expr {$$ = ~(int)$2;}
|'@' expr {$$ = sqrt($2);}
|expr '@' expr {$$ = $1*sqrt($3);}
|expr '!' {int i=1,s=1;for(;i<=$2;i++)s*=i;$$=s;}
|expr '^' expr {$$=pow($1,$3);}
|'('expr')' {$$ = $2;}
|SIN'('expr')' {$$ = sin($3*pi/180.0);}
|COS'('expr')' {$$ = cos($3*pi/180.0);}
|TAN'('expr')' {$$ = tan($3*pi/180.0);}
;
%%
void yyerror(char* s)
{
fprintf(stderr, "%s\n", s);
}
int main(void)
{
printf("A simple calculator.\n可以用的運算符:+-*/&|~!^@ \n要注意的是三角函數使用時要加括號。 例:sin(60)\n");
yyparse();
return 0;
}
Makefile 文件(只在linux下可用,注意文件名得是Makefile,大小寫敏感)
- Makefile 文件最基礎教程:
https://blog.csdn.net/qq_35208390/article/details/78488099
all: prog clean
prog: 1a.l 1a.y
flex 1a.l
bison -d 1a.y
gcc -o a lex.yy.c 1a.tab.c -lm
clean:
rm lex.yy.c 1a.tab.c 1a.tab.h