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.mlopen 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.mlinterpreter.mltrefoil4test.ml