系列文章目录
flex&bison系列第一章:flex Hello World
flex&bison系列第二章:写一个简单的单词统计工具Word Counter
flex&bison系列第三章:写一个简单的计算器Calculator
前言
在此记录下基于flex与bison写一个简单的计算器程序(Calculator)的过程,以备查阅。
本章的重点是为了初步了解bison的用法。
开发环境的配置请参考第一章 《flex&bison系列第一章:flex Hello World》。
为简单起见,我们只支持加减乘除四种运算,比如“1 + 2”、“1 * 2”等等。从第二章,我们知道可以用简单的正则表达式来识别整数(示例):
[0-9]+
我们可以再进一步,识别负整数(示例):
^[-+][0-9]+
以上主要是词法分析,一般写在flex脚本中。而语法分析,一般写在bison脚本中。
在bison脚本中,我们用(BNF范式)来定义表达式的语法。比如,乘法的定义如下(示例):
%token number
factor: number
| factor mutiple number { $$ = $1 * $3; }
;
这里的“number”代表一个token,这是词法分析首先要识别的基本单元,即整数。而“factor”仅仅是个单词而已,没有特殊的意义,我们可以简单地把它理解为乘法和除法里的“因素”。factor的写法其实是个递归的写法,相当于C语言的如下写法(示例):
int multiple(int factor, int number)
{
return factor * number;
}
// factor = number
int factor = 2;
// factor = 2 * 3 = 6
// factor = factor mutiple number
factor = multiple(factor, 3);
// factor = 6 * 4 = 24
// factor = factor mutiple number
factor = multiple(factor, 4);
或者,我们也可以这样直接的写出来(示例):
// factor = 2 * 3 * 4 = 24
// factor = factor mutiple number
factor = multiple(multiple(factor, 3), 4);
一、Simple Calculator
这个Simple Calculator程序要做的事很简单,具体如下:
- 利用flex脚本检测各种操作符、整数等token
- 利用bison脚本计算出结果
- 当检测到回车键时,输出最后结果
首先,我们要写一个简单的flex脚本文件(示例):
/**
* simple-calculator.l
*/
%{
#include "simple-calculator.tab.h"
%}
%option noyywrap
%%
"+" { return ADD; }
"-" { return SUB; }
"*" { return MUL; }
"/" { return DIV; }
^[-+][0-9]+ { yylval = atoi(yytext); return NUMBER;}
[0-9]+ { yylval = atoi(yytext); return NUMBER;}
\n {return EOL; }
[ \t] {}
. { printf("Unrecognized character: %s\n", yytext);}
%%
注意到,这里有一些变量没有定义,如:
ADD, SUB, MUL, DIV, NUMBER, EOL
其实,这些变量都是在我们的bison脚本中定义的。bison脚本如下(示例):
/**
* simple-calculator.y
*/
%{
#include <stdio.h>
extern int yylex();
void yyerror(const char* s)
{
printf("Error: %s\n", s);
}
%}
%token NUMBER
%token ADD SUB MUL DIV
%token EOL
%%
calclist: /* Empty rule */
| calclist exp EOL { printf("= %d\n", $2); }
;
exp: factor
| exp ADD factor { $$ = $1 + $3; }
| exp SUB factor { $$ = $1 - $3; }
;
factor: NUMBER
| factor MUL NUMBER { $$ = $1 * $3; }
| factor DIV NUMBER { $$ = $1 / $3; }
;
%%
int main(int argc, char **argv)
{
yyparse();
return 0;
}
注意到这里比较有意思的是,加减法和乘除法分开写的:
exp: factor
| exp ADD factor { $$ = $1 + $3; }
| exp SUB factor { $$ = $1 - $3; }
;
factor: NUMBER
| factor MUL NUMBER { $$ = $1 * $3; }
| factor DIV NUMBER { $$ = $1 / $3; }
;
这是为了保证乘除法的优先级比加减法更高。以下写法则达不到“优先运算乘除法”的效果(示例):
exp: NUMBER
| exp MUL NUMBER { $$ = $1 * $3; }
| exp DIV NUMBER { $$ = $1 / $3; }
| exp ADD NUMBER { $$ = $1 + $3; }
| exp SUB NUMBER { $$ = $1 - $3; }
;
二、编译
1. 生成C代码
运行如下命令即可生成C代码(示例):
flex -o simple-calculator.yy.c simple-calculator.l
bison -d -o simple-calculator.tab.c simple-calculator.y
其生成的C代码文件名为“simple-calculator.yy.c”、“simple-calculator.tab.c”、“simple-calculator.tab.h”。
2. 编译
我们可以用clang或gcc对以上生成的C代码进行编译(示例):
# Set up C++ standard library and header path for clang
export SDKROOT=$(xcrun --sdk macosx --show-sdk-path)
# Build with clang
clang -o simple-calculator simple-calculator.tab.c simple-calculator.yy.c
# Or, build with gcc
gcc -o simple-calculator simple-calculator.tab.c simple-calculator.yy.c
以上命令会生成一个名为“simple-calculator”的可执行程序。
三、运行
可以用如下命令运行Simple Calculator程序(示例):
# Use Enter to print the result
./simple-calculator
接下来,程序会等待用户的输入。我们可以随意打一个整数算式(示例):
-2 + 3 * 4
用回车键即可打印出结果(示例):
= 10
总结
我们基于flex和bison,用C++写了一个很简单的计算器程序,并且编译运行成功。完整源码示例请参看:
https://github.com/wuzhanglin/flex-bison-examples