1 Guidelines
2 Assignment
3 NO PARSING
4 How To Develop This Program
5 Values
6 Language Forms
6.1 A note on mutation of environments
6.2 Where is mutation required?
7 Primitive Functions
8 Incomplete Specification
9 Hints
10 An Example
11 Requirements
12 Handin Instructions

Assignment 1, CSC431, Winter 2010

1 Guidelines

You and your partner must choose one of the supported class languages. Currently, that includes PLT Scheme, F#, and Scala. Let me know if you can get together a critical mass (about ten students, including one maintainer) to support another language.

2 Assignment

You must implement an interpreter for the language that you’ll later be writing a compiler for. Let’s call this language Footle. I don’t know why I picked that name. Your solution will map Footle programs to answers, as described below.

3 NO PARSING

Your assignment does not need to include a parser.

More specifically, your assignment must map programs to answers, but you must decide on (and document) your representation for the input programs (that is, your Abstract Syntax Trees, or ASTs). If you design your representation carefully, it will also make a good target for your parser.

4 How To Develop This Program

If this process sounds strange to you, follow these steps.

First, examine the specification of the inputs and outputs (that is, the Footle expressions and values) below.

Second, decide how you’re going to represent each one. The representation you choose should make it easy to access the sub-elements of each value; those would likely be classes in Scala, datatypes in F#, structs or define-types in PLT Scheme, etc.

In a statically typed language (e.g. F# or Scala or typed scheme) you’ll also need to explicitly formulate a representation for the union of all these forms. That is: what type do you give to an arbitrary value? Again, nearly every statically typed language has a means of doing this. Ocaml (and, by extension, presumably F#) has datatypes, Scala has "case class" definitions, etc.

In a dynamically typed language (e.g. PLT Scheme), you don’t need to tell the compiler about your datatypes, but you do need to tell those reading your code. You must therefore provide a comment in the form of a data definition that conveys the same information.

Third, figure out what the inputs and outputs of the interpreter are going to be. For instance, you’ll probably want to add an environment as an additional input. In my interpreter, I added another argument to hold the ’return’ jump target, but that may depend on how you implement ’return’ (more below).

Fourth, write some test cases; when the interpreter is called with this, it should produce that, etc. I cannot stress enough the importance of specifying these test cases before writing the program, and of starting with very simple test cases. It’s the simplest test cases that are going to show you how to implement the corresponding language forms; the more complex test cases help to isolate more subtle issues. If you start with a giant hairy test case, you’re going to spend hours debugging simple problems.

Finally, if you do discover a problem with a complex test case, don’t just dive into the debugger; debuggers are the fool’s first resort, and you can spend hours just watching the values bounce around. Instead:

The "thinking" part is generally the hardest.

5 Values

The values are the possible results of interpreting an expression. They include the following:

A closure contains a list of parameters, a body expression, and an environment. If it’s not clear to you what a closure is or why it should contain an environment, ask someone who’s taken CSC 430 recently.

6 Language Forms

In the Footle interpreter, there is no distinction between statements and expressions, or between programs and expressions. Everything is just an expression. This simplifies the interpreter.

The words ’interpret’ and ’evaluate’ are used interchangeably in this text.

The words ’field’ and ’slot’ are used interchangeably in this text.

That’s it! Well, unless we decide to add more things in later assignments.

6.1 A note on mutation of environments

Mutating environments: don’t do it. Your interpreter will wind up buggy. More specifically, make sure that when you extend an environment with new bindings, that references to the old environment are still unchanged, containing all the same bindings. The easiest way to guarantee this is never to mutate a variable that refers to the environment. (Note that mutating the bindings within an environment is not only okay, but in fact required in order to implement variable mutation. I’m just talking about mutating the environment itself.)

6.2 Where is mutation required?

Mutation is required in several places: the value associated with each binding must be mutable, the value associated with each slot must be mutable, and the set of slots associated with an object must be mutable (so you can add fields on the fly).

7 Primitive Functions

You’re required to implement a number of primitive functions. These primitive functions should appear in a base environment used by all calls to the interpreter.

8 Incomplete Specification

I’m sure there are ambiguities, missing primitives, and probably outright errors in this specification. Post to the newsgroup when you discover them.

9 Hints

For Heaven’s sake, develop this project incrementally. For instance, you can develop an interpreter that only handles constants. Then, you can add primitives, and the application of primitives. Don’t try to swallow the elephant in one bite.

10 An Example

It’s hard to provide an example, because the inputs to your function are going to be chosen from your own representations, and therefore specific to your team. However, I can show you an example of the representation that I’m using, and what it’s supposed to produce. With luck, you can read and understand this example.

  (check-equal?
   (test-interp '(letrec ([even? (x) (return (if (zero? x)
                                                 #t
                                                 (odd? (- x 1))))]
                          [odd? (x) (return (if (zero? x)
                                                #f
                                                (even? (- x 1))))])
                   (and (odd? 13)
                        (and (not (even? 13))
                             (even? 14)))))
                true)

11 Requirements

Your solution should consist of several things: a README file, a TEAM file, a go.sh file, and source and test case files.

12 Handin Instructions

You’ll hand in your code simply by creating a branch called Assignment1 in your subdirectory of the private part of the class repository. Whatever’s there when the deadline occurs is your submission.