Start by reading the informal description in hw6 if you haven't already.
The definition below describes the syntax and semantics of Trefoil v3 programs. Italicized words are technical terms being defined.
Many of the concepts are the same as Trefoil v2. We do not repeat them here. Instead, we describe only the changes and new features.
The syntax has four levels: characters, tokens, parenthesized symbol trees, and abstract syntax trees (ASTs). Only the AST level changes compared to Trefoil v2.
A program consists of a sequence of bindings.
A binding is one of the following:
define
with exactly two arguments.(define (f x y) (* y (+ x 2)))
f
with parameters x
and y
and the given
body expressionAn expression is one of the following:
let
, which
is updated below.let
as follows.let
and exactly two arguments(let ((x 3) (y 4)) (+ x (+ y 1)))
cond
and any number of arguments, each
of which is a cond clause.cond
expression with two clauses.
(define (sum l)
(cond
((nil? l) 0)
(true (+ (car l) (sum (cdr l))))))
(f 0 true)
f
with the two arguments 0
and true
The meaning of a Trefoil v3 program is the same as in Trefoil v2. We only need to describe the semantics of the new binding and expression forms. We will also adjust the definition of the dynamic environment.
We change the definition of a dynamic environment to map strings to entries. An entry is either:
The two operations on dynamic environments are lookup and extension. The lookup operation looks up the name in the map and returns the corresponding entry. The extension operation takes an old dynamic environment, a name, and an entry, and returns a new dynamic environment that is the same as the old except that the given name now maps to the given entry.
We update the semantics to take into account that the dynamic environment now maps to entries, not just values.
We extend the semantics for the new kinds of bindings.
(define (f params) e)
, where e
stands for
any expression and params
stands for a possibly empty sequence of
symbols representing the parameter names. Call the current dynamic environment
env
.
Return the env
extended with f
maps to the function entry ((define (f
params) e)
, env
). In other words, we capture the current dynamic
environment env
and store it together with the entire function binding for
f
in the entry.We update the semantics to take into account that the dynamic environment now maps to entries, not just values.
x
where x
stands for any
variable name. The semantics is to perform a lookup operation for x
in the
current dynamic environment. If x
maps to a variable entry with value v
,
return v
. Otherwise signal an error.x
maps to a function, signal an error.x
doesn't map to anything in the current environment,
signal an error.We change the semantics of let
as follows.
(let (defs) body)
, where body
stands for any
expression and defs
stands for any sequence of letdefs. Each letdef is
of the form (xi ei)
where xi
is an arbitrary symbol and ei
is an
arbitrary expression. The semantics is to iterate through all the letdefs and
evaluate each ei
in the current dynamic environment to a value vi
. Then
evaluate body
in a new dynamic environment constructed by extending the
current dynamic environment with mappings xi -> vi
for each xi
and vi
from the sequences above. Call the resulting value v
. Return v
.ei
are evaluated in the old dynamic environment. So
variables from earlier letdefs in the list are not in scope for any
ei
. (This is different from nested let expressions, where outer/earlier
variables would be available.)We extend the semantics for the new kinds of expressions.
(cond clauses)
where clauses
stands for any
sequence (possibly empty) of cond clauses. Each cond clause is of the form
(pi bi)
where pi
and bi
stand for arbitrary expressions. The semantics
is to iterate over the clauses in order. For each clause (pi bi)
, evaluate
pi
in the current dynamic environment to a value vi
. If vi
is false
,
continue the loop. Otherwise, if vi
is anything whatsoever besides
false
, stop the loop and evaluate bi
in the current dynamic environment
to a value and return that. If the loop reaches the end of the sequence of
clauses without finding any pi
that evaluates to something besides false
,
then signal an error.(f args)
, where f
stands for any
function name, and args
stands for any sequence (possibly empty) of
expressions to be passed as arguments to f
. Call the current dynamic
environment callenv
. The semantics is to first lookup f
in callenv
. If
f
is not mapped to anything, or maps to anything besides a function entry,
signal an error. From the function entry mapped to by f
, let (define (f
params) body)
be f
's function binding and defenv
its defining
environment. If args
and params
are sequences of different lengths, signal
an error. Next, evaluate each element of args
in left-to-right order in
callenv
. This results in a sequence of values. Call that sequence vals
.
Then evaluate body
in a new dynamic environment constructed by extending
defenv
with mappings param -> val
for each param
and corresponding value
val
in the lists params
and values
, and with another mapping from f
to its function entry (to implement recursion). Return the result of evaluating
body
.