Type Declarations
- Arrays
The size of the array does not belong to the type. Index of arrays starts from 0 and ends at
size
- 1.let type int_array = array of int var table := int_array[100] of 0 in ... end
Arrays are initialized with the same instance of value. This leads to aliasing for entities with pointer semantics (strings, arrays and records).
let type rec = { val : int } type rec_arr = array of rec var table := rec_arr[2] of rec { val = 42 } in table[0].val := 51 /* Now table[1].val = 51. */ end
Use a loop to instantiate several initialization values.
let type rec = { val : int } type rec_arr = array of rec var table := rec_arr[2] of nil in for i := 0 to 1 do table[i] := rec { val = 42 }; table[0].val := 51 /* table[1].val = 42. */ end
- Records
Records are defined by a list of fields between braces. Fields are described as
fieldname : type-id
and are separated by a comma. Field names are unique for a given record type.let type indexed_string = {index : int, value : string} in ... end
- Classes
See also Method Declarations.
Classes define a set of attributes and methods. Empty classes are valid. Attribute declaration is like variable declaration; method declaration is similar to function declaration, but uses the keyword
method
instead offunction
.There are two ways to declare a class. The first version (known as canonical) uses
type
, and is similar to record and array declaration:let type Foo = class extends Object { var bar := 42 method baz() = print("Foo.\n") } in /* ... */ end
The second version (known as alternative or Appel’s) doesn’t make use of
type
, but introduces classes declarations directly. This is the syntax described by Andrew Appel in his books:let class Foo extends Object { var bar := 42 method baz() = print("Foo.\n") } in /* ... */ end
For simplicity reasons, constructs using the alternative syntax are considered as syntactic sugar for the canonical syntax, and are desugared by the parser into this first form, using the following transformation:
class name [ extends super ] { classfields } => type name = class [ extends super ] { classfields }
where
name
,super
andclassfields
are respectively the class name, the super class name and the contents of the class (attributes and methods).In the rest of the section, Appel’s form will be often used, to offer a uniform reading with his books, but remember that the main syntax is the other one, and Appel’s syntax is to be desugared into the canonical one.
Declarations of class members follow the same rules as variable and function declarations: consecutive method declarations constitute a chunk of methods, while a block of attributes contains only a single one attribute declaration (several attribute declarations thus form several chunks). An extra rule holds for class members: there shall be no two attributes nor two methods with the same name within the same class definition.
let class duplicate_attrs { var a := 1 method m() = () /* Error, duplicate attribute in the same class. */ var a := 2 } class duplicate_meths { method m() = () var a := 1 /* Error, duplicate method in the same class. */ method m() = () } in end
Note that this last rule applies only to the strict scope of the class, not to the scopes of inner classes.
let type C = class { var a := 1 method m() = let type D = class { /* These members have same names as C's, but this is allowed since they are not in the same scope. */ var a := 1 method m() = () } in end } in end
Objects of a given class are created using the keyword
new
. There are no constructors nor destructors in Tiger, so the attributes are always initialized by the value given at their declaration.let class Foo { var bar := 42 method baz() = print("Foo.\n") } class Empty { } var foo1 : Foo := new Foo /* As for any variable, the type annotation is optional. */ var foo2 := new Foo in /* ... */ end
The access to a member (either an attribute or a method) of an object from outside the class uses the dotted notation (as in C++, Java, C#, etc…). There are no visibility qualifier/restriction (i.e. all attributes of an object accessible in the current scope are accessible in read and write modes), and all its methods can be called.
let class Foo { var bar := 42 method baz() = print("Foo.\n") } var foo := new Foo in print_int(foo.bar); foo.baz() end
To access a member (either an attribute or a method) from within the class where it is defined, use the
self
identifier (equivalent to C++’s or Java’sthis
), which refers to the current instance of the object.let class Point2d { var row : int := 0 var col : int := 0 method print_row() = print_int(self.row) method print_col() = print_int(self.col) method print() = ( print("("); self.print_row(); print(", "); self.print_col(); print(")") ) } in /* ... */ end
The use of
self
is mandatory to access a member of the class (or of its super class(es)) from within the class. A variable or a method not preceded byself.
won’t be looked up in the scope of the class.let var a := 42 function m() = print("m()\n") class C { var a := 51 method m() = print("C.m()\n") method print_a() = (print_int(a); print("\n")) method print_self_a() = (print_int(self.a); print("\n")) method call_m() = m() method call_self_m() = self.m() } var c := new C in c.print_a(); /* Print `42'. */ c.print_self_a(); /* Print `51'. */ c.call_m(); /* Print `m()'. */ c.call_self_m() /* Print `C.m()'. */ end
self
cannot be used outside a method definition. In this respect,self
cannot appear in a function or a class defined within a method (except within a method defined therein, of course).let type C = class { var a := 51 var b := self /* Invalid. */ method m () : int = let function f () : int = self.a /* Invalid. */ in f() + self.a /* Valid. */ end } var a := new C in a := self /* Invalid. */ end
self
is a read-only variable and cannot be assigned.The Tiger language supports single inheritance thanks to the keyword
extends
, so that a class can inherit from another class declared previously, or declared in the same chunk of class declarations. A class with no manifest inheritance (noextends
statement following the class name) automatically inherits from the built-in classObject
(this feature is an extension of Appel’s object-oriented proposal).Inclusion polymorphism is supported as well: when a class Y inherits from a class X (directly or through several inheritance links), any object of Y can be seen as an object of type X. Hence, objects have two types: the static type, known at compile time, and the dynamic or exact type, known at runtime, which is a subtype of (or identical to) the static type. Therefore, an object of static type Y can be assigned to a variable of type X.
let /* Manifest inheritance from Object: an A is an Object. */ class A extends Object {} /* Implicit inheritance from Object: a B is an Object. */ class B {} /* C is an A. */ class C extends A {} var a : A := new A var b : B := new B var c1 : C := new C /* When the type is not given explicitly, it is inferred from the initialization; here, C2 has static and dynamic type C. */ var c2 := new C /* This variable has static type A, but dynamic type C. */ var c3 : A := new C in /* Allowed (upcast). */ a := c1 /* Forbidden (downcast). */ /* c2 := a */ end
As stated before, a class can inherit from a class [1] declared previously (and visible in the scope), or from a class declared in the same chunk of type declarations (recall that a class declaration is in fact a type declaration). Recursive inheritance is not allowed.
let /* Allowed: A declared before B. */ class A {} class B extends A {} /* Allowed: C declared before D. */ class C {} var foo := -42 class D extends C {} /* Allowed: forward inheritance, with E and F in the same block. */ class F extends E {} class E {} /* Forbidden: forward inheritance, with G and H in different blocks. */ class H extends G {} var bar := 2501 class G {} /* Forbidden: recursive inheritance. */ class I extends J {} class J extends I {} /* Forbidden: recursive inheritance and forward inheritance with K and L in different blocks. */ class K extends L {} var baz := 2097 class L extends K {} /* Forbidden: M inherits from a non-class type. */ class M extends int {} in /* ... */ end
All members from the super classes (transitive closure of the is a relationship) are accessible using the dotted notation, and the identifier
self
when they are used from within the class.Attribute redefinition is not allowed: a class cannot define an attribute with the same name as an inherited attribute, even if it has the same type. Regarding method overriding, see Method Declarations.
Let us consider a chunk of type definitions. For each class of this chunk, any of its members (either attributes or methods) can reference any type introduced in scope of the chunk, including the class type enclosing the considered members.
let /* A block of types. */ class A { /* Valid forward reference to B, defined in the same block as the class enclosing this member. */ var b := new B } type t = int class B { /* Invalid forward reference to C, defined in another block (binding error). */ var c := new C } /* A block of variables. */ var v : t := 42 /* Another block of types. */ class C { } in end
However, a class member cannot reference another member defined in a class defined later in the program, in the current class or in a future class (except if the member referred to is in the same chunk as the referring member, hence in the same class, since a chunk of members cannot obviously span across two or more classes). And recall that class members can only reference previously defined class members, or members of the same chunk (e.g. a chunk of methods).
let /* A block of types. */ class X { var i := 1 /* Valid forward reference to self.o(), defined in the same block of methods. */ method m() : int = self.o() /* Invalid forward reference to self.p(), defined in another (future) block of methods (type error). */ method n() = self.p() /* Valid (backward) reference to self.i, defined earlier. */ method o() : int = self.i var j := 2 method p() = () var y := new Y /* Invalid forward reference to y.r(), defined in another (future) class (type error). */ method q() = self.y.r() } class Y { method r() = () } in end
To put it in a nutshell: within a chunk of types, forward references to classes are allowed, while forward references to members are limited to the chunk of members where the referring entity is defined.
- Recursive types
Types can be recursive,
let type stringlist = {head : string, tail : stringlist} in ... end
or mutually recursive, if declared in the same chunk, in Tiger,
let type indexed_string = {index : int, value : string} type indexed_string_list = {head : indexed_string, tail : indexed_string_list} in ... end
but there shall be no cycle. This is invalid:
let type a = b type b = a in ... end
- Type equivalence
Two types are equivalent if they are issued from the same type construction (array or record construction, or primitive type). As in C, unlike Pascal, structural equivalence is rejected.
Type aliases do not build new types, hence they are equivalent.
let type a = int type b = int var x : a := 1 var y : b := 2 in x = y /* OK */ end let type a = {foo : int} type b = {foo : int} var va := a{foo = 1} var vb := b{foo = 2} in va = vb end
This example is invalid, and must be rejected with the exit status set to 5.