zoukankan      html  css  js  c++  java
  • 求值器

    求值器

    • 之前有篇博文谈了表达式求值的东西,重点是讲递归下降,尤其是对于中缀表达式,这样去求值显得十分清晰:清晰体现在什么地方呢?
      很简单,用中缀转后缀、后缀求值这样利用一个符号栈、一个数字栈来做的话,优先级比较避免麻烦;而常见的求值还有就是模拟人工求值的方法,比如先看括号匹配啦,然后分解成各种小情况进行求值,这样做的话效率肯定不赖,但是代码写完就再也不想去看了,因为逻辑不够清晰,界面比较混乱,所有的情况是人为去脑力分解的。
    • 而递归下降不同,递归下降的步骤几近机械化,先写出文法,然后消除左递归,然后用一个一个的getSomething()来递归的求值,不论要添加什么东西进去都易如反掌,而且函数界面十分清晰

    求值器是什么

    其实这里的求值器就是一个简单的解释器模型,即将你输入的字符串当作源程序来执行。从这种意义上来讲,我们之前的表达式求值已经是一个比较简单的解释器了。
    现在我们要加入其它的一些特性,比如全局变量、函数等等。
    添加变量和函数最重要的不是执行,而是求值环境

    求值环境

    上一篇已经谈到了环境的构造方法,其实就是约定一个这样的界面:
    (extend-env! variable value env)
    然后我们(lookup variable env)的时候可以得到刚才的value的值

    eval 核心

    eval就是我们的求值器,它的主要工作是根据输入字符串的特点进行解析并分派,分派到各个不同的eval-subfunction 里面去求值,eval允许递归

    (define (eval exp env)
        (cond  [(self-evaluting? exp) exp]
               [(variable? exp) (lookup exp global-env)]
               [(quoted? exp) (cadr exp)]
               [(define-variable? exp) (set! global-env  (eval-define exp env)) global-env]
               [(arit-expr? exp) (eval-arit exp env)]
               [(lambda? exp) (eval-lambda exp env)]
            ))
    

    这里面展示了几种简单类型的分派,分派的方法和SICP里面是一样的:

    1. 为每一种类型的表达式写一个谓词函数;
    2. 为每一种类型的表达式再写一个求值函数

    tips:适当使用一些中括号,可以让程序更加清楚,不然全是小括号会看着很累

    谓词函数

    谓词函数很简单,大多就是一些简单的判断的组合。
    比如self-evaluting? 其实就只有数字和字符串,可以借助racket的number? string?来实现

    而比如像define、lambda、arit-expr等都有一个共同的特点,即识别它们靠表达式的第一个元素,因此抽提一个函数:

    (define (is-begin-with exp tag)
       (if(pair? exp) 
          (eq? (car exp) tag)
           false))
    

    就可以更方便的实现它们的谓词函数了。

    求值函数

    在有了上述的分派界面之后,最有难度和价值的部分其实就是求值函数内部了:

    1. 对于全局变量我们要考虑如何去取它的值;
    2. 对于lambda函数,我们要提取它的形参、函数体;
    3. 对于函数调用,我们要将实参绑定到lambda函数的形参上,然后调用函数体;
    4. 对于算术表达式,充分利用递归性质会非常简单
    5. if-else表达式,也是抽提各个子部分,然后去执行满足条件的那个步骤
    6. cond表达式其实可以写作if-else的多层嵌套,所以我们可以在表达式内部就将读取到的cons表达式转为等价的if表达式,这样就无需写新的求值函数了,只需一个转换函数
     
     eg. (cond [(< a 0) #t]
               [(> a 2) #t]
               [else #f] )
    等价于 (if (< a 0)
                #t
                (if (> a 2)
                #t
                #f))
    

    More

    • 由于scheme本身的简洁,我们有时候需要更多的“语法糖”,而现在我们可以自己去把它们写进eval里面,用自己设计的语法去coding,it's cool!
      比如SICP习题中,之前我们写过和见过很多的语法糖,比如for、while、foreach、map、list、queue等,我们可以将它们加入进来,作为语法的一部分直接使用。

    • 不止如此,我们完全可以不按照scheme的语法来写,比如按照python的语法来写也是可行的(python的列表推导式还是很好用的,其实就是从函数式编程中吸收进来的),python 的语法还是十分简洁而易用的,我们可以用scheme来模拟python 的一条一条的语法。

    • 最后,其实加上词法分析之后,我们也完全可以用C++来写这个解释器,那样就是一个真正完整的解释器了,毕竟用scheme来解释scheme 难度要低一些啦,但是意思是到位了。

    最后

    最近考试比较多,并且我们的PA十分强大(做一个完整的模拟器,可参考QEMU),但是很费时间,所以手头空余时间比较少。。
    之前开始的regex引擎只写到AST生成、NFA生成,后面的DFA生成、DFA最小化、以及运行还没有开始,期中考试之后再开始啦。

    tips:

    • 学累的时候可以去学学一些新的语言,一般也就几个小时到一天不等就可以掌握基本语法了,比如java、python等。学这么多语法其实没用,但无聊的时候学学可以涨涨信心,以便下阶段更好的学下去!
    • 其实Qt更适合业余休闲啦,打开的demo都可以玩好长时间,哈哈哈。。。
  • 相关阅读:
    547. Friend Circles
    399. Evaluate Division
    684. Redundant Connection
    327. Count of Range Sum
    LeetCode 130 被围绕的区域
    LeetCode 696 计数二进制子串
    LeetCode 116 填充每个节点的下一个右侧节点
    LeetCode 101 对称二叉树
    LeetCode 111 二叉树最小深度
    LeetCode 59 螺旋矩阵II
  • 原文地址:https://www.cnblogs.com/gaoduan/p/4075997.html
Copyright © 2011-2022 走看看