# 5.2.2汇编器

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

汇编器将机器的控制器表达式序列转换为相应的机器指令列表，每个指令都有其执行功能。 总体而言，汇编器与我们在第4章中研究的评估器非常相似-存在一种输入语言（在这种情况下为寄存器机器语言），并且我们必须针对该语言中的每种表达式执行适当的操作。

为每条指令生成执行函数的技术正是我们在 [4.1.7](80) 节中使用的技术，它通过将分析与运行时执行分开来加快评估程序的速度。 正如我们在第4章中看到的那样，可以在不知道变量实际值的情况下对Scheme表达式进行很多有用的分析。 类似地，在这里，无需知道机器寄存器的实际内容，就可以对寄存器机器语言表达式进行非常有用的分析。 例如，我们可以通过指向寄存器对象的指针来替换对寄存器的引用，并且可以通过指向标签所指定的指令序列中的位置的指针来替换对标签的引用。

在生成指令执行功能之前，汇编器必须知道所有标签均指的是什么，因此它首先扫描控制器文本以将标签与指令分开。 在扫描文本时，它会构造一个指令列表和一个表，该表将每个标签与指向该列表的指针相关联。 然后，汇编器通过为每个指令插入执行函数来扩充指令列表。

汇编功能是汇编器的主要入口。 它以控制器文本和机器模型为参数，并返回要存储在模型中的指令序列。 组装调用`extract_labels`，从提供的控制器文本中构建初始指令列表和标签表。 `extract_labels`的第二个参数是用于处理这些结果的函数：该函数使用`update_insts`生成指令执行函数并将其插入指令列表，然后返回修改后的列表。

```js
function assemble(controller_text, machine) {
    function receive(insts, labels) {
        update_insts(insts, labels, machine);
        return insts;
    }

    return extract_labels(controller_text, receive);
}
```

`Extract_labels`将列表文本（控制器指令表达式的序列）和接收函数作为参数。 将用两个值来调用Receive：（1）指令数据结构的列表实例，每个实例包含来自文本的指令； （2）称为标签的表格，该表格将文本中的每个标签与标签指定的列表ins中的位置相关联。

```js
function extract_labels(text, receive) {
    function helper(insts, labels) { 
        const next_inst = head(text);

        return is_string(next_inst)
            ? receive(insts, pair(make_label_entry(next_inst, insts), labels))
            : receive(pair(make_instruction(next_inst), insts), labels);
    }

    return text === undefined || is_null(text)
        ? receive(null, null)
        : extract_labels(tail(text), helper);
}
```

`Extract_labels`的工作方式是依次扫描文本的元素并累积inst和标签。 如果元素是符号（因此是标签），则将适当的条目添加到标签表。 否则，该元素将累积到insts列表中。 [[1]](103#footnote-1)

`Update_insts`修改指令列表，该列表最初仅包含指令文本，以包括相应的执行功能：

```js
function update_insts(insts, labels, machine) {
    const pc = get_register(machine, "pc");
    const flag = get_register(machine, "flag");
    const stack = machine("stack");
    const ops = machine("operations");

    const set_iep = set_instruction_execution_proc;
    const make_ep = make_execution_function;
    return map(i => set_iep(i,
                            make_ep(instruction_text(i),
                                    labels,
                                    machine,
                                    pc,
                                    flag,
                                    stack,
                                    ops)),
               insts);
}
```

机器指令数据结构仅将指令文本与相应的执行功能配对。 当`extract_labels`构造指令时，执行功能尚不可用，稍后由`update_insts`插入。

```js
function make_instruction(text) {
    return pair(text, null);
}

function instruction_text(inst) {
    return head(inst);
}

function instruction_execution_proc(inst) {
    return tail(inst);
}

function set_instruction_execution_proc(inst, proc) {
    set_tail(inst, proc); 
}
```

模拟器未使用该指令文本，但便于进行调试（请参阅练习 [5.16](105#ex_5.16) ）。

标签表的元素是成对的：

```js
function make_label_entry(label_name, insts) {
    return pair(label_name, insts);
}
```

Entries will be looked up in the table with

```js
function lookup_label(labels, label_name) {
    const val = assoc(label_name, labels);

    return val === undefined
        ? error(label_name, "Undefined label: ASSEMBLE")
        : tail(val);
}
```

<exercise>The following register-machine code is ambiguous, because the label here is defined more than once:

```js
"start"
    go_to(label(here)),
    "here",
    assign("a", list(const(3))),
    go_to(label(there)),
    "here",
    assign("a", list(const(4))),
    go_to(label(there)),
    "there",
```

With the simulator as written, what will the contents of register a be when control reaches there? Modify the `extract_labels` function so that the assembler will signal an error if the same label name is used to indicate two different locations.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_103_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]](103#footnote-link-1) Using the receive function here is a way to get `extract_labels` to effectively return two values—labels and insts—without explicitly making a compound data structure to hold them. An alternative implementation, which returns an explicit pair of values, is

```js
function extract_labels(text, receive) { 
    if (is_null(text)) {
        return pair(null, null);

    } else {
        const result = extract_labels(tail(text));
        const insts = head(result);
        const labels = tail(result);
        const next_inst = head(text);

        return is_string(next_inst)
            ? pair(insts, pair(make_label_entry(next_inst, insts), labels))
            : pair(pair(make_instruction(next_inst), insts), labels);
    }
}
```

which would be called by assemble as follows:

```js
function assemble_alternative(controller_text, machine) {
    const result = extract_labels(controller_text);
    const insts = head(result);
    const labels = tail(result);

    update_insts(insts, labels, machine);

    return insts;
}
```

You can consider our use of receive as demonstrating an elegant way to return multiple values, or simply an excuse to show off a programming trick. An argument like receive that is the next function to be invoked is called a <quote>continuation.</quote> Recall that we also used continuations to implement the backtracking control structure in the amb evaluator in section <ref name="sec:amb-implementation">[4.3.3](88)</ref>.

