Copyright © 1996, 1997 Lucent Technologies Inc. All rights reserved.

8.1 Terms

The basic elements of expressions are terms:


term: identifier constant real-constant string-constant nil ( expression-list ) term . identifier term -> term term ( expression-listopt ) term [ expression ] term [ expression : expression ] term [ expression : ] term ++ term --
The operators on terms all associate to the left, and their order of precedence, with tightest listed first, is as follows:
			.
			->
			() [] ++ --

8.1.1 Simple terms

The first five kinds of term are constants and identifiers. Constants have a type indicated by their syntax. An identifier used in an expression is often a previously declared data object with a particular data type; when used as a term in an expression it denotes the value stored in the object, and the term has the declared object's type. Sometimes, as discussed below, identifiers used in expressions are type names, function names, or module identifiers.

8.1.2 Parenthesized terms

A comma-separated list of expressions enclosed in parentheses is a term. If a single expression is present in the list, the type and value are those of the expression; the parentheses affect only the binding of operators in the expression of which the term is a part. If there is more than one expression in the list, the value is a tuple. The member types and values are taken from those of the expressions.

8.1.3 Selection

A term of the form


term . identifier
denotes selection of a member of an adt. The term must be a type name or yield an object; its type must be adt or ref adt; the identifier must be a member of the adt. The result denotes the named member (either a data object or a function).

8.1.4 Module qualification

A term of the form


term -> term
denotes module qualification. The first term identifies a module: either it is a module type name, or it is an expression of module type. The second term is a constant name, type, or function specified within that module's declaration. Either the module type name or an object of the module's type suffices to qualify constants and types; functions directly exported by the module or contained within its adt must be qualified by an object of the module's type, initialized with load.

An example using an abridged version of an example above: given

	Linear: module {
		setflags: fn(flag: int);
		TRUNCATE: con 1;
		Vector: adt {
			make: fn(v: array of real): Vector;
			v: array of real;
		};
	};
one might say
	lin := load Linear "/dis/linear.dis";
	a: array of real;

	v1: lin->Vector;
	v2: Linear->Vector;
	lin->setflags(Linear->TRUNCATE);
	v1 = lin->(Linear->Vector).make(a);
	v1 = lin->v1.make(a);
	v1 = lin->v1.add(v1);
	v1.v = nil;
Here, the declarations for v1 and v2 are equivalent; either a module type name (here, Linear) or a handle (here, lin) suffices to identify the module. In the call to setflags, a handle is required for the call itself; the type name is sufficient for the constant.

When calling a function associated with an adt of another module, it is necessary to identify both the module and the adt as well as the function. The two calls to the make function illustrate two ways of doing this. In the first,

	v1 = lin->(Linear->Vector).make(a);
the module handle lin is specified first, then the type name of the Vector adt within it, and then the function. In the second call
	v1 = lin->v1.make(a);
instead of using a type name to specify the adt, an instance of an object of the appropriate type is used instead. In the first example, the parentheses are required because the qualification operators associate to the left.
	v1 = lin->Vector.make(a);	# Wrong
	v1 = lin->Linear->Vector.make(a);	# Wrong
The first is wrong because the same lin can't serve as a qualifier for both the type and the call; the second is wrong because lin->Linear is meaningless.

Using import makes the code less verbose:

	lin := load Linear "/usr/dmr/limbo/linear.dis";
	Vector, TRUNCATE, setflags: import lin;	
	a: array of real;

	v1: Vector;
	v2: Vector;
	setflags(TRUNCATE);
	v1 = Vector.make(a);
	v1 = v1.make(a);
	v1 = v1.add(v1);
	v1.v = nil;

8.1.5 Function calls

The interpretation of an expression in the form


term ( expression-listopt )
depends on the declaration of the term. If it is the (perhaps qualified) name of an adt, then the expression is a cast; this is discussed in §8.2.11 below. If the term is the (perhaps qualified) name of a function, the expression means a function call; this is discussed here.

A plain identifier as the term names a function defined in the current module or imported into it. A term qualified by using the selection operator . specifies a function member of an adt; a term using -> specifies a function defined in another module.

Function calls in Limbo create a copy of each argument of value type, and the execution of a function cannot affect the value of the corresponding actual argument. For arguments of reference type, execution of the function may affect the value of the object to which the reference refers, although it cannot change the argument itself. The actual arguments to a function are evaluated in an unspecified order, although any side effects caused by argument evaluation occur before the function is called.

Function calls may be directly or indirectly recursive; objects declared within each function are distinct from those in their dynamic predecessors.

Functions (§4.3, §7) may either return a value of a specified type, or return no value. If a function returns a value, it has the specified type. A call to a function that returns no value may appear only as the sole expression in a statement (§9.1).

8.1.6 Subscripting and slicing

In a term of the form


term [ expression ]
the first term must be an array or a string, and the bracketed expression must have int type. The whole term designates a member of the array or string, indexed by the bracketed expression; the index origin is 0. For an array, the type of the whole term is the type from which the array is constructed; for a string, the type is an int whose value is the Unicode character at that position in the string.

It is erroneous to refer to a nonexisting part of an array or string. (A single exception to this rule, discussed in §8.4.1 below, allows extending a string by assigning a character at its end.)

In a term of the form


term [ expression : expression ]
the first term must be an array or a string, and the whole term denotes a slice of it. The first expression is the lower bound, and the second is the upper. If e1 is the first expression and e2 is the second, then in a[e1:e2] it must be the case that 0<=e1, e1<=e2, e2<=len a, where len gives the number of elements in the array or string. When the term is an array, the value is an array of the same type beginning at the indicated lower bound and extending to the element just before the upper bound. When the term is a string, the value is similarly the substring whose first character is indexed by the lower bound and whose last character lies just before the upper bound.

Thus, for both arrays and strings, the number of elements in a[e1:e2] is equal to e2-e1.

A slice of the form a[e:] means a[e:len a].

When a string slice is assigned to another string or passed as an argument, a copy of its value is made.

A slice of an array produces a reference to the designated subarray; a change to an element of either the original array or the slice is reflected in the other.

In general, slice expressions cannot be the subject of assignments. However, as a special case, an array slice expression of the form a[e1:] may be assigned to. This is discussed in §8.4.1.

The following example shows how slices can be used to accomplish what would need to be done with pointer arithmetic in C:

	fd := sys->open( ... );
	want := 1024;
	buf := array[want] of byte;
	b := buf[0:];
	while (want>0) {
		got := sys->read(fd, b, want);
		if (got<=0)
			break;
		b = b[got:];
		want -= got;
	}
Here the array buf is filled by successive calls to sys->read that may supply fewer bytes than requested; each call stores up to want bytes starting at b[0], and returns the number of bytes stored. The invariant is that the slice b always refers to the part of the array still to be stored into.

8.1.7 Increment and decrement

A term of the form


term ++
is called a post-increment. The term must be an lvalue (see §8.4 below) and must have an arithmetic type. The type and value of the whole term is that of the incremented term. After the value is taken, 1 of the appropriate type is added to the lvalue. The result is undefined if the same object is changed more than once in the same expression.

The term


term --
behaves analogously to the increment case except that 1 is subtracted from the lvalue.

05/Jun/97