Due: Thursday, December 7, 11:59pm Pacific Time
This homework is the last step in our Trefoil journey as a whole class. (You may choose to keep going in the final project or beyond!) You will implement an interpreter for version 4 of Trefoil, which extends version 3 to support structs, pattern matching, and first-class functions.
You should start by making a copy of your hw6 solution and then making the following changes:
trefoil3.ml
to trefoil4.ml
.open Trefoil3lib
open Trefoil4lib
trefoil3test.ml
to trefoil4test.ml
open Trefoil3lib
open Trefoil4lib
src/dune
file with the updated file
from the HW7 starter
code.newtests.ml
file into your trefoil4test.ml
file. These
are additional provided tests for the new features.ast-starter.ml
into the top of your
ast.ml
file, just after the open
line.interpreter-starter.ml
into your
interpreter.ml
file, above interpret_expression
.dynamic_env
and env_entry
to ast.ml
, as you will need them
as part of implementing closures/first-class functions. You will need to use OCaml's and
syntax
to make the definition of the dynamic_env
simultaneous with expr
.The Trefoil v4 language is an extension of Trefoil v3. We only describe the differences.
There is one new kind of binding, the struct
binding, which declares a new
struct type. For example, the binding (struct my-record name age)
declares a
struct called my-record
with fields name
and age
. We have discussed this
binding extensively in lecture. In short, it generates a constructor, a
predicate (question-mark) function, and field accessor functions for each field.
See the Trefoil v4 language spec for a formal
description.
There are several new kinds of expressions:
'
.match
expression for pattern matching as discussed in lecture.lambda
expressions for anonymous functionsprint
expressions which evaluate a trefoil expression and then print itThere are also three new "internal" forms of expression, which you will use to implement struct bindings. By "internal", we mean that there is no way for the Trefoil programmer to write these expressions directly in their code. Instead, when the Trefoil programmer writes a struct binding, your interpreter will autogenerate some functions that use these internal expressions in their bodies. Then the Trefoil programmer can call those autogenerated functions to indirectly use these new expressions. Here they are:
There is one additional internal expression for closures, which store a function definition and its defining environment as a value.
We also make one change to the Trefoil v3 semantics of expressions:
=
operator now works on all values except for closures, not just integers.Patterns are a new syntactic category in Trefoil v4 that represent "the left hand side of a match clause", similar to patterns in OCaml that you are used to using.
There are 8 kinds of patterns, which we have discussed informally in lecture, and which are discussed in detail in the spec.
Extend your previous homework solution to implement the changes described in the Trefoil v4 language spec. Write tests that cover all the normal and error case behavior for all new language features.
We highly recommend keeping your tests from previous homeworks around to help you find bugs. You do not need to resubmit tests that you submitted for HW6, but you can if you want to. Note that some tests may require adjusting.
You can do anything in whatever order you want, but here is one suggested path.
...
| Symbol of string
expr_of_pst
case for symbols. You may find the following pseudocode
useful, but note that some things may change depending on your implementation.
if String.get sym 0 = '\''
then Symbol (String.sub sym 1 (String.length sym - 1))
else Variable sym
interpret_expression
in interpreter.ml
.expr_of_pst
to parse the new function call syntax correctly.FunctionEntry
and refactor all uses of it to use Closure
instead,
as discussed in lecture 23 (11/29/23).=
=
.=
. Be sure to test the behavior when comparing closures, and when comparing closures
nested inside other data structures such as cons
(both these cases signal an error).binding
node for struct bindings.binding_of_pst
.interpret_binding
as follows:StructConstructor
AST node in expr
following the spec.StructValue
in their programs, there is no need
to parse it.StructConstructor
in interpret_expression
following the spec.interpret_binding
case for struct bindings, generate a function (closure)
that calls StructConstructor
. Be sure to pass the right number of arguments.StructConsturctor
. So, you cannot use the parser to generate this
binding, and you must instead construct it manually using the AST
constructors for function bindings, StructConstructor
, and variables.x1
, x2
, ..., xN
, where N
is the
number of fields in the struct. Note that N
will be different for different structs.StructPredicate
in the expr
type. Again, no need to parse it.StructPredicate
in interpret_expression
following the spec.interpret_binding
, generate a
function binding for the struct's predicate function following the spec.StructAccess
in the expr
type. No need to parse it.StructAccess
in interpret_expression
.interpret_binding
, generate
function bindings to access each of the structs fields.cond
from previous homeworks.ast-starter.ml
implements an AST for patterns. We
have provided AST nodes for wildcard patterns (_
) and cons patterns. There
is also a parsing function as usual called pattern_of_pst
, which we have
implemented for the two provided patterns (plus given you some hints about
other cases).expr
type.Cond
, except that match
takes a "scrutinee" expression as its first argument, in addition to a
list of clauses. Also, a cond clause is two expressions, but a match
expression is a pattern and an expression.expr_of_pst
to parse match expressions.cond
, with the differences noted
above.pattern_of_pst
somewhere.interpret_pattern
in the starter
code. We have implemented the semantics of wildcard patterns and cons patterns
for you.interpret_expression
.cond
, with the differences
noted above. After evaluating the scrutinee to a value, loop through
the clauses and
check if the clause pattern matches the scrutinee's value by calling
interpret_pattern
. If yes, be sure to evaluate the right hand side
of the clause in an appropriately extended environment.pattern
type in ast.ml
.nil
, etc.) are syntactically valid as both expressions
and patterns. Don't get confused though -- they have different
semantics depending on whether they are an expression or pattern!pattern_of_pst
.int_of_string_opt
for you. All you have to do is
pass the result to your constructor.B
".
In the code, we implement this using an option
type, specifically
dynamic_env option
None
in the code, and "yes and B
" corresponds
to Some B
in the code, where B
is implemented as a list of string * value
,
or in other words, a dynamic environment.interpret_pattern
.interpret_expression
at all, only
interpret_pattern
.pattern
type in ast.ml
.pattern_of_pst
.interpret_pattern
in interpreter.ml
.pattern
type in ast.ml
.pattern_of_pst
.interpret_pattern
in interpreter.ml
.pattern
type in ast.ml
.pattern_of_pst
.interpret_pattern
in interpreter.ml
.pattern
type in ast.ml
.pattern_of_pst
.interpret_pattern
in interpreter.ml
.pattern
type in ast.ml
.pattern_of_pst
.interpret_pattern
in interpreter.ml
.sum_with_match_error
" passes.ast.ml
, write a function vars_of_pattern
that takes a pattern
and returns a list of strings containing all the variable names that
appear anywhere in the pattern.expr_of_pst
in the case for match
expressions, first parse the
pattern, then check that vars_of_pattern
applied to the parsed
pattern returns a list with no duplicates.Submit these files to Gradescope:
ast.ml
interpreter.ml
trefoil4test.ml