• C语言解释器的实现表达式解析(四)


    1. BNF定义

    2.表达式解析

    3. 后缀表达式

    4.后缀表达式到中间代码

    5.中间代码的表示

    1. BNF定义


       虽然不想多提理论知识,但是有些东西还是避免不了。在解析表达式的时候,我们必须知道它的BNF定义,这样解析起来就非常方便了。所谓的BNF定义,相信大家看一眼就知道了:
       exp_additive           -> exp_multiplicative ( "+"|"-" ) exp_multiplicative
       exp_multiplicative   -> exp_cast ( "*"|"/"|"%" ) exp_cast
       exp_cast                 -> ...
       意思是:
       加法表达式可以表示为  “乘法表达式 + 乘法表达式”
       乘法表达式可以表示为  “类型转换表达式 *或/或% 类型转换表达式”
       ...
      
       知道了整个C语言的BNF定义,我们就可以很简单的按照这个定义来解析了。整个C的BNF定义可以查看以下的链接:
       http://lists.canonical.org/pipermail/kragen-hacks/1999-October/000201.html


      
    2. 表达式解析


       知道了上面的BNF定义,那么我们的解析代码就可以这么写:
       void exp_additive(){
           char op;
           exp_multiplicative();
           while(
               (op = OPERATOR( '+' )) ||
                op = OPERATOR( '-' )) ){
                get_token();
                exp_multiplicative();
                ...
           }
       }

        void exp_multiplicative(){
            char op;
            exp_cast();
            while(
                (op = OPERATOR( '*' )) ||
                (op = OPERATOR( '/' )) ||
                (op = OPERATOR( '%' )) ){
                get_token();
                exp_cast();
                ...
            }
        }
        过程是这样的:
        a. 调用exp_additive时,先调用exp_multiplicative
        b. 然后判断后面是否是 + 或 -,如果是,再次调用exp_multiplicative
        这样就完成了加法表达式的解析。如果非要问为什么这么写就能解析出表达式,那么我们可以举个例子:
            a = a * b + c * d;
        那么,他的语法树应该是这样的:

        
        (图4.2 语法树)
        我们向下递归调用的过程,其实就是构造这个语法树的过程。但是我们不会真的创建出这个语法树,而是保存了一个与它等价的一种形式--后缀表达式,其实后缀表达式就是语法树的后续遍历。

    3. 后缀表达式


       什么是后缀表达式?我们还是从例子出发,上面的表达式,转化成后缀表达式就是这样子的:
            a a b * c d * + =
       为什么要写成这种奇怪的形式?我们不是吃饱了撑着,从左往右分别查看这个表达式您就知道原因了。
            a 
            a 
            b 
            *  得到*号,那么拿前面的两个变量a b求和
            c
            d
            *  得到*号,那么拿前面的两个变量c d求和
            +  的到+号,获取前面的两个变量 a*b  c*d 的结果,求和
            =  得到=号,将前面的结果赋给a
       为了生成后缀表达式,我们要改造上面的解析函数。
       void exp_additive(){
           char op;
           exp_multiplicative();
           while(
               (op = OPERATOR( '+' )) ||
                op = OPERATOR( '-' )) ){
                get_token();
                exp_multiplicative();
                EXP_OPR( op );           <--将运算符入栈
           }
       }

        void exp_multiplicative(){
            char op;
            exp_cast();
            while(
                (op = OPERATOR( '*' )) ||
                (op = OPERATOR( '/' )) ||
                (op = OPERATOR( '%' )) ){
                get_token();
                exp_cast();
                EXP_OPR( op );           <--将运算符入栈
            }
        }
        那么解析完成以后,我们的栈中就会形成后缀表达式了。有了表达式的后缀形式,我们就可以很轻松的产生后缀表达式的中间代码了。
      


    4.后缀表达式到中间代码


      首先我们先说明一下我们的中间代码是怎样的一种形式,这里暂且叫它为三元表达式,是因为这个种中间代码的形式是固定的。例如,紧接上节的例子,表达式 a = a * b + c * d;的中间代码最终应该是这样子的:
      @1 = a * b;
      @2 = c * d;
      @3 = @1 + @2;
      @4 = a  = @3;
      其中以@开头的都是我们为之产生的中间变量。生成上述的中间代码后,将会对我们后续的解析提供很大的帮助,应为它结构固定,所以我们不用再去解析源程序,而是通过这个中间代码产生最终的执行代码。这里先声明下,我所说的执行代码,不是真正意义上得可执行代码,而是能够被我的软件解析的命令序列。其实它已经非常接近汇编代码。但是我们的目标是解析执行,并不产生汇编代码,所以产生简单的命令序列已经可以完成目标了。
     
      我们前面解析表达式,产生后缀形式,为的就是生产这种中间表达式。表达式"a = a * b + c * d;"的后缀形式是"a a b * c d * + =;" 我们要根据这个后缀形式产生中间代码的过程如下:
        

       
       
    5.中间代码的表示


      typedef struct _code code_t;
      typedef struct _code * pcode_t;
      struct _code{
          char opr;
          struct{
              int  i, n, t;
          }lab;
          v_t var[4];
          code_t * next;
      };
      它是一个链表,每个节点保存了一个形如"@1 = a * b;"的中间代码。其中,opr表示运算符"*";lab表示该节点为一个LAB,留到后面章节讲解;var表示运算变量,如上面表达式的"@1, a, b"
      这样子,当一个表达式解析完成后,会生成一个链表,表示该表达式的中间代码。

  • 相关阅读:
    学习笔记datatablexml转换
    立即执行函数
    MySQL基操—1.Linux下安装(CentOS6/7yum、rpm、tar)
    Linux1.11.shell(环境变量配置文件)
    三国HR评估报告
    毕业5年后拉开差距的原因
    职业的选择
    WPF图片的缩放节省内存
    .asmx支持post请求或者get请求调用
    ionic3遇到的刷新页面服务器关闭的问题
  • 原文地址:https://www.cnblogs.com/linxr/p/2304742.html
Copyright © 2020-2023  润新知