美文网首页lex&&yacc技术文章
Lex & Yacc 学习笔记(2)- 简单计算器

Lex & Yacc 学习笔记(2)- 简单计算器

作者: hemny | 来源:发表于2018-08-16 17:05 被阅读308次

    一、背景

    构建一个简单计算器,识别输入的计算表达式并计算结果。通过计算器程序 来说明 lex & yacc 的开发过程 和 Lex 的结构规范。

    二、环境

    ➢ 系统: CentOS 7.5
    ➢ 编译器: gcc - 4.8.5
    ➢ lex: flex 2.5.37
    ➢ Yacc: bison (GNU Bison) 3.0.4

    安装 flex 和 bison 。
    yum install flex bison

    如果在编译链接过程中出现以下错误:
    /usr/bin/ld: cannot find -lfl
    请重新安装flex:
    yum remove flex
    yum install flex

    三、简单计算器实现

    计算器实现整数的 +、-、*、/、% 五种简单运算。

    词法分析程序 cal.l

    %{
    #include "cal.tab.h"
    extern int yylval;
    %}
    
    %%
    [0-9]+  { yylval = atoi(yytext); return NUMBER; }
    [ \t]   ;       /* ignore white space */
    \n  return 0;   /* logical EOF */
    .   return yytext[0];
    %%
    

    代码中定义了四条规则,前面的部分就是模式,处于一行的开始位置,后面部分是动作,也就是,输入中匹配到了这个模式的时候,对应进行什么动作(就像机器人接受到了什么样的指令,然后会执行相应的动作一样)

    第一个模式,匹配连续一个或者多个数字,匹配到之后就返回标签NUMBER。
    第二个模式,匹配空格,没有任何操作,忽略所有空格。
    第三个模式,匹配一个换行符,匹配到之后结束匹配。
    第四个模式,匹配出了\n之外的字符,返回该字符。

    总体来说,匹配到连续数字,则返回NUMBER;忽略空格;换行结束;匹配到任何其他字符返回字符。

    语法分析程序 cal.y

    %{
    #include <stdio.h>
    %}
    %token NAME NUMBER
    %%
    statement:  NAME '=' expression
        |   expression      { printf("= %d\n", $1); }
        ;
    
    expression: expression '+' NUMBER   { $$ = $1 + $3; }
        |   expression '-' NUMBER   { $$ = $1 - $3; }
        |   expression '*' NUMBER   { $$ = $1 * $3; }
        |   expression '/' NUMBER   { $$ = $1 / $3; }
        |   expression '%' NUMBER   { $$ = $1 % $3; }
        |   NUMBER          { $$ = $1; }
        ;
    %%
    int main()
    {
        yyparse();
        return 0;
    }
    
    int yyerror(char *s)
    {
        printf("%s/n",s);
        return 0;
    }
    

    编译运行过程

    [appusr@postgre cal]$ ls -l
    total 8
    -rw-rw-r--. 1 appusr appusr 200 Aug 16 11:27 cal.l
    -rw-rw-r--. 1 appusr appusr 533 Aug 16 14:11 cal.y
    
    /* 1.  编译lex文件,生成lex.yy.c文件 */
    [appusr@postgre cal]$ flex cal.l
    [appusr@postgre cal]$ ls -l
    total 52
    -rw-rw-r--. 1 appusr appusr   200 Aug 16 11:27 cal.l
    -rw-rw-r--. 1 appusr appusr   533 Aug 16 14:11 cal.y
    -rw-rw-r--. 1 appusr appusr 44068 Aug 16 15:58 lex.yy.c
    
    /* 2. 编译yacc文件,生成cal.tab.h 与cal.tab.c文件 */
    [appusr@postgre cal]$ bison -d cal.y
    [appusr@postgre cal]$ ls -l
    total 100
    -rw-rw-r--. 1 appusr appusr   200 Aug 16 11:27 cal.l
    -rw-rw-r--. 1 appusr appusr 44255 Aug 16 15:58 cal.tab.c
    -rw-rw-r--. 1 appusr appusr  2063 Aug 16 15:58 cal.tab.h
    -rw-rw-r--. 1 appusr appusr   533 Aug 16 14:11 cal.y
    -rw-rw-r--. 1 appusr appusr 44068 Aug 16 15:58 lex.yy.c
    
    /* 3. 链接生成的.c 文件,并生成相应的可执行文件 cal */
    [appusr@postgre cal]$ gcc -o cal cal.tab.c lex.yy.c -lfl
    [appusr@postgre cal]$ ls -l
    total 128
    -rwxrwxr-x. 1 appusr appusr 28632 Aug 16 15:58 cal
    -rw-rw-r--. 1 appusr appusr   200 Aug 16 11:27 cal.l
    -rw-rw-r--. 1 appusr appusr 44255 Aug 16 15:58 cal.tab.c
    -rw-rw-r--. 1 appusr appusr  2063 Aug 16 15:58 cal.tab.h
    -rw-rw-r--. 1 appusr appusr   533 Aug 16 14:11 cal.y
    -rw-rw-r--. 1 appusr appusr 44068 Aug 16 15:58 lex.yy.c
    
    /* 4. 运行可执行文件cal,计算简单表达式  */
    [appusr@postgre cal]$ ./cal
    22*33
    = 726
    [appusr@postgre cal]$
    

    四、Lex 的结构规范

    lex是用来生成词法分析器的
    lex源文件扩展名.l
    分为三个段:定义段、规则段、用户子程序段

    /* 定义段 */
    %{
    ...
    %}
    ...
    
    %%
        /* 规则段 */
    ...
    
    %%
        /* 用户子程序段 */
    ...
    
    

    三个段用 %% 进行分隔:

    1. 定义段

    定义段包括文字块、定义、内部表声明、起始条件和转换。
    C语言的注释、头文件包含等一般就放在%{%}之间,这一部分的内容会被直接复制到输出文件的开头部分。
    例如:

    %{
    #include "cal.tab.h"
    extern int yylval;
    %}
    

    2. 规则段

    规则段为一系列匹配模式和动作,模式一般使用正则表达式书写,动作部分为C代码:

    模式1 {动作1 (C代码)}
    
    例如:
    [0-9]+  { yylval = atoi(yytext); return NUMBER; }
    

        在输入和模式1匹配的时候,执行动作部分的代码。
        C代码被逐字拷贝到生成的C文件中。

    当lex扫描程序运行时,它把输入与规则段的模式进行匹配。
    ➢ 每次发现一个匹配(被匹配的输入称为标记(token))时就执行与那种模式相关的C代码。
    ➢ 如果模式后面跟着 | 符号,则该模式将使用与文件中下一个模式相同的C代码。
    ➢ 当输入字符不匹配模式时,词法分析程序的动作就好像它匹配上了代码ECHO的模式,ECHO将标记的拷贝写到输出。

    3. 用户子程序段

    这里为C代码,会被原样复制到c文件中,一般这里定义一些辅助函数等,如动作代码中使用到的辅助函数。
    如果重新定义input()、unput()、output()、或者yywrap(),新的版本或者支持子程序,都可以放在这里。

    词法分析器所做的,就是在输入中寻找字符的模式(pattern)。
    在词法分析器中,我们要给定我们需要识别的模式,因此需要使用一种方式来描述模式,这就是常用的正则表达式。

    五、总结

    本文通过亲自动手构建一个计算器实现来示范 lex & yacc 的开发过程 和 Lex 的结构规范,加深 lex & yacc 的编程理解。

    相关文章

      网友评论

        本文标题:Lex & Yacc 学习笔记(2)- 简单计算器

        本文链接:https://www.haomeiwen.com/subject/mthobftx.html