Next: Variable Declarations, Up: Declarations [Contents]
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 are defined by a list of fields between braces. Fields are described as “fieldname : type-id” and are separated by a coma. Field names are unique for a given record type.
let type indexed_string = {index : int, value : string} in ... end
(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 of function
.
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 and Classfields are respectively the class name, the super class name and the contents of the class (attributes and methods) of the class.
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 block (or chunk) of methods, while a block of attributes contains only a single one attribute declaration (several attribute declarations thus form several blocks). An extra rule holds for class members: there shall be no two attributes with the same name in the same class definition, nor two methods with the name.
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 in Tiger (nor destructors), 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 to 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’s this), 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 by ‘self.
’ 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 block of class
declarations. A class with no manifest inheritance (no extends
statement following the class name) automatically inherits from the
built-in class Object
(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 run time, 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 class1 declared previously (and visible in the scope), or from a class declared in the same block 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 block of type definitions. For each class of this block, any of its members (either attributes or methods) can reference any type introduced in scope of the block, 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 block as the referring member, hence in the same class, since a block 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 block of members (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 block of members where the referring entity is defined.
Types can be recursive,
let type stringlist = {head : string, tail : stringlist} in ... end
or mutually recursive (if they are 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
let type a = b type b = a in ... end
is invalid.
Two types are equivalent iff 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 a := 1 var b := 2 in a = b /* 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
is invalid, and must be rejected with exit status set to 5.
Next: Variable Declarations, Up: Declarations [Contents]