In addition to defining the external syntax of expressions, representation of components, the evaluator implementation must also define the data structures that the evaluator manipulates internally, as part of the execution of a program, such as the representation of procedures functions and environments and the representation of true and false.
| Original | JavaScript | |
| For conditionals, we accept anything to be true that is not the explicit false object. | In order to limit the predicate expressions of conditionals to proper predicates (expressions that evaluate to a boolean value) as we do throughout this book, we insist here that the function is_truthy gets applied only to boolean values, and we accept only the boolean value true to be truthy. The opposite of is_truthy is called is_falsy.[1] |
| Original | JavaScript |
| (define (true? x) (not (eq? x false))) (define (false? x) (eq? x false)) | function is_truthy(x) { return is_boolean(x) ? x : error(x, "boolean expected, received"); } function is_falsy(x) { return ! is_truthy(x); } |
To handle primitives, we assume that we have available the following procedures: functions:
Compound procedures functions are constructed from parameters, procedure function bodies, and environments using the constructor make-procedure: make_function:
| Original | JavaScript |
| (define (make-procedure parameters body env) (list 'procedure parameters body env)) (define (compound-procedure? p) (tagged-list? p 'procedure)) (define (procedure-parameters p) (cadr p)) (define (procedure-body p) (caddr p)) (define (procedure-environment p) (cadddr p)) | function make_function(parameters, body, env) { return list("compound_function", parameters, body, env); } function is_compound_function(f) { return is_tagged_list(f, "compound_function"); } function function_parameters(f) { return list_ref(f, 1); } function function_body(f) { return list_ref(f, 2); } function function_environment(f) { return list_ref(f, 3); } |
| Original | JavaScript | |
|
We saw in section 4.1.1 that the evaluation of a sequence terminates when a return statement is encountered, and that the evaluation of a function application needs to return the value undefined if the evaluation of the function body does not encounter a return statement. In order to recognize that a value resulted from a return statement, we introduce return values as evaluator data structures. function make_return_value(content) { return list("return_value", content); } function is_return_value(value) { return is_tagged_list(value, "return_value"); } function return_value_content(value) { return head(tail(value)); } |
The evaluator needs operations for manipulating environments. As explained in section 3.2, an environment is a sequence of frames, where each frame is a table of bindings that associate variables symbols with their corresponding values. We use the following operations for manipulating environments:
| Original | JavaScript | |
|
|
To implement these operations we represent an environment as a list of frames. The enclosing environment of an environment is the cdr tail of the list. The empty environment is simply the empty list.
| Original | JavaScript |
| (define (enclosing-environment env) (cdr env)) (define (first-frame env) (car env)) (define the-empty-environment '()) | function enclosing_environment(env) { return tail(env); } function first_frame(env) { return head(env); } const the_empty_environment = null; |
| Original | JavaScript |
| (define (make-frame variables values) (cons variables values)) (define (frame-variables frame) (car frame)) (define (frame-values frame) (cdr frame)) (define (add-binding-to-frame! var val frame) (set-car! frame (cons var (car frame))) (set-cdr! frame (cons val (cdr frame)))) | function make_frame(symbols, values) { return pair(symbols, values); } function frame_symbols(frame) { return head(frame); } function frame_values(frame) { return tail(frame); } |
To extend an environment by a new frame that associates variables symbols with values, we make a frame consisting of the list of variables symbols and the list of values, and we adjoin this to the environment. We signal an error if the number of variables symbols does not match the number of values.
| Original | JavaScript |
| (define (extend-environment vars vals base-env) (if (= (length vars) (length vals)) (cons (make-frame vars vals) base-env) (if (< (length vars) (length vals)) (error "Too many arguments supplied" vars vals) (error "Too few arguments supplied" vars vals)))) | function extend_environment(symbols, vals, base_env) { return length(symbols) === length(vals) ? pair(make_frame(symbols, vals), base_env) : error(pair(symbols, vals), length(symbols) < length(vals) ? "too many arguments supplied" : "too few arguments supplied"); } |
To look up a
variable
symbol
in an environment, we scan the list of
variables
symbols
in the first frame. If we find the desired
variable,
symbol,
we return the corresponding element in the list of values. If we do not
find the
variable
symbol
in the current frame, we search the enclosing environment, and so on.
If we reach the empty environment, we signal an
unbound
variable
"unbound name"
error.
| Original | JavaScript |
| (define (lookup-variable-value var env) (define (env-loop env) (define (scan vars vals) (cond ((null? vars) (env-loop (enclosing-environment env))) ((eq? var (car vars)) (car vals)) (else (scan (cdr vars) (cdr vals))))) (if (eq? env the-empty-environment) (error "Unbound variable" var) (let ((frame (first-frame env))) (scan (frame-variables frame) (frame-values frame))))) (env-loop env)) | function lookup_symbol_value(symbol, env) { function env_loop(env) { function scan(symbols, vals) { return is_null(symbols) ? env_loop(enclosing_environment(env)) : symbol === head(symbols) ? head(vals) : scan(tail(symbols), tail(vals)); } if (env === the_empty_environment) { error(symbol, "unbound name"); } else { const frame = first_frame(env); return scan(frame_symbols(frame), frame_values(frame)); } } return env_loop(env); } |
| Original | JavaScript | |
| To set a variable to a new value in a specified environment, we scan for the variable, just as in lookup-variable-value, and change the corresponding value when we find it. | To assign a new value to a symbol in a specified environment, we scan for the symbol, just as in lookup_symbol_value, and change the corresponding value when we find it. |
| Original | JavaScript |
| (define (set-variable-value! var val env) (define (env-loop env) (define (scan vars vals) (cond ((null? vars) (env-loop (enclosing-environment env))) ((eq? var (car vars)) (set-car! vals val)) (else (scan (cdr vars) (cdr vals))))) (if (eq? env the-empty-environment) (error "Unbound variable - - SET!" var) (let ((frame (first-frame env))) (scan (frame-variables frame) (frame-values frame))))) (env-loop env)) | function assign_symbol_value(symbol, val, env) { function env_loop(env) { function scan(symbols, vals) { return is_null(symbols) ? env_loop(enclosing_environment(env)) : symbol === head(symbols) ? set_head(vals, val) : scan(tail(symbols), tail(vals)); } if (env === the_empty_environment) { error(symbol, "unbound name -- assignment"); } else { const frame = first_frame(env); return scan(frame_symbols(frame), frame_values(frame)); } } return env_loop(env); } |
The method described here is only one of many plausible ways to represent environments. Since we used data abstraction to isolate the rest of the evaluator from the detailed choice of representation, we could change the environment representation if we wanted to. (See exercise 4.9.) In a production-quality Lisp JavaScript system, the speed of the evaluator's environment operations—especially that of variable symbol lookup—has a major impact on the performance of the system. The representation described here, although conceptually simple, is not efficient and would not ordinarily be used in a production system.[3]
| Original | JavaScript | |
| Scheme allows us to create new bindings for variables by means of define, but provides no way to get rid of bindings. Implement for the evaluator a special form make-unbound! that removes the binding of a given symbol from the environment in which the make-unbound! expression is evaluated. This problem is not completely specified. For example, should we remove only the binding in the first frame of the environment? Complete the specification and justify any choices you make. |
Our language distinguishes constants from variables by using
different keywords—const
and let—and prevents
assignment to constants. However, our interpreter
does not make use of this distinction; the function
assign_symbol_value will happily
assign a new value to a given symbol, regardless whether it is declared
as a constant or a variable.
Correct this flaw by calling the function
error whenever an attempt is
made to use a constant on the left-hand side of an assignment.
You may proceed as follows:
|
| Original | JavaScript | |
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.
|
predicateexpression. JavaScript's notion of truthiness and falsiness is captured by the following variants of is_truthy and is_falsy: function is_truthy(x) { return ! is_falsy(x); } function is_falsy(x) { return (is_boolean(x) && !x ) || (is_number(x) && (x === 0 || x !== x )) || (is_string(x) && x === "") || is_null(x) || is_undefined(x); } The test x !== x is not a typo; the only JavaScript value for which x !== x yields true is the value NaN (
Not a Number), which is considered to be a falsy number (also not a typo), along with 0. The numerical value NaN is the result of certain arithmetic border cases such as 0 / 0.
The terms truthy
and falsy
were coined
by
Douglas Crockford, one of whose books
(Crockford 2008) inspired this JavaScript adaptation.