Expressions

L-values

The l-values (whose value can be read or changed) are: elements of arrays, fields of records, instances of classes, arguments and variables.

Valueless expressions

Some expressions have no value: procedure calls, assignments, ifs with no else clause, loops and breaks. Empty sequences () and lets with an empty body are also valueless.

Nil

The reserved word nil refers to a value from a record or a class type. Do not use nil where its type cannot be determined.

let
  type any_record = {any : int}
  var nil_var : any_record := nil
  function nil_test(parameter : any_record) : int = ...
  var invalid := nil             /* no type, invalid */
in
  if nil <> nil_var then
    ...
  if nil_test(nil_var) then
    ...
  if nil = nil then ...         /* no type, invalid */
end
Integers

An integer literal is a series of decimal digits (therefore it is non-negative). Since the compiler targets 32-bit architectures, since it needs to handle signed integers, a literal integer value must fit in a signed 32-bit integer. Any other integer value is a scanner error.

Booleans

There is no Boolean type in Tiger: they are encoded as integers, with the same semantics as in C, i.e. 0 is the only value standing for false, anything else stands for true.

Strings

A string constant is a possibly empty series of printable characters, spaces or escapes sequences (see Lexical Specifications) enclosed between double quotes.

let
  var s := "\t\124\111\107\105\122\n"
in
  print(s)
end
Record instantiation

A record instantiation must define the value of all the fields and in the same order as in the definition of the record type.

Class instantiation

An object is created with new. There are no constructors in Tiger, so new takes only one operand, the name of the type to instantiate.

Function call

Function arguments are evaluated from the left to the right. Arrays and records arguments are passed by reference, strings and integer are passed by value.

The following example:

let
  type my_record = {value : int}
  function reference(parameter : my_record) =
    parameter.value := 42
  function value(parameter : string) =
    parameter := "Tiger is the best language\n"
  var rec1 := my_record{value = 1}
  var str := "C++ rulez"
in
  reference(rec1);
  print_int(rec1.value);
  print("\n");
  value(str);
  print(str);
  print("\n")
end

results in:

    42

    C++ rulez
Boolean operators

Tiger Boolean operators normalize their result to 0 or 1. For instance, because & and | can be implemented as syntactic sugar, one could easily make 123 | 456 return 1 or 123: make them return 1. Andrew Appel does not enforce this for & and |; we do, so that the following program has a well defined behavior:

print_int("0" < "9" | 42)
Arithmetic

Arithmetic expressions only apply on integers and return integers. Available operators in Tiger are: +, -, * and /.

Comparisons

Comparison operators (=, <>, <=, <, >= and >) return a Boolean value.

Integer and string comparison

All the comparison operators apply to pairs of strings and pairs of integers, with obvious semantics.

String comparison

Comparison of strings is based on the lexicographic order.

Array and record comparison

Pairs of arrays and pairs of records of the same type can be compared for equality (=) and inequality (<>). Identity equality applies, i.e. an array or a record is only equal to itself (shallow equality), regardless of the contents equality (deep equality). The value nil can only be compared against a value which type is that of a record or a class. As such nil = nil is invalid.

Arrays, records and objects cannot be ordered: <, >, <= and >= are valid only for pairs of strings or integers.

Void comparison

In conformance with Andrew Appel’s specifications, any two void entities are equal.

Assignments

Assignments yield no value. The following code is syntactically correct, but type incorrect:

let
  var foo := 1
  var bar := 1
in
  foo := (bar := 2) + 1
end

Note that the following code is valid:

let
  var void1 := ()
  var void2 := ()
  var void3 := ()
in
  void1 := void2 := void3 := ()
end
Array and record assignment

Array and record assignments are shallow, not deep, copies. Therefore aliasing effects arise: if an array or a record variable a is assigned another variable b of the same type, then changes on b will affect a and vice versa.

let
  type bar = {foo : int}
  var rec1 := bar{foo = 1}
  var rec2 := bar{foo = 2}
in
  print_int(rec1.foo);
  print(" is the value of rec1\n");
  print_int(rec2.foo);
  print(" is the value of rec2\n");
  rec1 := rec2;
  rec2.foo = 42;
  print_int(rec1.foo);
  print(" is the new value of rec1\n")
end
Polymorphic (object) assignments

Upcasts are valid for objects because of inclusion polymorphism.

let
  class A {}
  class B extends A {}
  var a := new A
  var b := new B
in
  a := b
end

Upcasts can be performed when defining a new object variable, by forcing the type of the declared variable to a super class of the actual object.

let
  class C {}
  class D extends C {}
  var c : C := new D
in
end

Tiger does not provide a downcast feature performing run time type identification (RTTI), like C++’s dynamic_cast.

let
  class E {}
  class F extends E {}
  var e : E := new F
  var f := new F
in
  /* Invalid: downcast.  */
  f := e
end
Polymorphic (object) branching
Upcasts are performed when branching between two class instantiations.
Since every class itnherits from Object, you will always find a common root.
let
  class A {}
  class B extends A {}
in
  if 1 then
    new A
  else
    new B
end
Sequences
A sequence is a possibly empty series of expressions separated by semicolons and enclosed by parenthesis. By convention, there are no sequences of a single expression (see the following item).
The sequence is evaluated from the left to the right. The value of the whole sequence is that of its last expression.
let
  var a := 1
in
  a := (
         print("first exp to display\n");
         print("second exp to display\n");
         a := a + 1;
         a
       ) + 42;
  print("the last value of a is : ");
  print_int(a);
  print("\n")
end
Parentheses

Parentheses enclosing a single expression enforce syntactic grouping.

Lifetime

Records and arrays have infinite lifetime: their values lasts forever even if the scope of their creation is left.

let
  type bar = {foo : int}
  var rec1 := bar{foo = 1}
in
  rec1 := let
            var rec2 := bar{foo = 42}
          in
            rec2
          end;
  print_int(rec1.foo);
  print("\n")
end
If then else

In an if then else expression:

if exp1 then
  exp2
else
  exp3

exp1 is typed as an integer, exp2 and exp3 must have the same type which will be the type of the entire structure. The resulting type cannot be that of nil.

If then

In an if then expression:

if exp1 then
  exp2

exp1 is typed as an integer, exp2 must have no value. The whole expression has no value either.

While

In a while expression:

while exp1 do
  exp2

exp1 is typed as an integer, exp2 must have no value. The whole expression has no value either.

For

The following for loop:

for id := exp1 to exp2 do
  exp3
introduces a fresh variable, id, which ranges from the value of exp1 to that of exp2, inclusive, by steps of 1.
The scope of id is restricted to exp3. In particular, id cannot appear in exp1 nor exp2. The variable id cannot be assigned to.
The type of both exp1 and exp2 is integer, they can range from the minimal to the maximal integer values. The body exp3 and the whole loop have no value.
Break

A break terminates the nearest enclosing while or for loop. A break must be enclosed by a loop. A break cannot appear inside a definition (e.g. between let and in), except if it is enclosed by a loop, of course.

Let in end

In the let in end expression:

let
  decs
in
  exps
end

decs is a sequence of declaration and exps is a sequence of expressions separated by a semi-colon. The whole expression has the value of exps.