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 trueThe 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.