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-worldmatch 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 falsenilcons 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
ith 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 truev1 and v2 are both falsev1 and v2 are both nilv1 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:
vB" (where B is a dynamic environment), indicating that the
pattern matches v and introduces bindings BGiven 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 v1B1", ask p2 if it matches v2B2", 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 vv 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".