目标:参考范例程序, 用 Flex 和 Bison 实现一个功能更为强大的计算器,包含以下运算:
a) 加、减、乘、除运算
b) 乘方、开方运算
c) 位运算
– 与 & 、或 |、非 ~…
d) 阶乘运算 !
e)sin cos tan
sin(SIN*pi/180.0)//把角度变成弧度即把180度变成π
- 1
如果要写实验报告的话,请先看报告书的要求,一边截图一边编程,免得写报告的时候,浪费时间。
一 环境配置:Windows/Ubuntu+flex、bison
\1 使用Windows+CodeBlocks+flex、bison的环境(有两种方式)
方式1 借助codeblcoks编译、运行。
链接:http://pan.baidu.com/s/1nvkiURB 密码:khty
1)下载百度云里的flex和bison。放到windows环境下。
2)把.l文件和.y文件复制到该文件下
3)在2)文件夹的地址栏(也就是下图画红圈的地方),输入cmd
4)在cmd里输入
flex -ocalc.c calc.l
bison -ocalc.tab.h calc.y //注意-o后面没有空格
- 1
- 2
这样,会生成两个文件,calc.tab.h 和 calc.c
然后,把生成的.c文件(calc.c),丢进 codeblocks里,编译,运行。
方式2 配置MinGW直接在cmd下编译、运行。
链接:http://pan.baidu.com/s/1nvkiURB 密码:khty
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
- 1
- 2
- 3
- 4
这种方式,会生成两个文件,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 //如果有提示信息表示安装成功
- 1
- 2
- 3
编译和运行:
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果有 正确的 Makefile文件 的话,直接输入:
sudo make
./calc
- 1
- 2
二 一个简单的示例代码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("无效的输入字符");
%%
- 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
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;
}
- 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
- 43
- 44
- 45
- 46
- 47
- 48
- 49
三 把 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
\3 网络上 一些代码的补充解释(比较详细)
转自:https://my.oschina.net/costaxu/blog/107714
\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
- 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
\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
- 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
七 编译、运行的时候,常见错误以及对策
1) ……shift/reduce conflict……
最常见的情况是:在.l 和.y文件中没有添加相应 符号,或者没有写优先级
- 1
- 2
2) 在原来的只能用整数的示例程序里添加 小数 的功能
在 .l 和 .y 文件里添加 #define YYSTYPE double
在.l文件里 atoi(yytext)改为 atof(yytext)
//一般会有错误提示,按照错误提示一个个改就好了。
为所有用到整数型的地方,添加强制类型转换 (int)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
3) pow的未定义引用
两种可能
.y文件里没有添加math.h头文件
gcc -o calc lex.yy.c calc.tab.c -lm //没有添加-lm
- 1
- 2
- 3
- 4
转自:
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的默认选项。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
以上,如有疏漏,敬请指正。
八 源代码
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;}
- 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
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
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;
}
- 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
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10