CS442: Assignment 2

The purpose of this assignment is to implement an interpreter for a simple functional programming language with ML-style let-polymorphism.

The grammar for the language is as follows:

   <exp> ::= <id> 
           | <num>
           | ( <exp> <exp> )
           | lambda <id>.<exp>
           | lazy <id>.<exp>
           | rec <id>.<exp>
           | let <id>=<exp> in <exp> end
The <id> and <num> symbols in the grammar denote identifiers (strings starting with a letter followed by letters and digits) and non-negative integer numerals (strings of digits), respectively. None of the keywords mentioned in the above grammar may be used as an identifier. You may relax the syntax for application to allow "(<exp> <exp> ... <exp>)" to stand for "( ... (<exp> <exp>) ... <exp>)", as long as all the valid strings in the original grammar parse correctly.

The "lambda" and "lazy" abstractions allow forming functions that, when applied, evaluate their arguments eagerly and lazily, respectively. The "rec" abstraction allows forming recursive abstractions, and the "let ... in ... end" allows local polymorphic declarations (let-polymorphism). The types considered are as follows:

   <type> ::= int | bool | ( <type> -> <type> )

To facilitate operations on built-in types (integers and booleans), certain identifiers denote built-in functions as follows:

The completed assignment should read a sequence of semicolon-terminated strings in the above grammar from standard input and, for valid programs, print out the type of the expression and the value resulting from evaluation the expression. The program should terminate when "end-of-file" is detected. For example, the following is a typical interaction with the interpreter (computer responses italicized):

   ((plus 2) 3);
   lambda x.((plus 1) x);
   lambda x.x;
   let mult = rec f.lambda m.lambda n.
              (((if ((eq m) 0)) 0) ((plus n) ((f ((plus m) (neg 1))) n)))
       ((mult 3) 4) 
   let mult = rec f.lambda m.lambda n.
              (((if ((eq m) 0)) 0) ((plus n) ((f ((plus m) (neg 1))) n) ))
   let fact = rec f.lambda n.
              (((if ((eq n) 0)) 1) ((mult n) (f ((plus n) (neg 1))) ))
      (fact 10)
   end end;
   let fib = rec f.lambda n.
              (((if ((eq n) 1)) 1)  
               (((if ((eq n) 2)) 1) 
                ((plus (f ((plus n) (neg 2))))
                       (f ((plus n) (neg 1))))))
      (fib 8)
   (plus ((eq 1) 2));
   Type: cannot unify (int->(int->int))
                 with (bool->'a2)
   lambda x.(x x);
   Type error: cannot unify 'a1
                       with ('a1->'a2)

The deliverable of this assignment is a source code for the complete interpreter, consisting of a lexical analyzer (tokenizer), a parser, a type inference module, and an evaluator. Both the later two modules should also be able to print out the results (i.e., type annotations and values) in a format comparable to the above example.


The completed assignment will be assessed by running tests exercising functionality of the interpreter and its ability to detect incorrect programs (i.e., syntactic and typing errors).


There are several extensions of the assignment that will be considered toward extra bonus marks (listed here in the order of difficulty):
  1. adding products (pairs of arbitrary types; easy),
  2. adding lists (of arbitrary type; easy)
  3. adding variant types (medium)
  4. adding named records (hard)
  5. adding inductive types (datatype; hard)