In this chapter, the REXX concept of "conditions" is described. Conditions allow the programmer to handle abnormal control flow, and enable him to assign special pieces of REXX code to be executed in case of certain incidences.


  1. What are Conditions

In this section, the concept of "conditions" are explained: What they are, how they work, and what they mean in programming.

    1. What Do We Need Conditions for?

      1. Terminology

First, let's look at the terminology used in this chapter. If you don't get a thorough understanding of these terms, you will probably not understand much of what is said in the rest of this chapter.


[Incident:]

A situation, external or internal to the interpreter, which it is required to respond to in certain pre-defined manners. The interpreter recognizes incidents of several different types. The incident will often have a character of "suddenness", and will also be independent of the normal control flow.

[Event:]

Data Structure describing one incident, used as a descriptor to the incident itself.

[Condition:]

Names the REXX concept that is equivalent to the incident.

[Raise a Condition:]

The action of transforming the information about an incident into an event. This is done after the interpreter senses the condition. Also includes deciding whether to ignore or produce an event.

[Handle a Condition:]

The act of executing some pre-defined actions as a response to the event generated when a condition was raised.

[(Condition) Trap:]

Data Structure containing information about how to handle a condition.

[(Trap) State:]

Part of the condition trap.

[(Condition) Handler:]

Part of the condition trap, which points to a piece of REXX code which is to be used to handle the condition.

[(Trap) Method:]

Part of the condition trap, which defined how the condition handler is to be invoked to handle the condition.

[Trigger a Trap:]

The action of invoking a condition handler by the method specified by the trap method, in order to handle a condition.

[Trap a Condition:]

Short of trigger a trap for a particular condition.

[Current Trapped Condition:]

The condition currently being handled. This is the same as the most recent trapped condition on this or higher procedure level.

[(Pending) Event Queue:]

Data Structure storing zero or more events in a specific order. There are only one event queue. The event queue contains events of all condition types, which have been raised, but not yet handled.

[Default-Action:]

The pre-defined default way of handling a condition, taken if the trap state for the condition raised is OFF.

[Delay-Action:]

The pre-defined default action taken when a condition is raised, and the trap state is DELAY.

  1. The Mythical Standard Condition

REXX Language Level 4.00 has six different conditions, and REXX Language Level 5.00 has seven. However, each of these is a special case of a mythical, non-existing, standard condition. In order to better understand the real conditions, we start by explaining how a standard condition work.


In the examples below, we will call our non-existing standard condition MYTH. Note that these examples will not be executable on any REXX implementation.

    1. Information Regarding Conditions (data structures)

There are mainly five conceptual data structures involved in conditions.


[Event queue.]

There is one interpreter-wide queue of pending conditions. Raising a condition is identical to adding information about the condition to this queue (FIFO). The order of the queue is the same order in which the conditions are to be handled.


Every entry in the queue of pending conditions contains some information about the event: the line number of the REXX script when the condition was raised, a descriptive text and the condition type.

[Default-Action.]

To each, there exists information about the default-action to take if this condition is raised but the trap is in state OFF. This is called the "default-action". The standard default-action is to ignore the condition, while some conditions may abort the execution.

[Delay-Action.]

Each condition will also have delay-action, which tells what to do if the condition is raised when condition trap is in state DELAY. The standard delay-action is to queue the condition in the queue of pending conditions, while some conditions may ignore it.

[Condition traps.]

For each condition there is a trap which contains three pieces of status information: the state; the handler; and the method. The state can be ON, OFF or DELAY.


The handler names the REXX label in the start of the REXX code to handle the event. The method can be either SIGNAL or CALL, and denotes the method in which the condition is to be handled. If the state is OFF, then neither handler nor method is defined.

[Current Trapped Condition.]

This is the most recently handled condition, and is set whenever a trap is triggered. It contains information about method, which condition, and a context-dependent description. In fact, the information in the current trapped condition is the same information that was originally put into the pending event queue.


Note that the event queue is a data structure connected to the interpreter itself. You operate on the same event queue, independent of subroutines, even external ones. On the other hand, the condition traps and the current trapped condition are data structures connected to each single routine. When a new routine is called, it will get its own condition traps and a current trapped condition. For internal routines, the initial values will be the same values as those of the caller. For external routines, the values are the defaults.


