Assignment 7, CSC430, Spring 2014
1 Goal
Extend the Assignment 4 evaluator (no classes) to include a type system.
2 Guidelines
2.1 Contracts & Test Cases
For this and all remaining assignments, every function you develop must come with the following things:
A commented header line that expresses the result of the function in terms of its inputs, written in English. Be as precise as you can within the space of a line or two. The header lines must include input and output types.
Test cases. A function without test cases is incomplete. Write the test cases first, please.
2.2 Handling errors
Your parser and interpreter must detect errors and explicitly signal them by calling (error ...). We will consider an error raised internally by Scheme to be a bug in your code.
For example, Scheme signals a "divide by zero" error if you attempt to evaluate (/ 1 0). Since we use Scheme’s division function to implement division in CFWAE, you may be tempted to leave it to Scheme to signal division by zero errors for you. However, you must signal the error yourself by explicitly testing for division by zero before calling Scheme’s division procedure.
2.3 Language Level & File Format
#lang plai-typed
Your solution should take the form of a single file. Solve each problem separately, and make sure that each solution appears in a separate part of the file, with comments separating each problem’s solution.
Hand in your solution using the handin server. For help with the handin server, please see the course web page.
3 The Assignment
This assignment will build on Assignment 4, with a few features from Assignment 5. As always, you are welcome to use my solution to the previous assignment as the basis for your work.
4 Syntax of BOZOR7
A BOZOR7 program consists of a single expression.
The concrete syntax of the BOZOR7 expressions with these additional features can be captured with the following EBNFs.
BOZOR7 | = | num | ||
| | true | |||
| | false | |||
| | string | |||
| | id | |||
| | {array BOZOR7 BOZOR7} | |||
| | {ref BOZOR7[BOZOR7]} | |||
| | {BOZOR7[BOZOR7] <- BOZOR7} | |||
| | {id <- BOZOR7} | |||
| | {begin BOZOR7 BOZOR7 ...} | |||
| | {if BOZOR7 BOZOR7 BOZOR7} | |||
| | {with {[id : Ty] = BOZOR7} ... BOZOR7} | |||
| | {fn {[id : Ty] ...} BOZOR7} | |||
| | {operator BOZOR7 BOZOR7} | |||
| | {rec [id : Ty = BOZOR7] BOZOR7} | |||
| | {BOZOR7 BOZOR7 ...} |
Ty | = | Num | ||
| | Bool | |||
| | String | |||
| | {Ty ... -> Ty} | |||
| | [Ty] |
operator | = | + | ||
| | - | |||
| | * | |||
| | / | |||
| | eq? | |||
| | <= |
... where an id is not true, false, with, if, fn, array, set, =, begin, rec, or one of the operator names.
4.1 Strings
You must add strings to the language, as you did for Assignment 5. In fact, I strongly suggest that you simply copy your implementation of strings from Assignment 5.
Here’s the specification from Assignment 5, in case it’s useful:
Add strings to the language. This will require a new form of AST for string literals, a new form of string value, and a rule that evaluates string literals into string values.
In addition, you will need to extend the eq? function to work correctly on strings. Specifically, eq? must return true for two strings containing the same sequence of characters.
4.2 Recursion
Add recursion, using a rec form.
This one is not like Assignment 5. In particular, the desugaring strategy we used for Assignment 5 won’t play nicely with type-checking.
Instead, add a new expression form, and a new evaluation rule. The evaluation rule should follow these steps:
Allocate a new location.
Place a dummy value in that location (you could use the string "UNDEFINED").
Bind the given name to the new location to make a new environment.
Use the new environment to evaluate the right-hand-side expression in the rec.
Put the new value into the new location.
Use the new environment to evaluate the body expression
Here’s a simple example of a use of rec :
{rec [loop : {Num -> Num} = {fn {[n : Num]} {if {<= 0 n} {loop {- n 1}} 34}}] {loop 9}}
This will count from 9 down to 0, and then return 34.
4.3 Type Checking
Implement a type checker for your language. Note that since functions must now come annotated with types for arguments, you will need to have a type parser that parses types. For Heaven’s sake, don’t make this part of your expression parser. Note that the types of functions are extended to handle multiple arguments. So, for instance, the type {Num String -> Bool} refers to a function that accepts two arguments, a number and a string, and returns a boolean.
All type rules are standard. The rules for type-checking arrays should follow directly from the rules that we gave in class.
As in the book, arrays will be homogeneous; you cannot put a different type of thing into an array. In a similar way, you cannot mutate a variable to contain a different type.
4.3.1 Binops
Type-checking binops is more or less as you might expect. For instance, a + should receive two numbers, and will produce a number. The <= operator will take two numbers, and return a boolean.
The eq? operator is a bit different. In order to match its behavior in earlier assignments, the eq? operator should allow arguments of any type at all, and produce a boolean.
5 Suggested Implementation Strategy
Copy your string implementation from A5.
Copy your ’rec’ test cases from A5.
Add ’rec’ as an actual language form.
(Add ’rec’ to the set of keywords.)
Add a data definition for Types.
Add a parser for the language of types.
(Add ’->’ to the set of keywords.)
Add a definition for Type Environments
Add bogus type parameters to funs.
Add a type checker. Write your test cases first!
Add your type checker to top-eval. Type checking should happen after parsing and before evaluation.
6 Interface
Make sure that you include the following functions, and that they match this interface:
procedure
(parse s) → ExprC
s : s-expression
procedure
(type-check e env) → Ty
e : ExprC env : TEnv
value
empty-tenv : TEnv
Use *either* of the following two interfaces for interp:
procedure
(interp e env sto) → (A*S Value)
e : ExprC env : Environment sto : Store
procedure
(interp e env) → (Computation Value)
e : ExprC env : Environment
procedure
(top-eval s) → string
s : s-expression
(define (top-eval [classes : (listof s-expression)] [body : s-expression]) : string (serialize (a*s-v (interp (parse-prog classes s) empty-env empty-store))))
Here’s the code you might want to use if you are writing in a monadic form:
(define (top-eval [body : s-expression]) : string (run/val (do [v <- (interp (parse-prog classes s) empty-env)] (lift (serialize v)))))
Your body can be different if you really want, but the types must match these.