Given the evaluator, we have in our hands a description (expressed in JavaScript) of the process by which JavaScript statements and expressions are evaluated. One advantage of expressing the evaluator as a program is that we can run the program. This gives us, running within JavaScript, a working model of how JavaScript itself evaluates expressions. This can serve as a framework for experimenting with evaluation rules, as we shall do later in this chapter.
Our evaluator program reduces expressions ultimately to the application of primitive functions. Therefore, all that we need to run the evaluator is to create a mechanism that calls on the underlying JavaScript system to model the application of primitive functions.
There must be a binding for each primitive function name and operator, so that when evaluate evaluates the function expression of an application of a primitive, it will find an object to pass to apply. We thus set up a global environment that associates unique objects with the names of the primitive functions and operators that can appear in the expressions we will be evaluating. The global environment also includes bindings for undefined and other names, so that they can be used as constants in expressions to be evaluated.
function setup_environment() {
return extend_environment(append(primitive_function_symbols,
primitive_constant_symbols),
append(primitive_function_objects,
primitive_constant_values),
the_empty_environment);
}
const the_global_environment = setup_environment();
It does not matter how we represent primitive function objects, so long as apply can identify and apply them using the functions is_primitive_function and apply_primitive_function. We have chosen to represent a primitive function as a list beginning with the string "primitive" and containing a function in the underlying JavaScript that implements that primitive.
function is_primitive_function(fun) {
return is_tagged_list(fun, "primitive");
}
function primitive_implementation(fun) { return head(tail(fun)); }
The function setup_environment will get the primitive names and implementation functions from a list:[1] const primitive_functions = list(list("head", head ), list("tail", tail ), list("pair", pair ), list("is_null", is_null ), list("+", (x, y) => x + y ), $\langle{}$more primitive functions$\rangle$ ); const primitive_function_symbols = map(f => head(f), primitive_functions); const primitive_function_objects = map(f => list("primitive", head(tail(f))), primitive_functions);
Similar to primitive functions, we define other primitive constants that are installed in the global environment by the function setup_environment. const primitive_constants = list(list("undefined", undefined), list("math_PI", math_PI) $\langle{}$more primitive constants$\rangle$ ); const primitive_constant_symbols = map(c => head(c), primitive_constants); const primitive_constant_values = map(c => head(tail(c)), primitive_constants);
To apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:[2]
function apply_primitive_function(fun, arglist) {
return apply_in_underlying_javascript(
primitive_implementation(fun), arglist);
}
For convenience in running the metacircular evaluator, we provide a driver loop that models the read-evaluate-print loop of the underlying JavaScript system. It prints a prompt and reads an input program as a string. It transforms the program string into a tagged-list representation of the statement as described in section 4.1.2—a process called parsing and accomplished by the primitive function parse. We precede each printed result by an output prompt so as to distinguish the value of the program from other output that may be printed. The driver loop gets the program environment of the previous program as argument. As described at the end of section 3.2.4, the driver loop treats the program as if it were in a block: It scans out the declarations, extends the given environment by a frame containing a binding of each name to "*unassigned*", and evaluates the program with respect to the extended environment, which is then passed as argument to the next iteration of the driver loop.
const input_prompt = "M-evaluate input: ";
const output_prompt = "M-evaluate value: ";
function driver_loop(env) {
const input = user_read(input_prompt);
if (is_null(input)) {
display("evaluator terminated");
} else {
const program = parse(input);
const locals = scan_out_declarations(program);
const unassigneds = list_of_unassigned(locals);
const program_env = extend_environment(locals, unassigneds, env);
const output = evaluate(program, program_env);
user_print(output_prompt, output);
return driver_loop(program_env);
}
}
function user_read(prompt_string) {
return prompt(prompt_string);
}
function user_print(string, object) {
function prepare(object) {
return is_compound_function(object)
? "< compound-function >"
: is_primitive_function(object)
? "< primitive-function >"
: is_pair(object)
? pair(prepare(head(object)),
prepare(tail(object)))
: object;
}
display(string + " " + stringify(prepare(object)));
}
Now all we need to do to run the evaluator is to initialize the global environment and start the driver loop. Here is a sample interaction:
const the_global_environment = setup_environment(); driver_loop(the_global_environment);
M-evaluate input:
function append(xs, ys) {
return is_null(xs)
? ys
: pair(head(xs), append(tail(xs), ys));
}
M-evaluate value: undefined
M-evaluate input:
append(list("a", "b", "c"), list("d", "e", "f"));
M-evaluate value: ["a", ["b", ["c", ["d", ["e", ["f", null]]]]]]
arraysin JavaScript.) Thus, the arglist is transformed into a vector—here using a while loop (see exercise 4.7): function apply_in_underlying_javascript(prim, arglist) { const arg_vector = []; // empty vector let i = 0; while (!is_null(arglist)) { arg_vector[i] = head(arglist); // store value at index $\texttt{i}$ i = i + 1; arglist = tail(arglist); } return prim.apply(prim, arg_vector); // $\texttt{apply}$ is accessed via $\texttt{prim}$ } We also made use of apply_in_underlying_javascript to declare the function apply_generic in section 2.4.3.