7. Control Flow within procedures
7.1. Unconditional flow
7.1.1. sequence
To perform a sequential set of operations in TDF, one uses the constructor sequence:
statements: LIST(EXP) result: EXP x -> EXP x
Each of the statements
are evaluated in order, throwing away their results. Then, result
is evaluated and its result is the result of the sequence.
A translator is free to rearrange the order of evaluation if there is no observable difference other than in time or space. This applies anywhere I say “something is evaluated and then ...”. We find this kind of statement in definitions of local variables in section 5.3, and in the controlling parts of the conditional constructions below.
For a more precise discussion of allowable reorderings see () .
7.2. Conditional flow
7.2.1. labelled, make_label
All simple changes of flow of control within a TDF procedure are done by jumps or branches to LABELs, mirroring what actually happens in most computers. There are three constructors which introduce LABELs; the most general is labelled which allows arbitrary jumping between its component EXPs:
placelabs_intro: LIST(LABEL) starter: EXP x places: LIST(EXP) -> EXP w
Each of the EXPs in places
is labelled by the corresponding LABEL in placelabs_intro
; these LABELs are constructed by make_label applied to a TDFINT uniquely drawn from the LABEL name-space introduced by the enclosing PROPS. The evaluation starts by evaluating starter
; if this runs to completion the result of the labelled is the result of starter.
If there is some jump to a LABEL in placelabs_intro
then control passes to the corresponding EXP in places
and so on. If any of these EXPS runs to completion then its result is the result of the labelled; hence the SHAPE of the result, w, is the LUB of the SHAPEs of the component EXPs.
Note that control does not automatically pass from one EXP to the next; if this is required the appropriate EXP must end with an explicit goto.
7.2.2. goto, make_local_lv, goto_local_lv, long_jump, return_to_label
The unconditional goto is the simplest method of jumping. In common with all the methods of jumping using LABELs, its LABEL parameter must have been introduced in an enclosing construction, like labelled, which scopes it.
One can also pick up a label value of SHAPE POINTER {code} (usually implemented as a program address) using make_local_lv for later use by an "indirect jump" such as goto_local_lv . Here the same prohibition holds - the construction which introduced the LABEL must still be active.
The construction goto_local_lv only permits one to jump within the current procedure; if one wished to do a jump out of a procedure into a calling one, one uses long_jump which requires a pointer to the destination frame (produced by current_env in the destination procedure) as well as the label value. If a long_jump is made to a label, only those local TAGs which have been defined with a visible ACCESS are guaranteed to have preserved their values; the translator could allocate the other TAGs in scope as registers whose values are not necessarily preserved.
A slightly "shorter" long jump is given by return_to_label. Here the destination of the jump must a label value in the calling procedure. Usually this value would be passed as parameter of the call to provide an alternative exit to the procedure.
7.2.3. integer_test, NTEST
Conditional branching is provided by the various _test constructors, one for each primitive SHAPE except BITFIELD. I shall use integer_test as the paradigm for them all:
nt: NTEST dest: LABEL arg1: EXP INTEGER(v) arg2: EXP INTEGER(v) -> EXP TOP
The NTEST nt
chooses a dyadic test (e.g. =, ≥, <, etc.) that is to be applied to the results of evaluating arg1
and arg2
. If arg1
nt
arg2
then the result is TOP; otherwise control passes to the LABEL dest
. In other words, integer_test acts like an assertion where if the assertion is false, control passes to the LABEL instead of continuing in the normal fashion.
Some of the constructors for NTESTs are disallowed for some _tests (e.g. proc_test) while others are redundant for some _tests; for example, not_greater_than is the same as less_than_or_equal for all except possibly floating_test. where the use of NaNs (in the IEEE sense) as operands may give different results.
7.2.4. case
There are only two other ways of changing flow of control using LABELs. One arises in ERROR_TREATMENTs which will be dealt with in the arithmetic operations. The other is case:
exhaustive: BOOL control: EXP INTEGER(v) branches: LIST(CASELIM) -> EXP (exhaustive ? BOTTOM : TOP)
Each CASELIM is constructed using make_caselim:
branch: LABEL lower: SIGNED_NAT upper: SIGNED_NAT -> CASELIM
In the case construction, the control
EXP is evaluated and tested to see whether its value lies inclusively between some lower
and upper
in the list of CASELIMs. If so, control passes to the corresponding branch
. The order in which these tests are performed is undefined, so it is probably best if the tests are exclusive. The exhaustive flag being true asserts that one of the branches will be taken and so the SHAPE of the result is BOTTOM. Otherwise, if none of the branches are taken, its SHAPE is TOP and execution carries on normally.
7.2.5. conditional, repeat
Besides labelled, two other constructors, conditional and repeat, introduce LABELs which can be used with the various jump instructions. Both can be expressed as labelled, but have extra constraints which make assertions about the use of the LABELs introduced and could usually be translated more efficiently; hence producers are advised to use them where possible. A conditional expression or statement would usually be done using conditional:
alt_label_intro: LABEL first: EXP x alt: EXP y -> EXP(LUB(x, y))
Here first
is evaluated; if it terminates normally, its result is the result of the conditional. If a jump to alt_label_intro
occurs then alt
is evaluated and its result is the result of the conditional. Clearly, this, so far, is just the same as labelled((alt_label_intro
), first
, (alt
)). However, conditional imposes the constraint that alt
cannot use alt_label_intro
. All jumps to alt_label_intro
are "forward jumps" - a useful property to know in a translator.
Obviously, this kind of conditional is rather different to those found in traditional high-level languages which usually have three components, a boolean expression, a then-part and an else-part. Here, the first
component includes both the boolean expression and the then-part; usually we find that it is a sequence of the tests (branching to alt_label_intro
) forming the boolean expression followed by the else-part. This formulation means that HLL constructions like "andif" and "orelse" do not require special constructions in TDF.
A simple loop can be expressed using repeat:
repeat_label_intro: LABEL start: EXP TOP body: EXP y -> EXP y
The EXP start
is evaluated, followed by body
which is labelled by repeat_label_intro
. If a jump to repeat_label_intro
occurs in body
, then body
is re-evaluated. If body
terminates normally then its result is the result of the repeat. This is just the same as:
labelled((repeat_label_intro), sequence((start), goto(repeat_label_intro)), (body))
except that no jumps to repeat_label_intro
are allowed in start
- a useful place to do initialisations for the loop.
Just as with conditionals, the tests for the continuing or breaking the loop are included in body
and require no special constructions.
7.3. Exceptional flow
A further way of changing the flow of control in a TDF program is by means of exceptions. TDF exceptions currently arise from three sources - overflow in arithmetic operations with trap ERROR_TREATMENT(see section 8.1.1), an attempt to access a value via a nil pointer using assign_with_mode, contents_with_mode or move_some(see section 7.3) or a stack overflow on procedure entry with PROCPROPS check_stack(see section 5.2.2) or a local_alloc_check.
Each of these exceptions have an ERROR_CODE ascribed to them, namely overflow, nil_access and stack_overflow. Each ERROR_CODE can be made into a distinct NAT by means of the constructor error_val; these NATs could be used, for example, to discriminate the different kinds of errors using a case construction.
When one of these exceptions is raised, the translator will arrange that a TOKEN, ~Throw, is called with the appropriate ERROR_CODE as its (sole) parameter. Given that every language has a different way of both characterising and handling exceptions, the exact expansion of ~Throw must be given by the producer for that language - usually it will involve doing a long_jump to some label specifying a signal handler and translating the ERROR_CODE into its language-specific representation.
The expansion of ~Throw forms part of the language specific environment required for the translation of TDF derived from the language, just as the exact shape of FILE must be given for the translation of C.