CSC 530 Assignment 2

CSC 530 Assignment 2 --
Adding Type Checking to the
Basic Lisp Interpreter of Assignment 1



ISSUED: Wednesday, 17 April 2002
DUE: Wednesday, 1 May 2002

General Requirements

To the Lisp interpreter of Assignment 1, add interpretation of the following new functions and data:

(deftype name type)
where type is
(array type [bounds])
where the optional bounds is either an integer, or (integer integer) pair, or a type var
(record fields)
where fields is a list of (name type) pairs in which all names must be unique; fields may be a single type var
(union fields)
where fields is a list of (name type) pairs in which all names and types must be unique; fields may be a single type var
(function args [outs] [suchthat])
where args and outs are lists of names or (name type) pairs; names and (name type) pairs can be mixed in a signature; the name in a (name type) pair may be empty; args and/or outs may each be a single type var; suchthat is of the form (suchthat predicate), and the predicate can reference type vars

To provide computation on values of the above types, add interpretation of the following functions on arrays, records, unions, and general types:

(index ((a (array ?x ?y)) (i int)) ((?y)))
(setelem ((a (array ?x ?y)) (i int) (val ?z)) ((array ?x ?y)))

(dot ((r (record ?x)) (f sym)) ((?y))
    (suchthat (field-type-of ?y (record ?x))))

(setfield ((r (record ?x)) (f sym) (val ?y)) ((record ?x)))

(isa ((u (union ?x)) (f sym)) (bool))
(dot ((u (union ?x)) (f sym)) ((?y)))

(typeof ((x ?x)) ((t ?t)))
(equiv ((t1 ?t1) (t2 ?t2)) (bool))
where field-type-of is defined as
(defun field-type-of ((field-type ?ft) (record-type ?rt)) ((bool))
    (cond ((null record-type) nil)
          ((equal field-type (cadar record-type)) t)
          (t field-type-of (cons 'record (cddr record-type)))))

To support definitions of typed functions and variables, update the definition of xdefun, and add a definition for xdefvar of the following forms:

(xdefun name args outs [suchthat] body)
where args, outs, and suchthat are as defined above for the function type


(xdefvar name type [value])
where type is as defined above and the optional value is the initial value assigned to the variable


In addition to typed parameters, the revised version of xdefun should support overloaded function definitions. Specifically, two or more functions of the same name can be declared with xdefun, as long as the types of function input arguments differ in at least one position. 1

Literal values for each of the types specified above are denoted as follows:

Type Literal Denotation
sym any quoted atom
int any atom for which integerp is true
real any atom for which numberp is true and integerp is false
string any atom for which stringp is true
bool t or nil
array a list, the elements of which meet the array's bounds and type specs
record a list, the elements of which meet the record's field specs
union a value whose type is one of field types
function the name of a defun'd function or a lambda expression whose signature matches the function type's args and outs specs

Discussion

The functions xdeftype, xdefvar, and xdefun are the three major forms of declarations found in most modern programming languages. They define, respectively, types, variables, and functions. The result of evaluating each of these functions is to create new bindings (on the alist) for the identifier being declared.

The four forms 2 array, record, union, and function are interpreted not as functions but as raw data data. Hence, these forms are only valid in declaration contexts where a type is valid. These construction forms provide the four most commonly available types of data construction found in modern programming languages.

The eight new type-specific functions (starting with index above) provide functionality to access and manipulate typed data. Here are the specs for these functions:

Function Specs
index Return the element at position i in the given array a, starting from position 0. Return nil if i is out of bounds.
setelem Return an updated version of the given array a, with the value at position i changed to the given value val. Return a unchanged if i is out of bounds.
dot Return the value of the field named f in the given record r. Return nil of there is no field of that name in r.
setfield Return an updated version of the given record r, with the value of the field named f changed to the given value val. Return r unchanged if there is no field of the given name.
isa Return t if a field of the given name f is in the given union u. Return nil otherwise.
dot Return the value of the field named f in the given union u. Return nil of there is no field of that name in u or the if the current value of u is not of the declared type of the field named f.
typeof Return the type t the given expression x. The type should be returned as a list datum, of the same form used to declare a type using deftype.
equiv Return t if the given types t1 and t2 are equivalent (per the rules defined below) nil otherwise.

Note that the two manipulation functions, setelem and setfield, are non-destructive.

Requirements for Type Checking

The existing typeless version of xeval from Assignment 1 should be retained, and enhanced to support the above new functions. This version of the interpreter will remain typeless, in that it will execute all the new functions defined above, but it will not perform any type checking.

To perform type checking, define two new interpretation functions named xcheck and xcheck+xeval. The xcheck function performs static type checking of an xLisp program by ensuring that all bindings to typed identifiers are type correct. In our simplified xLisp interpreter, bindings that must be type checked occur in two contexts:

  1. xsetq
  2. calls to any typed function

where a typed function is any function in which one or more arguments are typed. Specifically, these are any functions declared with a typed version of xdefun plus the eight type access and manipulation functions specified above. Note that this means that all existing typeless Lisp functions (e.g., car etc.) are not type checked.

The type checking rule used by xcheck should be structural equivalence. Specifically, two types are equivalent if

The function xcheck+xeval should perform dynamic type checking. That is, it enforces the same type checking rules as does xcheck, except that xcheck+xeval enforces the rules during evaluation. Specifically,

Reporting Type Checking Errors

In most programming languages, the type checker reports errors by referring to particular lines of a program on which the errors occur. In general, Lisp is not a line-oriented language, since programs are frequently typed in directly to the interpreter, or read from a large number of small files. Hence, an error reporting convention based on function name and expression position makes more sense for Lisp.

Therefore, type checking errors issued by xcheck should specify the name of the function and the ordinal expression position within that function where the error is detected. For example, given the program

(xdefun f ((x int) (y string))
    (xsetq x 10)
    (xsetq y 20)
)
the following error message should be printed
Error in function f, expression 2, calling function xsetq:
    type conflict in argument 2
        expected type: string
        given type: int
Note that within a function, the expression position will only go one level level deep. For example, if an error is detected within an xcond, the expression position will only be reported as the position of the xcond within the function, not at a subexpression position within the xcond. While this form of reporting is not production quality, it will do just fine for us.

If errors are detected in top-level expressions that are not within a function, the error reporting should give the message

Error in top-level expression E ...
where E is a pretty print of the entire offending expression.

Further examples of error message reporting are given in the sample files in the test-files subdirectory for Assignment 2. This is the directory


~gfisher/classes/530/assignments/2/test-files


Footnotes:

1 Note that overloading based on return types only is not required. We will consider this as an enhancement in a future assignment.

2 To be completely consistent with our naming conventions, we might have prefixed the names of each of these functions with "x". However, since none of these functions overlaps with existing Lisp functions we care about, we leave the "x" off.