The initial value for the event queue is to be empty. The default-action and the delay-action are static information, and will always retain their values during execution. The initial values for the condition traps are that they are all in state OFF. The initial value for the current trapped condition is that all information is set to the nullstring to signalize that no condition is currently being trapped.

    1. How to Set up a Condition Trap

How do you set the information in a condition trap? You do it with a SIGNAL or CALL clause, with the ON or OFF subkeyword. Remember that a condition trap contain three pieces of information? Here are the rules for how to set them:



The trap is said to be "enabled" when the state is either ON or DELAY, and "disabled" when the state is OFF. Note that neither the event queue, nor the current trapped condition can be set explicitly by REXX clauses. They can only be set as a result of incidents, when raising and trapping conditions.


It sounds very theoretical, doesn't it? Look at the following examples, which sets the trap MYTH:


/* 1 */ SIGNAL ON MYTH NAME TRAP_IT

/* 2 */ SIGNAL OFF MYTH

/* 3 */ CALL ON MYTH NAME MYTH_TRAP

/* 4 */ CALL ON MYTH

/* 5 */ CALL OFF MYTH


Line 1 sets state to ON, method to SIGNAL and handler to TRAP_IT. Line 2 sets state to OFF, handler and method becomes undefined. Line 3 sets state to ON, method to CALL, and handler to MYTH_TRAP. Line 4 sets state to ON, method to CALL and handler to MYTH (the default). Line 5 sets state to OFF, handler and method become undefined.


Why should method and handler become undefined when the trap in state OFF? For two reasons: firstly, these values are not used when the trap is in state OFF; and secondly, when you set the trap to state ON, they are redefined. So it really does not matter what they are in state OFF.


What happens to this information when you call a subroutine? All information about traps are inherited by the subroutine, provided that it is an internal routine. External routines do not inherit any information about traps, but use the default values. Note that the inheritance is done by copying, so any changes done in the subroutine (internal or external), will only have effect until the routine returns.

    1. How to Raise a Condition

How do you raise a condition? Well, there are really no explicit way in REXX to do that. The conditions are raised when an incident occurs. What sort of situations that is, depends on the context. There are in general three types of incidents, classified by the origin of the event:



For conditions trapped by method CALL, standard REXX requires an implementation to at least check for incidents and raise condition at clause boundaries. (But it is allowed to do so elsewhere too; although the actual triggering must only be performed at clause boundaries.) Consequently, you must be prepared that in some implementations, conditions trappable by method CALL might only be raised (and the trap triggered) at clause boundaries, even if they are currently trapped by method SIGNAL.


The seven standard conditions will be raised as result of various situations, read the section describing each one of them for more information.


+--------+ +----------+ /-----\ +--------+

|Incident| |Condition | / Trap \ Off |Default |

| occurs | -> |is raised | -> \ State / --> | action |

+--------+ +----------+ \-----/ +--------+

/ |

/On |Delay

/ |

/ v

+--------+/ /---------\ +------+

| Queue | Yes /DelayAction\ No |Ignore|

|an event| <-- \is queue? / --> | event|

+--------+ \---------/ +------+

|

v

/-------\

/Method is\

\ CALL? /

\-------/ \

/ \

/No Yes\

/ \ /---------\

/ \ / \

+-----------+ +-----------+ \ Decision /

| Set state | | Set state | \---------/

| OFF | | DELAY |

+-----------+ +-----------+ +-----------+

| Trigger | | | | |

| trap | | Return | | Action |

+-----------+ +-----------+ +-----------+


The triggering of a condition



When an incident occurs and the condition is raised, the interpreter will check the state of the condition trap for that particular condition at the current procedure level.





This process has be shown in the figure above. It shows how an incident makes the interpreter raise a condition, and that the state of the condition trap determines what to do next. The possible outcomes of this process are: to take the default-action; to ignore if delay-action is not to queue; to just queue and the continue execution; or to queue and trigger the trap.

    1. How to Trigger a Condition Trap

