Start by reading the informal description in hw7 if you haven't already.
The definition below describes the syntax and semantics of Trefoil v4 programs. Italicized words are technical terms being defined.
Many of the concepts are the same as Trefoil v3. 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 v3.
A program consists of a sequence of bindings.
A binding is one of the following:
struct
with at least one argument. All
the arguments must be symbols. The first argument is called the struct name.
The rest of the arguments (possibly none) are called the field names. The
field names must be distinct (but it is allowed to have a field with the
same name as the struct name(!)).(struct my-record name age)
(struct my-empty-record)
An expression is one of the following:
'hello-world
match
and at least one argument.match
expression with two clauses.
(define (sum l)
(match l
(nil 0)
((cons x xs) (+ x (sum xs)))))
lambda
and exactly two arguments.(lambda (x y) (+ x (- y 1)))
is an anonymous function of two arguments.Print expression: a node with head print
and exactly one argument, which is an expression.
We change the syntax of function call expressions to allow any expression to be called, not just a symbol.
((f 1) 2)
f
with the argument 1
and expects that to return a
closure that is then called with argument 2
.f
is just a variable
that evaluates to a closure.There are also three "internal" expressions used by the interpreter to implement structs. Trefoil users cannot write these expressions directly, so they have no concrete syntax as PSTs. But they do have purely abstract syntax as ASTs. So we describe them in terms of what fields their AST nodes have.
StructConstructor(s, es)
where s
stands for any string and es
stand for any list of expression ASTs.StructPredicate(s, e)
where
s
stands for any string and e
stands for any expression AST.StructAccess(s, i, e)
where
s
stands for any string, i
stands for any (OCaml) integer, and e
stands for any expression AST.Finally, there is one more "internal" expression used to implement first-class functions.
A pattern is one of the following:
_
(the underscore character)_
, nor any symbol that
starts with apostrophe.true
or the symbol false
nil
cons
and exactly two arguments, each of
which is a pattern._
, nor any symbol that
starts with an apostrophe. The node can have any number of arguments after the
head, each of which is a pattern.A value is an expression that satisfies one of the following additional constraints:
List of symbol keywords (cannot be used as variable names)
true
, false
, nil
, _
, and any symbol that starts with an apostropheList of node head keywords (cannot be used as function names)
test
, define
, +
, -
, *
, =
, if
, let
, cons
, nil?
, cons?
,
car
, cdr
, cond
, match
, struct
, _
, lambda
, print
, and any symbol that starts with
an apostropheThe meaning of a Trefoil v4 program is the same as in Trefoil v3. We only need to describe the semantics of the new binding and expression forms, as well as the semantics of patterns. We will also adjust the definition of the dynamic environment and the semantics of function calls.
We change the definition of a dynamic environment back to the version from Trefoil v2 (!) to map strings to values.
We extend the semantics for the new kind of binding.
(struct s fs)
where s
is the name of the struct
and fs
stands for a possibly empty sequence of symbols representing the
field names. The semantics is to extend the current dynamic environment with
the following mappings:s
maps to a function defined as if by the binding (define (s xs) StructConstructor("s", xs))
s?
maps to a function defined as if by the binding (define (s? x) StructPredicate("s", x))
f
in fs
, s-f
maps to a function defined as if by the
binding (define (s-f x) StructAccess(s, i, x))
where i
is the index of
the field f
in the sequence fs
.StructConstructor
, StructPredicate
and
StructAccess
. We have used a semi-concrete syntax to describe what AST the
interpreter should construct for these function bodies.)We change the semantics of the =
operator so that it works on all values.
(= e1 e2)
, where e1
and e2
stand for any
expressions. The semantics is to evaluate e1
to a value in the current
dynamic environment. Call that value v1
. Then evaluate e2
to a value in
the current dynamic environment. Call that value v2
. The result is a boolean
literal indicating whether v1
and v2
are structurally equal.We change the semantics of function calls to support calling arbitrary expressions:
(ecall args)
where ecall
stands for any expression,
and args
stands for any sequence (possibly empty) of
expressions to be passed as arguments to the function. Call the current dynamic
environment callenv
. The semantics is to first evaluate ecall
in callenv
.
Call the resulting value vcall
. If vcall
is anything besides a closure,
signal an error. Otherwise, let f_opt
be the optional function name, params
be the
parameter names, body
be the body, and defenv
be the defining environment. (All of
this comes out of the closure.) 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, if f_opt
is of the form Some(f)
,
also with another mapping from f
to vcall
(the whole closure)
(this implements recursion). Return the result of evaluating
body
.We also extend the semantics for the new kinds of user-facing expressions.
(match e clauses)
where e
stands for any
expression and clauses
stands for any sequence (possibly empty) of match
clauses. Each match clause is of the form (pi bi)
where pi
is a
pattern and bi
is an expression. (Note how this is different from cond
clauses, where both are expressions!!) The semantics is to first evaluate e
in the current dynamic environment to a value v
. Then iterate over the
clauses in order. For each clause (pi bi)
, ask pi
whether it matches v
.
If pi
answers "no", continue the loop. If pi
answers "yes, and B
", then
stop the loop and evaluate bi
in the current dynamic environment
extended by all the bindings in B
. Return the value the bi
evaluates
to. If the loop gets to the end of the sequence of clauses without finding any
pi
that matches v
, then signal an error.(lambda (params) body)
where params
stands for
any list of symbols and body
stands for any expression. The semantics
is to return a closure expression with None
name, params
as the parameter names,
body
as the body, and the current dynamic environment as the defining environment.(print e)
where e
stands for any expression.
The semantics is to evaluate e
to a value v
and then print v
in an
implementation-defined format. (In other words, the specification does not say exactly how
to print v
, it's up to you.)Even though the three "internal" struct expressions cannot be written directly by the Trefoil programmer, they can be created by the new kind of function call as well as the struct accessor and struct predicate functions introduced by struct bindings, so we need to give semantics to these "internal" expressions as well:
StructConstructor(s, es)
, where s
stands for any string and es
stand for any list of expression ASTs. Evaluate
the expressions in es
in the current dynamic environment to a sequence of
values vs
. Return StructConstructor(s, vs)
.StructPredicate(s, e)
where s
stands for any string and e
stands for any expression AST. Evaluate e
in
the current dynamic environment to a value v
. If v
is of the form
StructConstructor(s', vs)
and s
and s'
are equal as strings, return
true
. In all other cases, return false
.StructAccess(s, i, e)
where s
stands
for any string, i
stands for any (OCaml) integer, and e
stands for any
expression AST. Evaluate e
in the current dynamic environment to a value
v
. If v
is of the form StructConstructor(s', vs)
and s
and s'
are
equal as strings and i
is less than the length of vs
, then return the
i
th element of vs
. In all other cases, signal an error.Finally, a closure evaluates to itself. (Note that programmers cannot write closures directly.)
Structural equality is defined recursively as follows. Comparing two values for structural equality can return true/false or signal a runtime error.
Let v1
and v2
be two values. We say that v1
is structurally equal to
v2
if:
v1
and v2
are both integer literals and they are equal as integersv1
and v2
are both true
v1
and v2
are both false
v1
and v2
are both nil
v1
and v2
are both Trefoil-symbol values, and the symbols are equal as stringsv1
and v2
are both cons
values, say v1
is (cons v11 v12)
and v2
is
(cons v21 v22)
, and v11
is structurally equal to v21
and v12
is
structurally equal to v22
.cons
expression to be a
value, v11
, v12
, v21
, and v22
are all guaranteed to be values.)v1
and v2
are both struct constructor values, say v1
is
StructConstructor(s1, vs1)
and v2
is StructConstructor(s2, vs2)
, and
s1
and s2
are equal as strings, and vs1
and vs2
have the same length
as lists, and each element of vs1
is structurally equal to the corresponding
element of vs2
.vs1
and vs2
are values.)v1
or v2
are closures, signal a runtime error.The semantics of a pattern takes a value v
as an argument and returns one of
the following:
v
B
" (where B
is a dynamic environment), indicating that the
pattern matches v
and introduces bindings B
Given v
, we describe the semantics of each kind of pattern.
[]
" (the
empty dynamic environment)x
always answers "yes and [(x -> v)]
" (the dynamic
environment that maps x
to v
and has no other mappings)n
, where n
stands for any integer, examines v
:v
is an integer literal with same value as n
, answer "yes and []
"b
, where b
stands for any boolean, examines v
:v
is a boolean literal with the same value as b
, answer "yes and []
"v
:v
is a nil literal, answer "yes and []
"s
, where s
stands for any symbol-string, examines v
:v
is a Trefoil-symbol literal with the same value as s
, answer "yes and []
"(cons p1 p2)
, where p1
and p2
stand for any patterns, examines v
:v
is a cons expression, it can be written as (cons v1 v2)
where v1
and v2
are values. Ask p1
if it matches v1
B1
", ask p2
if it matches v2
B2
", answer "yes and B1 @ B2
", where @
denotes
appending the two dynamic environments together, as in OCamlv
is not a cons
expression, answer "no".(s ps)
, where s
stands for any struct name PST-symbol and
ps
stands for a (possibly empty) sequence of patterns, examines v
v
is a struct constructor expression, it can be written StructConstructor(s', vs)
,
where s'
is a struct name PST-symbol and vs
is a sequence of values.s
and s'
are not equal as strings, answer "no".ps
and vs
are sequences of different lengths, answer "no".ps
and vs
considering the i-th element of
each sequence. Call these elements pi
and vi
respectively. At each
iteration of the loop, ask pi
if it matches vi
.Bi
", continue the loop.pi
match vi
.
Collect all the Bi
from each iteration of the loop and concatenate them
all together. Call this concatenated environment B
. Answer "yes and B
".v
is not a struct constructor expression, answer "no".