# 5.5.6词汇寻址

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

编译器执行的最常见的优化之一是变量查找的优化。 到目前为止，我们已实现的编译器生成的代码将使用评估器计算机的`lookup_variable_value`操作。 它通过将变量与当前绑定的每个变量进行比较来搜索变量，并在运行时环境中逐帧向外工作。 如果框架深度嵌套或变量很多，此搜索可能会很昂贵。 例如，考虑以下问题：在由以下程序返回的函数的应用程序中评估表达式`x * y * z`时查找`x`的值

```js
let x = 3;
let y = 4;
(a, b, c, d, e) => {
    let y = a * b * x;
    let z = c + d + x;
    return x * y * z;
}
```

由于`let`表达只是`function declaration expression`组合的语法糖，因此该表达等同于

```js
((x, y) =>
    ((a, b, c, d, e) =>
        ((y, z) => x * y * z)(a * b * x, c + d + x)))(3, 4)
```

Each time `lookup_variable_value` searches for `x`, it must determine that the symbol `x` is not `eq?` to `y` or `z` (in the first frame), nor to `a`, `b`, `c`, `d`, or `e` (in the second frame). We will assume, for the moment, that our programs do not use `define`—that variables are bound only with `lambda`. Because our language is lexically scoped, the run-time environment for any expression will have a structure that parallels the lexical structure of the program in which the expression appears.[[1]](120#footnote-1) Thus, the compiler can know, when it analyzes the above expression, that each time the function is applied the variable `x` in `x * y * z` will be found two frames out from the current frame and will be the first variable in that frame.

我们可以通过发明一种新型的变量查找操作`lexical_address_lookup`来利用这一事实，该操作以环境和_词法地址_作为参数，该地址由两个数字组成：_帧号_ ]，它指定要通过多少帧，_位移号_，指定要在该帧中通过多少变量。 `Lexical_address_lookup`将产生相对于当前环境存储在该词法地址处的变量的值。 如果将`lexical_address_lookup`操作添加到我们的机器，则可以使编译器生成使用此操作而不是`lookup_variable_value`引用变量的代码。 同样，我们的编译代码可以使用新的`lexical_address_set`操作代替`set_variable_value`。

为了生成这样的代码，编译器必须能够确定将要编译引用的变量的词法地址。 程序中变量的词法地址取决于代码中的位置。 例如，在下面的程序中，表达式e1中`x`的地址为（2,0）-返回两个帧，并且该帧中的第一个变量。 此时`y`位于地址（0,0），`c`位于地址（1,2）。 在表达式e2中，![%5C%2C](img/4b19862177c4e90d5251360144517e34.jpg) `x`位于（1,0），![%5C%2C](img/4b19862177c4e90d5251360144517e34.jpg) `y`位于（1,1），并且`c`位于（0,2）。 `((x, y) =&gt; ((a, b, c, d, e) =&gt; ((y, z) =&gt; ![e_1](img/654004cd3d06f92ac3a84b7843bbf681.jpg))(![e_2](img/4fad213e27dd89bc4ddae55754746125.jpg), c + d + x)))(3, 4)`

编译器产生使用词法寻址的代码的一种方法是维护称为_编译时环境_的数据结构。 当执行特定的变量访问操作时，可以跟踪运行时环境中哪些变量将位于帧中的哪个位置。 编译时环境是一个框架列表，每个框架包含一个变量列表。 （由于没有在编译时计算值，因此当然没有值绑定到变量。）编译时环境成为`compile`的附加参数，并传递给每个代码生成器。 对`compile`的顶级调用使用空的编译时环境。 编译`lambda`主体时，`compile_lambda_body`通过包含函数参数的框架扩展编译时环境，以便使用该扩展环境编译构成主体的序列。 在编译的每个点，`compile_variable`和`compile_assignment`使用编译时环境以生成适当的词法地址。

