# 5.5.2编译表达式

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

在本节和下一节中，我们实现`compile`函数调度到的代码生成器。

## 编译链接代码

通常，每个代码生成器的输出都将以由`compile_linkage`函数生成的指令结尾，这些指令实现所需的链接。 如果链接为`return`，则必须生成指令`go_to(reg("continue"))`。 这需要`continue`寄存器，并且不修改任何寄存器。 如果链接为`next`，则我们不需要添加任何其他说明。 否则，链接就是一个标签，我们将为该标签生成一个`go_to`，这是一条不需要或修改任何寄存器的指令。 [[1]](116#footnote-1)

例如，如果`linkage`的值是&lt;split&gt;字符串&lt;/split&gt; `"branch25"`，则表达式`go_to(label(linkage))`的结果为列表`go_to(label("branch25"))`。 类似地，如果`x`的值是列表`list("a", "b", "c")`，则`list(1, 2, head(x))`评估为列表`list(1, 2, "a")`。

```js
function compile_linkage(linkage) {
    return linkage === "return"
        ? make_instruction_sequence(list("continue"), list(), list(go_to(reg("continue"))))
        : linkage === "next"
        ? empty_instruction_sequence()
        : make_instruction_sequence(list(), list(), list(go_to(label(linkage))));
}
```

链接代码由`preserving`寄存器`preserving`附加到指令序列，因为`return`链接将需要`continue`寄存器：如果给定的指令序列修改了`continue`并且链接代码需要它，[ `continue`将被保存和恢复。

```js
function end_with_linkage(linkage, instruction_sequence) {
    return preserving(list("continue"), instruction_sequence, compile_linkage(linkage));
}
```

## 编译简单表达式

用于自评估表达式，引号和变量的代码生成器构造指令序列，这些指令序列将所需的值分配给目标寄存器，然后按照链接描述符的指定进行操作。

```js
function compile_self_evaluating(exp, target, linkage) {
        return end_with_linkage(
            linkage,
            make_instruction_sequence(
                list(),
                list(target),
                list(assign(target, constant(exp)))));
    }

    function compile_quoted(exp, target, linkage) {
        return end_with_linkage(
            linkage,
            make_instruction_sequence(
                list(),
                list(target),
                list(assign(target, constant(stringify(exp))))));
    }

    function compile_variable(exp, target, linkage) {
        return end_with_linkage(
            linkage,
            make_instruction_sequence(
                list("env"),
                list(target),
                list(assign(target, op("lookup_variable_value"), constant(exp), reg("env")))));
    }
```

所有这些分配指令都会修改目标寄存器，而查找变量的指令则需要`env`寄存器。

分配和定义的处理方式与解释器中的处理方式相同。 我们以递归方式生成代码，该代码计算要分配给变量的值，并在其后附加两个指令序列，该序列实际设置或定义变量并将整个表达式的值（符号`ok`）分配给目标寄存器 。 递归编译具有目标`val`和链接`next`，以便代码将其结果放入`val`，并继续后面附加的代码。 保留`env`来完成附加操作，因为设置或定义变量需要环境，并且变量值的代码可以是复杂表达式的编译，可以通过任意方式修改寄存器。

```js
function compile_assignment(exp, target, linkage) {
    const variable = assignment_variable(exp);
    const get_value_code = compile(assignment_value(exp), "val", "next");

    return end_with_linkage(
        linkage,
        preserving(
            "env",
            get_value_code,
            make_instruction_sequence(
                list("env", "val"),
                list(target),
                list(
                    perform(
                        op("set_variable_value"),
                        constant(variable),
                        reg("val"),
                        reg("env")),
                    assign(target, constant(ok)))))); /// FIXME: ok??=
}

function compile_definition(exp, target, linkage) {
    const variable = definition_variable(exp);
    const get_value_code = compile(definition_value(exp), "val", "next");

    return end_with_linkage(
        linkage,
        preserving(
            "env",
            get_value_code,
            make_instruction_sequence()
                list("env", "val"),
                list(target),
                list(
                    perform(
                        op("define_variable"),
                        constant(variable),
                        reg("val"),
                        reg("env")),
                    assign(target, constant(ok)))))); /// FIXME: ok??=
}
```

附加的两个指令序列需要`env`和`val`并修改目标。 请注意，尽管我们为该序列保留了`env`，但并未保留`val`，因为`get_value_code`被设计为将其结果显式放置在`val`中，以供该序列使用。 （实际上，如果我们确实保存了`val`，则会出现一个错误，因为这会导致`get_value_code`运行后立即恢复`val`的先前内容。）

## 编译条件表达式

使用给定目标和链接编译的`if`表达式的代码的形式为`![%5Clangle%20%5Ctextit%7Bcompilation%20of%20predicate%2C%20target%7D](img/e5b5797ed3554c728eebba650230d67d.jpg) val![%5Ctextit%7B%2C%20linkage%7D](img/5911d9f51f22962e0db06b973680b8cb.jpg) next ![%5Crangle](img/4e5a6afd1302745fb047196f1d72ec8e.jpg) test(op("is_false"), reg("val")), branch(label("false_branch")), "true_branch", ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20consequent%20with%20given%20target%20and%20given%20linkage%20or%7D](img/9889764c89b0bf7590d38f49526c1862.jpg) after_if ![%5Crangle](img/4e5a6afd1302745fb047196f1d72ec8e.jpg) "false_branch", ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20alternative%20with%20given%20target%20and%20linkage%7D%20%5Crangle](img/18ed12ed106c75ec48c146245286d214.jpg) "after_if",`

要生成此代码，我们将编译谓词，结果和替代项，并将结果代码与用于测试谓词结果的指令以及新生成的标签（用于标记真假分支和条件结束）进行组合。 [[2]](116#footnote-2) 在这种代码排列中，如果测试为假，则必须围绕true分支。 仅有的一点复杂之处在于如何处理真正分支的链接。 如果条件链接为`return`或标签，则true和false分支都将使用此相同链接。 如果链接为`next`，则条件分支以条件分支末尾的假分支代码跳转到标签结束。

```js
function compile_if(exp, target, linkage) {
    let t_branch = make_label("true_branch");
    let f_branch = make_label("false_branch");
    let after_if = make_label("after_if");
    let consequent_linkage = linkage === "next" ? after_if : linkage;
    let p_code = compile(if_predicate(exp), "val", "next");
    let c_code = compile(if_consequent(exp), target, consequent_linkage);
    let a_code = compile(if_alternative(exp), target, linkage);

    return preserving(list("env", "continue"),
                      p_code,
                      append_instruction_sequences(
                          make_instruction_sequence(list("val"),
                                                    list(),
                                                    list(
                                                        test(op("is_false"), reg("val")),
                                                        branch(label(f_branch)))),
                          parallel_instruction_sequences(
                              append_instruction_sequences(t_branch, c_code),
                              append_instruction_sequences(f_branch, a_code)),
                          after_if));
}
```

`Env`被保留在谓词代码周围，因为它是真分支和假分支所需要；而`continue`被保留是因为这些分支中的链接代码可能需要它。 使用 [5.5.4](118) 部分中描述的特殊组合器`parallel_instruction_sequences`附加true和false分支的代码（它们不会顺序执行）。

&lt;split&gt;FIXME：&lt;/split&gt;请注意，`cond`是派生表达式，因此编译器要做的所有工作就是应用`cond-&gt;if`转换器（来自 [4.1.2节） ]](75) ）并编译生成的`if`表达。

## 编译顺序

序列的编译（从功能体或显式`begin`表达式）与它们的评估平行。 编译序列的每个表达式-具有为序列指定的链接的最后一个表达式，以及具有链接`next`的其他表达式（以执行序列的其余部分）。 附加了各个表达式的指令序列以形成单个指令序列，从而保留了`env`（该序列的其余部分需要）和`continue`（可能是序列末尾的链接所必需的）。

```js
function compile_sequence(seq, target, linkage) {
    return is_last_exp(seq)
        ? compile(first_exp(seq), target, linkage)
        : preserving(
            list("env", "continue"),
            compile(first_exp("seq"), target, "next"),
            compile_sequence(rest_exps(seq), target, linkage));
}
```

## 编译`lambda`表达式

函数定义表达式表达式构造函数。 函数定义表达式的目标代码必须采用`![%5Clangle%20%5Ctextit%7Bconstruct%20procedure%20object%20and%20assign%20it%20to%20target%20register%7D%20%5Crangle](img/a2924c8f06e204456d463157f19548b3.jpg) ![%5Clangle%20%5Ctextit%7Blinkage%7D%20%5Crangle](img/597cda07c1eef650789c0c6327a686e1.jpg)`的形式

编译函数定义表达式时，我们还会生成函数主体的代码。 尽管在构造函数时不会执行主体，但是将其插入到函数定义代码之后的目标代码中很方便。 如果函数定义表达式的链接是标签或`return`，则可以。 但是，如果链接为`next`，则需要使用跳转到插入在函数体后面的标签的链接来跳过函数体的代码。 因此，目标代码的格式为`![%5Clangle%20%5Ctextit%7Bconstruct%20procedure%20object%20and%20assign%20it%20to%20target%20register%7D%5Crangle](img/1d021d630fd20fe63b5661baa3e1e1d0.jpg) ![%5Clangle%20%5Ctextit%7Bcode%20for%20given%20linkage%7D%5Crangle%5C%20%5Ctextit%7Bor%7D](img/4c41d223b4ccfd79e32dbb2e45a27ac1.jpg) go_to(label("after_lambda")) ![%5Clangle%20%5Ctextit%7Bcompilation%20of%20procedure%20body%7D%20%5Crangle](img/5627ccc9676de43f8036ab5c2f90d90c.jpg) "after_lambda",`

`Compile_function_expression`生成用于构造功能对象的代码，然后生成用于功能主体的代码。 通过将当前环境（定义点的环境）与已编译函数体的入口点（新生成的标签）结合起来，可以在运行时构造函数对象。 [[3]](116#footnote-3)

```js
function compile_function_expression(exp, target, linkage) {
    let proc_entry = make_label("entry");
    let after_fexp = make_label("after_lambda"); /// FIXME: lambda
    let lambda_linkage = linkage === "next" ? after_lambda : linkage;

    return append_instruction_sequences(
        tack_on_instruction_sequence(
            end_with_linkage(
                lambda_linkage,
                make_instruction_sequence(
                    list("env"), 
                    list(target),
                    list(assign(target, op("make_compiled_procedure"), label(proc_entry), reg("env")))), 
            compile_function_expression_body(exp, proc_entry)),
            after_lambda));
}
```

`Compile_function_expression`使用特殊的组合器`tack_on_instruction_sequence`（ [5.5.4](118) 节），而不是`append_instruction_sequences`将函数主体附加到`lambda`表达式代码，因为主体是 输入组合序列时不执行的指令序列的一部分； 而是按顺序排列，仅是因为那是放置它的方便位置。

`Compile_lambda_body`构造函数主体的代码。 此代码以入口点的标签开头。 接下来的指令将使运行时评估环境切换到评估函数主体的正确环境，即函数的定义环境，扩展为包括形式参数到函数所使用的参数的绑定。 叫。 之后是组成函数体的表达式序列的代码。 该序列使用链接`return`和目标`val`进行编译，因此它将通过从函数返回`val`中的函数结果而结束。

```js
function compile_lambda_body(exp, proc_entry) {
    let formals  = lambda_parameters(exp);
    return append_instruction_sequences(
        make_instruction_sequence(
            list("env", "proc", "argl"),
            list("env"),
            list(proc_entry,
                 assign("env", op("compiled_procedure_env"), reg("proc")),
                 assign("env", op("extend_environment"), constant(formals), reg("argl"), reg("env")))),
        compiled_sequence(lambda_body(exp), "val", "return"));
}
```

* * *

[[1]](116#footnote-link-1) This function uses a feature of JavaScript called _backquote_ (or _quasiquote_) that is handy for constructing lists. Preceding a list with a backquote symbol is much like quoting it, except that anything in the list that is flagged with a comma is evaluated.

[[2]](116#footnote-link-2) We can't just use the labels `true_branch`, `false_branch`, and `after_if` as shown above, because there might be more than one `if` in the program. The compiler uses the function `make_label` to generate labels. `Make_label` takes a symbol as argument and returns a new symbol that begins with the given symbol. For example, successive calls to `make_label("a")` would return `a1`, `a2`, and so on. `Make_label` can be implemented similarly to the generation of unique variable names in the query language, as follows:

```js
let label_counter = 0;

        function new_label_number() {
            label_counter = label_counter + 1;
            return label-counter;
        }

        function make_label(name) {
            return name + stringify(new_label_number());
        }
```

 [[3]](116#footnote-link-3) We need machine operations to implement a data structure for representing compiled functions, analogous to the structure for compound functions described in section <ref name="sec:eval-data-structures">[4.1.3](76)</ref>:

```js
function make_compiled_procedure(entry, env) {
            return list("compiled_procedure", entry, env);
        }

        function is_compiled_procedure(proc) {
            return is_tagged_list(proc, "compiled_procedure");
        } 

        function compiled_procedure_entry(c_proc) {
            return head(tail(c_proc));
        }

        function compiled_procedure_env(c_proc) {
            return head(tail(tail(c_proc)));
        }
```

