Original JavaScript
 Original JavaScript Exercise 5.48 We argued in section 4.1.6 that internal definitions for block structure should not be considered real defines. Rather, a procedure body should be interpreted as if the internal variables being defined were installed as ordinary lambda variables initialized to their correct values using set!. Section 4.1.6 and exercise 4.24 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 procedure body. 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. Exercise 5.49 In JavaScript, an attempt to assign a new value to a name that is declared as a constant leads to an error. Exercise 4.16 shows how to detect such errors at run time. With the techniques presented in this section, we can detect attempts to assign a new value to a constant at compile time. For this purpose, extend the functions compile_lambda_body and compile_block to record in the compile-time environment whether a name is declared as a variable (using let or as a parameter), or as a constant (using const or function). Modify compile_assignment to report an appropriate error when it detects an assignment to a constant. 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.
 Original JavaScript Exercise 5.50 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 5.43 we increased the efficiency of compiled code by open-coding primitive procedures. Our implementation treated the names of open-coded procedures as reserved words. If a program were to rebind such a name, the mechanism described in exercise 5.43 would still open-code it as a primitive, ignoring the new binding. For example, consider the procedure (lambda (+ * 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 procedures. (The code will work correctly as long as the program does not define or set! these names.) 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. Exercise 5.51 Knowledge about constants at compile time opens the door to many optimizations that allow us to generate more efficient object code. In addition to the extension of the compile-time environment in exercise 5.49 to indicate names declared as constants, we may store the value of a constant if it is known at compile time, or other information that can help us optimize the code. A constant declaration such as const $name$ = $literal$; allows us to replace all occurrences of $name$ within the scope of the declaration by $literal$ so that $name$ doesn't have to be looked up in the runtime environment. This optimization is called constant propagation. Use an extended compile-time environment to store literal constants, and modify compile_name to use the stored constant in the generated assign instruction instead of the lookup_symbol_value operation. Function declaration is a derived component that expands to constant declaration. Let us assume that the names of primitive functions in the global environment are also considered constants. If we further extend our compile-time environment to keep track of which names refer to compiled functions and which ones to primitive functions, we can move the test that checks whether a function is compiled or primitive from run time to compile time. This makes the object code more efficient because it replaces a test that must be performed once per function application in the generated code by one that is performed by the compiler. Using such an extended compile-time environment, modify compile_function_call so that if it can be determined at compile time whether the called function is compiled or primitive, only the instructions in the compiled_branch or the primitive_branch are generated. Replacing constant names with their literal values as in part (a) paves the way for another optimization, namely replacing applications of primitive functions to literal values with the compile-time computed result. This optimization, called constant folding, replaces expressions such as 40 + 2 by 42 by performing the addition in the compiler. Extend the compiler to perform constant folding for arithmetic operations on numbers and for string concatenation. 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.

[1] This is not true if we allow internal definitions, unless we scan them out. See exercise 5.48.
[2] This is the modification to variable lookup required if we implement the scanning method to eliminate internal definitions (exercise 5.48). We will need to eliminate these definitions in order for lexical addressing to work.
[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 5.48, 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.