打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
!!!C语言解释器的实现 bnf

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 阅读(8265) 评论(10) 编辑 收藏
评论列表
  
#1楼 2011-12-28 15:00 陈梓瀚(vczh)  
问题是这世界上非二元操作符多了去了……
  
#2楼 2011-12-28 16:02 Geek_Ling  
先给博主个推荐~ 等有时间了再看。。。
  
#3楼 2011-12-28 17:30 扑来树袋熊  
引用陈梓瀚(vczh):问题是这世界上非二元操作符多了去了……

赞同,教科书上用栈加后缀式的单纯写法能解决的问题有限。
  
#4楼[楼主] 2011-12-28 18:36 linxr  
@陈梓瀚(vczh)
引用陈梓瀚(vczh):问题是这世界上非二元操作符多了去了……

非二元运算符就不会变通吗?我当然是举了最简单的例子了。比如:
a = 3 > 2 ? 1 : 0;
中间 ?:就不是这种情况,但是?:运算可以转化成if else的处理方式。
再比如:
i++;
++是单目运算符,那么我们出栈的时候,当然只取一个操作数啦。原理都是变通的,所有的C语言的运算符都可以转成后缀形式,值不够有些要做些额外的处理罢了。
  
#5楼[楼主] 2011-12-28 18:37 linxr  
引用hellotony:
引用陈梓瀚(vczh):问题是这世界上非二元操作符多了去了……

赞同,教科书上用栈加后缀式的单纯写法能解决的问题有限。

问题都是互通的,有时绕个弯就解决了!
  
#6楼 2011-12-28 18:42 陈梓瀚(vczh)  
@linxr
while你怎么办,函数调用怎么办,while中间break怎么办,其实主要说的是这些。当然这些都是相当容易做到可以倒着把程序写出来的——就是繁琐啊。
  
#7楼[楼主] 2011-12-28 19:06 linxr  
引用陈梓瀚(vczh):
@linxr
while你怎么办,函数调用怎么办,while中间break怎么办,其实主要说的是这些。当然这些都是相当容易做到可以倒着把程序写出来的——就是繁琐啊。

控制语句的处理和表达式的解析方式是不一样的,函数的调用也不一样呀。再接下去我会说明的。
  
#8楼 2012-01-04 11:24 fjb2080  
像吴老师的东西
  
#9楼[楼主] 2012-01-04 12:49 linxr  
@fjb2080
谁是吴老?呵呵
  
#10楼 2014-01-17 10:11 garbageMan  
引用* 得到*号,那么拿前面的两个变量a b求和
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【数据结构与算法】逆波兰表达式、波兰表达式
30个Python程序员需要知道的编程技巧,可以让你的工作事半功倍!
学c语言最重要的知识点总结为学c语言发愁的同学转走背一下吧(指针星号在非变量定义的时候它是一个操作符访问指针所指向存储空间)
JS匿名自执行函数(IIFE)
(运算符)乐创DIY C语言讲义​——3.7节(2)
More Effective C++ (M1 -to- M8)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服