Previous: Declarations, Up: Semantics [Contents]
The ‘l-values’ (whose value can be read or changed) are: elements of arrays, fields of records, instances of classes, arguments and variables.
Some expressions have no value: procedure calls, assignments, if
s
with no else
clause, loops and break
. Empty sequences
(‘()’) and let
s with an empty body are also valueless.
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
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.
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”.
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
A record instantiation must define the value of all the fields and in the same order as in the definition of the record type.
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 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
Tiger Boolean operators normalize their result to 0/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 expressions only apply on integers and return integers. Available operators in Tiger are : +,-,* and /.
Comparison operators (‘=’, ‘<>’, and ‘<=’, ‘<’, ‘>=’, ‘>’) return a Boolean value.
All the comparison operators apply to pairs of strings and pairs of integers, with obvious semantics.
Comparison of strings is based on the lexicographic order.
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 be compared against a value which
type is that of a record or a class, e.g. ‘nil = nil’ is invalid.
Arrays, records and objects cannot be ordered: ‘<’, ‘>’, ‘<=’, ‘>=’ are valid only for pairs of strings or integers.
In conformance with A. Appel’s specifications, any two void entities are equal.
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 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
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 doesn’t 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
Upcast are performed when branching between two class instantiations.
Since every class inherits 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
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 enclosing a single expression enforce syntactic grouping.
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
In an if-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
.
In an if-expression:
if exp1 then exp2
exp1 is typed as an integer, and exp2 must have no value. The whole expression has no value either.
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.
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.
A break terminates the nearest enclosing loop (while
or
for
). 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.
In the let-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.
Previous: Declarations, Up: Semantics [Contents]