The Tiger language: an informal overview
Table of Contents
Tiger is a small typed programming language similar to Pascal. It was created by Andrew Appel for his compiler books. This variant of Tiger differs very slightly from the one described by Appel.
This document describes the language informally, mostly by example.
1 The language
A Tiger program is an expression that results in a value. This is a Tiger program:
42
This program computes a value of the built-in type int
.
This is also a Tiger program:
print("Hi!\n")
This program computes a value of type unit
, which is the "value-less" type. An expression of type unit
is most often useful because of the side-effects it performs.
Larger Tiger programs are the result of composing expressions together.
1.1 Sequencing
If e1
, e2
, and e3
are arbitrary expressions then the expression (e1; e2; e3)
evaluates to the value of e3
after first evaluating e1
and e2
and ignoring their values.
For example,
(42; print("Hi!\n"); "abc"; 10)
results in the value 10
and prints the string "Hi!\n"
to the standard output device.
The "empty" sequence (()
) is of type unit
.
1.2 Variables
The Tiger expression
let d1 d2 ... in e end
introduces a scope in which the declarations d1
, d2
, etc apply in the evaluation of the expression e
.
There are three kinds of declarations: variables, types, and functions. We'll start with variables.
A variable declaration assigns the result of an expression to a name. The type of the expression can be explicitly marked, but most often it's optional.
For example,
let var x: int := 10 var y := x + 1 var z: string := "abc" in x + y end
1.3 Functions
This is a Tiger program with a simple function:
let function add(x: int, y: int): int = x + y in add(2, 3) end
Functions with a result-type of unit
are called procedures and written as follows:
let function greet(name: string) = (print("Hi, "); print(name); print("!\n")) in greet("Joe") end
A collection of functions may be grouped with the and
keyword to make them mutally-recursive:
let function is_even(x: int): int = if x = 0 then 1 else is_odd(x - 1) and function is_odd(x: int): int = if x = 0 then 0 else is_even(x - 1) in is_even(41) end
Functions may be nested inside of other functions arbitrarily:
let function triple(s: string): string = let var buffer := "" function append(s: string) = buffer := concat(buffer, s) in append(s); append(s); append(s); buffer end in triple("abc") end
1.4 Types
There are two built-in types in Tiger: int
and string
.
There are three ways to define new types.
1.4.1 Arrays
An array type is defined in terms of the type of its elements. Two different array types with the same element type are considered to be distinct types.
For example,
let type numbers = array of int type scores = array of int var x := numbers[10] of 100 var y := scores of [1, 2, 3] in x[8] := 200; print_int(y[0]) end
Here, numbers
and scores
are distinct types that cannot be interchanged.
x
is a value of type numbers
that initially consists of 100 elements of the value 10
.
y
is a value of type scores
that consists of three elements: 1, 2, 3.
The expression size e
is an int
value which is the declared size of the array-valued expression e
.
1.4.2 Records
A record type is a value that consists of named sub-values called fields.
let type person = {name: string, age: int} var p1 := person {name="Joe", age=66} var p2: person := nil in print(p.name) end
As with arrays, every record type is distinct even if it has the same fields.
The special value nil
can be assigned to record values. A variable can only be initially assigned the value nil
if its type is explicitly marked (as with p2
here).
Records may be defined recursively:
let type list = {head: int, tail: list} in list {head=10, tail=list {head=20, tail=list {head=30, tail=nil}}} end
1.4.3 Aliases
A type alias defines a new name for an existing type. The two types are treated interchangably.
For example,
let type width = int var x: width := 10 var y: int := 20 in x + y end
1.4.4 Mutually-recursive types
Type declarations separated by the keyword and
form a mutually-recursive group.
For example,
let type tree = {root: item, children: forest} and type forest = {head: tree, tail: forest} and type item = string function leaf(x: string): tree = tree {root=x, children=nil} function cons(x: tree, f: forest): forest = forest {head=x, tail=f} in tree {root="Z", children=cons(leaf("A"), cons(leaf("B"), cons(leaf("C"), nil)))} end
1.5 Values
1.5.1 Integers and strings
Integers in Tiger are always 64 bit with two's complement representation. The arithmetic operators +
, -
, *
, and /
behave as one would expect.
e1 & e2
is non-zero when both e1
and e2
are non-zero. e1 | e2
is non-zero when either e1
or e2
is non-zero. not(e)
is non-zero when e
is zero and zero when e
is non-zero.
String literal like "Hi"
support a limited number of "escape" characters, like "\n"
.
1.5.2 while
-loops and break
A while
loop evaluates an int
-valued condition and if the result is non-zero, evaluates a ()
-valued body.
let var x := 10 in while x <> 0 do (print_int(x); print_line(); x := x - 1) done
The break keyword may be used to terminate a while loop early.
while 1 do break
1.5.3 for
loops
for i := 1 to 10 do (print_int(i); print_line())
break
may also be used inside a for
loop to terminate early.
1.5.4 Comparisons
Two values of the same type may always be compared for equality (=
) or inequality (<>
).
Integers and strings are compared structurally: two expressions with the same logical value compare equal. As a consequence, two distinct string
values with the same contents cannot be distinguished in Tiger programs. These values may also be compared according to the <
, <=
, >
, and >=
operators. Strings are compared lexicographically.
The equality of record and array values is defined according to physical identity.
1.5.5 Operator precedence
Operator | Precedence | Associativity |
---|---|---|
:= |
1 | |
& , | |
2 | |
= , <> , < , <= , > , >= |
3 | |
+ , - |
4 | left |
* , / |
5 | left |
- , size |
6 | right |
2 Built-ins
Tiger has some functions built-in to the language.
chr(x: int): string
– A single-character string consisting of the character with ASCII codex
. The program aborts with an error ifx
is not a valid ASCII character code.ord(s: string): int
– The ASCII character code for the first character in the strings
. If the string is empty, the result is-1
.concat(s1: string, s2: string): string
– The concatenation of two strings into a new string.len(s: string): int
– The number of ASCII characters in a string.substring(s: string, start: int, count: int): string
– A new string consisting ofcount
characters froms
, beginning at the zero-based indexstart
. The program aborts with an error if the substring is out of range ofs
.print(s: string)
– Print a string to the standard output device. If there's a system error, the program aborts.print_int(x: int)
– Print an integer in base-10 to the standard output device. If there's a system error, the program aborts.print_line()
– Print a newline character to the standard output device. If there's a system error, the program aborts.flush()
– Flush the standard output device, which is buffered.read_char(): string
– Read a single-character string from the standard input device. If no character is available, the result is the empty string. If there's a system error, the program aborts.random(a: int, b: int): int
– Compute a pseudo-random value in the range \(\left[a,b\right)\). If the range is invalid, the program aborts.seed(x: int)
– Seed the random generator.error(message: string)
– Abort the program with a user-defined message.