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"
  这样子,当一个表达式解析完成后,会生成一个链表,表示该表达式的中间代码。

posted @ 2011-12-28 14:17  linxr  阅读(11166)  评论(10编辑  收藏  举报