Assignment 7, CSC430, Spring 2014
1 Goal
2 Guidelines
2.1 Contracts & Test Cases
2.2 Handling errors
2.3 Language Level & File Format
3 The Assignment
4 Syntax of BOZOR7
4.1 Strings
4.2 Recursion
4.3 Type Checking
4.3.1 Binops
5 Suggested Implementation Strategy
6 Interface
6.0.1.6

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:

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

For this assignment, you must develop your solutions using the
#lang plai-typed
language level. Your test cases must use the (test ...) or the (test/exn ...) forms.

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:

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
Parses an expression.

procedure

(type-check e env)  Ty

  e : ExprC
  env : TEnv
Type-check an expression.

value

empty-tenv : TEnv

The empty type environment.

Use *either* of the following two interfaces for interp:

procedure

(interp e env sto)  (A*S Value)

  e : ExprC
  env : Environment
  sto : Store
Interprets an expression, with a given environment and store.

procedure

(interp e env)  (Computation Value)

  e : ExprC
  env : Environment
Interprets an expression, with a given environment and (hidden) store.

procedure

(top-eval s)  string

  s : s-expression
Combines parsing, type-checking, evaluation, and serialization. Here’s the code you should probably use for this function if you’re not writing in a monadic form:

(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.