What are the situations where a condition trap might be triggered? It depends on the method currently set in the condition trap.


If the method is SIGNAL, then the interpreter will explicitly trigger the relevant trap when it has raised the condition after having sensed the incident. Note that only the particular trap in question will be triggered in this case; other traps will not be triggered, even if the pending event queue is non-empty.


In addition, the interpreter will at each clause boundary check for any pending events in the event queue. If the queue is non-empty, the interpreter will not immediately execute the next normal statement, but it will handle the condition(s) first. This procedure is repeated until there are no more events queued. Only then will the interpreter advance to execute the next normal statement.


Note that the REXX standard does not require the pending events to be handled in any particular order, although the model shown in this documentation it will be in the order in which the conditions were raised. Consequently, if one clause generates several events that raise conditions before or at the next clause boundary, and these conditions are trapped by method CALL. Then, the order on which the various traps are triggered is implementations-dependent. But the order in which the different instances of the same condition is handled, is the same as the order of the condition indicator queue.

    1. Trapping by Method SIGNAL

Assume that a condition is being trapped by method SIGNAL, that the state is ON and the handler is MYTH_TRAP. The following REXX clause will setup the trap correctly:


SIGNAL ON MYTH NAME MYTH_TRAP


Now, suppose the MYTH incident occurs. The interpreter will sense it, queue an event, set the trap state to OFF and then explicitly trigger the trap, since the method is SIGNAL. What happens when the trap is triggered?



That's it for method SIGNAL. If you want to continue trapping condition MYTH, you have to execute a new SIGNAL ON MYTH clause to set the state of the trap to ON. But no matter how quick you reset the trap, you will always have a short period where it is in state OFF. This means that you can not in general use the method SIGNAL if you really want to be sure that you don't loose any MYTH events, unless you have some control over when MYTH condition may arise.


Also note that since the statement being executed is terminated; all active loops on the current procedure level are terminated; and the only indication where the error occurred is the line number (the line may contain several clauses), then it is in general impossible to pick up the normal execution after a condition trapped by SIGNAL. Therefore, this method is best suited for a "graceful death" type of traps. If the trap is triggered, you want to terminate what you were doing, and pick up the execution at an earlier stage, e.g. the previous procedure level.

    1. Trapping by Method CALL

Assume that the condition MYTH is being trapped by method CALL, that the state is ON and the handler is MYTH_HANDLER.


The following REXX clause will setup the trap correctly:


CALL ON MYTH NAME MYTH_HANDLER


Now, suppose that the MYTH incident occurs. When the interpreter senses that, it will raise the MYTH condition. Since the trap state is ON and the trap method is CALL, it will create an event and queue it in the pending event queue and set the trap state to DELAY. Then it continues the normal execution. The trap is not triggered before the interpreter encounters the next clause boundary. What happens then?



During the triggering of a trap by method CALL at a clause boundary, the state of the trap is not normally changed, it will continue to be DELAY, as was set when the condition was raised. It will continue to be in state DELAY until return from the condition handler, at which the state of the trap in the caller will be changed to ON. If, during the execution of the condition trap, the state of the condition being trapped is set, that change will only last until the return from the condition handler.


Since new conditions are generally delayed when an condition handler is executing, new conditions are queued up for execution. If the trap state is changed to ON, the pending event queue will be processed as named at the next clause boundary. If the state is changed to OFF, the default action of the conditions will be taken at the next clause boundary.

    1. The Current Trapped Condition

The interpreter maintains a data structure called the current trapped condition. It contains information relating the most recent condition trapped on this or higher procedure level. The current trapped condition is normally inherited by subroutines and functions, and restored after return from these.



The information stored in the current trapped condition can be retrieved by the built-in function CONDITION(). The syntax format of this function is:


CONDITION(option)


where option is an option string of which only the first character matters. The valid options are: Condition name, Description, Instruction and State. These will return: the name of the current trapped condition; the descriptive text; the method; and the current state of the condition, respectively. The default option is Instruction. See the documentation on the built-in functions. See also the description of each condition below.


