Assignments 5 and 6: Functional Programming (an interpreter)
In Assignments 5 and 6 of this class, we’ll “prototype” a functional programming language that is similar to ML (minus static type inference). Our prototype will be a simple interpreter like the one you wrote for the calculator in the previous exercise. We’ll start out with a small base language that supports arithmetic expressions, conditionals, functions, and let expressions. Then we’ll grow the language to support more advanced features like tuples, data structures, pattern matching, and list comprehensions.
Assignment 5: The Base Language Due Thursday, February 13th, at 11:59pm
Turn in your interp.js (and classes.js, if needed) through Canvas.

Here’s what the concrete syntax of the base language looks like, and how we’ll represent it as abstract syntax in JavaScript:
Concrete Syntax JS AST
e ::=
primValue
e1 op e2
if e1 then e2 else e3
x
let x = e1 in e2 p
fun x -> e p
ef ea
new Lit(primValue)
op ∈
{+, -, *, /, %, =, !=, <, >, &&, ||}
Note: the = and != operators in our language have the same semantics as JavaScript’s === and !== operators, respectively.
new BinOp(op, e1, e2)
new If(e1, e2, e3)
new Var(x)
new Let(new Var(x)p, e1, e2)
new Fun(new Var(x)p, e)
new Call(ef, ea)
x ::= an identifier starting with a lowercase letter a JavaScript string
primValue ::= a JavaScript number or boolean literal
The classes that we use to represent AST nodes (Lit, BinOp, If, etc.) are declared in this file.

As in the calculator exercise, we have already written a parser Our parser is generated from the Ohm grammar in this page, and the set of semantic actions that produce ASTs is here. for this language. Your job is to write the interpreter, which will be a function called interp that takes an AST of an expression and returns the value that it produces: function interp(ast) { // do your thing! } The language of values is defined below:
v ::=
primValue
new Closure(new Fun()fun, env)
Please do all your work in the files called interp.js and classes.js.

Unit Tests Unit tests are not just for Fortune-500 companies, you know! In fact, it’s a great idea to write unit tests when you’re prototyping or implementing a programming language. Unit tests give you the freedom to change your mind about the implementation strategy (e.g., make dramatic changes to the current prototype, or even start again from scratch) without the risk that you will unknowingly break something that used to work. They will save you a huge amount of time and effort in the long run, guaranteed.

We have included some unit tests for your prototype below. These will run automatically every time you refresh this page. Please note that these tests are not meant to be comprehensive, they’re just a few examples to give you a better understanding of the semantics of the language. (We expect you will still need more information on the semantics of the language, and may want to know the motivation behind some of our design decisions. Feel free to ask us in class or on Piazza.)

We strongly recommend that you add your own unit tests — feel free to share them with other students on Piazza, too! Adding tests is easy: all you have to do is edit asst5-tests.js, the format is pretty self-explanatory. (The tests for Assignment 6 are in asst6-tests.js.)

Assignment 6: Extensions

Due Thursday, February 20th, at 11:59pm
Turn in your interp.js (and classes.js, if needed) through Canvas.

Next, we’ll make several extensions to the language:

  1. Add support for let rec so that you can write recursive functions.
  2. Add support for tuples, e.g., (1, 2, 3 + 4) should evaluate to (1, 2, 7).
  3. Now augment your interpreter so that data constructors, which are simply identifiers that start with an uppercase letter, and can be used to build user-defined data structures. For example, the value Cons (1, Cons (2, Nil)) uses the data constructors Cons and Nil to represent the list [1; 2].
  4. Add a match expression to support ML-style pattern-matching on primitives and data structures.
  5. Add support for destructuring let and fun, i.e., extend your implementations of let and fun to accept any pattern (not just an identifier) on the left hand side. For example, the expression let (x, y) = (1, 2 + 3) in x + y should evaluate to 6, and (fun (Point (x, y)) -> x - y) (Point (3, 4)) should evaluate to -1. In the case of a match failure, let and fun should throw an exception.
  6. Add Haskell-style list comprehensions.
  7. Add Scheme-style delay and force operations.

Here’s how your language will grow to accommodate these extensions:

Concrete Syntax JS AST
e ::=
let rec x = e1 in e2
(e1,,en)
C e
let p = e1 in e2
match e with
    p1 -> e1
  |
  | pn -> en
[e | p <- elist [, epred]]
delay e
force e
new LetRec(x, e1, e2)
new Tuple([e1,,en])
new Datum(C, e)
new Let(p, e1, e2)
new Match( e, [p1,, pn], [e1,, en])
new ListComp(e, p, elist [, epred])
new Delay(e)
new Force(e)
p ::=
Matches any value that is === to the pattern. primValue ()
Matches any value. _ ()
Matches any value, binds it to x. x ()
Matches a tuple of the specified form. (p1,, pn)
Matches a data value of the specified form. C p
new Lit(primValue)
new Wildcard()
new Var(x)
new Tuple([p1,, pn])
new Datum(C, p)
C ::= an identifier starting with an uppercase letter a JavaScript string
v ::=
new Tuple([v1,, vn])
new Datum(C, v)

Playground
Epilogue

What we’ve got at this point is a prototype of a functional language that is very similar to ML. So go ahead and give yourself a good pat on the back, you deserve it! This kind of naive implementation style, where you always favor simplicity over performance, is very useful to researchers and language designers because (i) it enables you to get something working quickly, and with minimal effort — something to think with! — and (ii) the simplicity of the prototype makes it easy for you to experiment with changes and extensions to the semantics of the language.

If you’re interested in pushing this project further, here are a few things you might like to try: