求值器
求值器
- 之前有篇博文谈了表达式求值的东西,重点是讲递归下降,尤其是对于中缀表达式,这样去求值显得十分清晰:清晰体现在什么地方呢?
很简单,用中缀转后缀、后缀求值这样利用一个符号栈、一个数字栈来做的话,优先级比较避免麻烦;而常见的求值还有就是模拟人工求值的方法,比如先看括号匹配啦,然后分解成各种小情况进行求值,这样做的话效率肯定不赖,但是代码写完就再也不想去看了,因为逻辑不够清晰,界面比较混乱,所有的情况是人为去脑力分解的。 - 而递归下降不同,递归下降的步骤几近机械化,先写出文法,然后消除左递归,然后用一个一个的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里面是一样的:
- 为每一种类型的表达式写一个谓词函数;
- 为每一种类型的表达式再写一个求值函数
tips:适当使用一些中括号,可以让程序更加清楚,不然全是小括号会看着很累
谓词函数
谓词函数很简单,大多就是一些简单的判断的组合。
比如self-evaluting? 其实就只有数字和字符串,可以借助racket的number? string?来实现
而比如像define、lambda、arit-expr等都有一个共同的特点,即识别它们靠表达式的第一个元素,因此抽提一个函数:
(define (is-begin-with exp tag)
(if(pair? exp)
(eq? (car exp) tag)
false))
就可以更方便的实现它们的谓词函数了。
求值函数
在有了上述的分派界面之后,最有难度和价值的部分其实就是求值函数内部了:
- 对于全局变量我们要考虑如何去取它的值;
- 对于lambda函数,我们要提取它的形参、函数体;
- 对于函数调用,我们要将实参绑定到lambda函数的形参上,然后调用函数体;
- 对于算术表达式,充分利用递归性质会非常简单
- if-else表达式,也是抽提各个子部分,然后去执行满足条件的那个步骤
- 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都可以玩好长时间,哈哈哈。。。