編譯原理_計算器_flex、bison實現(詳細輔助理解)


目標:參考範例程序, 用 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

地址欄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

地址欄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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章