Assignment 3
Table of Contents
This assignment due (roughly) Thursday 9/25 by 8:00 PM. You'll need to submit
a .zip
file containing a dune project named salt1
on Gradescope.
You can find the starter code at the newly minted salt392
repository. Please make sure to run dune clean
before you submit,
i.e., don't submit the _build
directory.
In this assignment you'll be implementing the subset of Rust that we discussed in lecture. It would be a bit too much at the moment to implement this in Rust, so we're going to use OCaml.
As we discuss endlessly in CS320
, there three things we need to
implement for an interpreter: a parser, a type checker and an
evaluator.
Parser
For this course, we're not going to worry about parsers, the interpreter comes with a parser. But this has a couple caveats:
- it's more convenient to build a single parser and use it for every interpreter we'll use. This means our parser──and in particular our AST──has a lot of stuff we won't be working with initially.
Since we're using a single parser, we might as well trick it out with nice error messages. To do this, our AST carries metadata about the location of subexpressions in the source program (this is how we know where the error is when we display the message). This isn't terribly complex, but it does affect the way we work with ASTs slightly: we cannot pattern match on an AST directly, we have to pattern match on its associated data, which we get by record access (ASTs are represented as records). This is why──in
saltO
, for example──you'll see things like:match expr.expr with | ... | _ -> Error (not_implemented expr.meta)
expr.expr
is the "actual" AST whereasexpr.meta
is the location information forexpr
.
Type Checker
We know what to do here. I've given you some typing rules, you need to implement them.
Just one thing: in order to simplify things, we'll always work
within ('a, Error_msg.t) result
to deal with type errors.1 The module Error_msg
deals with error messages.
For the most part, you don't need to know what's going on here (though
I recommend looking if you're interested). The only important
function is Error_msg.mk
, which takes the location metadata of an
AST and a string for the message itself. To make things
easy on you, I'll always include a collection of smart constructors
in a module called Errors
for the kinds of errors you will encounter
in writing the type checker. When you're writing an error branch in
the type checker, you should use Error e
, where e
uses a smart
constructor. For example, from salt0
:
match e.expr with | PlaceExpr (Var x) -> ( match Map.find_opt x ctxt with | None -> Error (unknown_var e.meta x) | Some ty -> Ok ty ) | ...
unknown_var
is a smart constructor which takes a variable name and
the location information for that variable. For example, running the
interpreter on the program:
fn main() { let x = 2; let z = x + y; }
will display the error message:
error: unknown variable `y` --> lib/example.rs:3:17 | 3 | let z = x + y; | ^ unknown variable `y`
Again, there are quite a few parts of the AST that we're not
using. You should use the not_implemented
smart constructor when you
come across something we haven't talked about.
There is one notable tricky case for types: BorrowTy
.
- For now, always assume that a reference is
Immutable
. - Eventually, we will need references to keep track of multiple place
expressions, so the constructor
BorrowTy
keeps track of a list of place expressions. For now, always assume that the list is a singleton. BorrowTy
also keeps track of the underlying type associated the reference. This is does so that it's possible to pretty-print the borrow type as it would be printed by Rust (e.g.,&i32
and not&x
).
Evaluator
You know the evaluation rules, you need to build the evaluator. And
there's no need to deal with errors; because of type soundness, if your
program type checks, evaluation is guaranteed to succeed (which
means assert false
will be used quite a bit).
Closing Remarks
There's a lot I'm probably forgetting to mention here. This is very
much an experiment, y'all are helping me stress test these
assignments. Please ask lots of questions, and let me know if you
have any thoughts about improvements. Also, if you want a hard mode
for this assignment, I'll just say that there's a couple parts of the
language that are "fair game" in the sense that they don't require new
concepts, but are not implemented in the base salt1
. These include:
- Booleans
- Assert statements
- Arithmetic/Boolean operators
Generally speaking, explore the AST, see if there's anything you want to add. If you're really feeling up to it, extend the parser(!)
Footnotes:
This allows us to have a uniform error-handling approach for the parser and the type checker.