我们将用flex和bison实现一个计算器。
使用flex生成词法分析器。
/* 这里是声明部分 */
%%
"+" { printf("加\n"); }
"-" { printf("减\n"); }
"*" { printf("乘\n"); }
"/" { printf("除\n"); }
"|" { printf("绝对值\n"); }
[0-9]+ { printf("数字 %s\n", yytext); } /*yytext:指向本次匹配的输入串*/
\n { printf("换行\n"); }
[ \t] {}
. { printf("输入数字 %s\n", yytext); }
%%
/* 这里是用户代码段(user code) */
使用如下命令生成词法分析器:
flex cacl.l
运行上面的命令后生成了一个名为lex.yy.c的文件。
[coder@25e5a4630172 code]$ ls
cacl.l lex.yy.c
也可以指定生成的文件名,命令如下:
# 指定生成的文件名
[coder@25e5a4630172 code]$ flex -o cacl.yy.c cacl.l
[coder@25e5a4630172 code]$ ls
cacl.l cacl.yy.c
有了.c文件此时就可以使用gcc或者llvm编译生成可执行文,到这里我们的.c文件已经可以实现了识别数字以及加减乘除符号的功能。下面我们进行编译:
[coder@25e5a4630172 code]$ gcc -o cacl cacl.yy.c -lfl
[coder@25e5a4630172 code]$ ls
cacl cacl.l cacl.yy.c
下面进行简单的测试:
[coder@25e5a4630172 code]$ ./cacl
123
数字 123
换行
456
数字 456
换行
3*4
数字 3
乘
数字 4
换行
我们可以把现在的编译流程写入一个makefile里,后面可以直接复用,然后只用关心.l文件。其内容如下:
all:
flex -o cacl.yy.c cacl.l
gcc -o cacl cacl.yy.c -lfl
clean:
rm -rf cacl.yy.c cacl
编译时执行如下命令:
make clean && make
其执行结果如下:
[coder@25e5a4630172 code]$ make clean && make
rm -rf cacl.yy.c cacl
flex -o cacl.yy.c cacl.l
gcc -o cacl cacl.yy.c -lfl
[coder@25e5a4630172 code]$ ls
cacl cacl.l cacl.yy.c Makefile
前面的例子中仅仅是识别了数字和算术符号,并没有对识别出来的符号和数字进行计算。要实现计算功能,需要增加语法分析。
首先需要增加一个cacl.y的文件,用来处理计算规则,其内容如下:
%token NUMBER
%left '+' '-' '*' '/'
%{
#include
#include
void yyerror(char *s);
int yylex();
%}
%%
prog:
| prog expr '\n' { printf("= %d\n", $2); };
expr:
NUMBER {{ printf("read number %d\n", $$); }};
| expr '+' expr {{ $$ = $1 + $3; }}
| expr '-' expr {{ $$ = $1 - $3; }}
| expr '*' expr {{ $$ = $1 * $3; }}
| expr '/' expr {{ $$ = $1 / $3; }}
;
%%
void yyerror(char *s) {
fprintf(stderr, "%s\n", s);
}
int main() {
yyparse();
return 0;
}
编写完成后使用bison来进行编译,生成.c和.h文件,其中.h文件是提供给外部文件引用的。
# --defines指定生成的头文件名称, -o指定生成的.c文件名称
# 如果只指定了-o参数,则所有内容都在一个文件中
bison -d cacl.y --defines=cacl.tab.h -o cacl.tab.c
此时有了.y文件,同时确定了.y文件对外的.h文件后,我们需要修改.l文件,使其生成的token能够作为语法分析的输入,并进行计算。我们修改cacl.l,修改后的内容如下:
%{
#include
void yyerror(char *);
#include "cacl.tab.h"
%}
/* 以上是声明部分 */
%%
[0-9]+ { yylval=atoi(yytext); return NUMBER;}
[-+*/\n] {return *yytext;}
[ \t] ;
yyerror("Error");
%%
/* 以上是编译规则 */
int yywarp(void)
{
return 1;
}
/* 以上是用户代码*/
修改完成后,还是像之前一样编译。
[coder@25e5a4630172 code]$ flex -o cacl.yy.c cacl.l
[coder@25e5a4630172 code]$ ls
cacl.l cacl.tab.h cacl.y Makefile
cacl.tab.c cacl.test cacl.yy.c
[coder@25e5a4630172 code]$ gcc cacl.yy.c cacl.tab.c -o cacl -lfl
[coder@25e5a4630172 code]$ ls
cacl cacl.tab.c cacl.test cacl.yy.c
cacl.l cacl.tab.h cacl.y Makefile
进行一个简单的测试,测试结果如下:
[coder@25e5a4630172 code]$ ./cacl
2*3
read number 2
read number 3
= 6
34-12
read number 34
read number 12
= 22
56d
read number 56
d= 56
我们还可以完善一下makefile。
all:
bison -d cacl.y --defines=cacl.tab.h -o cacl.tab.c
flex -o cacl.yy.c cacl.l
gcc -o cacl cacl.yy.c cacl.tab.c -lfl
clean:
rm -rf cacl.yy.c cacl cacl.tab.c cacl.tab.h
然后编译如下:
[coder@25e5a4630172 code]$ make clean && make
rm -rf cacl.yy.c cacl cacl.tab.c cacl.tab.h
bison -d cacl.y --defines=cacl.tab.h -o cacl.tab.c
flex -o cacl.yy.c cacl.l
gcc -o cacl cacl.yy.c cacl.tab.c -lfl
[coder@25e5a4630172 code]$ ls
cacl cacl.tab.c cacl.y Makefile
cacl.l cacl.tab.h cacl.yy.c
在上面实现的计算器例子中,是按从左到右的顺序来执行的,并没有运算符号的优先级。如下面的加法和乘法的结合就会计算出错:
[coder@25e5a4630172 code]$ ./cacl
1*3+2*6
read number 1
read number 3
read number 2
read number 6
= 30
更改内容如下:
--- a/code/cacl.y
+++ b/code/cacl.y
@@ -1,5 +1,6 @@
%token NUMBER
-%left '+' '-' '*' '/'
+%left '+' '-'
+%left '*' '/'
%{
此时的执行结果如下:
[coder@25e5a4630172 code]$ ./cacl
1*3+2*6
read number 1
read number 3
read number 2
read number 6
= 15
代码变更如下:
diff --git a/code/cacl.y b/code/cacl.y
index 862939e..323448c 100644
--- a/code/cacl.y
+++ b/code/cacl.y
@@ -11,13 +11,15 @@
%%
prog:
- | prog expr '\n' { printf("= %d\n", $2); };
+ | prog expr '\n' { printf("= %d\n", $2); };
expr:
+ expr '+' term { $$ = $1 + $3; }
+ | expr '-' term { $$ = $1 - $3; }
+ | term
+ term:
NUMBER {{ printf("read number %d\n", $$); }};
- | expr '+' expr {{ $$ = $1 + $3; }}
- | expr '-' expr {{ $$ = $1 - $3; }}
- | expr '*' expr {{ $$ = $1 * $3; }}
- | expr '/' expr {{ $$ = $1 / $3; }}
+ | term '*' term { $$ = $1 * $3; }
+ | term '/' term { $$ = $1 / $3; }
;
%%
执行结果如下:
[coder@25e5a4630172 code]$ ./cacl
1*4-6/2
read number 1
read number 4
read number 6
read number 2
= 1
首先需要修改词法分析,即修改cacl.l,其内容修改如下,主要是增加对括号的识别:
diff --git a/code/cacl.l b/code/cacl.l
index c07b94c..188590f 100644
--- a/code/cacl.l
+++ b/code/cacl.l
@@ -7,7 +7,7 @@
/* 以上是声明部分 */
%%
[0-9]+ { yylval=atoi(yytext); return NUMBER;}
-[-+*/\n] { return *yytext; }
+[-+*/()\n] {return *yytext;}
[ \t] ;
yyerror("Error");
然后要修改语法分析,增加对括号的解析,主要修改内容如下:
diff --git a/code/cacl.y b/code/cacl.y
index 323448c..4a7b015 100644
--- a/code/cacl.y
+++ b/code/cacl.y
@@ -9,7 +9,6 @@
%}
%%
-
prog:
| prog expr '\n' { printf("= %d\n", $2); };
expr:
@@ -17,9 +16,13 @@
| expr '-' term { $$ = $1 - $3; }
| term
term:
+ | term '*' factor { $$ = $1 * $3; }
+ | term '/' factor { $$ = $1 / $3; }
+ | factor
+ ;
+ factor:
NUMBER {{ printf("read number %d\n", $$); }};
- | term '*' term { $$ = $1 * $3; }
- | term '/' term { $$ = $1 / $3; }
+ | '(' expr ')' { $$ = $2; }
;
%%
重新编译后运行,运行结果如下:
[coder@25e5a4630172 code]$ ./cacl
3*(2+4)+3*4
read number 3
read number 2
read number 4
read number 3
read number 4
= 30