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); }

In JavaScript, `return` statements
are only allowed within function bodies. Any evaluation of such statements
outside of function bodies should lead to an error, a service provided by
the function `eval_toplevel`.

function eval_toplevel(stmt) { // wrap program in block const program_block = make_block(stmt); const value = evaluate(program_block, the_global_environment); if (is_return_value(value)) { error("return not allowed " + "outside of function definitions"); } else { return value; } }

function read_eval_print_loop(history) { const prog = prompt("History:" + history + "\\n\\n" + "Enter next: "); if (prog === null) { display("session has ended"); } else { const res = parse_and_eval(prog); read_eval_print_loop(history + "\\n" + stringify(prog) + " ===> " + stringify(user_print(res))); } }

function parse_and_eval(str) { return eval_toplevel(parse(str)); }

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) ? "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:

read_eval_print_loop("");

const undefined = "defined";

function f(undefined) { return undefined + 1; } f(2);

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` in
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