用flex和bison做一个命令行计算器-第一次尝试

晚上胃难受睡不着,所以找点事情干。随手拿起好久以前买的《flex & bison》,想起自己曾经想用flex和bison做个脚本语言玩玩,但是一直没有付诸实践,正好趁此机会实践一番顺便打发时间又缓解胃部不适一举多得。

之前在项目中做的一套通讯协议描述语法和一套测试脚本语法都是用C#手工写的语法解析,虽然那样做更清楚内部怎么运转的,但是开发效率太低和维护成本太高了,还是早日学会使用工具比较好。

实践下来,发现在看书时觉得不值得一试的超简单例子原来也要耗掉很多时间调试,真是骄兵必败呀,以后万万不可轻视书上任何示例。

第一次用flex和bison,所以实验程序很大部分都是照搬书上的,只是自己加入了正负数的语法,并且调试了一番确定书上的示例并不完全正确。

下面是flex的文法描述文档(calc.l):

%{ 
    #define YYSTYPE double  
 
    #include "calc.tab.h" 
 
    #ifdef CALC_LEX  
        YYSTYPE yylval;
    #endif 
%} 
 
%% 
"+"                             { return ADD;    } 
"-"                             { return SUB;    } 
"*"                             { return MUL;    } 
"/"                             { return DIV;    } 
"|"                             { return ABS;    } 
"("                             { return OP;     } 
")"                             { return CP;     } 
n                              { return EOL;    } 
[ t]                           { /* ignore */   } 
([0-9]+.?[0-9]*|.[0-9]+)      { yylval = atof(yytext); return NUMBER; } 
.                               { return UNKNOW; } 
%% 
 
#ifdef CALC_LEX  
int main (int argc, char** argv) {
    int token;
 
    while (token = yylex()) {
        printf("%d", token);
 
        if (token == NUMBER) {
            printf(" = %fn", yylval);
        } else {
            printf("n");
        }
    }
 
    return 0;
}
#endif 

下面是bison的语法描述文档(calc.y):

%{ 
    #define YYSTYPE double  
    #include <stdio.h> 
%} 
 
%token NUMBER
%token ADD SUB MUL DIV ABS OP CP
%token EOL UNKNOW
 
%% 
calclist:  
        | calclist exp EOL { printf("= %fn", $2); }  
        ; 
 
exp: factor  
   | exp ADD factor { $$ = $1 + $3; }  
   | exp SUB factor { $$ = $1 - $3; }  
   ; 
 
factor: term 
      | ADD term        { $$ = $2 >= 0 ? $2 : - $2; }  
      | SUB term        { $$ = $2 >= 0 ? - $2 : $2; } 
      | factor MUL term { $$ = $1 * $3; }  
      | factor DIV term { $$ = $1 / $3; }  
      ; 
 
term: NUMBER  
    | ABS exp ABS   { $$ = $2 >= 0 ? $2 : - $2; }  
    | OP exp CP     { $$ = $2; }  
    ; 
%% 
 
int main (int argc, char** argv) {
    yyparse();
    return 0;
}
 
yyerror (char *s) {
    fprintf(stderr, "error: %sn", s);
}

下面是Makefile的代码(复制出来的代码,请把空格替换成tab):

calc: calc.y calc.l
    bison -d calc.y  
    flex -o calc.lex.c calc.l  
    cc -o $@ calc.tab.c calc.lex.c -lfl  
 
calc.lex: calc.y calc.l
    bison -d calc.y  
    flex -o calc.lex.c calc.l  
    cc -D CALC_LEX -o $@ calc.lex.c -lfl  
 
clean:  
    rm calc  
    rm calc.lex  
    rm calc.lex.c  
    rm calc.tab.h  
    rm calc.tab.c

执行make默认编译计算器可执行文件,make calc.lex编译文法解析测试程序。

看了下时间,前后大概花了两个多小时,天都已经亮了,看似简单得很的东西搞起来还真没那么简单 :)

这个计算器只是个相当粗糙的实验品,直接用的是C的加减乘除计算,所以必然有C同样得数值益处和浮点数精度问题,也没办法像Erlang那样数值爱计算多长就多长,也没有内置函数和自定义函数的功能,这些高级内容以后慢慢实践吧。