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

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’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 chunk 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 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.