The flow of control within a MAL program block can be changed by tagging a statement with either RETURN, YIELD, BARRIER, CATCH, LEAVE,REDO, or EXIT.
The flow modifiers RETURN and YIELD mark the end of a call and return one or more results to the calling environment. The RETURN and YIELD are followed by a target list or an assignment, which is executed first.
The BARRIER (CATCH) and EXIT pair mark a guarded statement block. They may be nested to form a proper hierarchy identified by their primary target variable, also called the control variable.
The LEAVE and REDO are conditional flow modifiers. The control variable is used after the assignment statement has been evaluated to decide on the flow-of-control action to be taken. Built-in controls exists for booleans and numeric values. The guarded block is opened when the control variable holds true, when its numeric value >= 0, or when it is a non-empty string. The nil value blocks entry in all cases.
Once inside the guarded block you have an option to prematurely LEAVE it at the exit statement or to REDO interpretation just after the corresponding barrier statement. Much like 'break' and 'continue' statements in the programming language C. The action is taken when the condition is met.
The EXIT marks the exit for a block. Its optional assignment can be used to re-initialize the barrier control variables or wrap-up any related administration.
The guarded blocks can be properly nested to form a hierarchy of basic blocks. The control flow within and between blocks is simple enough to deal with during an optimizer stage. The REDO and LEAVE statements mark the partial end of a block. Statements within these blocks can be re-arranged in accordance with the data-flow dependencies. The partial blocks order can not be changed that easily. It depends on the mutual exclusion of the data flows within each partial block.
Common guarded blocks in imperative languages are the for-loop and if-then-else constructs. They can be simulated as follows.
Consider the statement for(i=1;i<10;i++) print(i). The (optimized) MAL block to implement this becomes:
i:= 1; barrier B:= i<10; io.print(i); i:= i+1; redo B:= i<10; exit B;
Translation of the statement if(i<1) print("ok"); else print("wrong"); becomes:
i:=1; barrier ifpart:= i<1; io.print("ok"); exit ifpart; barrier elsepart:= i>=1; io.print("wrong"); exit elsepart;
Note that both guarded blocks can be interchanged without affecting the outcome. Moreover, neither block would have been entered if the variable happens to be assigned nil.
The primitives are sufficient to model a wide variety of iterators, whose pattern look like:
barrier i:= M.newIterator(T); elm:= M.getElement(T,i); ... leave i:= M.noMoreElements(T); ... redo i:= M.hasMoreElements(T); exit i:= M.exitIterator(T);
The semantics obeyed by the iterator implementations is as follows. The redo expression updates the target variable i and control proceeds at the first statement after the barrier when the barrier is opened by i. If the barrier could not be re-opened, execution proceeds with the first statement after the redo. Likewise, the leave control statement skips to the exit when the control variable i shows a closed barrier block. Otherwise, it continues with the next instruction. Note, in both failed cases the control variable is possibly changed.
A recurring situation is to iterate over the elements in a BAT. This is supported by an iterator implementation for BATs as follows:
barrier (idx,hd,tl):= bat.newIterator(B); ... redo (idx,hd,tl):= bat.hasMoreElements(B); exit (ids,hd,tl);
Where idx is an integer to denote the row in the BAT, hd and tl denote values of the current element.