4.1.7 从执行中分离出语法分析
通过优化,让系统分析仅执行一次,我们能把解释器转化成更有效率的。
我们分拆了eval,它有一个表达式和一个环境,成为两个部分。程序
analyze仅有一个参数是表达式。它执行系统分析和返回一个新的程序,
是执行程序,它在执行分析的表达式中封装了要被做的工作.这个程序
执行以一个环境作为它的参数,完成解析.在一个表达式上分析仅执行了
一次,这节省了工作,而执行程序可能被调用许多次.
由进行了分析与执行的分离,eval现在变成了如下的样子:
(define (eval exp env)
((analyze) exp) env)
调用analyze的结果是执行程序被应用到环境中. analyze程序与执行原始的
eval版本的相同的案例分析,除了程序仅执行分析,而不是全面的解释:
(define (analyze exp)
(cond ((self-evaluating? exp)
(analyze-self-evaluating exp))
((variable? exp) (analyze-variable exp))
((quoted? exp) (analyze-quoted exp))
((assignment? exp) (analyze-assignment exp))
((definition? exp) (analyze-definition exp ))
((if? exp) (analyze-if exp))
((lambda? exp) (analyze-lambda exp))
((begin? exp) (analyze-sequence (begin-actions exp)))
((cond? exp) (analyze (cond->if exp))
((application? exp) (analyze-application exp))
(else (error "Unknown expression type --EVAL" exp))
)
)
这是最简单的语法分析程序,它处理自解释的表达式.
它返回一个执行程序,这个程序忽略它的环境参数并且
仅返回表达式:
(define (analyze-self-evaluating exp)
(lambda (env) exp))
对于一个引用的表达式,我们能够得到一个效率的提高.
通过从引用的文本中抽取一次,在分析的阶段而不是执行的阶段.
(define (analyze-quoted exp)
(let ((qval (text-of-quotation exp)))
(lambda (env) qval)
)
)
查找一个变量的值必须仍然在执行阶段被完成,因为这依赖已知的环境.
(define (analyze-variable exp)
(lambda (env) (lookup-variable-value exp env))
)
当环境已经被提供时,analyze-assignment也必须直到执行时才实际设置变量.
然而,事实是在分析期间,analyze-assignment表达式能被递归的分析,在效率
方面,是很大的收获,因为analyze-value表达式仅分析一次.对于
定义也是一样的.
(define (analyze-assignment exp)
(let ((var (assignment-variable exp))
(vproc (analyze (assignment-value exp))))
(lambda (env)
(set-variable-value! var (vproc env) env)
'ok
))
)
(define (analyze-definition exp)
(let ((var (definition-variable exp))
(vproc (analyze (definition-value exp))))
(lambda (env)
(define-value! var (vproc env) env)
'ok
)
)
)
对于条件表达式,在分析期间,我们抽取与分析判断子,
真值的语句,假值的语句.
(define (analyze-if exp)
(let ((pproc (analyze (if-predicate exp)))
(cproc (analyze (if-consequent exp)))
(aproc (analyze (if-alternative exp))))
(lambda (env)
(if (true? (pproc env))
(cproc env)
(aproc env))
)
))
分析一个lambda表达式在效率方面也有很大的收获:我们仅分析
lambda的程序体一次,而程序的结果可能被应用许多次.
(define (analyze-lambda exp)
(let ((vars (lambda-parameters exp))
(bproc (analyze-sequence (lambda-body exp))))
(lambda (env) (make-procedure var bproc env)))
)
表达式的序列的分析是更有用的.在序列中的任何一个表达式被分析,
得到一个执行程序.这些执行程序组合来生成一个执行程序,
它有一个环境作为参数,具有环境的对单独的执行程序进行顺序调用
作为参数.
(define (analyze-sequence exps)
(define (sequentially proc1 proc2)
(lambda (env) (proc1 env) (proc2 env)))
(define (loop first-proc rest-procs)
(if (null? rest-procs)
first-proc
(loop (sequentially first-proc (car rest-procs))
(cdr rest-procs))))
(let ((procs (map analyze exps)))
(if (null? procs)
(error "Empty sequence --ANALYZE")
)
(loop (car procs) (cdr procs))
)
)
为了分析一个程序,我们分析,操作符和操作数,并且
组装一个执行程序,它来调用操作符执行程序
(为了得到实际被应用的程序)和操作数执行程序(为了得到实际参数)
我们然后把这些给execute-application程序,它类似于4.1.1部分中的apply.
execute-application程序 不同于apply的是一个复合的程序的程序体已经被
分析过了,所有不需要再分析了。替代的是,我们在扩展的环境中,仅调用
程序体的执行程序即可。
(define (analyze-application exp)
(let ((fproc (analyze (operator exp)))
(aprocs (map analyze (operands exp))))
(lambda (env)
(execute-application (fproc env)
(map (lambda (aproc) (aproc env))
aprocs))))
)
(define (execute-application proc args)
(cond ((primitive-procedure? proc)
(apply-primitive-procedure proc args))
((compound-procedure? proc)
((procedure-body proc)
(extend-environment (procedure-parameters proc)
args
(procedure-environment proc))))
(else (error "Unknown procedure type --EXECUTE-APPLICATION"
proc) )
)
)
我们的新的解释器使用与4.1.2,4.1.3,4.1.4部分中的相同的数据结构,
语法程序,运行时支持程序。
练习4.22
在这部分中扩展解释器以支持标识符let.(见练习4.6)
练习4.23
阿丽莎不理解为什么analyze-sequence程序需要如此地复杂。
其它的分析程序可以直接地转换相应地解释程序(或者解释的子句)
她期望analyze-sequence程序看起来如下的样子:
(define (analyze-sequence exps)
(define (execute-sequence procs env)
(cond ((null? (cdr procs))
((car procs) env))
(else ((car procs) env)
(executee-sequence (cdr procs) env)
)
))
(let ((procs (map analyze exps)))
(if (null? procs)
(error "Empty sequence --ANALYZE")
)
(lambda (env) (execute-sequence procs env))
)
)
伊娃为阿丽莎解释了这个版本的程序在分析时,
做了太多的解释序列的工作.阿丽莎的序列执行的程序,没有对内嵌的
单独的执行程序进行调用,为了调用它们进行轮循:在效果上看,
尽管序列中的单独的表达式已经被分析了,序列本身却没有分析.
比较analyze-sequence的这两个版本.例如,当序列只有一个表达式时,
考虑它们的共同的情况.阿丽莎的程序生成的执行程序做什么工作?
在上文中的程序的情况呢?比较一下对于这两个版本的程序,如果一个序列
有两个表达式的情况,是怎么样的?
练习4.24
设计和执行一些实现来比较原始的元解释器与这部分中的解释器的执行
速度如何?为各种程序,使用你的结果来评估,分析与执行阶段花费的
时间的比例如何?