prev up next

3) Expressions

We have already seen arithmetic expressions and string concatenation expressions. Such an expression may be assigned to a variable in an assignment statement, or passed as an argument in an object creation statement, or passed as an arguemnt to a message in a message send statement.

If we have an object descriptor with signature name(arg1,...,argn)~ then name( followed by n expressions followed by ) is an expression with the value of the object created by this call. Note name( should be regarded as an indivisible symbol, as name with some white space followed by ( means something else which will be covered later. Aldwych is limited in its type checking, but it will flag an error if an expression which evaluates to an object is put in the place for a non-object argument and vice-versa.

If P is an object variable, then P.mess(arg1,...,argm) is an expression which evaluates to the return value of sending message mess(arg1,...,argm) to P, that is, it is equivalent to using a variable v in an expression and having a separate statement P.mess(arg1,...,argm)->v. In this case there is no type-checking, so it is up to the programmer to ensure that P can take a message of that sort which returns a value or an object as the expression in used. A message send expression can have multiple messages, but return variables must be given for all but the last for every message that requires a return variable. For example, the expression P.mess1(x).mess2(y)->z.mess3(u) is equivalent to v with separate statement P.mess1(x).mess2(y)->z.mess3(u)->v

If an expression evaluates to an object, then that expression may be used instead of an object variable name to send messages. For example, given the time objects of part 2, time(5,50).add(20).clone has the value of an object representing the time 6:10. The object representing the time 5:50 is constructed and sent the message add(20) followed by clone without it being given a name, so we refer to it as an anonymous object. Using this, the following:

#main ==
   aio\stdio().fwrite(Time.toString).nl,
   time(5,50).add(20).clone->Time;
will cause 6:10 to be printed. The stdio object is anonymous here, but the object returned from the clone message is named Time. That object could be anonymous as well, giving:
#main == aio\stdio().fwrite(time(5,50).add(20).clone.toString).nl;
The code could also be written as:
#main ==
   aio\stdio().fwrite(Time.toString).nl,
   Time<-time(5,50).add(20).clone;
since here the value of the expression time(5,50).add(20).clone is assigned to Time.

As a.b.c represents the return value of the c message sent to a after b is sent to it, if we want to send c to the return value of b sent to a we use (a.b).c. For example,

#main ==
   aio\stdio().fwrite(t>" ">Time.toString).nl,
   Time<-time(5,50),
   t<-(Time.clone).add(30).toString;
will cause 6:20 5:50 to be printed. A clone of the object referred to be Time is created, its time is advanced by 30 minutes and the result of sending the toString message to it after that is put in t. Here t<-Time.clone.add(30).toString would not make sense, as it send a clone message without a return to Time, but Time has no rule to handle such a message. Note also in this example, the expression Time.toString is used to mean the return value from sending the toString message to Time, and this expression forms part of a larger expression formed using the string concatenation operator.

An alternative way of writing t<-(Time.clone).add(30).toString is t<-Time~clone.add(30).toString. The construct a~b sends b to a but subsequent messages in the expression are sent to the object which is returned from the b message. This avoids complex use of brackets, for example, a~b~c.d is equivalent to ((a.b).c).d.

Single rule procedures

Single rule procedures have a header like object descriptors, but in the place of ~ they have <==> which is followed by an expression formed from the arguments to the procedure, and then a semicolon. For example, the following is a procedure which gives the square of a number:
#square(m) <==> m*m;
A main procedure which tests this is:
#main ==
   aio\stdio()
     .fwrite("Enter an integer: ")
     .readInt->n
     .fwrite("Its square is: ">+square(n))
     .nl;
A procedure call, like square(n) above, evaluates to the expression in the procedure with the parameters replaced by the arguments of the call. The expression square(n) may be used in an assignment statement, for example x<-square(n), or as part of a more complex expression in an assignment, for example, x<-square(n)+2. But a call to a procedure with a return variable may be a statement on its own, such as square(n)->x. This is for completeness, analogous to x<-b.mess and b.mess->x. It does not have a different meaning than x<-square(n).

Procedures may have objects amongst their arguments. For example, using time objects, and including a main to demonstrate it:

#halfHourLater(T) <==> T~clone.add(30).toString;

#main ==
   aio\stdio()
       .fwrite("Enter hours: ").readInt->hrs
       .fwrite("Enter minutes: ").readInt->mins
       .fwrite("Half an hour later is: ">halfHourLater(Time))
       .nl,
   Time<-time(hrs,mins);
Here, a halfHourLater call takes a time as its argument and returns the string representing the time half an hour later.

Procedures can return objects rather than values. In this case, their headings have a different pattern. Here is a version of halfHourLater which returns the object representing the time half an hour later than its argument (together with a main to demonstrate it):

#halfHourLater(T)~ ==<= T~clone.add(30).clone;

#main ==
   aio\stdio()
       .fwrite("Enter hours: ").readInt->hrs
       .fwrite("Enter minutes: ").readInt->mins
       .fwrite("Half an hour later is: ">Later.toString)
       .nl,
   Time<-time(hrs,mins),
   Later<-halfHourLater(Time);
In this version of halfHourLater the first clone message ensures that the object passed as a parameter is not changed by the add(30) message, while the second clone message ensures a return is made from the procedure only after the add(30) message has been handled. The following would be legal Aldwych:
#halfHourLater(T)~ ==<= L, T~clone->L, L.add(30);
but due to the concurrency there is no guarantee here that the toString message will be sent to Later after the add(30) message inside halfHourLater. This version of halfHourLater shows how a local variable can be used inside a rule, in this case, L. A local variable has scope only within the rule and must have exactly one writer, and be used at least once.

prev up next