Expressions Components - SICP Comparison Edition" />
Original | JavaScript | |
Programmers write programs as text, i.e. sequences of characters, entered in a programming environment or a text editor. To run our evaluator, we need to start with a representation of this program text as a JavaScript value. In section 2.3.1 we introduced strings to represent text. We would like to evaluate programs such as "const size = 2; 5 * size;" from section 1.1.2. Unfortunately, such program text does not provide enough structure to the evaluator. In this example, the program parts "size = 2" and "5 * size" look similar, but carry very different meanings. Abstract syntax functions such as declaration_value_expression would be difficult and error-prone to implement by examining the program text. In this section, we therefore introduce a function parse that translates program text to a tagged-list representation, reminiscent of the tagged data of section 2.4.2. For example, the application of parse to the program string above produces a data structure that reflects the structure of the program: a sequence consisting of a constant declaration associating the name size with the value 2 and a multiplication. parse("const size = 2; 5 * size;"); list("sequence", list(list("constant_declaration", list("name", "size"), list("literal", 2)), list("binary_operator_combination", "*", list("literal", 5), list("name", "size")))) The syntax functions used by the evaluator access the tagged-list representation produced by parse. |
The evaluator is reminiscent of the symbolic differentiation program discussed in section 2.3.2. Both programs operate on symbolic expressions. data. In both programs, the result of operating on a compound expression an object is determined by operating recursively on the pieces of the expression object and combining the results in a way that depends on the type of the expression. object. In both programs we used data abstraction to decouple the general rules of operation from the details of how expressions the objects are represented. In the differentiation program this meant that the same differentiation procedure function could deal with algebraic expressions in prefix form, in infix form, or in some other form. For the evaluator, this means that the syntax of the language being evaluated is determined solely by the procedures that classify and extract pieces of expressions. the syntax of the language being evaluated is determined solely by parse and the functions that classify and extract pieces of the tagged lists produced by parse.
Original | JavaScript | |
Figure 4.3 depicts the abstraction barrier formed by the syntax predicates and selectors, which interface the evaluator to the tagged-list representation of programs, which in turn is separated from the string representation by parse. Below we describe the parsing of program components and list the corresponding syntax predicates and selectors, as well as constructors if they are needed. |
Original | JavaScript | |
Here is the specification of the syntax of our language:
|
Literal expressions are parsed into tagged lists with tag "literal" and the actual value. \[ \begin{align*} \ll\ \mathit{literal}\textit{-}\mathit{expression}\ \gg & = \texttt{list("literal", }\mathit{value}\texttt{)} \end{align*} \] where $value$ is the JavaScript value represented by the $literal$-$expression$ string. Here $\ll\ $$literal$-$expression$$\ \gg$ denotes the result of parsing the string $literal$-$expression$. parse("1;"); list("literal", 1) parse("'hello world';"); list("literal", "hello world") parse("null;"); list("literal", null) The syntax predicate for literal expressions is is_literal. function is_literal(component) { return is_tagged_list(component, "literal"); } It is defined in terms of the function is_tagged_list, which identifies lists that begin with a designated string: function is_tagged_list(component, the_tag) { return is_pair(component) && head(component) === the_tag; } The second element of the list that results from parsing a literal expression is its actual JavaScript value. The selector for retrieving the value is literal_value. function literal_value(component) { return head(tail(component)); } literal_value(parse("null;")); null In the rest of this section, we just list the syntax predicates and selectors, and omit their declarations if they just access the obvious list elements. We provide a constructor for literals, which will come in handy: function make_literal(value) { return list("literal", value); } The tagged-list representation for names includes the tag "name" as first element and the string representing the name as second element. \[ \begin{align*} \ll\ \mathit{name}\ \gg & = \texttt{list("name", }\mathit{symbol}\texttt{)} \end{align*} \] where $symbol$ is a string that contains the characters that make up the $name$ as written in the program. The syntax predicate for names is is_name. The symbol is accessed using the selector symbol_of_name. We provide a constructor for names, to be used by operator_combination_to_application: function make_name(symbol) { return list("name", symbol); } We do not need to distinguish between expressions and expression statements. Consequently, parse can ignore the difference between the two kinds of components: \[ \ll\ \mathit{expression}\texttt{;}\ \gg {=} \ll\ \mathit{expression}\ \gg \] Function applications are parsed as follows: \[ \ll\ \mathit{fun}\textit{-}\mathit{expr}\texttt{(}\mathit{arg}\textit{-}\mathit{expr}_1\texttt{, }\ldots\texttt{, } \mathit{arg}\textit{-}\mathit{expr}_n \texttt{)}\ \gg \\ = \\ \texttt{list("application",} \ll\ \mathit{fun}\textit{-}\mathit{expr}\gg\texttt{, list(} \ll\ \mathit{arg}\textit{-}\mathit{expr}_1\;\gg \texttt{, }\ldots\texttt{, } \ll\ \mathit{arg}\textit{-}\mathit{expr}_n\;\gg \texttt{))} \] We declare is_application as the syntax predicate and function_expression and arg_expressions as the selectors. We add a constructor for function applications, to be used by operator_combination_to_application: function make_application(function_expression, argument_expressions) { return list("application", function_expression, argument_expressions); }
Conditional expressions
are parsed as follows:
A lambda expression
whose body is an expression is parsed as if the
body consisted of a block containing a single return statement whose
return expression is the body of the lambda expression.
\[
\ll\ \texttt{(}\mathit{name}_1\texttt{, }\ldots\texttt{, } \mathit{name}_n \texttt{) =>}\ \mathit{expression}\ \gg \\
= \\
\ll\ \texttt{(}\mathit{name}_1\texttt{, }\ldots\texttt{, } \mathit{name}_n \texttt{) => \{}\ \textbf{return} \ \mathit{expression}\texttt{;}\ \texttt{\}}\ \gg
\]
A lambda expression whose body is a block is parsed as follows:
A sequence statement packages a sequence of statements into a single statement. A sequence of statements is parsed as follows: \[ \ll\ \mathit{statement}_1 \cdots \mathit{statement}_n\;\gg \\ = \\ \texttt{list("sequence", list(} \ll\ \mathit{statement}_1\;\gg \texttt{, } \ldots \texttt{, } \ll\ \mathit{statement}_n\;\gg \texttt{))} \] The syntax predicate is is_sequence and the selector is sequence_statements. We retrieve the first of a list of statements using first_statement and the remaining statements using rest_statements. We test whether the list is empty using the predicate is_empty_sequence and whether it contains only one element using the predicate is_last_statement.[4] function first_statement(stmts) { return head(stmts); } function rest_statements(stmts) { return tail(stmts); } function is_empty_sequence(stmts) { return is_null(stmts); } function is_last_statement(stmts) { return is_null(tail(stmts)); } Blocks are parsed as follows:[5] \[ \begin{align*} \ll\ \texttt{\{}\ \mathit{statements}\ \texttt{\}}\ \gg & = \texttt{list("block",}\ \ll\ \mathit{statements}\ \gg \texttt{)} \end{align*} \] Here $statements$ refers to a sequence of statements, as shown above. The syntax predicate is is_block and the selector is block_body. Return statements are parsed as follows: \[ \begin{align*} \ll\ \textbf{return}\ \mathit{expression} \texttt{;}\ \gg & = \texttt{list("return\_statement",}\ \ll\ \mathit{expression}\ \gg \texttt{)} \end{align*} \] The syntax predicate and selector are, respectively, is_return_statement and return_expression. Assignments are parsed as follows: \[ \begin{align*} \ll\ \mathit{name}\ \ \texttt{=}\ \ \mathit{expression}\ \gg & = \texttt{list("assignment", }\ll\ \mathit{name}\gg \texttt{, }\ll\ \mathit{expression}\ \gg \texttt{)} \end{align*} \] The syntax predicate is is_assignment and the selectors are assignment_symbol and assignment_value_expression. The symbol is wrapped in a tagged list representing the name, and thus assignment_symbol needs to unwrap it. function assignment_symbol(component) { return symbol_of_name(head(tail(component)))); } Constant and variable declarations are parsed as follows: \[ \ll\ \textbf{const}\ \mathit{name}\ \ \texttt{=}\ \ \mathit{expression}\texttt{;}\ \gg \\ = \\ \texttt{list("constant\_declaration", } \ll\ \mathit{name}\ \gg \texttt{, }\ll\ \mathit{expression}\ \gg \texttt{)}\\[3mm] \ll\ \textbf{let}\ \mathit{name} \ \ \texttt{=}\ \ \mathit{expression}\texttt{;}\ \gg \\ = \\ \texttt{list("variable\_declaration", } \ll\ \mathit{name}\ \gg \texttt{, }\ll\ \mathit{expression}\ \gg \texttt{)} \] The selectors declaration_symbol and declaration_value_expression apply to both kinds. function declaration_symbol(component) { return symbol_of_name(head(tail(component))); } function declaration_value_expression(component) { return head(tail(tail(component))); } The function function_decl_to_constant_decl needs a constructor for constant declarations: function make_constant_declaration(name, value_expression) { return list("constant_declaration", name, value_expression); }
Function declarations
are parsed as follows:
|
Original | JavaScript | |
Some special forms in our language can be defined in terms of expressions involving other special forms, rather than being implemented directly. One example is cond, which can be implemented as a nest of if expressions. For example, we can reduce the problem of evaluating the expression (cond ((> x 0) x) ((= x 0) (display 'zero) 0) (else (- x))) to the problem of evaluating the following expression involving if and begin expressions: (if (> x 0) x (if (= x 0) (begin (display 'zero) 0) (- x))) Implementing the evaluation of cond in this way simplifies the evaluator because it reduces the number of special forms for which the evaluation process must be explicitly specified. We include syntax procedures that extract the parts of a condexpression, and a procedure cond->if that transforms cond expressions into if expressions. A case analysis begins with cond and has a list of predicate-action clauses. A clause is an else clause if its predicate is the symbol else.[6] (define (cond? exp) (tagged-list? exp 'cond)) (define (cond-clauses exp) (cdr exp)) (define (cond-else-clause? clause) (eq? (cond-predicate clause) 'else)) (define (cond-predicate clause) (car clause)) (define (cond-actions clause) (cdr clause)) (define (cond->if exp) (expand-clauses (cond-clauses exp))) (define (expand-clauses clauses) (if (null? clauses) 'false ; no else clause (let ((first (car clauses)) (rest (cdr clauses))) (if (cond-else-clause? first) (if (null? rest) (sequence->exp (cond-actions first)) (error "ELSE clause isn't last - - COND->IF" clauses)) (make-if (cond-predicate first) (sequence->exp (cond-actions first)) (expand-clauses rest)))))) |
Some syntactic forms in our language can be defined in terms of components involving other syntactic forms, rather than being implemented directly. One example is function declaration, which evaluate transforms into a constant declaration whose value expression is a lambda expression.[8] function function_decl_to_constant_decl(component) { return make_constant_declaration( function_declaration_name(component), make_lambda_expression( function_declaration_parameters(component), function_declaration_body(component))); } Implementing the evaluation of function declarations in this way simplifies the evaluator because it reduces the number of syntactic forms for which the evaluation process must be explicitly specified. Similarly, we define operator combinations in terms of function applications. Operator combinations are unary or binary and carry their operator symbol as second element in the tagged-list representation: \[ \ll\ \mathit{unary}\textit{-}\mathit{operator}\ \ \mathit{expression}\ \gg \\ = \\ \texttt{list("unary\_operator\_combination", "}\mathit{unary}\textit{-}\mathit{operator}\texttt{"},\ \texttt{list(}\ll\ \mathit{expression}\ \gg \texttt{))} \] where $unary$-$operator$ is ! (for logical negation) or -unary (for numeric negation), and \[ \ll\ \mathit{expression}_1\ \mathit{binary}\textit{-}\mathit{operator}\ \ \mathit{expression}_2\;\gg \\ = \\ \texttt{list("binary\_operator\_combination", "}\mathit{binary}\textit{-}\mathit{operator}\texttt{"},\\ \texttt{list(}\ll\ \mathit{expression}_1\;\gg\texttt{,}\ \ll\ \mathit{expression}_2\;\gg \texttt{))} \] where $binary$-$operator$ is +, -, *, /, %, ===, !==, >, <, >= or <=. The syntax predicates are is_operator_combination, is_unary_operator_combination, and is_binary_operator_combination, and the selectors are operator_symbol, first_operand, and second_operand. The evaluator uses operator_combination_to_application to transform an operator combination into a function application whose function expression is the name of the operator: function operator_combination_to_application(component) { const operator = operator_symbol(component); return is_unary_operator_combination(component) ? make_application(make_name(operator), list(first_operand(component))) : make_application(make_name(operator), list(first_operand(component), second_operand(component))); } |
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.
|
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 | |
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.
|
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 | |
Recall special forms and and or from section 1.1.6: | Recall from section 1.1.6 that the logical composition operations && and || are syntactic sugar for conditional expressions: The logical conjunction $\mathit{expression}_1$ && $\mathit{expression}_2$ is syntactic sugar for $ \mathit{expression}_1$ ? $\mathit{expression}_2$ : false, and the logical disjunction $\mathit{expression}_1$ || $\mathit{expression}_2$ is syntactic sugar for $ \mathit{expression}_1$ ? true : $\mathit{expression}_2$. |
Original | JavaScript | |
|
They are
parsed as follows:
$\ll\ \mathit{expression}_1\ \ \mathit{logical}\textit{-}\mathit{operation}\ \ \mathit{expression}_2\;\gg \ \ = $ list("logical_composition", "$logical$-$operation$", list($\ll\ $$expression$$_1\;\gg$, $\ll\ $$expression$$_2\;\gg$))where $logical$-$operation$ is && or ||. Install && and || as new syntactic forms for the evaluator by declaring appropriate syntax functions and evaluation functions eval_and and eval_or. Alternatively, show how to implement && and || as derived components. |
Original | JavaScript | |
Scheme allows an additional syntax for cond clauses, (test => recipient) If test evaluates to a true value, then recipient is evaluated. Its value must be a procedure of one argument; this procedure is then invoked on the value of the test, and the result is returned as the value of the cond expression. For example (cond ((assoc 'b '((a 1) (b 2))) => cadr) (else false)) returns 2. Modify the handling of cond so that it supports this extended syntax. |
|
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.
|
Original | JavaScript | |
Let* is similar to let, except that the bindings of the let* variables are performed sequentially from left to right, and each binding is made in an environment in which all of the preceding bindings are visible. For example (let* ((x 3) (y (+ x 2)) (z (+ x y 5))) (* x z)) returns 39. Explain how a let* expression can be rewritten as a set of nested let expressions, and write a procedure let*->nested-lets that performs this transformation. If we have already implemented let (exercise 4.9) and we want to extend the evaluator to handle let*, is it sufficient to add a clause to eval whose action is (eval (let*->nested-lets exp) env) or must we explicitly expand let* in terms of non-derived expressions? |
The language Scheme includes a variant of
let called
let*. We could approximate
the behavior of
let* in JavaScript by stipulating
that a
let* declaration implicitly
introduces a new block whose body includes the declaration and all
subsequent statements of the statement sequence in which the
declaration occurs. For example, the program
let* x = 3;
let* y = x + 2;
let* z = x + y + 5;
display(x * z);
displays 39 and could be seen as a shorthand for
{
let x = 3;
{
let y = x + 2;
{
let z = x + y + 5;
display(x * z);
}
}
}
|
Original | JavaScript | |
Named letis a variant of let that has the form (let var bindings body) The bindings and body are just as in ordinary let, except that var is bound within body to a procedure whose body is body and whose parameters are the variables in the bindings. Thus, one can repeatedly execute the body by invoking the procedure named var. For example, the iterative Fibonacci procedure (section 1.2.2) can be rewritten using named let as follows: (define (fib n) (let fib-iter ((a 1) (b 0) (count n)) (if (= count 0) b (fib-iter (+ a b) a (- count 1))))) Modify let->combination of exercise 4.9 to also support named let. |
JavaScript supports
while loops that execute a given
statement repeatedly. Specifically,
while ($predicate$) { $body$ }
evaluates the $predicate$, and
if the result is true, evaluates the
$body$
and then evaluates
the whole while loop again.
Once the $predicate$ evaluates
to false, the
while loop terminates.
For example, recall the imperative-style version of the iterative
factorial function from section 3.1.3:
function factorial(n) {
let product = 1;
let counter = 1;
function iter() {
if (counter > n) {
return product;
} else {
product = counter * product;
counter = counter + 1;
return iter();
}
}
return iter();
}
We can formulate the same algorithm using a while loop as follows:
function factorial(n) {
let product = 1;
let counter = 1;
while (counter <= n) {
product = counter * product;
counter = counter + 1;
}
return product;
}
While loops are parsed 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.
|
Original | JavaScript | |
By using data abstraction, we were able to write an eval procedure that is independent of the particular syntax of the language to be evaluated. To illustrate this, design and implement a new syntax for Scheme by modifying the procedures in this section, without changing eval or apply. |
The result of evaluating the body of a function is determined by
its return statements.
Following up on footnote 6
and the evaluation of declarations in
section 4.1.1,
this exercise addresses the question of what should be the result of
evaluating a JavaScript program that consists of a sequence of
statements (declarations, blocks, expression statements, and conditional
statements) outside of any function body.
For such a program, JavaScript
statically
distinguishes between value-producing and
non-value-producing statements. (Here
|