# 4.1.7将句法分析与执行分开

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

<subsection wip="yes">Note: this section is a work in progress!

上面实现的评估器很简单，但是效率很低，因为表达式的语法分析与表达式的执行交织在一起。 因此，如果程序执行多次，则其语法将被分析多次。 考虑例如使用以下`factorial`定义评估`factorial(4)`：

```js
function factorial(n) {      
    return n === 1
           ?
           factorial(n - 1) * n;
}
```

每次调用`factorial`时，评估者都必须确定主体是条件表达式，并提取谓词。 只有这样，它才能评估谓词并分派其值。 每次评估表达式`factorial(n - 1) * n`或子表达式`factorial(n - 1)`和`n - 1`时，评估器必须在`evaluate`中执行案例分析，以确定该表达式是应用程序，并且必须提取其运算符和操作数。 这种分析是昂贵的。 重复执行此操作很浪费。

通过安排事物，我们可以将评估器转换为效率更高的工具，从而使语法分析仅执行一次。 [[1]](80#footnote-1) 我们将`evaluate`（它包含一个表达式和一个环境）分为两部分。 函数`analyze`仅采用表达式。 它执行语法分析并返回一个新函数_执行函数_，该函数封装了在执行所分析的表达式中要完成的工作。 执行函数将环境作为其参数并完成评估。 这样可以节省工作，因为`analyze`在表达式上仅被调用一次，而执行函数则可以被多次调用。

随着分析和执行的分离，`evaluate`现在变为

```js
function evaluate(exp, env) {      
    return (analyze(exp))(env);
}
```

调用`analyze`的结果是要应用于环境的执行功能。 `analyze`功能与 [4.1.1](74) 部分的原始`eval`执行的案例分析相同，不同之处在于，我们分派给它们的功能仅执行分析，而不是完全执行 评价：

```js
function analyze(stmt) {
    return is_self_evaluating(stmt)
           ? analyze_self_evaluating(stmt)
         : is_name(stmt)
           ? analyze_name(stmt)
         : is_constant_declaration(stmt)
           ? analyze_constant_declaration(stmt)
         : is_variable_declaration(stmt)
           ? analyze_variable_declaration(stmt)
         : is_assignment(stmt)
           ? analyze_assignment(stmt)
         : is_conditional_expression(stmt)
           ? analyze_conditional_expression(stmt)
         : is_function_definition(stmt)
           ? analyze_function_definition(stmt)
         : is_sequence(stmt)
           ? analyze_sequence(sequence_actions(stmt))
         : is_block(stmt)
           ? analyze_block(block_body(stmt))
         : is_return_statement(stmt)
           ? analyze_return_statement(stmt)
         : is_application(stmt)
           ? analyze_application(stmt)
         : error(stmt, "Unknown statement type in analyze");
}
```

这是最简单的语法分析功能，它可以处理自我评估表达式。 它返回一个忽略其环境参数的执行函数，仅返回表达式：

```js
function analyze_self_evaluating(stmt) {      
    return env => stmt;
}
```

在执行阶段仍然必须查找名称的值，因为这取决于对环境的了解。 [[2]](80#footnote-2)

```js
function analyze_name(stmt) {      
    return env => lookup_name_value(
                      name_of_name(stmt, env));
}
```

功能`analyze_assignment`还必须将实际设置变量推迟到提供环境后才执行。 但是，可以在分析过程中（递归）分析函数`assignment_value`表达式，这是效率的主要提高，因为现在仅对函数`assignment_value`表达式进行一次分析。 常量和变量声明也是如此。

```js
function analyze_assignment(stmt) {
    const variable = assignment_variable(stmt);
    const vfun = analyze(assignment_value(stmt));
    return env => {
               set_variable_value(variable,
                   vfun(env), env);
               return "ok";
           };
}
function analyze_variable_declaration(stmt) {	   
    const name = 
        variable_declaration_name(stmt);
    const vfun =
        variable_declaration_value(stmt);
    return env => {
               declare_variable(name, 
                                vfun(env),
				env);
	       return "ok";
           };
}
function analyze_constant_declaration(stmt) {	   
    const name = 
        constant_declaration_name(stmt);
    const vfun =
        constant_declaration_value(stmt);
    return env => {
               declare_constant(name, 
                                vfun(env),
				env);
	       return "ok";
           };
}
```

对于条件表达式，我们在分析时提取并分析谓词，结果和替代项。

```js
function analyze_conditional_expression(stmt) {	
    const pfun = analyze(cond_expr_pred(stmt));
    const cfun = analyze(cond_expr_cons(stmt));
    const afun = analyze(cond_expr_alt(stmt));
    return env => is_true(pfun(env))
                  ? cfun(env) 
		  : afun(env);
}
```

分析函数定义表达式还可以大大提高效率：即使对函数定义求值而得的函数可以多次应用，我们也只分析函数主体一次。

```js
function analyze_function_definition(stmt) {
    const vars = 
        function_definition_parameters(stmt);
    const bfun =
        analyze(function_definition_body(stmt);
    return env =>
	make_function(vars, bfun, env);
}
```

对语句序列的分析更加复杂。 [[3]](80#footnote-3) 分析序列中的每个语句，产生执行函数。 将这些执行函数组合在一起，以生成一个执行函数，该执行函数将环境作为参数并以该环境作为参数依次调用每个单独的执行函数。

```js
function analyze_sequence(stmts) {
    function sequentially(fun1, fun2) {
        return env => {
                   fun1(env);
                   fun2(env);
               };
    }
    function loop(first_fun, rest_funs) {
        return is_null(rest_funs)
               ? first_fun
               : loop(sequentially(first_fun,
                          head(rest_funs)),
                      tail(rest_funs));
    }
    const funs = map(analyze, stmts);
    return is_null(funs) 
           ? env => undefined
           : loop(head(funs), tail(funs));
}
```

为了分析应用程序，我们分析了运算符和操作数，并构造了一个执行函数，该函数调用运算符执行函数（以获取要应用的实际函数）和操作数执行函数（以获取实际参数）。 然后将它们传递给`execute_application`，它是 [4.1.1](74) 部分中的`apply`的类似物。 功能`execute_application`与`apply`的不同之处在于，已经对复合功能的功能体进行了分析，因此无需进行进一步分析。 相反，我们只是在扩展环境上为主体调用执行函数。

```js
function analyze_application(stmt) {	
    const ffun = analyze(operator(stmt));
    const afuns = map(analyze, operands(stmt));
    return env =>
           execute_application(ffun(env),
               map(afun => afun(env), afuns));
}
function execute_application(fun, args) {	       
    return is_primitive_function(fun)
           ? apply_primitive_function(fun, args)
           : is_compound_function(fun) 
             ? (function_body(fun))
               (extend_environment(
                    function_parameters(fun),
                    args,
                    function_environment(fun)))
             : error(fun, "unknown function type in " +
                          "execute_application");
}
```

我们的新型评估程序使用与 [4.1.2](75)[4.1.3](76)[4.1.4](77) 。

<split><exercise>Extend the evaluator in this section to support conditional expressions.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_80_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></split><exercise>Alyssa P. Hacker doesn't understand why `analyze_sequence` needs to be so complicated. All the other analysis functions are straightforward transformations of the corresponding evaluation functions (or `eval` clauses) in section <ref name="sec:core-of-evaluator">[4.1.1](74)</ref>. She expected `analyze_sequence` to look like this:

```js
function analyze_sequence(stmts) {	
    function execute_sequence(funs, env) {
        if (is_null(tail(funs))) {
            return head(funs)(env);
        } else {
            head(funs)(env);
            execute_sequence(tail(funs), 
                             env);
        }
    }
    const funs = map(analyze, stmts);
    return is_null(funs)
           ? env => undefined
           : env => execute_sequence(funs, 
                                     env);
}
```

Eva Lu Ator explains to Alyssa that the version in the text does more of the work of evaluating a sequence at analysis time. Alyssa's `sequence_execution` function, rather than having the calls to the individual execution functions built in, loops through the functions in order to call them: In effect, although the individual expressions in the sequence have been analyzed, the sequence itself has not been. Compare the two versions of `sequence_execution`. For example, consider the common case (typical of function bodies) where the sequence has just one expression. What work will the execution function produced by Alyssa's program do? What about the execution function produced by the program in the text above? How do the two versions compare for a sequence with two expressions?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_80_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><exercise>Design and carry out some experiments to compare the speed of the original metacircular evaluator with the version in this section. Use your results to estimate the fraction of time that is spent in analysis versus execution for various functions.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_80_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]](80#footnote-link-1) This technique is an integral part of the compilation process, which we shall discuss in chapter 5\. Jonathan Rees wrote a Scheme interpreter like this in about 1982 for the T project (<citation>Rees and Adams 1982</citation>). <citation>Marc Feeley 1986</citation> (see also <citation>Feeley and Lapalme 1987</citation>) independently invented this technique in his master's thesis.

[[2]](80#footnote-link-2) There is, however, an important part of the variable search that _can_ be done as part of the syntactic analysis.

[[3]](80#footnote-link-3) See exercise <ref name="ex:analyze-sequence">[4.14](80#ex_4.14)</ref> for some insight into the processing of sequences.