练习 **找不到ex：lexical-address-start****带有ex：lexical-address-end**[5.43](120#ex_5.43) 描述了编译时环境的另一种用法。

<exercise>Write a function `lexical_address_lookup` that implements the new lookup operation. It should take two arguments—a lexical address and a run-time environment—and return the value of the variable stored at the specified lexical address. `Lexical_address_lookup` should signal an error if the value of the variable is the symbol `*unassigned*`.[[2]](120#footnote-2) Also write a function `lexical_address_set` that implements the operation that changes the value of the variable at a specified lexical address.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_120_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>Modify the compiler to maintain the compile-time environment as described above. That is, add a compile-time-environment argument to `compile` and the various code generators, and extend it in `compile_lambda_body`.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_120_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>Write a function `find_variable` that takes as arguments a variable and a compile-time environment and returns the lexical address of the variable with respect to that environment. For example, in the program fragment that is shown above, the compile-time environment during the compilation of expression ![e_1](img/654004cd3d06f92ac3a84b7843bbf681.jpg) is `list(list("y", "z"), list("a", "b", "c", "d", "e"), list("x", "y"))`. `Find_variable` should produce

```js
find_variable("c", list(list("y", "z"), list("a", "b", "c", "d", "e"), list("x", "y")));
      [1, 2]
```

```js
find_variable("x", list(list("y", "z"), list("a", "b", "c", "d", "e"), list("x", "y")));
      [2, 0]
```

```js
find_variable("w", list(list("y", "z"), list("a", "b", "c", "d", "e"), list("x", "y")));
      not-found
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_120_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>Using `find_variable` from exercise <ref name="ex:find-variable">[5.40](120#ex_5.40)</ref>, rewrite `compile_variable` and `compile_assignment` to output lexical-address instructions. In cases where `find-variable` returns `not_found` (that is, where the variable is not in the compile-time environment), you should have the code generators use the evaluator operations, as before, to search for the binding. (The only place a variable that is not found at compile time can be is in the global environment, which is part of the run-time environment but is not part of the compile-time environment.[[3]](120#footnote-3) Thus, if you wish, you may have the evaluator operations look directly in the global environment, which can be obtained with the operation `op("get_global_environment")`, instead of having them search the whole run-time environment found in `env`.) Test the modified compiler on a few simple cases, such as the nested `lambda` combination at the beginning of this section.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_120_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>We argued in section <ref name="sec:internal-definitions">[4.1.6](79)</ref> that internal definitions for block structure should not be considered <quote>real</quote> `define`s. Rather, a function body should be interpreted as if the internal variables being defined were installed as ordinary `lambda` variables initialized to their correct values using assignment. Section <ref name="sec:internal-definitions">[4.1.6](79)</ref> and exercise <ref name="ex:internal-defs">[4.8](79#ex_4.8)</ref> showed how to modify the metacircular interpreter to accomplish this by scanning out internal definitions. Modify the compiler to perform the same transformation before it compiles a function body.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_120_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>In this section we have focused on the use of the compile-time environment to produce lexical addresses. But there are other uses for compile-time environments. For instance, in exercise <ref name="ex:open-code">[5.37](119#ex_5.37)</ref> we increased the efficiency of compiled code by open-coding primitive functions. Our implementation treated the names of open-coded functions as reserved words. If a program were to rebind such a name, the mechanism described in exercise <ref name="ex:open-code">[5.37](119#ex_5.37)</ref> would still open-code it as a primitive, ignoring the new binding. For example, consider the function

```js
(+ * a b x y) => (a * x) + (b * y)
```

which computes a linear combination of `x` and `y`. We might call it with arguments `+matrix`, `*matrix`, and four matrices, but the open-coding compiler would still open-code the `+` and the `*` in `(a * x) + (b * y)` as primitive `+` and `*`. Modify the open-coding compiler to consult the compile-time environment in order to compile the correct code for expressions involving the names of primitive functions. (The code will work correctly as long as the program does not `define` or assignment these names.)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_120_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]](120#footnote-link-1) This is not true if we allow internal definitions, unless we scan them out. See exercise <ref name="ex:compile-internal-defs">[5.42](120#ex_5.42)</ref>.

[[2]](120#footnote-link-2) This is the modification to variable lookup required if we implement the scanning method to eliminate internal definitions (exercise <ref name="ex:compile-internal-defs">[5.42](120#ex_5.42)</ref>). We will need to eliminate these definitions in order for lexical addressing to work.

[[3]](120#footnote-link-3) Lexical addresses cannot be used to access variables in the global environment, because these names can be defined and redefined interactively at any time. With internal definitions scanned out, as in exercise <ref name="ex:compile-internal-defs">[5.42](120#ex_5.42)</ref>, the only definitions the compiler sees are those at top level, which act on the global environment. Compilation of a definition does not cause the defined name to be entered in the compile-time environment.

