暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用flex和bison编写程序

原创 growdu 2023-11-03
362

使用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 <stdio.h>
    #include <math.h>
    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<stdlib.h>
    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/
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论