使用flex和bison编写程序

使用flex和bison编写程序

我们将用flex和bison实现一个计算器。

词法分析(token识别)

使用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
  • 使用%left来实现优先级

更改内容如下:

--- 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

refernce

  1. https://www.less-bug.com/posts/flex-and-bison-of-actual-combat-a-simple-calculator/

你可能感兴趣的:(c,编译器,数据库,算法,linux,运维,数据库)