# 5.4.1显式控制评估器的核心

> 译者： [https://sicp.comp.nus.edu.sg/chapters/110](https://sicp.comp.nus.edu.sg/chapters/110)

评估器的核心要素是从`eval_dispatch`开始的指令序列。 这对应于 [4.1.1](74) 部分中所述的元圆形评估器的`evaluate`功能。 当控制器从`eval_dispatch`启动时，它将在`env`指定的环境中评估`exp`指定的表达式。 评估完成后，控制器将转到存储在`continue`中的入口点，并且`val`寄存器将保存表达式的值。 与元圆`eval`一样，`eval_dispatch`的结构也是要评估的表达式的句法类型的案例分析。 [[1]](110#footnote-1)

```js
"eval_dispatch",
          test(op("is_self_evaluating"), reg("exp")),
          branch(label("ev_self_eval")),
          test(op("is_variable"), reg("exp")),
          branch(label("ev_variable")),
          test(op("is_quoted"), reg("exp")),
          branch(label("ev_quoted")), /// FIXME
          test(op("is_assignment"), reg("exp")),
          branch(label("ev_assignment")),
          test(op("is_definition"), reg("exp")),
          branch(label("ev_definition")),
          test(op("is_conditional_statement"), reg("exp")),
          branch(label("ev_if")),
          test(op("is_function_expression"), reg("exp")),
          branch(label("ev_lambda")),
          test(op("is_block"), reg("exp")),
          branch(label("ev_begin")),
          test(op("is_application"), reg("exp")),
          branch(label("ev_application")),
          go_to(label("unknown_expression_type")),
```

## 评估简单表达式

数字和字符串（它们是自评估的），变量，引号和`function definition`表达式没有要评估的子表达式。 对于这些，评估器只需将正确的值放在`val`寄存器中，然后在`continue`指定的入口点处继续执行。 通过以下控制器代码执行对简单表达式的求值：

```js
"ev_self_eval",
    assign("val", reg("exp")),
    go_to(reg("continue")),

"ev_variable",
    assign("val", op("lookup_variable_value"), reg("exp"), reg("env")),
    go_to(reg("continue")),

"ev_quoted",
    assign("val", op("text_of_quotation"), reg("exp")),
    go_to(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")),
    go_to(reg("continue")),
```

观察`ev_lambda`如何使用`unev`和`exp`寄存器保存lambda表达式的参数和主体，以便它们可以与`env`中的环境一起传递给`make_procedure`操作。

## 评估功能应用

函数应用程序由包含运算符和操作数的组合指定。 运算符是一个子表达式，其值是一个函数，而操作数是子表达式，其值是应将函数应用到的参数。 元圆`eval`通过递归调用自身以评估组合的每个元素，然后将结果传递给`apply`来执行应用程序的实际功能，从而处理应用程序。 显式控制评估器执行相同的操作； 这些递归调用由`goto`指令实现，并使用堆栈保存寄存器，这些寄存器将在递归调用返回后恢复。 在每次调用之前，我们将谨慎确定必须保存的寄存器（因为稍后需要它们的值）。 [[2]](110#footnote-2)

我们通过评估运算符以产生一个函数来开始评估应用程序，该函数随后将应用于评估的操作数。 为了评估运算符，我们将其移至`exp`寄存器并转到`eval-dispatch`。 `env`寄存器中的环境已经是评估操作员的正确环境。 但是，我们保存`env`，因为稍后将需要它来评估操作数。 我们还将操作数提取到`unev`中并将其保存在堆栈中。 我们设置了`continue`，以便在对操作员进行评估之后，`eval-dispatch`将在`ev-appl-did-operator`处恢复。 但是，首先，我们保存`continue`的旧值，该值告诉控制器在应用程序后应从何处继续。

```js
"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")),
          go_to(label("eval_dispatch")),
```

从评估运算符子表达式返回后，我们继续评估组合的操作数并将结果自变量累积在`argl`中的列表中。 首先，我们还原未评估的操作数和环境。 我们将`argl`初始化为一个空列表。 然后，我们将`proc`寄存器分配通过评估运算符产生的功能。 如果没有操作数，我们将直接转到`apply-dispatch`。 否则，我们将`proc`保存在堆栈上并启动参数评估循环： [[3]](110#footnote-3)

```js
"ev_appl_did_operator",
           restore("unev"),                  // the operands
           restore("env"),
           assign("argl", op("empty_arglist")),
           assign("fun", reg("val")),       // the operator
           test(op("has_no_operands"), reg("unev")),
           branch(label("apply_dispatch")),
           save("fun"),
```

参数评估循环的每个循环都从`unev`中的列表评估操作数，并将结果累加到`argl`中。 为了评估操作数，我们将其设置为`continue`后将其放置在`exp`寄存器中，然后转到`eval_dispatch`，以便在参数累加阶段恢复执行。 但是首先我们保存到目前为止累积的参数（保存在`argl`中），环境（保存在`env`中）和要评估的其余操作数（保存在`unev`中）。 为评估最后一个操作数做出了特殊情况，该操作在`ev_appl_last_arg`中处理。

```js
"ev_appl_operand_loop",
           save("argl"),
           assign("exp", (op("first_operand"), reg("unev")),
           test(op("is_last_operand"), reg("unev")),
           branch(label("ev_appl_last_arg")),
           save("env"),
           save("unev"),
           assign("continue", (label("ev_appl_accumulate_arg"))),
           go_to(label("eval_dispatch")),
```

评估操作数后，该值将累积到`argl`中保存的列表中。 然后将操作数从`unev`中未评估的操作数列表中删除，并继续进行参数评估。

```js
"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")),
          go_to(label("ev_appl_operand_loop")),
```

对最后一个参数的评估处理方式有所不同。 在进入`eval-dispatch`之前，无需保存环境或未评估的操作数列表，因为在评估最后一个操作数之后将不需要它们。 因此，我们从评估返回到一个特殊的入口点`ev-appl-accum-last-arg`，该入口点将还原参数列表，累积新参数，还原保存的函数并退出以执行该应用程序。 [[4]](110#footnote-4)

```js
"ev_appl_last_arg",
      assign("continue", label("ev_appl_accum_last_arg")),
      go_to(label("eval_dispatch")),

"ev_appl_accum_last_arg",
      restore(argl),
      assign("argl", op("adjoin_arg"), reg("val"), reg("argl")),
      restore("fun"),
      go_to(label("apply_dispatch")),
```

参数评估循环的详细信息决定了解释程序评估组合操作数的顺序（例如，从左至右或从右至左-参见练习 [3.8](51#ex_3.8) ）。 此顺序不是由元循环评估器确定的，后者从实现该基础计划的基础Scheme继承了其控制结构。 [[5]](110#footnote-5) 由于`first\?operand`选择器（用于`ev-appl-operand-loop`以从`unev`提取连续的操作数）被实现为`head`，而`rest-operands`选择器被实现为`tail`， 显式控件评估器将按从左到右的顺序评估组合的操作数。

## 功能应用

入口点`apply-dispatch`对应于后圆形评估器的`apply`功能。 到`apply-dispatch`时，`proc`寄存器包含要应用的函数，`argl`包含必须对其应用的评估参数列表。 `continue`的保存值（最初传递给`eval-dispatch`并保存在`ev-application`），该值指示函数应用程序的结果返回何处。 应用程序完成后，控制器将转移到保存的`continue`指定的入口点，并将应用程序的结果存储在`val`中。 与后圆`apply`一样，有两种情况需要考虑。 要应用的函数是原始函数，或者是复合函数。

```js
"apply_dispatch",
          test(op("is_primitive_procedure"), reg("fun")),
          branch(label("primitive_apply")),
          test(op("is_compound_procedure"), reg("fun")),
          branch(label("compound_apply")),
          go_to(label("unknown_procedure_type")),
```

我们假设实现了每个原语，以便从`argl`获得其参数并将其结果放入`val`中。 为了指定机器如何处理原语，我们将必须提供一系列控制器指令以实现每个原语，并安排`primitive_apply`分发给由`fun`内容标识的原语的指令。 由于我们对求值过程的结构感兴趣，而不是对基元的细节感兴趣，因此我们将仅使用`apply-primitive-procedure`操作，该操作将`proc`中的函数应用于`argl`中的参数。 为了使用 [5.2](101)[4.1.4](77) 部分中的元圆评估器。 计算完原始应用程序的值后，我们将还原`continue`并转到指定的入口点。

```js
"primitive_apply",
          assign("val", op("apply_primitive_procedure"), reg("fun"), reg("argl")),
          restore("continue"),
          go_to(reg("continue")),
```

要应用复合函数，我们就像使用元圆评估器一样进行。 我们构造一个框架，将函数的参数绑定到参数，使用该框架扩展函数所携带的环境，并在此扩展的环境中评估构成函数主体的表达式序列。 下文 [5.4.2](111) 部分中介绍的`Ev-sequence`处理序列评估。

```js
"compound_apply",
          assign("unev", op("procedure_parameters"), reg("fun")),
          assign("env", op("procedure_environment") reg("fun")),
          assign("env", op("extend_environment"), reg("unev"), reg("argl"), reg("env")),
          assign("unev", op("procedure_body"), reg("fun")),
          go_to(label("ev_sequence")),
```

`Compound-apply`是解释器中唯一为`env`寄存器分配新值的位置。 与元循环评估器一样，新环境是由函数所携带的环境以及参数列表和要绑定的变量的相应列表构成的。

* * *

[[1]](110#footnote-link-1) In our controller, the dispatch is written as a sequence of `test` and `branch` instructions. Alternatively, it could have been written in a data-directed style (and in a real system it probably would have been) to avoid the need to perform sequential tests and to facilitate the definition of new expression types. A machine designed to run JavaScript would probably include a `dispatch-on-type` instruction that would efficiently execute such data-directed dispatches.

[[2]](110#footnote-link-2) This is an important but subtle point in translating algorithms from a procedural language, such as JavaScript, to a register-machine language. As an alternative to saving only what is needed, we could save all the registers (except `val`) before each recursive call. This is called a _framed-stack_ discipline. This would work but might save more registers than necessary; this could be an important consideration in a system where stack operations are expensive. Saving registers whose contents will not be needed later may also hold onto useless data that could otherwise be garbage-collected, freeing space to be reused.

[[3]](110#footnote-link-3) We add to the evaluator data-structure functions in section <ref name="sec:eval-data-structures">[4.1.3](76)</ref> the following two functions for manipulating argument lists:

```js
const empty_arglist = list();

function adjoin_arg(arg, arglist) {
    return append(arglist, list(arg));
}
```

We also use an additional syntax function to test for the last operand in a combination:

```js
function is_last_operand(ops) {
    return is_null(tail(ops));
}
```

 [[4]](110#footnote-link-4) The optimization of treating the last operand specially is known as _evlis tail recursion_ (see <citation>Wand 1980</citation>). We could be somewhat more efficient in the argument evaluation loop if we made evaluation of the first operand a special case too. This would permit us to postpone initializing `argl` until after evaluating the first operand, so as to avoid saving `argl` in this case. The compiler in section <ref name="sec:compilation">[5.5](114)</ref> performs this optimization. (Compare the `construct-arglist` function of section <ref name="sec:compiling-combinations">[5.5.3](117)</ref>.)

[[5]](110#footnote-link-5) The order of operand evaluation in the metacircular evaluator is determined by the order of evaluation of the arguments to `pair` in the function `list-of-values` of section <ref name="sec:core-of-evaluator">[4.1.1](74)</ref> (see exercise <ref name="ex:arg-eval-order">[4.1](74#ex_4.1)</ref>).

