Given the evaluator, we have in our hands a description (expressed in JavaScript) of the process by which JavaScript 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, so that when
`evaluate`
evaluates the operator 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
that can appear in the expressions we will be evaluating.
`undefined`,
`NaN` and
`Infinity`,
so that they can be used as constants
in expressions to be evaluated.

function setup_environment() { const primitive_function_names = map(f => head(f), primitive_functions); const primitive_function_values = map(f => make_primitive_function(head(tail(f))), primitive_functions); const primitive_constant_names = map(f => head(f), primitive_constants); const primitive_constant_values = map(f => head(tail(f)), primitive_constants); return extend_environment( append(primitive_function_names, primitive_constant_names), append(primitive_function_values, primitive_constant_values), the_empty_environment); }

It does not matter how we represent primitive functions,
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 make_primitive_function(impl) { return list("primitive", impl); } function is_primitive_function(fun) { return is_tagged_list(fun, "primitive"); } function primitive_implementation(fun) { return list_ref(fun, 1); }

The function `setup_environment`
will get the primitive
names and implementation functions
from a list:[1]

const primitive_functions = list( list("display", display ), list("error", error ), list("+", (x, y) => x + y ), list("-", (x, y) => x - y ), list("*", (x, y) => x * y ), list("/", (x, y) => x / y ), list("%", (x, y) => x % y ), list("===", (x, y) => x === y), list("!==", (x, y) => x !== y), list("<", (x, y) => x < y), list("<=", (x, y) => x <= y), list(">", (x, y) => x > y), list(">=", (x, y) => x >= y), list("!", x => ! x) );

To apply a
primitive function,
we simply apply the implementation
function
to the arguments, using the underlying
JavaScript
system:

function apply_primitive_function(fun, argument_list) { return apply_in_underlying_javascript( primitive_implementation(fun), argument_list); }

For convenience in running the metacircular evaluator, we provide a *
driver loop* that models the underlying JavaScript system. It prints a
*prompt*, reads an input expression from a pop-up window,
evaluates this expression in a suitable environment, and prints the
result on the next pop-up window. We precede each printed
result by an *output prompt* so as to distinguish the value of
the expression from the output that may be printed.

const input_prompt = "M-Eval input: "; const output_prompt = "M-Eval output: "; function driver_loop(env, history) { const input = prompt(history + input_prompt); if (input === null) { display("session has ended"); } else { const program = parse(input); const locals = local_names(program); const temp_values = map(x => no_value_yet, locals); const new_env = extend_environment(locals, temp_values, env); const res = evaluate(program, new_env); driver_loop(new_env, history + "\\n" + input_prompt + input + "\\n" + output_prompt + stringify(user_print(res))); } }

We use a special printing
function `user_print`,
to avoid printing the environment part of a compound
function, which may be a very long list
(or may even contain cycles).

function user_print(object) { return is_compound_function(object) ? "compound function" + stringify(function_parameters(object)) + stringify(function_body(object)) + "<environment>" : 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-Eval input: function append(xs,ys) { if (is_null(xs)) { return ys; } else { return pair(head(xs),append(tail(xs),ys)); } } M-Eval value: undefined M-Eval input: append(list('a', 'b', 'c'), list('d', 'e', 'f')); M-Eval value: ['a', ['b', ['c', ['d', ['e', ['f', null]]]]]]

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] Any
function
defined in the underlying
JavaScript
can be used as a primitive for the metacircular evaluator. The name of a primitive
installed in the evaluator need not be the same as the name
of its implementation in the underlying
JavaScript;
the names are the same here because the metacircular evaluator implements
JavaScript
itself.
Thus, for example, we could put
`list("first", head)`
or
`list("square", x => x * x)`
in the list of
`primitive_functions`.

[2]
JavaScript's `apply`
method of function objects expects arguments in an array. Thus, the
`argument_list` is transformed into
an array using a `while` loop:
We have made use of
`apply_in_underlying_javascript`
to define the function
`apply` in
section 2.4.3.

function apply_in_underlying_javascript(prim, argument_list) { const argument_array = []; let i = 0; while (!is_null(argument_list)) { argument_array[i] = head(argument_list); i = i + 1; argument_list = tail(argument_list); } return prim.apply(prim, argument_array); }

4.1.4
Running the Evaluator as a Program