Start with the FWAE (syntactic) interpreter with static scoping, eager evaluation, and cached substitution. Augment this interpreter in two ways:
Add a simple conditional construct called if0
.
The concrete syntax for if0
is
{if0 [FWAE] [FWAE] [FWAE]}
The first expression to if0
should evaluate to a
number. If that number is zero, return the value of the second
expression (the "then" part); otherwise, return the value of the third
expression (the "else" part). For example:
{if0 {+ 5 -5} {- 8 2} 10}
would evaluate to (num 6).
Extend functions to take multiple arguments. For example, the following function definition (and naturally its application) should now be valid in the concrete syntax:
{fun {x y} {+ {* x x} {* y y}}}
We remarked in class that the with
and
fun
cases are remarkably similar. Indeed, we can express
with
expressions through fun
and
app
. Specifically,
{with {name named-expr} body}
can be written as
{{fun {name} body} named-expr}
(Convince yourself that this transformation is valid--meaning that
it preserves the behavior of with
--in the substitution
semantics.)
This similarity also extends to with
expressions that
bind multiple variables. We could write
{with {{x 3} {y 4}} {+ x y}}
as the expression
{{fun {x y} {+ x y}} 3 4}
Add support for multi-armed with
to your parser, but
instead of producing concrete syntax for with
, have the
parser rewrite with
into function application as shown in
the examples above. Once you make this change, you no longer need to
support with
in your interpreter in order to
support it in your language.
With the addition of if0
, we can now write functions
such as factorial in the syntax of our language:
{with {fac {fun {n} {if0 n 1 {* n {fac {- n 1}}}}}} {fac 5}}
Run this example through the interpreter you wrote for this assignment. Do you get the expected answer (since we know what factorial of 5 should evaluate to)? If not, explain why in terms of how the interpreter evaluates, how the environments work, or other technical terms. Don't edit your interpreter though! Simply answer this based on what the interpreter does do.
When we first added the substitution cache (but before we added closures), we accidentally got an interpreter with dynamic scoping instead of static scoping. For the following example program (from class), it appeared that using a queue instead of a stack for the environment would have yielded the correct answer, without requiring us to introduce closures.
{with {x 3} {with {f {fun {y} {+ x y}}} {with {x 5} {f 4}}}}
Would a queue-based strategy for environments implement static scoping properly in the general case? Either justify that it would or provide a counterexample program and explain why the queue-based implementation would fail to yield static scoping for it.
You do not need to implement an interpreter for this question. This is a paper and pencil exercise only.
Turn in the new parser and new interpreter, as well as a written (plain text fine) answer to questions 3 and 4.