Note that the State option do not return the state at the time when the condition was raised or the trap was triggered. It returns the current state of the trap, and may change during execution. The other information in the current trapped condition may only change when a new condition is trapped at return from subroutines.

  1. The Real Conditions

We have now described how the standard condition and condition trap works in REXX. Let's look at the seven conditions defined which do exist. Note that none of these behaves exactly as the standard condition.

    1. The SYNTAX condition

The SYNTAX condition is of internal origin, and is raised when any syntax or runtime error is discovered by the REXX interpreter. It might be any of the situations that would normally lead to the abortion of the program and the report of a REXX error message, except error message number 4 (Program interrupted), which is handled by the HALT condition.


There are several differences between this condition and the standard condition:



The descriptive text returned by CONDITION() when called with the Description option for condition SYNTAX, is implementation dependent, and may also be a nullstring. Consult the implementation-specific documentation for more information.

    1. The HALT condition

The HALT condition of external origin, which is raised as a result of an action from the user, normally a combination of keys which tries to abort the program. Which combination of keys will vary between operating systems. Some systems might also simulate this event by other means than key combinations. Consult system for more information.


The differences between HALT and the standard condition are:



The standard allows the interpreter to limit the search for situations that would set the HALT condition, to clause boundaries. As a result, the response time from pressing the key combination to actually raising the condition or triggering the trap may vary, even if HALT is trapped by method SIGNAL. If a clause for some reason has blocked execution, and never finish, you may not be able to break the program.


The descriptive text returned by CONDITION() when called with the Description option for condition HALT, is implementation dependent, and may also be a nullstring. In general, it will describe the way in which the interpreter was attempted halted, in particular if there are more than one way to do raise a HALT condition. Consult the implementation documentation for more information.

    1. The ERROR condition

The ERROR is a condition of mixed origin, it is raised when a command returns a return value which indicates error during execution. Often, commands return a numeric value, and a particular value is considered to mean success. Then, other values might raise the ERROR condition.


Differences between ERROR and the standard condition:



Unfortunately, there is no universal standard on return values. As stated, they are often numeric, but some operating system use non-numeric return values. For those which do use numeric values, there are no standard telling which values and ranges are considered errors and which are considered success. In fact, the interpretation of the value might differ between commands within the same operating system.


Therefore, it is up to the REXX implementation to define which values and ranges that are considered errors. You must expect that this information can differ between implementations as well as between different environments within one implementation.


The descriptive text returned by CONDITION() when called with the Description option for condition ERROR, is the command which caused the error. Note that this is the command as the environment saw it, not as it was entered in the REXX script source code.

    1. The FAILURE condition

The FAILURE is a condition of mixed origin, it is raised when a command returns a return value which indicates failure during execution, abnormal termination, or when it was impossible to execute a command. It is a subset of the ERROR condition, and if it is in state OFF, then the ERROR condition will be raised instead. But note that an implementation is free to consider all return codes from commands as ERRORs, and none as FAILURES. In that case, the only situation where a FAILURE would occur, is when it is impossible to execute a command.


Differences between FAILURE and the standard condition:



As for ERROR, there is no standard the defines which return values are failures and which are errors. Consult the system and implementation independent documentation for more information.


The descriptive text returned by CONDITION() when called with the Description option for condition FAILURE, is the command which caused the error. Note that this is the command as the environment saw it, not as it was entered in the REXX script source code.

    1. The NOVALUE condition

The NOVALUE condition is of internal origin. It is raised in some circumstances if the value of an unset symbol (which is not a constant symbol) is requested. Normally, this would return the default value of the symbol. It is considered bad programming practice not to initialize variables, and setting the NOVALUE condition is one method of finding the parts of your program that uses this programming practice.


Note however, there are only three instances where this condition may be raised: that is when the value of an unset (non-constant) symbol is used requested: in an expression; after the VAR subkeyword in a PARSE clause; and as an indirect reference in either a template, a DROP or a PROCEDURE clause. In particular, this condition is not raised if the VALUE() or SYMBOL() built-in functions refer to an unset symbol.


Differences between NOVALUE and the standard condition are:



