Flex/Bison实现计算器
Flex、Bison介绍
Flex介绍可以看一看下面这篇,有详细介绍:
Flex介绍,使用
Bison 基本上与 Yacc 兼容,并且在 Yacc 之上进行了改进。它经常和 Flex (词法分析器生成器)一起使用。Bison的文件以.y为后缀,生成的文件为 xxx.tab.c和xxx.tab.h
源程序结构
声明部分
%%
翻译规则
%%
辅助性C语言例程
声明部分
%{}%
声明
主要包括规则部分的语义过程和过程部分所需的头文件的引用说明、数据定义、全局变量以及函数原型定义等。%
声明
对词法单元的声明,eg:%token DIGIT
用户可以使用%left
和%right
来指明其后面的运算符具有相同的优先级和结合性规则。
翻译规则
<产生式头>→<产生体>1|<产生体>2|……|<产生体>n
另一种写法
<产生式头>:
<产生体>1 {<语义动作>1}
|<产生体>2 {<语义动作>2}
……
|<产生体>n {<语义动作>n}
eg:
line: expr ‘\n’ {printf("%lf",$1);}
$$
表示左部语法变量的属性值,$i表示右边第i个文法符号的值,语义动作是根据$i
计算$$
的值
辅助性C语言例程
使用Dev-C++编译运行
calc.l
%{
#include<stdlib.h>
void yyerror(char*);
#include "calc.tab.h"
%}
%%
\.?[0-9]+|[0-9]+\.[0-9]* {yylval=atof(yytext);return INTEGER;}
[-+()/*,\n] {return *yytext;}
[ \t] ;
. yyerror("Error");
%%
int yywrap(void)
{
return 1;
}
calc.y
%token INTEGER
%left '+' '-'
%left '*' '/'
%{
#define YYSTYPE double
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
void yyerror(char*);
int yylex(void);
%}
%%
program:
program expr '\n' {printf("%lf\n",$2);}
|
;
expr:
INTEGER
|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、在Flex、Bison安装目录下使用cmd。执行:
win_flex -ocalc.c calc.l
win_bison -ocalc.tab.h calc.y //注意-o后面没有空格
2、编译calc.c
使用Dev-C++打开calc.c,编译运行就可以了哦。
使用MinGW在cmd运行
- MinGw的安装
MinGw的安装在这里就不一一赘述了,一路next,安装成功后,记得配置环境变量
,一定需要配置。
这里的calc.l
和calc.y
与之前的是一样的。下面是编译运行:
win_flex calc.l
win_bison -o calc.tab.h calc.y //注意-o后面有没有空格都可以
gcc -o calc lex.yy.c calc.tab.h //编译
calc //运行calc.exe
源码
calc.l
%{
#include "calc.tab.h"
#define YYSTYPE double
void yyerror(char*);
int yywrap();
%}
%%
[a-z] {yylval = *yytext - 'a';return VARIABLE;}
\.?[0-9]+|[0-9]+\.[0-9]* {yylval = atof(yytext);return INTEGER;}
[-+()=/*&|~!^,@\n] {return *yytext;}
sin {return SIN;}
cos {return COS;}
tan {return TAN;}
sqrt {return SQRT;}
log {return LOG;}
pow {return POW;}
[ \t] ;
. yyerror("error");
%%
int yywrap() {
return 1;
}
calc.y
%token INTEGER VARIABLE SIN COS TAN POW SQRT LOG
%left '+' '-'
%left '*' '/'
%left '&'
%left '|'
%left '^'
%right '@''~'
%left '!'
%right UMINUS
%{
#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("%g\n", $1);}
|VARIABLE '=' expr {sym[(int)$1] = $3;}
;
expr:
INTEGER
|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 %prec UMINUS {$$ = -$2;}
|POW'('expr','expr')' {$$ = pow($3,$5);}
|SIN'('expr')' {$$ = sin($3*pi/180.0);}
|COS'('expr')' {$$ = cos($3*pi/180.0);}
|TAN'('expr')' {$$ = tan($3*pi/180.0);}
|SQRT'('expr')' {$$ = sqrt($3);}
|LOG'('expr')' {$$ = log10($3);}
|'('expr')' {$$ = $2;}
;
%%
void yyerror(char* s) {
fprintf(stderr, "%s\n", s);
}
int main(void) {
yyparse();
return 0;
}
几个小说明
1、undefined reference to ‘yylex’、In function ‘main’、WinMain
可以参考:yylex
2、lex.yy.c:(.rdata$.refptr.yylval[.refptr.yylval]+0x0): undefined reference to ‘yylval’
未定义yylval
、编译运行不对,修改编译
win_flex -ocalc.c calc.l
win_bison -ocalc.tab.h calc.y //注意-o后面没有空格,calc为文件名
3、undefined reference to ‘yywrap’
可以在前面加上%option noyywrap
,或者直接写上
int yywrap(void)
{
return 1;
}
4、有可能在本地有输出,但在在线平台没有输出,将calc.y
中输出部分改成下面这样
program:
program statement '\n'
|
;
statement:
expr {printf("%lf\n", $1);}
|
;
5、输出时整数小数区分,以及小数后面多余0
的处理
取整判断小数与整数,取余去掉多余的0
不过通过多次尝试,然后发现C语言提供了简便方法,哈哈哈
如果对你有帮助,点个赞呗。
参考文献
【1】《编译原理》(龙书)
【2】《编译原理》第二版 蒋宗礼、姜守旭