This is a big specification and there is a lot of code for you to pick through! Don't hesitate to ask for clarification on any part of the project (up to and including requests for better documentation for some part of the provided code).
This package depends on the files from Project 4.
Remember that you may change the provided code so long as you keep the interfaces the same (so that we can use everyone's testers on everyone's assignments). If you invested a lot of time into writing a good algorithm for code generation for the last assignment, you should definitely bring it over for this assignment.
In Oat, identifiers that begin with uppercase letters are types; identifiers beginning with lowercase letters are non-types. In particular, in all of the places you saw an IDENT in the previous assignment, you are now required to use a lower-case identifier.
Programs in Oat have the following top-level structure:
It is now mandatory that all constant definitions be floated to the top of the file. Constants are still non-mutually-recursive; they may also not refer to any classes save for Object. Class declarations, which are covered in detail below, are mutually recursive (modulo some details) with other class declarations and function declarations.
new[len](exp)'s semantics have changed: exp is now evaluated for every location a0..aLEN-1 in the new array (in order). Code for this is provided.
The types from the previous assignment return, along with some new ones:
Note that references (R) have been split between not-null references and nullable references. A regular string may now not take on the value null; however, the nullable string? may do so.
Oat provides a safe way to cast from a nullable type to a regular type. This is demonstrated in the example code above:
Classes must have superclasses, constructors, and unique names:
Classes may also have zero or more methods and zero or more fields. The semicolon after a field definition is mandatory (as with constants) and the parenthesis around methods are mandatory (as with toplevel functions). Methods may be overridden provided that the return type of the derived method is narrower and the argument types of the derived method are wider. A field name, once declared in some ancestor class, may not be re-declared in any child of that ancestor.
Since Oat does not support overloading or static methods, every class must have exactly one constructor (which is declared with the new keyword). This constructor can accept arguments (the first pair of parenthesis) and pass those arguments to a superclass constructor.
At runtime, Oat always checks that notnull members of a new object are initialized correctly when the object's constructor exits.
Oat allows checked downcasts for objects at runtime:
If at runtime the Object d is discovered to have type Dog, adog is set to d's current value and the left-hand branch of the cast is evaluated. If not, the right-hand branch is evaluated (and adog is not bound).
There are several new syntactic forms beyond those that were in the arrproc language. These include:
Aside from some updates to the core record types to support objects, the story of compilation hasn't changed very much since Project 4.
This is a group project. Groups of size 1 or 2 are acceptable.
Complete typecheck as defined in fulltypecheck.mli. There is some code in fulltypecheck.ml to get you started. Part of your grade for this section depends on the quality of the error messages you raise for the user. Simply printing Type error. for badly-formed programs is insufficient. Give more details about what kind of error occurred.
There are three different judgement types
Oat's rules for subtyping (
Function names must be unique and they may not share names with constants. Class names must also be unique. Parent classes must be declared before child classes (so you don't have to check for cycles in the inheritance hierarchy).
We suggest the following multi-pass algorithm for typechecking:
Typechecking constants occurs under the empty binding context.
The signature context contains nothing but constants that have
been declared previously to the constant being checked and
no classes but Object. Constant names should be unique.
Registering a class name requires that you verify that the class name is unique and that the class's superclass exists. Checking the well-formedness of a class signature is a little more involved:
Typechecking functions occurs under the binding context consisting only of that function's argument names. The signature context contains all constants, all functions (including the one being checked), and all classes. This is nearly the same as it was in the last assignment but with the addition of subtyping and a parameterized judgment.
Checking a DeclFunction uses rule
Checking a MemberFunction on a class C uses rule
Checking a MemberConstructor on a class C with parent
P uses rule
Oat's rules for typing expressions depend on a signature context
The following rules are brand new for this assignment.
The following rules differ in behavior depending on the judgment type.
The next group of rules are old rules that have been updated for this assignment.
Fullil defines the record classtbl, which you should fill out with the vtable and runtime type information for each class:
For example, the fully-specified classtbl for the class:
is:
Which, in assembled form, becomes:
An object of type WindupDog will at runtime occupy 3 words. It is first allocated using the newcls runtime service routine, which returns a pointer to the second word. Thus, the field numbers (for FieldDeref, FieldMove and FieldNullCheck) look like:
The Oat calling convention for methods requires that the this pointer be passed on the stack as though it were the first parameter to a cdecl function. The argument is given the name .this so as to prevent possible clashes with user-provided argument names. Method labels should be decorated with the decorate_method_name function.
Calling a method C.f() requires that you look up the index for f in C's vtable at compile time. You can then use this index to emit an InvokeVirtual instruction, which takes care of both the runtime lookup in the vtable and adding the this pointer to the argument list.
Oat allows a child class C to invoke any of its parent class's member functions, even if C overrides one of these functions. For this it provides the super keyword. super.f() requires that you make a regular InvokeCdecl call to f. You should use get_static_decorated_method to get the label for the method from the superclass (which should be available from the typing information on the Super node). You will need to explicitly pass the this pointer as the first argument to the InvokeCdecl instruction.
Assigning into fields and reading from fields requires the use of the FieldMove and FieldDeref instructions. For some field C.f, you will need to look up the index of f in C's runtime object representation.
As stated before, objects are allocated at runtime using the newcls service routine. newcls takes a single argument that represents the number of words to allocate including the word for the vtable pointer. It returns a pointer to the word directly following the vtable pointer.
Constructors are always statically linked and called with InvokeCdecl. If you have a cgcxt_class record, you can get the label for its constructor using the decorate_ctor_name function. Like member functions, their first argument is always .this, which starts out as a pointer into an allocated but uninitialized object record. The return value of a constructor is always ignored.
The base case for any object construction is always the constructor for class Object. This constructor needs only set the vtable of the passed-in .this to Object's vtable.
For any other constructor on class C with parent P:
You can receive extra credit for this project by extending the language to support first-class functions. We have provided AST nodes for function types and function expressions. You do not need full support for closures to be awarded extra credit points, but you should let us know about your plans so that we can tell you how many points you might be given.
The different parts that you can complete include:
You should target using only explicit fun expressions to introduce closures; don't try to give anything that can be executed a function type. You do not need to support, for instance, storing a methods (some_fun = obj.method) or top-level functions (some_fun = string_cat). While you should allow closures to participate in subtyping as was discussed in class, you should not attempt to allow closures to participate in runtime type tests (cast()).
Edit the included readme.txt file, making sure to fill in your name(s) and email address(es). If you want any part of your project to count for extra credit, be sure to document it.
You should also spend some time to ensure that your code is readable and well-commented. We have some suggestions and requirements for your programming style. You will definitely lose points for the file submission requirements (code must compile, 80 column limit, no tabs); however, barring any particularly egregious cases, you may take the remaining rules as suggestions.
Part of your grade will be based on testing. Some example tests and utility functions are provided in full_test.ml. You must use the testing framework provided and explicitly note any modifications in your readme.txt file. We ask that you name your test cases regularly: choose a username from those in your group. All test names and files should contain that username followed by an underscore as a prefix. (For example, if your username is cat and you have written a test case called alive, that test should be called cat_alive.)
The grading breakdown for this project is as follows:
Remember that your raw score will be scaled— that is, more difficult assignments will be worth more than less difficult assignments, even if the more difficult assignment has fewer available points.
First, make sure to clean your project with omake clean. All of the files you've written should be in projects/full. Submit your assignment on ENIAC. Your project must run on ENIAC, including all test cases. Be sure to submit from ENIAC and not from some other Linux machine; otherwise you will get an error.
Switch to the projects directory and type: