The central element in the evaluator is the sequence of instructions beginning
at eval-dispatch. This corresponds to the eval procedure of the
metacircular evaluator described in 4.1.1. When the controller
starts at eval-dispatch, it evaluates the expression specified by
exp in the environment specified by env. When evaluation is
complete, the controller will go to the entry point stored in continue,
and the val register will hold the value of the expression. As with the
metacircular eval, the structure of eval-dispatch is a case
analysis on the syntactic type of the expression to be evaluated.
求值器的核心是从 eval-dispatch 开始的指令序列。它对应于 4.1.1 中元循环求值器的 eval 过程。控制器从 eval-dispatch 出发时,会在 env 寄存器所指定的环境中对 exp 寄存器所指定的表达式求值。求值完成后,控制器将跳转到 continue 中保存的入口点,而 val 寄存器则保存该表达式的值。与元循环的 eval 一样,eval-dispatch 的结构是对待求值表达式的语法类型进行分情况分析。
Subheading: Evaluating simple expressions
小节标题:简单表达式的求值
Numbers and strings (which are self-evaluating), variables, quotations, and
lambda expressions have no subexpressions to be evaluated. For these,
the evaluator simply places the correct value in the val register and
continues execution at the entry point specified by continue.
Evaluation of simple expressions is performed by the following controller code:
数、字符串(它们是自求值的)、变量、引用以及 lambda 表达式都没有需要求值的子表达式。对于这些情况,求值器只需将正确的值置入 val 寄存器,然后继续执行 continue 所指定的入口点处的代码。简单表达式的求值由以下控制器代码完成:
Observe how ev-lambda uses the unev and exp registers to
hold the parameters and body of the lambda expression so that they can be
passed to the make-procedure operation, along with the environment in
env.
Subheading: Evaluating procedure applications
请注意 ev-lambda 如何利用 unev 和 exp 寄存器分别保存 lambda 表达式的参数和体,以便将它们连同 env 中的环境一起传递给 make-procedure 操作。
小节标题:过程应用的求值
A procedure application is specified by a combination containing an operator
and operands. The operator is a subexpression whose value is a procedure, and
the operands are subexpressions whose values are the arguments to which the
procedure should be applied. The metacircular eval handles applications
by calling itself recursively to evaluate each element of the combination, and
then passing the results to apply, which performs the actual procedure
application. The explicit-control evaluator does the same thing; these
recursive calls are implemented by goto instructions, together with use
of the stack to save registers that will be restored after the recursive call
returns. Before each call we will be careful to identify which registers must
be saved (because their values will be needed later).
过程应用由一个包含运算符和运算对象的组合式来指定。运算符是一个子表达式,其值为一个过程;运算对象是若干子表达式,其值为应传给该过程的实际参数。元循环的 eval 通过递归调用自身来对组合式的每个元素求值,然后将结果传给 apply,由 apply 执行实际的过程应用。显式控制求值器执行同样的操作;这些递归调用通过 goto 指令来实现,同时借助栈来保存那些在递归调用返回后需要恢复的寄存器。在每次调用之前,我们都会仔细确认哪些寄存器必须被保存(因为它们的值在之后还会用到)。
We begin the evaluation of an application by evaluating the operator to produce
a procedure, which will later be applied to the evaluated operands. To
evaluate the operator, we move it to the exp register and go to
eval-dispatch. The environment in the env register is already
the correct one in which to evaluate the operator. However, we save env
because we will need it later to evaluate the operands. We also extract the
operands into unev and save this on the stack. We set up
continue so that eval-dispatch will resume at
ev-appl-did-operator after the operator has been evaluated. First,
however, we save the old value of continue, which tells the controller
where to continue after the application.
我们从对运算符求值开始,以得到一个过程,该过程随后将被应用于求值后的运算对象。为了对运算符求值,我们将其移入 exp 寄存器并跳转至 eval-dispatch。此时 env 寄存器中的环境正是对运算符求值所需的正确环境。然而,由于稍后对运算对象求值时还需要用到 env,我们会将其保存起来。我们还将运算对象提取到 unev 中,并将其压栈保存。我们设置 continue,使得 eval-dispatch 在运算符求值完成后跳转到 ev-appl-did-operator。但在此之前,我们先保存 continue 的旧值,该值告知控制器在应用结束后应继续何处。
Upon returning from evaluating the operator subexpression, we proceed to
evaluate the operands of the combination and to accumulate the resulting
arguments in a list, held in argl. First we restore the unevaluated
operands and the environment. We initialize argl to an empty list.
Then we assign to the proc register the procedure that was produced by
evaluating the operator. If there are no operands, we go directly to
apply-dispatch. Otherwise we save proc on the stack and start
the argument-evaluation loop:
从对运算符子表达式的求值中返回后,我们继续对组合式的运算对象逐一求值,并将所得实参累积到保存在 argl 中的表里。首先恢复尚未求值的运算对象和环境,将 argl 初始化为空表;然后将对运算符求值所得的过程赋值给 proc 寄存器。若没有运算对象,则直接跳转至 apply-dispatch;否则将 proc 压栈保存,并启动实参求值循环:
Each cycle of the argument-evaluation loop evaluates an operand from the list
in unev and accumulates the result into argl. To evaluate an
operand, we place it in the exp register and go to eval-dispatch,
after setting continue so that execution will resume with the
argument-accumulation phase. But first we save the arguments accumulated so
far (held in argl), the environment (held in env), and the
remaining operands to be evaluated (held in unev). A special case is
made for the evaluation of the last operand, which is handled at
ev-appl-last-arg.
实参求值循环的每次迭代从 unev 中的表里取出一个运算对象并求值,将结果累积到 argl 中。为对一个运算对象求值,我们将其置入 exp 寄存器,设置 continue 使执行在完成后恢复至实参累积阶段,然后跳转至 eval-dispatch。但在此之前,我们先保存已累积的实参(保存在 argl 中)、环境(保存在 env 中),以及尚待求值的运算对象(保存在 unev 中)。对最后一个运算对象的求值作为特殊情况处理,在 ev-appl-last-arg 处执行。
When an operand has been evaluated, the value is accumulated into the list held
in argl. The operand is then removed from the list of unevaluated
operands in unev, and the argument-evaluation continues.
每当一个运算对象完成求值,其值便被累积到 argl 所保存的表中。随后该运算对象从 unev 中尚未求值的运算对象表里移除,实参求值循环继续执行。
Evaluation of the last argument is handled differently. There is no need to
save the environment or the list of unevaluated operands before going to
eval-dispatch, since they will not be required after the last operand is
evaluated. Thus, we return from the evaluation to a special entry point
ev-appl-accum-last-arg, which restores the argument list, accumulates
the new argument, restores the saved procedure, and goes off to perform the
application.
最后一个实参的求值方式有所不同。由于在最后一个运算对象求值完成后,环境和尚未求值的运算对象表就不再需要了,因此在跳转至 eval-dispatch 之前无需保存它们。于是,我们从求值中返回时转向专用入口点 ev-appl-accum-last-arg,由它恢复实参表、累积新实参、恢复已保存的过程,然后执行应用操作。
The details of the argument-evaluation loop determine the order in which the
interpreter evaluates the operands of a combination (e.g., left to right or
right to left—see Exercise 3.8). This order is not determined by the
metacircular evaluator, which inherits its control structure from the
underlying Scheme in which it is implemented. Because
the first-operand selector (used in ev-appl-operand-loop to
extract successive operands from unev) is implemented as car and
the rest-operands selector is implemented as cdr, the
explicit-control evaluator will evaluate the operands of a combination in
left-to-right order.
Subheading: Procedure application
实参求值循环的细节决定了解释器对组合式中各运算对象的求值顺序(例如从左到右或从右到左——参见练习 3.8)。元循环求值器并不规定这一顺序,因为它继承了底层 Scheme 实现的控制结构。而在显式控制求值器中,由于取首运算对象的选择函数(在 ev-appl-operand-loop 中用于从 unev 中依次提取运算对象)实现为 car,取余下运算对象的选择函数实现为 cdr,因此显式控制求值器将以从左到右的顺序对组合式的运算对象求值。
小节标题:过程应用
The entry point apply-dispatch corresponds to the apply procedure
of the metacircular evaluator. By the time we get to apply-dispatch,
the proc register contains the procedure to apply and argl
contains the list of evaluated arguments to which it must be applied. The
saved value of continue (originally passed to eval-dispatch and
saved at ev-application), which tells where to return with the result of
the procedure application, is on the stack. When the application is complete,
the controller transfers to the entry point specified by the saved
continue, with the result of the application in val. As with the
metacircular apply, there are two cases to consider. Either the
procedure to be applied is a primitive or it is a compound procedure.
入口点 apply-dispatch 对应于元循环求值器的 apply 过程。到达 apply-dispatch 时,proc 寄存器中保存的是待应用的过程,argl 中保存的是已求值的实参表。栈上保存着 continue 的值(最初传给 eval-dispatch,并在 ev-application 处保存),它指示过程应用结束后应返回何处。应用完成后,控制器将跳转到所保存的 continue 所指定的入口点,且 val 中保存应用结果。与元循环的 apply 相同,这里有两种情况需要考虑:待应用的过程要么是基本过程,要么是复合过程。
We assume that each primitive is implemented so as to obtain its arguments from
argl and place its result in val. To specify how the machine
handles primitives, we would have to provide a sequence of controller
instructions to implement each primitive and arrange for primitive-apply
to dispatch to the instructions for the primitive identified by the contents of
proc. Since we are interested in the structure of the evaluation
process rather than the details of the primitives, we will instead just use an
apply-primitive-procedure operation that applies the procedure in
proc to the arguments in argl. For the purpose of simulating the
evaluator with the simulator of 5.2 we use the procedure
apply-primitive-procedure, which calls on the underlying Scheme system
to perform the application, just as we did for the metacircular evaluator in
4.1.4. After computing the value of the primitive application,
we restore continue and go to the designated entry point.
我们假定每个基本过程都已被实现为从 argl 中获取实参并将结果置入 val。若要规定机器处理基本过程的具体方式,就需要为每个基本过程提供一组控制器指令,并安排 primitive-apply 根据 proc 中的内容分派到对应基本过程的指令处。由于我们关注的是求值计算过程的结构而非基本过程的细节,这里转而使用 apply-primitive-procedure 操作,直接将 proc 中的过程应用于 argl 中的实参。为了在 5.2 的模拟器上模拟求值器,我们使用过程 apply-primitive-procedure,它调用底层 Scheme 系统来执行应用,正如我们在 4.1.4 的元循环求值器中所做的那样。在计算出基本过程应用的值之后,恢复 continue 并跳转到指定的入口点。
To apply a compound procedure, we proceed just as with the metacircular
evaluator. We construct a frame that binds the procedure’s parameters to the
arguments, use this frame to extend the environment carried by the procedure,
and evaluate in this extended environment the sequence of expressions that
forms the body of the procedure. Ev-sequence, described below in
5.4.2, handles the evaluation of the sequence.
对复合过程的应用,我们的处理方式与元循环求值器完全相同:构造一个将过程的参数绑定到实参的框架,用该框架扩展过程所携带的环境,然后在这一扩展环境中对构成过程体的表达式序列求值。5.4.2 中将介绍的 Ev-sequence 负责处理该序列的求值。
Compound-apply is the only place in the interpreter where the env
register is ever assigned a new value. Just as in the metacircular evaluator,
the new environment is constructed from the environment carried by the
procedure, together with the argument list and the corresponding list of
variables to be bound.
Compound-apply 是解释器中唯一对 env 寄存器赋新值的地方。与元循环求值器相同,新环境由过程所携带的环境连同实参表和对应的待绑定变量表共同构成。
eval-dispatch
(test (op self-evaluating?) (reg exp))
(branch (label ev-self-eval))
(test (op variable?) (reg exp))
(branch (label ev-variable))
(test (op quoted?) (reg exp))
(branch (label ev-quoted))
(test (op assignment?) (reg exp))
(branch (label ev-assignment))
(test (op definition?) (reg exp))
(branch (label ev-definition))
(test (op if?) (reg exp))
(branch (label ev-if))
(test (op lambda?) (reg exp))
(branch (label ev-lambda))
(test (op begin?) (reg exp))
(branch (label ev-begin))
(test (op application?) (reg exp))
(branch (label ev-application))
(goto (label unknown-expression-type)) ev-self-eval
(assign val (reg exp))
(goto (reg continue))
ev-variable
(assign val
(op lookup-variable-value)
(reg exp)
(reg env))
(goto (reg continue))
ev-quoted
(assign val
(op text-of-quotation)
(reg exp))
(goto (reg continue))
ev-lambda
(assign unev
(op lambda-parameters)
(reg exp))
(assign exp
(op lambda-body)
(reg exp))
(assign val
(op make-procedure)
(reg unev)
(reg exp)
(reg env))
(goto (reg continue)) ev-application
(save continue)
(save env)
(assign unev (op operands) (reg exp))
(save unev)
(assign exp (op operator) (reg exp))
(assign
continue (label ev-appl-did-operator))
(goto (label eval-dispatch)) ev-appl-did-operator
(restore unev) ; the operands
(restore env)
(assign argl (op empty-arglist))
(assign proc (reg val)) ; the operator
(test (op no-operands?) (reg unev))
(branch (label apply-dispatch))
(save proc) ev-appl-operand-loop
(save argl)
(assign exp
(op first-operand)
(reg unev))
(test (op last-operand?) (reg unev))
(branch (label ev-appl-last-arg))
(save env)
(save unev)
(assign continue
(label ev-appl-accumulate-arg))
(goto (label eval-dispatch)) ev-appl-accumulate-arg
(restore unev)
(restore env)
(restore argl)
(assign argl
(op adjoin-arg)
(reg val)
(reg argl))
(assign unev
(op rest-operands)
(reg unev))
(goto (label ev-appl-operand-loop)) ev-appl-last-arg
(assign continue
(label ev-appl-accum-last-arg))
(goto (label eval-dispatch))
ev-appl-accum-last-arg
(restore argl)
(assign argl
(op adjoin-arg)
(reg val)
(reg argl))
(restore proc)
(goto (label apply-dispatch)) apply-dispatch
(test (op primitive-procedure?) (reg proc))
(branch (label primitive-apply))
(test (op compound-procedure?) (reg proc))
(branch (label compound-apply))
(goto (label unknown-procedure-type)) primitive-apply
(assign val (op apply-primitive-procedure)
(reg proc)
(reg argl))
(restore continue)
(goto (reg continue)) compound-apply
(assign unev
(op procedure-parameters)
(reg proc))
(assign env
(op procedure-environment)
(reg proc))
(assign env
(op extend-environment)
(reg unev)
(reg argl)
(reg env))
(assign unev
(op procedure-body)
(reg proc))
(goto (label ev-sequence))