The descriptive text returned by calling CONDITION() with the Description option, is the derived (i.e. tail has be substituted if possible) name of the variable that caused the condition to be raised.

    1. The NOTREADY condition

The condition NOTREADY is a condition of mixed origin. It is raised as a result of problems with stream I/O. Exactly what causes it, may vary between implementations, but some of the more probable causes are: waiting for more I/O on transient streams; access to streams not allowed; I/O operation would block if attempted; etc. See the chapter; Stream Input and Output for more information.


Differences between NOTREADY and the standard condition are:



The descriptive text returned by CONDITION() when called with the Description option for condition NOTREADY, is the name of the stream which caused the problem. This is probably the same string that you used as the first parameter to the functions that operates on stream I/O. For the default streams (default input and output stream), the string returned by CONDITION() will be nullstrings.


Note that if the NOTREADY trap is in state DELAY, then all I/O for files which has tried to raise NOTREADY within the current clause will be simulated as if operation had succeeded.

    1. The LOSTDIGITS condition

The condition LOSTDIGITS was introduced in Language Level 5.00. It is raised as a result of any arithmetic operation which results in the loss of any digits. i.e. If the number of significant digits in the result of an artihmetic operation would exceed the currently defined number of digits via NUMERIC DIGITS, then the LOSTDIGITS condition is raised.


Differences between LOSTDIGITS and the standard condition are:



The descriptive text returned by CONDITION() when called with the Description option for condition NOTREADY, is the name of the stream which caused the problem. This is probably the same string that you used as the first parameter to the functions that operates on stream I/O. For the default streams (default input and output stream), the string returned by CONDITION() will be nullstrings.


  1. Further Notes on Conditions

    1. Conditions under Language Level 3.50

The concept of conditions was very much expanded from REXX language level 3.50 to level 4.00. Many of the central features in conditions are new in level 4.00, these include:


    1. Pitfalls when Using Condition Traps

There are several pitfalls when using conditions:


    1. The Correctness of this Description

In this description of conditions in REXX, I have gone further in the description of how conditions work, their internal data structures, the order in which things are executed etc., than the standard does. I have tried to interpret the set of distinct statements that is the documentation on condition, and design a complete and consistent system describing how such conditions work. I have done this to try to clarify an area of REXX which at first glance is very difficult and sometimes non-intuitive.


I hope that the liberties I have taken have helped describe conditions in REXX. I do not feel that the adding of details that I have done in any way change how conditions work, but at least I owe the reader to list which concepts that are genuine REXX, and which have been filled in by me to make the picture more complete. These are not a part of the standard REXX.



I really hope that these changes has made the concept of conditions easier to understand, not harder. Please feel free to flame me for any of these which you don't think is representative for REXX.

  1. Conditions in Regina

Here comes documentation that are specific for the Regina implementation of REXX.

    1. How to Raise the HALT condition

The implementation connect the HALT condition to an external event, which might be the pressing of certain key combination. The common conventions of the operating system will dictate what that combination of keystrokes is.


Below is a list, which describes how to invoke an event that will raise the HALT condition under various the operating systems which Regina runs under.


  1. Possible Future extensions


Table of Contents

Conditions 1

1What are Conditions 1

1.1What Do We Need Conditions for? 1

1.1.1Terminology 1

2The Mythical Standard Condition 2

2.1Information Regarding Conditions (data structures) 2

2.2How to Set up a Condition Trap 3

2.3How to Raise a Condition 4

2.4How to Trigger a Condition Trap 6

2.5Trapping by Method SIGNAL 6

2.6Trapping by Method CALL 7

2.7The Current Trapped Condition 8

3The Real Conditions 9

3.1The SYNTAX condition 9

3.2The HALT condition 10

3.3The ERROR condition 10

3.4The FAILURE condition 11

3.5The NOVALUE condition 11

3.6The NOTREADY condition 12

3.7The LOSTDIGITS condition 12

4Further Notes on Conditions 13

4.1Conditions under Language Level 3.50 13

4.2Pitfalls when Using Condition Traps 13

4.3The Correctness of this Description 13

5Conditions in Regina 14

5.1How to Raise the HALT condition 15

6Possible Future extensions 15


13