# 4.1.4以程序运行评估器

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

给定评估者后，我们将获得对JavaScript表达式进行评估的过程的描述（以JavaScript表示）。 将评估程序表示为程序的一个优点是我们可以运行该程序。 这为我们提供了一个在JavaScript中运行的，有关JavaScript本身如何评估表达式的工作模型。 就像我们将在本章后面介绍的那样，它可以作为试验评估规则的框架。

我们的评估程序最终将表达式简化为原始函数的应用。 因此，运行评估器所需要做的就是创建一种机制，该机制调用底层JavaScript系统来对原始函数的应用程序进行建模。

每个原始函数名称必须有一个绑定，以便`evaluate`评估原始应用程序的运算符时，它将找到要传递给`apply`的对象。 因此，我们建立了一个全局环境，将唯一的对象与将出现在我们要评估的表达式中的原始函数的名称相关联。 &lt;split&gt;全局环境还包括符号`undefined`，`NaN`和`Infinity`的绑定，因此它们可以用作要评估的表达式中的常量。&lt;/split&gt;

```js
function setup_environment() {
    const primitive_function_names =
        map(f => head(f), primitive_functions);
    const primitive_function_values =
        map(f => make_primitive_function(head(tail(f))),
            primitive_functions);
    const primitive_constant_names =
        map(f => head(f), primitive_constants);
    const primitive_constant_values =
        map(f => head(tail(f)),
            primitive_constants);
    return extend_environment(
               append(primitive_function_names, 
                      primitive_constant_names),
               append(primitive_function_values, 
                      primitive_constant_values),
               the_empty_environment);
}
```

 <split>只要`apply`可以使用函数`is_primitive_function`和`apply_primitive_function`识别并应用它们，我们如何表示原始函数就无关紧要。 我们选择将基本功能表示为以字符串`"primitive"`开头的列表，并在实现该基本功能的底层JavaScript中包含一个功能。

```js
function make_primitive_function(impl) {
    return list("primitive", impl);
}
function is_primitive_function(fun) {
   return is_tagged_list(fun, "primitive");
}
function primitive_implementation(fun) {
   return list_ref(fun, 1);
}
```</split> 

函数`setup_environment`将从列表中获取原始名称和实现函数： [[1]](77#footnote-1)

```js
const primitive_functions = list(
       list("display",       display          ),
       list("error",         error            ),
       list("+",             (x, y) => x + y  ),
       list("-",             (x, y) => x - y  ),
       list("*",             (x, y) => x * y  ),
       list("/",             (x, y) => x / y  ),
       list("%",             (x, y) => x % y  ),
       list("===",           (x, y) => x === y),
       list("!==",           (x, y) => x !== y),
       list("<",             (x, y) => x <   y),
       list("<=",            (x, y) => x <=  y),
       list(">",             (x, y) => x >   y),
       list(">=",            (x, y) => x >=  y),
       list("!",              x     =>   !   x)
       );
```

 <split>与原始函数类似，我们定义通过函数`setup_environment`安装在全局环境中的原始值。

```js
const primitive_constants = list(list("undefined", undefined),
                                 list("NaN",       NaN),
                                 list("Infinity",  Infinity),
                                 list("math_PI",   math_PI)
                                );
```</split> 

要应用原始函数，我们只需使用底层JavaScript系统将实现函数应用于参数即可： &lt;split&gt;[[2]](77#footnote-2)&lt;/split&gt;

```js
function apply_primitive_function(fun, argument_list) {
    return apply_in_underlying_javascript(
                primitive_implementation(fun),
                argument_list);     
}
```

 <split>为了方便运行元循环评估器，我们提供了一个_驱动程序循环_，用于对基础JavaScript系统进行建模。 它打印_提示_，从弹出窗口中读取输入表达式，在合适的环境中评估该表达式，然后在下一个弹出窗口中打印结果。 我们在每个打印结果之前都带有_输出提示_，以便从可以打印的输出中区分出表达式的值。

```js
const input_prompt = "M-Eval input: ";
const output_prompt = "M-Eval output: ";

function driver_loop(env, history) {
    const input = prompt(history + input_prompt);
    if (input === null) {
        display("session has ended");
    } else {
        const program = parse(input);
        const locals = local_names(program);	    
        const temp_values = map(x => no_value_yet,
                                locals);
        const new_env = extend_environment(locals, temp_values, env);
        const res = evaluate(program, new_env);

        driver_loop(new_env, history + "\\n" + 
                             input_prompt + input + "\\n" +
                             output_prompt + stringify(user_print(res)));
    }
}
```

We transform the input string that is read by `prompt` into a tagged-list representation of the statement according to the description in section 4.1.2, a process called parsing and accomplished by the primitive function `parse`. Similar to the evaluation of blocks in <ref name="sec:core-of-evaluator">[4.1.1](74)</ref>, we create a new environment by extending the given environment by a binding of the local names in the program to the special value `no_value_yet`, and evaluate the program with respect to the new environment. Passing the new environment in the recursive call of `driver_loop` ensures that subsequent programs can refer to previously declared names.</split>

我们使用特殊的打印功能`user_print`来避免打印复合功能的环境部分，该功能可能是一个很长的列表（甚至可能包含循环）。

```js
function user_print(object) {
   return is_compound_function(object)
       ? "compound function" +
         stringify(function_parameters(object)) +
         stringify(function_body(object)) +
         "<environment>"
       : object;
}
```

现在，运行评估器所需要做的就是初始化全局环境并启动驱动程序循环。 这是一个示例交互：

```js
const the_global_environment = setup_environment();
driver_loop(the_global_environment, "");
```

```js
M-Eval input:
function append(xs,ys) { 
    if (is_null(xs)) {
       return ys; 
    } else {
       return pair(head(xs),append(tail(xs),ys)); 
    }
}
M-Eval value: undefined

M-Eval input:    
append(list('a', 'b', 'c'), list('d', 'e', 'f'));

M-Eval value: ['a', ['b', ['c', ['d', ['e', ['f', null]]]]]]
```

<exercise>Eva Lu Ator and Louis Reasoner are each experimenting with the metacircular evaluator. Eva types in the definition of `map`, and runs some test programs that use it. They work fine. Louis, in contrast, has installed the system version of `map` as a primitive for the metacircular evaluator. When he tries it, things go terribly wrong. Explain why Louis's `map` fails even though Eva's works.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_77_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

* * *

[[1]](77#footnote-link-1) Any function defined in the underlying JavaScript can be used as a primitive for the metacircular evaluator. The name of a primitive installed in the evaluator need not be the same as the name of its implementation in the underlying JavaScript; the names are the same here because the metacircular evaluator implements JavaScript itself. Thus, for example, we could put `list("first", head)` or `list("square", x => x * x)` in the list of `primitive_functions`.

[[2]](77#footnote-link-2) JavaScript's `apply` method of function objects expects arguments in an array. Thus, the `argument_list` is transformed into an array using a `while` loop:

```js
function apply_in_underlying_javascript(prim, argument_list) {
   const argument_array = [];
   let i = 0;
   while (!is_null(argument_list)) {
      argument_array[i] = head(argument_list);
      i = i + 1;
      argument_list = tail(argument_list);
   }
   return prim.apply(prim, argument_array);
}
```

We have made use of `apply_in_underlying_javascript` to define the function `apply` in section <ref name="sec:data-directed">[2.4.3](42)</ref>.

