Next: Exception handling
Up: Control structures
Previous: Booleans and branching
In
closure.cecil:
abstract representation closure;
The
closure representation is the parent of all closure
expressions. Many control structures are defined on this object. But
it's a regular object, so other user-defined objects can be
descendants (e.g., see
eval.cecil in the compiler directory tree).
method loop(c@closure:&():void):none;
The
loop method invokes its closure argument endlessly. It
never returns normally. To exit the loop, the closure must do a
non-local return or invoke some closure that does (e.g., using
the
exit control structure). All other looping constructs are
built upon this method.
method while(cond:&():bool, c:&():void):void;
while implements a standard while-do loop. E.g.:
while({ i < last }, {
...
i := i.succ;
});
method while_true(cond:&():bool, c:&():void):void;
- same as
while_true
method while_false(cond:&():bool, c:&():void):void;
method while(cond:&():bool):void;
method while_true(cond:&():bool):void;
method while_false(cond:&():bool):void;
method until(c:&():void, cond:&():bool):void;
method until_true(c:&():void, cond:&():bool):void;
method until_false(c:&():void, cond:&():bool):void;
Other while-do and do-until loops are implemented using the
{while,until}[_{true,false}]
methods.
The one-argument while_{true,false}
methods simply evaluate
their argument test until it returns
true or
false, respectively,
presumably for its side effects. The until_
versions evaluate their
body closure and then the test until the test returns
true or
false, as appropriate.
method exit(c@closure:&(exit:&():none):void):void;
method exit_value(c:&(exit:&(`T):none):`T):T;
method exit_continue(c:&(exit:&():none, continue:&():none):void):void;
method exit_value_continue(c:&(exit:&(`T):none, continue:&():none):T):T;
The exit[_value][_continue]
constructs support evaluating a block
of code (the
body closure), breaking out of it if the
break closure
is evaluated inside
body. The _value
versions return a
value. The _continue
versions allows
body to be
restarted from the beginning when the
continue closure is evaluated.
For example, to execute some code, but perhaps quit early:
exit(&(break:&():none){
...
if(..., { eval(break) });
- skip the rest of the body of exit
...
- fall off bottom
});
This idiom supports breaking out of any sort of looping or non-looping
piece of code: just wrap the thing in an
exit or exit_value
control
structure and invoke the
break block where the control structure should be
exited.
method loop_exit(c:&(exit:&():none):void):void;
method loop_exit_value(c:&(exit:&(`T):none):void):T;
method loop_exit_continue(c:&(exit:&():none, continue:&():none):void):void;
method loop_exit_value_continue(c:&(exit:&(`T):none, continue:&():none):void):T;
For loops, some additional methods are defined for convenience.
The loop_exit[_value][_continue]
methods evaluate the
body closure
again and again until the
break closure is evaluated inside
body. For the _continue
version, execution of body can be restarted
from the beginning by evaluating the
continue closure. The _value
version returns a value.
To write a simple loop with a break statement:
loop_exit(&(break:&():none){
...
if(..., { eval(break) });
- exit loop conditionally
...
- loop
});
To loop and compute a value:
let result:int := loop_exit_value(&(break:&(int):none){
...
if(..., { eval(break, theResult) });
- exit loop, returning theResult
...
});
If both
break and
continue are desired for an arbitrary iterating
construct, such as
do, two
exit methods should be used, as in the
following example. The outer method encloses the iterator and
provides breaking out of the loop, while the inner method encloses
the loop body and provides continuing to the next iteration:
exit(&(break:&():none){
10.do(&(i:int){
- for i := 0 to 10-1 do
exit(&(continue:&():none){
...
if(..., break);
- break out of loop
...
if(..., continue);
- continue the iteration by jumping
- to the end of the loop body
...
});
});
});
template object case_pair[T];
extend type case_pair[`T] subtypes case_pair[`S >= T];
field cond(@:case_pair[`T]):&():bool;
field stmt(@:case_pair[`T]):&():T;
method case(c:&():bool, s:&():`T):case_pair[T];
method else(s:&():`T):case_pair[T];
method switch(t@:ordered_collection[case_pair[`T]]):T;
method unrolled_switch(t@:i_vector[case_pair[`T]]):T;
Case statements are supported through the
switch,
case, and
else
methods. The elements of
switch's argument collection (usually a
vector literal expression) are evaluated in turn, until one of the
test blocks evaluates to
true or the
else case is found. Then the
corresponding
do block is evaluated and its result returned as the
result of the
switch method. (Thus
switch is very much like Lisp's
cond.) The
switch method dies with a run-time error if none of the
cases match and there is no
else case. To illustrate:
let result:string :=
switch([case({ x < 0 }, { "negative" }),
case({ x = 0 }, { "zero" }),
else( { "positive" })]);
Unfortunately, unlike most Cecil control structures, the
switch construct
is not as efficient as the C version: a vector object is created and
filled in with objects containing real closures, and a bunch of
messages get sent. So you might wish to use chained
if expressions
instead of
switch expressions in the most time-critical parts of
your program. (Recently, some optimizations have been implemented
that often transform
switch statements of this form into an
if-then-else chain, but only if you invoke unrolled_switch
instead
of
switch, and object creations still remain unless debug_support
is
disabled.)
There's an inconsistency between the closure representation and closure
types, however. The closure representation is not parameterized, but
closure types (using the &(...):...
syntax) effectively are
parameterized by the closure's argument and result types. The closure
representation should be parameterized, too. The language defines the
standard contravariant subtyping relationship among closure types, i.e.:
extend &(T1, ..., TN):T subtypes &(`S1 >= T1, ..., `SN >= TN):(`S <= T)
Next: Exception handling
Up: Control structures
Previous: Booleans and branching
The Cecil project