Blocks¶
A block represents the basic 4GL feature which structures or groups code. There are two major types of block: top level blocks and inner blocks. A block groups code for several purposes:
- flow of control
- transaction processing
- scoping related operations (certain 4GL resources such as buffers or frames have behavior that depends on the entry, iteration and exit from the block to which they are associated or "scoped")
This chapter will discuss the Java replacement for inner block processing, block properties, conditions and related facilities. For details on how the control-flow features convert, please see the Control Flow chapter. For details on transaction processing and UNDO, see the Transactions chapter. For details on query-related semantics, see the chapters in Part 5. For details about the top-level blocks, details are at _Top Level Blocks chapter.
Introduction¶
In the 4GL, code can be structured into related sections called blocks. In Progress v11, the following types of blocks are available:
Block | Type | Supported |
---|---|---|
external procedure | top-level | Yes |
internal procedure | top-level | Yes |
user-defined functions | top-level | Yes |
class method (instance or static) | top-level | Yes |
class property getter/setter (instance or static) | top-level | Yes |
class constructor (instance or static) | top-level | Yes |
class destructor | top-level | Yes |
trigger | top-level | Yes |
DO |
inner | Yes |
FOR |
inner | Yes |
EDITING |
inner | Yes |
REPEAT |
inner | Yes |
None of the block types introduced after Progress v11 are supported at this time.
After conversion, the information for each block is split into a "header" and a "body". All code that is executed in a block is contained in the block body. The description of the block itself and any explicitly controlled options are defined in the block header.
The lines of code in a block's body are executed "top to bottom". All code in the same block will share the same behavior in terms of transaction processing. Absent any explicit changes to the flow of control (by language statements that cause a flow of control change) or the generation of a condition (such as an ERROR
) which will cause the flow of control to change, all code in a given block is executed (if the block body is entered) or all the code is not executed (if the block body is not entered).
Some blocks can be made to iteratively execute the code contained in the block body. Such blocks are known as looping blocks. Progress provides blocks that can explicitly loop based on programmer controlled expressions in the block header. All of these looping blocks are supported in the converted code.
Blocks support transaction processing which is the ability to undo (reverse) edits to variables or to the database which occurred within the scope of that block, when a condition is raised or when an explicit UNDO
statement is encountered. Some blocks can have their support for transactions explicitly coded via the TRANSACTION
keyword in the block header.
All blocks except for the DO
block have some default behavior when abnormal conditions occur. Inner blocks (except for EDITING
) allow that behavior to be explicitly specified via ON phrases
in the block header.
Some blocks provide retry support which is the re-execution of a block with the same (original) state as the optional action in response to an abnormal condition. The support for retry is not limited to looping blocks. Even non-looping blocks can support retry.
Some blocks can take parameters and be called explicitly (external procedures, internal procedures, user-defined functions, class methods, class constructors), are otherwise invoked as a callback (triggers), on exception (CATCH
), or when the block terminates (FINALLY
). Triggers are special in that they are top-level blocks but they can only be invoked indirectly in response to an event (usually associated with the user interface but that is not required) that has occurred. In this document, such blocks are called "top-level blocks".
Other blocks (all forms of DO
, REPEAT
, FOR
, EDITING
) are not top-level blocks and as such can only be contained inside another block (top-level or otherwise). In this document, such blocks are called "inner blocks". Inner blocks are the only kind of block that can be labeled and that label can then be referenced in language statements that explicitly change the flow of control such as UNDO
statement, LEAVE
statement, NEXT
statement and in ON phrases
.
Some blocks can have their definitions made nested inside another block. The top level blocks (except for triggers) cannot be nested. Inner blocks can be nested. Do not confuse nesting with recursion. The top level blocks can be called recursively (they can call themselves directly or indirectly causing multiple instantiations on the call stack). But the top level blocks cannot have their definitions made on a nested basis. For example, an external procedure cannot be defined inside another external procedure.
The following summarizes these behaviors:
Block Type | Looping | Default Transaction Level | TRANSACTION Option | ON Phrases | Retry Support | Parameters | Callable (Via Name) | Labeled | Nestable |
---|---|---|---|---|---|---|---|---|---|
external procedure | no | sub-transaction | no | no | yes | yes | yes (filename in a RUN statement) | no | no |
internal procedure | no | sub-transaction | no | no | yes | yes | yes (RUN statement) | no | no |
function | no | sub-transaction | no | no | yes | yes | yes (in an expression) | no | no |
class method | no | sub-transaction | no | no | yes | yes | yes (in an expression) | no | no |
class constructor | no | sub-transaction | no | no | yes | yes | yes (in an expression) | no | no |
class destructor | no | sub-transaction | no | no | yes | yes | yes (in an expression) | no | no |
class property getter/setter | no | sub-transaction | no | no | yes | yes | yes (in an expression) | no | no |
trigger | no | sub-transaction | no | no | yes | no | no | no | yes |
DO |
no | no transaction | yes | yes | yes (depending on options) | no | no | yes | yes |
DO TO DO WHILE DO TO WHILE |
yes | no transaction | yes | yes | yes (depending on options) | no | no | yes | yes |
REPEAT (all forms) |
yes | sub-transaction | yes | yes | yes | no | no | yes | yes |
FOR FOR FIRST FOR LAST |
no (unless there is an additional table defined with EACH table) | sub-transaction | yes | yes | yes | no | no | yes | yes |
FOR EACH |
yes | sub-transaction | yes | yes | yes | no | no | yes | yes |
EDITING |
yes | sub-transaction | no | no | yes | no | no | yes | yes (editing blocks for other frames can be directly contained and any kind of editing block can be indirectly contained) |
TODO: CATCH/FINALLY
Block Conversion¶
The conversion process in FWD will map each encountered block to an API in the BlockManager
class. This class is used by FWD to provide a 4GL-specific runtime implementation for any Progress block. Basically, it provides an external API that allows the Progress-compatible code block semantics to be provided with a minimum of code in the caller. This class encodes the "scaffolding" necessary to properly process code blocks with the right behavior. The external interface is designed to be statically imported to make access simple, short and easy to read. The API has been designed to minimize name conflicts. The scaffolding code uses the TransactionManager
extensively to implement the proper behavior.
There is an exception to the rule presented above - if a looping block can be optimized to use directly the Java looping statements, the BlockManager
APIs will be bypassed and a compatible Java statement will be emitted instead. Please see the Control Flow chapter of this book for when and how this is done.
The actual 4GL code for each block will be emitted as lambda functions for an instance of the Block
class. Any runtime API in the BlockManager
class which defines a 4GL block will take as parameter a Block
instance, with the 4GL code to be executed. Note that both classes mentioned here and all other block-related classes are part of the com.goldencode.p2j.util
package. As any converted program uses at least one or more APIs in BlockManager
, the methods in this class will be statically imported in each and every converted program.
The exported APIs from the BlockManager
class will be presented together with each 4GL block in the next sections. Here, we will focus on the Block
class: when and why lambda expressions are emitted at its constructor.
The Block
class allows the user to specify the following lambda expressions, each having its own purpose in the final converted code: Init()
, Enter()
, Body()
and Fini()
. From these, only Body()
is mandatory to specify, when there is actual code being executed in that block. The others are optional and emitted only in special cases.
Init()¶
Provides a "callback" to initialize state once before the body gets executed. This is guaranteed to be called AFTER the transaction manager scope opens but BEFORE the block body is ever entered. It will only be called once no matter whether the block iterates or whether there is a retry.
All block-specific fields generated during conversion (to help maintain the state of this block) will be emitted as fields declared in the lambda expression which holds the 4GL code. Mainly, here will be added the code to register record buffers, frames and variables for scope processing, initialize queries and other block-related state.
Enter()¶
This lambda is emitted in the converted code only in some special cases of the FOR
block. It provides a "callback" to execute user-defined logic at the top of the block body but before the block body is executed. This is guaranteed to be called AFTER the transaction manager scope opens but BEFORE the block body is entered. It will be called once per iteration of the block or once if the block is not iterating. It WILL be called on retry.
Body()¶
This implements the delegated code to be executed as part of this instance.
Fini()¶
This implements the delegated code to be executed as part of the FINALLY
block.
Looping Blocks¶
The looping blocks can be made to iteratively execute the code contained in the block body. Progress provides blocks that can explicitly loop based on programmer controlled expressions in the block header - these blocks will be explained in the next sections.
Almost all of the looping blocks are managed by BlockManager
APIs. But, some looping blocks are converted to compatible Java statements - in a runtime perspective, these kind of blocks are called non-managed blocks. As during the execution the control-flow can switch from a nested managed to an outer non-managed block, any managed inner block which is enclosed in one or more non-managed blocks must be aware of them - for more details about how the control-flow can switch in such cases and how the converted code looks like, please see the Control Flow chapter of this book.
The looping blocks share some common properties, and their syntax looks like:
[ label: ] <block-type> <query-related clauses> [ variable = expression1 TO expression2 [ BY k ] ] [ WHILE expression ] [ TRANSACTION ] [ on-error-phrase ] [ on-endkey-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] [ frame-phrase ] ... END.
here:
<block-type>
is one ofDO
,REPEAT
orFOR
<query-related clauses>
are query specifications which are distinct for each block type- the
TO/BY
andWHILE
clauses are converted in a similar manner for almost all cases, please see the TO Clause and WHILE Clause sections in the Control Flow chapter of this book. - The
ON ... phrase
clauses define the action which will be performed when the specified event is caught by this block. For more details, see the ON Phrase section of this chapter.
ON Phrase¶
The ON phrase
is block property used by 4GL to handle an unusual event that may occur during the course of processing, also called a condition. Although each looping block handles some conditions by default, explicit condition handling can be specified via the ON phrase
, which has the following syntax:
ON <condition> UNDO [ label1 ] [ , LEAVE [ label2 ] | , NEXT [ label2 ] | , RETRY [ label1 ] | , RETURN { ERROR | NO-APPLY } [ return-string ] ]
or
ON ERROR UNDO [label1], THROW
where <condition>
is one of the ERROR
, ENDKEY
, QUIT
or STOP
conditions.
This section will explain the conditions only in a conversion point of view. For more details about how each condition works and how the implicit labels are resolved, please see the Conditions section of the Control Flow chapter of this book.
ON phrases
which specify conditions handled by a block are converted to OnPhrase
instances added to an array which is passed to the BlockManager
APIs via the on
parameter. The OnPhrase
class is used to instantiate an object which is mapped to a single ON phrase
, by passing these parameters to its constructor (they are optional, depending on how the ON phrase
is specified):
condition
is a value from theBlockManager.Condition
enum, usingCondition.ERROR
,Condition.ENDKEY
,Condition.STOP
andCondition.QUIT
to map each of the possible handled conditions, respectively.action
is a value from theBlockManager.Action
enum, usingAction.LEAVE
,Action.NEXT
,Action.RETRY
,Action.RETURN_NORMAL
,Action.RETURN_CONSUME
,Action.RETURN_ERROR
andAction.THROW
, to map each of the possible actions, respectively.label
is the label of the block that is the target of the undovalue
is the data to be returned to the caller of the top-level block from which the control flow is going to return.
The array passed as a parameter to the BlockManager
APIs is built using this structure:
OnPhrase[] onPhrase<index> = new OnPhrase[] { new OnPhrase(<condition>, <action>, <label> [, <value> ]), [ new OnPhrase(<condition>, <action>, <label> [, <value> ]), ] ... };
where <index>
is an unique counter, starting from 0, which uniquely identifies the OnPhrase[]
arrays emitted for the current 4GL program. The <condition>
, <action>
, <label>
and <value>
are the OnPhrase
's constructor parameters, as explained above.
When specifying an ON phrase
for a certain block, it is not mandatory to specify the label or the explicit action to be taken when that condition is raised. Instead, 4GL uses a complex set of rules to determine the block which needs to be undone and the block to which the action needs to be applied - the conversion rules will always disambiguate the targeted block label and the performed action and in the converted code they will always be explicitly set. Details about this can be found in the Determining the Target and Meaning of UNDO, LEAVE, NEXT and RETRY section of the Control Flow chapter of this book.
Example 1:
do on error undo: ... end.
Converted code:
OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.ERROR, Action.RETRY, "blockLabel0") }; doBlock(TransactionType.SUB, "blockLabel0", onPhrase0, new Block((Body) () -> { ... })); });
Details:
For a DO
block, the ERROR
condition is converted to a default RETRY
action.
Example 2:
repeat on endkey undo: ... end.
Converted code:
OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.ENDKEY, Action.RETRY, "loopLabel0") }; repeat("loopLabel0", onPhrase0, new Block((Body) () -> { ... }));
Details:
For an ENDKEY
condition, the RETRY
action is used as default too.
Example 3:
repeat on stop undo, leave: ... end.
Converted code:
OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.STOP, Action.LEAVE, "loopLabel0") }; repeat("loopLabel0", onPhrase0, new Block((Body) () -> { ... }));
Details:
Here, a LEAVE
action for the current block is executed when a STOP
condition is raised.
Example 4:
repeat on error undo, return, on endkey undo, next: ... end.
Converted code:
OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.ERROR, Action.RETRY, "loopLabel0"), new OnPhrase(Condition.ENDKEY, Action.NEXT, "loopLabel0") }; repeat("loopLabel0", onPhrase0, new Block((Body) () -> { message(i); }));
Details:
When multiple conditions are specified for a block, each one is emitted on a distinct index in the parameter array.
DO Block¶
The DO
block in 4GL is the simplest way to group code together (as for the body of a trigger block or for a then
clause). The syntax of this block is:
[ label : ] DO { [ FOR record [ , record ] ... ] } [ preselect-phrase ] [ query-tuning-phrase ] [ variable = expression1 TO expression2 [ BY k ] ] [ WHILE expression ] [ TRANSACTION ] [ STOP-AFTER expression ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] { [ frame-phrase ] } : ... END.
where the query-tuning-phrase
is not yet supported by FWD.
This section will explain how the DO
block gets converted and will detail the clauses only from a non control-flow point of view; for insight details, please see DO Block section from the Control Flow chapter of this book.
Depending on the options set to the DO
block, this will either convert to a simple Java block or to some other Java statement (as explained in the Control Flow chapter of this book). In all other cases, the converted code will look like this:
<doBlock>([ <transaction type>, ] <label>, [ /* other parameters */, ] new Block((Init) () -> { /* state initialization code */ }, (Body) () -> { /* converted 4GL code */ }));
where:
<doBlock>
is one of thedoBlock
,doTo
,doWhile
ordoToWhile
APIs inBlockManager
- the
<transaction type>
is an optional parameter which will explicitly set the transaction type (please see the Transactions chapter of this book for when it gets emitted). It defaults to no-transaction. - the
<label>
is the block's label (for details about how it gets converted, please see the Control Flow chapter of this book). - depending on its configuration, other parameters will be emitted in the converted code between the
label
and theBlock
implementation. - the
init()
lambda expression contains the initialization code for this block: variables, buffers, queries, streams, etc. - the
body()
lambda expression contains the converted 4GL code for this block.
The STOP-AFTER
clause is converted as a BlockManager.stopAfter(<seconds>);
API call in the Body()
lambda expression. Any such blocks will always be converted as blocks managed via BlockManager
APIs, and not as Java blocks.
When the DO
block has clauses which don't allow it to be converted to a Java statement, the conversion rules will emit an API call to one of the BlockManager.do*
methods. The conversion rules will emit one of these APIs considering how the DO
block is used and depending on which of its associated clauses are present:
BlockManager API | DO Block Syntax | Details |
---|---|---|
doBlock |
[ label : ] DO { [ FOR record [ , record ] ... ] } [ preselect-phrase ] [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] { [ frame-phrase ] } : ... END. |
In cases when the DO block contains at least one of the on-phrases or one of the FOR, preselect-phrase or TRANSACTION clauses, the doBlock API will be emitted, with the condition that it doesn't contain the TO or WHILE clauses.In a few words, this API is emitted whenever the DO block can't be converted to a simple Java statement and it doesn't contain any looping clauses. |
doTo |
[ label : ] DO { [ FOR record [ , record ] ... ] } [ preselect-phrase ] variable = expression1 TO expression2 [ BY k ] [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] { [ frame-phrase ] } : ... END. |
This API is emitted whenever the TO clause exists (but not the WHILE clause) and also it has one of the database-related clauses or at least one on-phrase is present. |
doWhile |
[ label : ] DO { [ FOR record [ , record ] ... ] } [ preselect-phrase ] WHILE expression [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] { [ frame-phrase ] } : ... END. |
Similar to doTo , this API is emitted whenever the WHILE clause exists (but not the TO clause) and also it has one of the database-related clauses or at least one on-phrase is present. |
doToWhile |
[ label : ] DO { [ FOR record [ , record ] ... ] } [ preselect-phrase ] variable = expression1 TO expression2 [ BY k ] WHILE expression [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] { [ frame-phrase ] } : ... END. |
This API is used when both the TO and WHILE clauses are both present and also it has one of the database-related clauses or at least one on-phrase is present. |
For each of the above APIs, BlockManager
defines several versions which take different parameters (depending on its configuration):
- for
BlockManager.doBlock
the following APIs are defined:
doBlock(String label, Block block) doBlock(String label, OnPhrase[] on, Block block) doBlock(String[] enclosing, String label, Block block) doBlock(String[] enclosing, String label, OnPhrase[] on, Block block) doBlock(TransactionType lvl, String label, Block block) doBlock(TransactionType lvl, String label, OnPhrase[] on, Block block) doBlock(TransactionType lvl, String[] enclosing, String label, Block block) doBlock(TransactionType lvl, String[] enclosing, String label, OnPhrase[] on, Block block)
where:
lvl
is the transaction level to be honored. If missing, it defaults to no-transaction.enclosing
is the list of non-managed block names that enclose the current block.label
is the block name of the current block.on
is the list of user-defined on phrases.block
is the code to be executed.- for
BlockManager.doTo
the following APIs are defined:
doTo(String label, ToClause to, Block block) doTo(String label, ToClause to, OnPhrase[] on, Block block) doTo(String[] enclosing, String label, ToClause to, Block block) doTo(String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block) doTo(TransactionType lvl, String label, ToClause to, Block block) doTo(TransactionType lvl, String label, ToClause to, OnPhrase[] on, Block block) doTo(TransactionType lvl, String[] enclosing, String label, ToClause to, Block block) doTo(TransactionType lvl, String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block)
where the new parameters are:
to
is the clause that defines a loop condition. The loop control variable is initialized on the first pass through and on subsequent passes it is incremented by a given factor. Before the loop body is entered, the loop control variable is compared to the termination value (which is dynamically resolved at each loop iteration). When the termination condition is met, the loop ends.- for
BlockManager.doWhile
the following APIs are defined:
doWhile(String label, LogicalOp expr, Block block) doWhile(String label, LogicalOp expr, OnPhrase[] on, Block block) doWhile(String[] enclosing, String label, LogicalOp expr, Block block) doWhile(String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block) doWhile(TransactionType lvl, String label, LogicalOp expr, Block block) doWhile(TransactionType lvl, String label, LogicalOp expr, OnPhrase[] on, Block block) doWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, Block block) doWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block)
where the new parameters are:
expr
is the expression associated with theWHILE
clause that defines a loop condition. This expression is evaluated every time the loop body is about to be entered. The loop body will be entered unless the expression evaluates to false.- for
BlockManager.doToWhile
the following APIs are defined:
doToWhile(String label, ToClause to, LogicalOp expr, Block block) doToWhile(String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) doToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) doToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) doToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, Block block) doToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) doToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) doToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block)
where the expr
and the to
parameters have the same meaning as for the doWhile
and the doTo
APIs.
Example 1:
def var i as int. if i > 0 then do: message "positive". end.
Converted code:
if (_isGreaterThan(i, 0)) { message("positive"); }
Details:
When the DO
block is used only to group code together, it is converted to a simple Java block. The green color marks the 4GL and converted DO
block.
Example 2:
B1: do for book: message book.book-title. end.
Converted code:
doBlock("blockLabel0", new Block((Init) () -> { RecordBuffer.openScope(book); }, (Body) () -> { message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
In cases when the DO
block has record-scoping properties, the converted block must be managed by a BlockManager
API. In this case, the doBlock
API is emitted with the Init()
lambda expression containing the record scoping initialization code.
Example 3a:
b2: do: b3: do for book preselect each book: message book.book-title. leave b2. end. end.
Converted code:
b2: { PreselectQuery query0 = new PreselectQuery(); String[] enclBlocks0 = new String[] { "b2" }; doBlock(enclBlocks0, "b3", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { message((character) new FieldReference(book, "bookTitle").getValue()); leave("b2"); })); if (deferredLeave("b2")) break b2; }
Details:
When a non-managed block is referenced from an inner managed block, the managed block will take as parameter the list of enclosing non-managed blocks.
Example 3b:
do i = 1 to 10 stop-after 1: message i. end.
Converted code:
doTo("loopLabel1", toClause1, new Block((Body) () -> { stopAfter(1); message(i); }));
Details:
When a STOP-AFTER
clause is used, the block will convert to a BlockManager
API and stopAfter
will emit at the beginning of the block, before any other executed statement.
REPEAT Block¶
This block in its simple form it is used to execute a simple repeat (infinite loop) with the given configuration. Its syntax is:
[ label : ] REPEAT [ FOR record [ , record ] ... ] [ preselect-phrase ] [ query-tuning-phrase ] [ variable = expression1 TO expression2 [ BY k ] ] [ WHILE expression ] [ TRANSACTION ] [ STOP-AFTER expression ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] [ frame-phrase ] [ catch-block[ catch-block...]] [ finally-block ] : ... END.
where the query-tuning-phrase
is not supported.
There is no case when the REPEAT
block can be converted to a Java statement - in all cases, the conversion rules will emit a BlockManager.repeat*
API call and will look like this:
<repeatBlock>([ <transaction type>, ] <label>, [ /* other parameters */, ] new Block((Init) () -> { /* state initialization code */ /** CATCH blocks */ catchError(<qualified-error-type>.class, ex -> { ex_top.assign(ex); /** converted 4GL code for CATCH block */ }); }, (Body) () -> { /* converted 4GL code */ }, (Fini) () -> { /* converted 4GL code for FINALLY block */ }));
where:
<repeatBlock>
is one of therepeat
,repeatTo
,repeatWhile
orrepeatToWhile
APIs inBlockManager
- the
<transaction type>
is an optional parameter which will explicitly set the transaction type (please see the Transactions chapter of this book for when it gets emitted). It defaults to sub-transaction. - the
<label>
is the block's label (for details about how it gets converted, please see the Control Flow chapter of this book). - depending on its configuration, other parameters will be emitted in the converted code between the
label
and theBlock
implementation. - the
Init()
lambda expression contains the initialization code for this block: variables, buffers, queries, streams, etc. - the
Body()
lambda expression contains the converted 4GL code for this block. - the
Fini()
lambda expression contains the converted 4GL code for theFINALLY
block.
The conversion rules will emit one of the APIs defined by the BlockManager
class, considering how the REPEAT
block is used and depending on which of its associated clauses are present:
BlockManager API | REPEAT Block Syntax | Details |
---|---|---|
repeat |
[ label : ] REPEAT [ FOR record [ , record ] ... ] [ preselect-phrase ] [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] [ frame-phrase ] : ... END. |
In cases when the REPEAT block contains at least one of the on-phrases or one of the FOR, preselect-phrase or TRANSACTION clauses, the doBlock API will be emitted, with the condition that it doesn't contain the TO or WHILE <dfn>clauses</dfn>. |
repeatTo |
[ label : ] REPEAT [ FOR record [ , record ] ... ] [ preselect-phrase ] variable = expression1 TO expression2 [ BY k ] [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] [ frame-phrase ] : ... END. |
This API is emitted whenever the TO clause exists (but not the WHILE clause) and also it has one of the database-related clauses or at least one on-phrase is present. |
repeatWhile |
[ label : ] REPEAT [ FOR record [ , record ] ... ] [ preselect-phrase ] WHILE expression [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] [ frame-phrase ] : ... END. |
Similar to repeatTo , this API is emitted whenever the WHILE clause exists (but not the TO clause) and also it has one of the database-related clauses or at least one on-phrase is present. |
repeatToWhile |
[ label : ] REPEAT [ FOR record [ , record ] ... ] [ preselect-phrase ] [ query-tuning-phrase ] variable = expression1 TO expression2 [ BY k ] WHILE expression [ TRANSACTION ] [ on-endkey-phrase ] [ on-error-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] [ frame-phrase ] : ... END. |
This API is used when both the TO and WHILE clauses are both present and also it has one of the database-related clauses or at least one on-phrase is present. |
For each of the above APIs, BlockManager
defines several versions which take different parameters (depending on its configuration):
- for
BlockManager.repeat
the following versions are defined:
repeat(String label, Block block) repeat(String label, OnPhrase[] on, Block block) repeat(String[] enclosing, String label, Block block) repeat(String[] enclosing, String label, OnPhrase[] on, Block block) repeat(TransactionType lvl, String label, Block block) repeat(TransactionType lvl, String label, OnPhrase[] on, Block block) repeat(TransactionType lvl, String[] enclosing, String label, Block block) repeat(TransactionType lvl, String[] enclosing, String label, OnPhrase[] on, Block block)
where:
lvl
is the transaction level to be honored. If missing, it defaults to sub-transaction.enclosing
is the list of non-managed block names that enclose the current block.label
is the block name of the current block.on
is the list of user-defined on phrases.block
is the code to be executed.
- for
BlockManager.repeatTo
the following versions are defined:
repeatTo(String label, ToClause to, Block block) repeatTo(String label, ToClause to, OnPhrase[] on, Block block) repeatTo(String[] enclosing, String label, ToClause to, Block block) repeatTo(String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block) repeatTo(TransactionType lvl, String label, ToClause to, Block block) repeatTo(TransactionType lvl, String label, ToClause to, OnPhrase[] on, Block block) repeatTo(TransactionType lvl, String[] enclosing, String label, ToClause to, Block block) repeatTo(TransactionType lvl, String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block)
where the new parameters are:
to
is the clause that defines a loop condition. The loop control variable is initialized on the first pass through and on subsequent passes it is incremented by a given factor. Before the loop body is entered, the loop control variable is compared to the termination value (which is dynamically resolved at each loop iteration). When the termination condition is met, the loop ends.
- for
BlockManager.repeatWhile
the following versions are defined:
repeatWhile(String label, LogicalOp expr, Block block) repeatWhile(String label, LogicalOp expr, OnPhrase[] on, Block block) repeatWhile(String[] enclosing, String label, LogicalOp expr, Block block) repeatWhile(String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block) repeatWhile(TransactionType lvl, String label, LogicalOp expr, Block block) repeatWhile(TransactionType lvl, String label, LogicalOp expr, OnPhrase[] on, Block block) repeatWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, Block block) repeatWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block)
where the new parameters are:
expr
is the expression associated with theWHILE
clause that defines a loop condition. This expression is evaluated every time the loop body is about to be entered. The loop body will be entered unless the expression evaluates to false.
- for
BlockManager.repeatToWhile
the following versions are defined:
repeatToWhile(String label, ToClause to, LogicalOp expr, Block block) repeatToWhile(String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) repeatToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) repeatToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) repeatToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, Block block) repeatToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) repeatToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) repeatToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block)
where the expr
and the to
parameters have the same meaning as for the repeatWhile
and the repeatTo
APIs.
Details about how the TO
and WHILE
clauses get converted can be found in the Control Flow chapter of this book. The next examples will show how the REPEAT
block gets converted.
Example 4:
B0: repeat: message "infinite loop". end.
Converted code:
repeat("b0", new Block((Body) () -> { message("infinite loop"); }));
Details:
In its simplest form and without a LEAVE
statement to exit the block, the REPEAT
block will loop infinitely. Notice how the init()
method is not emitted, as the block has no special state to initialize.
Example 5:
B1: repeat for book: message book.book-title. end.
Converted code:
repeat("b1", new Block((Init) () -> { RecordBuffer.openScope(book); }, (Body) () -> { message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
When record scoping is added to the block, the init()
method is emitted and contains the buffer scope initialization code.
Example 6:
B2: repeat for book preselect each book: find next book. message book.book-title. end.
Converted code:
PreselectQuery query0 = new PreselectQuery(); repeat("b2", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { query0.next(); message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
This example shows how the REPEAT
block gets converted when it iterates over the records in a query - in this case, the query is emitted as an local variable before for the repeat
API call and the Init()
lambda contains the code to initialize the query.
FOR Block¶
The FOR
statement in 4GL comes in several versions: FOR
, FOR FIRST
, FOR LAST
and FOR EACH
. From these, the first three convert to the same BlockManager
API (the forBlock
method), while the FOR EACH
statement will convert to one of the BlockManager.forEach*
APIs, depending on its configuration.
The full syntax of the FOR
statement is:
[ label: ] FOR [ EACH | FIRST | LAST ] record-phrase [ , [ EACH | FIRST | LAST ] record-phrase ] ... [ query-tuning-phrase ] [ BREAK ] [ BY expression [ DESCENDING ] COLLATE ( string , strength [ , collation ] ) [ DESCENDING ] ] ... [ variable = expression1 TO expression2 [ BY k ] ] [ WHILE expression ] [ TRANSACTION ] [ STOP-AFTER expression ] [ on-error-phrase ] [ on-endkey-phrase ] [ on-quit-phrase ] [ on-stop-phrase ] [ frame-phrase ] ... END.
where the query-tuning-phrase
is not supported.
Considering this statement is used for iterating over the records returned by a query, this section will focus only on how each version of the statement gets converted in a block-related manner. For details about how the record-phrase
gets converted, please see the Queries chapter of this book. Also, for details about how the TO
and WHILE
looping clauses get converted, please see the FOR Block section from the Control Flow chapter of this book.
BlockManager
provides method to execute the simple FOR FIRST/LAST
block and the query FOR EACH
block. In each case, it executes the block with the given configuration, and explicit or default properties.
FOR FIRST/LAST [ WHILE ] [ TO ]
, viaforBlock
,forBlockWhile
,forBlockTo
,forBlockToWhile
. In this case, any TO or WHILE clause is treated as an additional condition that must be met before the block body is executed. Any TO or WHILE clause is treated as an additional condition that must be met before the block body is executed. Note that the presence of a TO or WHILE does not make this a loop. If neither the TO clause or WHILE expression is specified, this is a simple block. If both are specified then both of the conditions are tested with a logical AND. By default, the ON ENDKEY UNDO, LEAVE and ON ERROR UNDO, RETRY properties are supported. The evaluation of the TO or WHILE block termination conditions is done AFTER the record retrieval. This causes any found record to remain available if the block is never entered because of the TO or WHILE conditions.
The TO clause defines a block condition. The block control variable is initialized before entering the block. Before the block body is entered, the control variable is compared to the termination value (which is dynamically resolved at that moment). If the termination condition is met, the block is never entered.
The WHILE clause defines a block condition. This expression is evaluated before the block body is about to be entered. The block body will be entered unless the expression evaluates tofalse
.FOR EACH [ WHILE ] [ TO ]
, viaforEach
,forEachWhile
,forEachTo
,forEachToWhile
. Any TO or WHILE clause is treated as an additional condition that must be met before the loop body is executed. If both are specified then both of the conditions are tested with a logical AND. By default, theON ENDKEY UNDO, LEAVE
andON ERROR UNDO, RETRY
properties are supported.
The TO clause that defines a loop condition. The loop control variable is initialized on the first pass through and on subsequent passes it is incremented by a given factor. Before the loop body is entered, the loop control variable is compared to the termination value (which is dynamically resolved at each loop iteration). When the termination condition is met, the loop ends.
The WHILE clause that defines a loop condition. This expression is evaluated every time the loop body is about to be entered. The loop body will be entered unless the expression evaluates tofalse
.
The record retrieval that is associated with FOR blocks or loops must be implemented directly inside the code of the block itself or if there is a TO or WHILE clause then the query processing should be located in the Enter()
lambda expression. This means that the caller is responsible for all query processing. The record retrieval must be done on the first line of code in the block or in the enter
method to duplicate the Progress behavior. Note that this is required to handle all error cases and 4GL conditions properly.
In all cases, the FOR
block will get converted to a structure similar to this one:
/* declared fields*/ <forBlock>([ <transaction type>, ] <label>, [ /* other parameters */, ] new Block((Init) () -> { /* state initialization code */ }, (Enter) () -> { /* query iteration code */ }, (Body) () -> { /* converted 4GL code */ }));
where:
<forBlock>
is one of theforBlock
,forBlockWhile
,forBlockTo
,forBlockToWhile
,forEach
,forEachTo
,forEachWhile
orforEachToWhile
APIs inBlockManager
.- the
<transaction type>
is an optional parameter which will explicitly set the transaction type (please see the Transactions chapter of this book for when it gets emitted). It defaults to sub-transaction. - the
<label>
is the block's label (for details about how it gets converted, please see the Control Flow chapter of this book). - depending on its configuration, other parameters will be emitted in the converted code between the
label
and theBlock
implementation. - the
Enter()
lambda expression provides a "callback" to execute user-defined logic at the top of the block body but before the block body is executed. This is guaranteed to be called AFTER the transaction manager scope opens but BEFORE the block body is entered. It will be called once per iteration of the block or once if the block is not iterating. It WILL be called on retry. It is emitted only in cases when theFOR
statement has set theTO
or theWHILE
clause (or both). - the
init()
lambda expression contains the initialization code for this block: variables, buffers, queries, streams, etc. - the
body()
lambda expression contains the converted 4GL code for this block.
Similar to how the DO
and REPEAT
blocks get converted, each API version for the FOR
statement comes in 4 flavors, to satisfy any combination of its looping clauses: without the TO
or WHILE
clause, with only the TO
clause, with only the WHILE
clause and with both the TO
and WHILE
clauses.
This being a query block, the converted code does not depend on the query type; instead, all query-related fields will be emitted as local variables defined before the block API, and the query initialization code will be emitted in the Init()
lambda expression. The code which retrieves the next record is emitted either as the first line of code in the Body()
lambda expression or in the Enter()
lambda expression, when the TO
or WHILE
(or both) clause is set.
As with the DO
or REPEAT
blocks, please see the Control Flow chapter of this book for details about how the TO
and WHILE
clauses get converted.
FOR, FOR FIRST and FOR LAST Statements¶
These three statements convert to the same BlockManager
API - the forBlock
method. In this case, as all of them can return at most one record (FOR
will return the unique record satisfying the condition (or none), FOR FIRST
will return the first record and FOR LAST
will return the last record), the conversion rules separate these kind of blocks from the FOR EACH
block, which works with queries which can return more than one record..
Depending on its configuration, in this case the FOR
block will convert to one of the following APIs:
- when neither
TO
orWHILE
are used:
forBlock(String label, Block block) forBlock(String label, OnPhrase[] on, Block block) forBlock(String[] enclosing, String label, Block block) forBlock(String[] enclosing, String label, OnPhrase[] on, Block block) forBlock(TransactionType lvl, String label, Block block) forBlock(TransactionType lvl, String label, OnPhrase[] on, Block block) forBlock(TransactionType lvl, String[] enclosing, String label, Block block) forBlock(TransactionType lvl, String[] enclosing, String label, OnPhrase[] on, Block block)
where:
lvl
is the transaction level to be honored. If missing, it defaults to sub-transaction.enclosing
is the list of non-managed block names that enclose the current block.label
is the block name of the current block.on
is the list of user-defined on phrases.block
is the code to be executed.- when the
TO
clause is used:
forBlockTo(String label, ToClause to, Block block) forBlockTo(String label, ToClause to, OnPhrase[] on, Block block) forBlockTo(String[] enclosing, String label, ToClause to, Block block) forBlockTo(String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block) forBlockTo(TransactionType lvl, String label, ToClause to, Block block) forBlockTo(TransactionType lvl, String label, ToClause to, OnPhrase[] on, Block block) forBlockTo(TransactionType lvl, String[] enclosing, String label, ToClause to, Block block) forBlockTo(TransactionType lvl, String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block)
where the new parameters are:
to
is the clause that defines a loop condition. The loop control variable is initialized on the first pass through and on subsequent passes it is incremented by a given factor. Before the loop body is entered, the loop control variable is compared to the termination value (which is dynamically resolved at each loop iteration). When the termination condition is met, the loop ends.- when the
WHILE
clause is used:
forBlockWhile(String label, LogicalOp expr, Block block) forBlockWhile(String label, LogicalOp expr, OnPhrase[] on, Block block) forBlockWhile(String[] enclosing, String label, LogicalOp expr, Block block) forBlockWhile(String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block) forBlockWhile(TransactionType lvl, String label, LogicalOp expr, Block block) forBlockWhile(TransactionType lvl, String label, LogicalOp expr, OnPhrase[] on, Block block) forBlockWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, Block block) forBlockWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block)
where the new parameters are:
expr
is the expression associated with theWHILE
clause that defines a loop condition. This expression is evaluated every time the loop body is about to be entered. The loop body will be entered unless the expression evaluates to false.- when both the
TO
andWHILE
clauses are used:
forBlockToWhile(String label, ToClause to, LogicalOp expr, Block block) forBlockToWhile(String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) forBlockToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) forBlockToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) forBlockToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, Block block) forBlockToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) forBlockToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) forBlockToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block)
where the expr
and the to
parameters have the same meaning as for the forBlockWhile
and the forBlockTo
APIs.
Example 7:
B0: for book where recid(book) = 1: message book.book-title. end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); forBlock("b0", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, "book.recid = 1", null, "book.bookId asc"); }, (Body) () -> { query0.unique(); message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
This example converts the FOR
statement to a query which retrieves an unique result (if found). The conversion rules convert it to a forBlock
API call and the Block
implementation will have the query and record initialization code emitted in its init()
method.
Example 8:
B1: for first book: message book.book-title. end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); forBlock("b1", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { query0.first(); message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
This example shows that the FOR FIRST
(as with FOR LAST
too) gets converted to the same API call, forBlock
. As the difference is only in the type of query being executed, the converted code is structured the same as in the previous example.
Example 9:
B2: for last book i = 1 to 10: message book.book-title. end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); ToClause toClause0 = new ToClause(new FieldReference(book, "isbn"), 1, 10); forBlockTo(TransactionType.FULL, "b2", toClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.last(); }, (Body) () -> { message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
When either the TO
or WHILE
clause is present, it is needed to emit the query navigation code in a distinct method - the enter() method; this is because, to duplicate the 4GL behavior, the next record needs to be retrieved before the TO
or WHILE
clauses are evaluated (as they might contain reference to some field in the iterated buffer).
FOR EACH Statement¶
The FOR EACH
statement can iterate over the records from one or more buffers. Depending on its configuration, this block will convert to one of the following APIs:
- when neither
TO
orWHILE
are used:
forEach(String label, Block block) forEach(String label, OnPhrase[] on, Block block) forEach(String[] enclosing, String label, Block block) forEach(String[] enclosing, String label, OnPhrase[] on, Block block) forEach(TransactionType lvl, String label, Block block) forEach(TransactionType lvl, String label, OnPhrase[] on, Block block) forEach(TransactionType lvl, String[] enclosing, String label, Block block) forEach(TransactionType lvl, String[] enclosing, String label, OnPhrase[] on, Block block)
where:
lvl
is the transaction level to be honored. If missing, it defaults to sub-transaction.enclosing
is the list of non-managed block names that enclose the current block.label
is the block name of the current block.on
is the list of user-defined on phrases.block
is the code to be executed.- when the
TO
clause is used:
forEachTo(String label, ToClause to, Block block) forEachTo(String label, ToClause to, OnPhrase[] on, Block block) forEachTo(String[] enclosing, String label, ToClause to, Block block) forEachTo(String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block) forEachTo(TransactionType lvl, String label, ToClause to, Block block) forEachTo(TransactionType lvl, String label, ToClause to, OnPhrase[] on, Block block) forEachTo(TransactionType lvl, String[] enclosing, String label, ToClause to, Block block) forEachTo(TransactionType lvl, String[] enclosing, String label, ToClause to, OnPhrase[] on, Block block)
where the new parameters are:
to
is the clause that defines a loop condition. The loop control variable is initialized on the first pass through and on subsequent passes it is incremented by a given factor. Before the loop body is entered, the loop control variable is compared to the termination value (which is dynamically resolved at each loop iteration). When the termination condition is met, the loop ends.- when the
WHILE
clause is used:
forEachWhile(String label, LogicalOp expr, Block block) forEachWhile(String label, LogicalOp expr, OnPhrase[] on, Block block) forEachWhile(String[] enclosing, String label, LogicalOp expr, Block block) forEachWhile(String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block) forEachWhile(TransactionType lvl, String label, LogicalOp expr, Block block) forEachWhile(TransactionType lvl, String label, LogicalOp expr, OnPhrase[] on, Block block) forEachWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, Block block) forEachWhile(TransactionType lvl, String[] enclosing, String label, LogicalOp expr, OnPhrase[] on, Block block)
where the new parameters are:
expr
is the expression associated with theWHILE
clause that defines a loop condition. This expression is evaluated every time the loop body is about to be entered. The loop body will be entered unless the expression evaluates to false.- when both the
TO
andWHILE
clauses are used:
forEachToWhile(String label, ToClause to, LogicalOp expr, Block block) forEachToWhile(String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) forEachToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) forEachToWhile(String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) forEachToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, Block block) forEachToWhile(TransactionType lvl, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block) forEachToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, Block block) forEachToWhile(TransactionType lvl, String[] enclosing, String label, ToClause to, LogicalOp expr, OnPhrase[] on, Block block)
where the expr
and the to
parameters have the same meaning as for the forEachWhile
and the forEachTo
APIs.
Example 10:
B3: for each book: message book.book-title. end.
Converted code:
AdaptiveQuery query0 = new AdaptiveQuery(); forEach("b3", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { query0.next(); message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
Here, the FOR EACH
statement iterates over all records from the book table. Considering there is no TO
or WHILE
clause, it gets converted to a forEach
API call. Notice how the query navigation code - query3.next()
- is emitted at the beginning of the body()
method.
Example 11:
B4: for each book while book.book-title begins "FWD": message book.book-title. end.
Converted code:
AdaptiveQuery query0 = new AdaptiveQuery(); LogicalOp whileClause0 = () -> begins(book.getBookTitle(), "FWD"); forEachWhile("b4", whileClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.next(); }, (Body) () -> { message((character) new FieldReference(book, "bookTitle").getValue()); }));
Details:
In this case, the FOR EACH
query has a reference to the book.book-title
field (part of the iterated buffer) in its WHILE
clause. This is one of the reasons why the query navigation code must be emitted in the enter()
method, which is executed before the WHILE
expression gets evaluated - so that the buffer can be positioned on the next record prior to expression evaluation.
Conditions¶
A condition is an unusual event that may occur during the course of processing. The Progress programmer must be able to specify how each procedure responds to the range of possible conditions. Progress 4GL provides a mechanism to specify such behavior at the level of each block.
Progress defines blocks as having very specific "block properties" which define behavior associated with a block. These properties can be explicitly defined or are implicit. The beginning and end of each block corresponds to the beginning and end of a "scope". These scopes can be nested and this structure allows a Progress programmer to control the granularity to which a block's properties are applied.
In Progress, all blocks have some form of implicit and/or explicit condition processing, properties and transaction support. One of the most central behaviors Progress provides is UNDO
. This is similar to the concept of a rollback of a transaction and allows one to abort partial changes to variables (and other non-database resources) when a failure or problem is encountered. This is provided by default to reduce the amount of manual work by the programmer.
The behavior of which scope gets undone in transaction processing rollbacks is defined by block properties. In addition, these block properties also define how the block responds to each type of condition AFTER the UNDO
occurs. More specifically, the way that the application changes its flow of control in response to a condition is something that is defined by block properties (implicit or explicitly overridden where possible).
The following conditions are possible:
Condition | Default Action in Response |
---|---|
ERROR |
If a transaction is active, UNDO the closest enclosing block that has the ERROR property (this will also be a transaction or sub-transaction).If a transaction is not active, the UNDO will be a no operation.Then RETRY the same block (or loop) that has the ERROR property. |
ENDKEY |
If a transaction is active, UNDO the closest enclosing block that has the ENDKEY property (this will also be a transaction or sub-transaction).If a transaction is not active, the UNDO will be a no operation.Only blocks with the ENDKEY property will have an interactions counter. The interactions counter is a simple count of the number of input-blocking language statements have executed in the block. Any nested block that does not have an interactions counter of its own will have its interactions counted in the nearest enclosing block's interactions counter. This counter is used to determine the behavior of the END-ERROR event.Then LEAVE the same block (or loop) that has the ENDKEY property. |
STOP |
If a transaction is active, UNDO the block that defines the transaction (as opposed to a sub-transaction).If a transaction is not active, the UNDO will be a no operation.Then RETRY the "startup procedure". This means that the entire top-level procedure is run again from the top.Note that if this is generated based on a database disconnect, a special form of processing will occur where the default STOP behavior cannot be explicitly overridden until an exit to a scope in which no access is made to the database that was disconnected. At this point the normal STOP processing is re-imposed. See the Honoring STOP Conditions section of this chapter for more details. |
QUIT |
If a transaction is active, commit the block that defines the transaction (as opposed to a sub-transaction). If a transaction is not active, the commit will be a no operation. Exit to the operating system. |
END-ERROR |
Technically, this really isn't a separate condition, but rather a key in the UI which generates 2 different conditions based on context. An END condition is generated if the key press occurs during the first input-blocking language statement in the block. An ERROR condition is generated if the key press occurs during a subsequent input-blocking language statement in the block.The exception to this processing occurs for blocks that do not have the ENDKEY property (this can happen for some DO blocks and is always true for EDITING blocks). In such a case, the interactions counter is not maintained for the current block but rather is maintained at the level of the nearest enclosing block which has the ENDKEY property.This means that the normal conversion of END-ERROR to ERROR is thus modified in such blocks. In the EDITING case especially, it is likely that an ERROR will be generated (because the EDITING block itself counts as an interaction and thus with a larger interactions counter it is more likely that an ERROR will be generated). What is especially different here is that the ERROR will be propagated to the enclosing block that has the ENDKEY property! So even if the DO or EDITING block has the ERROR property in this case, if they also do not have the ENDKEY property and the interactions counter (for the enclosing ENDKEY block) is < 2 then the ERROR will NOT be processed by the current block.See above for ENDKEY and ERROR respectively. |
Some language statements and all assignments that can generate the ERROR
condition can be executed using the NO-ERROR
keyword, which disables the generation of this condition. When a failure occurs, instead of generating ERROR
, the ERROR-STATUS
system handle will have its state updated.
The possible actions in response to a condition are:
Action | Description |
---|---|
UNDO |
Rolls back all database, variable, temp-table and work-table modifications since the beginning of the target block. This target block must have opened a transaction or it must be enclosed within a block that opened a transaction AND must itself be a sub-transaction. In any other case, the UNDO is a no operation.There is no way of disabling UNDO . Any condition that is raised will always trigger an UNDO (with the exception of the implicit behavior for QUIT which is to commit instead of UNDO ). In addition, one of the actions below will also be executed. If not explicitly specified, the implicit action is always a RETRY . |
LEAVE |
Breaks (exits) out of the target block. If the target block is a top-level block, the LEAVE will be converted to a RETURN . |
RETRY |
The target block (non-loop or loop) is executed from the top with the same state as when it last executed. Since a retry of a block in which the state never changes from iteration to iteration would yield an infinite loop, Progress provides a feature to detect when an infinite loop could occur. This is called Infinite Loop Protection (ILP). RETRY will be converted to a LEAVE , NEXT or RETURN depending on the block type to which the RETRY is targeted. Please see the Infinite Loop Protection section of this chapter for details about how this is done. |
NEXT |
The next iteration of the loop is executed. If the associated block is not a loop, then this is automatically treated as a LEAVE or a RETURN (see below).Infinite Loop Protection (ILP) protects the NEXT action (when triggered from an ON phrase or from an UNDO statement). In certain cases, a NEXT will be converted to a LEAVE or RETURN by the infinite loop protection logic depending on the block type to which the RETRY is targeted - see the Infinite Loop Protection section of this chapter for details. |
RETURN |
The current procedure, function or trigger returns to the caller. There are 5 forms: 1. Raise an ERROR condition in the caller, using RETURN ERROR . In a function, this can be specified but the error is ignored (not raised).2. Return from a procedure or trigger block and allow normal processing to continue, using RETURN . If this executed from a procedure, then the RETURN-VALUE global character variable is set to the empty string "" which can be read in the caller.3. Return the value of one of the basic data types when returning from a function, using RETURN <expression> .4. Set the RETURN-VALUE global character variable which can be read in the caller when returning from a procedure, using RETURN <character_expression> .5. Return and disable normal event processing when returning from an event trigger block, using RETURN NO-APPLY . This can also be specified in other block types, but the NO-APPLY will be ignored. |
THROW |
If the external program or class is configured for BLOCK-LEVEL ON ERROR UNDO, THROW , any ERROR condition will be converted to a structured OO exception, which will bubble up to the parent block, until a CATCH handling this exception is found. |
In all actions (except RETURN
), the block which is the target of the action can be implicitly determined (if there is no label) or it can be explicitly specified using a user-defined label for the block.
One cannot UNDO
a specified label which is outside of the scope referenced in the associated action such as LEAVE
or RETRY
. In other words, the target of the action will determine the flow of control for the program. Any UNDO
operation must be scoped inside (more deeply nested than) or equal to the block which is the target for the other action otherwise a compiler error will result.
Blocks have properties that match a condition name. Thus there are ERROR, ENDKEY, STOP and QUIT properties. The existence of a property (implicitly or explicitly) in a given block means that that block provides a predictable sequence of actions in response to the condition (matching that property) being raised. To explicitly add a property to a block, one uses the ON ERROR
, ON ENDKEY
, ON STOP
or ON QUIT
clauses in the block header. For each of these explicit overrides, one may also specify UNDO, secondary_action
. UNDO
is required and cannot be avoided. The secondary action is one of those listed in the previous table. If no secondary action is specified, then RETRY
is the action executed by default.
The secondary action of an ON phrase
can have an explicit label defining its target block. If no label is specified but there is a label defined for the UNDO
portion of the ON phrase
, then that UNDO
label will also define the target block for the secondary action. If neither portion of the ON phrase
defines a label, then the target block will be determined implicitly. The logic for this is very complicated. Please see the Determining the Target and Meaning of UNDO, LEAVE, NEXT and RETRY section of this chapter for details.
The implicit behavior of each block type is different in regards to condition handling. Since all condition processing is handled based on whether the condition has a matching property for a given block, if a block doesn't have a property and that condition is raised, that block will be bypassed and the processing will occur in an enclosing block. This means that if (for example) a block has the ERROR
property, then in the case where an ERROR
condition is raised, if that block is the innermost block which has the ERROR
property, then that is the level at which implicit UNDO
and following implicit action occurs. The implicit action that occurs is defined differently for each condition. This default action ONLY OCCURS if the block implicitly has the matching property (e.g. the ENDKEY
property when the ENDKEY
condition is raised).
Please note that the UNDO
action is special since it always occurs first (if it occurs at all), before the secondary action. UNDO
cannot be disabled however it only occurs when a transaction is active in the current or a containing scope. Some blocks always have a given set of properties (implicit or explicit) and some blocks can only have explicit properties (i.e. DO
). If any property is not associated with a block, then no processing of that condition will occur in that scope. This means that the stack unwinds past that block without stopping to do any processing. Only those properties that are implicitly or explicitly associated with a scope will ever be processed in that scope.
Each property is independent. For example, if a block has an ERROR
property, that has no bearing on how it processes the ENDKEY
.
The following are the implicit properties that are inherently provided with each block type:
Block Type | ERROR | ENDKEY | STOP | QUIT |
---|---|---|---|---|
FOR |
Y | Y | N* | N* |
REPEAT |
Y | Y | N* | N* |
DO |
N* (except if the TRANSACTION keyword is present, then there is an implicit ERROR property) |
N | N | N |
procedure | Y | Y | Y* (only for the "startup procedure") | N* |
trigger | Y | Y | N* | N* |
editing | Y (for UPDATE except when the ERROR condition is generated by the END-ERROR key and the interactions counter is < 2) / N (for PROMPT-FOR and SET ) |
N | N | N |
function | Y (always LEAVES the function with an unknown value return) | Y (always LEAVES the function with an unknown value return) | N | N |
(*) All of these entries have been found to be different than documented in the official 4GL documentation.
The DO
block is the only block with no implicit behavior (except in the case where the TRANSACTION
keyword or ON phrases
are present). All DO
, REPEAT
and FOR
blocks can have the same set of explicit properties. If explicitly set, this definition overrides the default action.
The DO
block will have sub-transaction support if they contain any ON phrase
. The condition does not matter. So an ON QUIT UNDO, RETRY
is enough to force the DO
block to have the sub-transaction property.
Please note that the function block is quite special. In user defined functions, no user-input blocking statement can be executed. This means that PROMPT-FOR/SET/UPDATE/INSERT/CHOOSE/WAIT-FOR/PAUSE
and READKEY
are all illegal, there are very limited choices of editing database fields within a function. This combined with the fact that functions don't have the transaction property by default means that functions usually are sub-transactions. However, when a direct assignment (e.g. field = expression
) occurs, this will cause the function block to obtain TRANSACTION
level. There is no way to make a function block a "NO" transaction (lacking the sub-transaction or transaction properties). The ERROR
and END
conditions both default to LEAVE
and the return value will be the unknown value in these cases. This cannot be overridden. Because of this behavior and the fact that one cannot specify an ON phrase
with any of the 4 conditions, it is only possible to get a RETRY
at the function block level by using the UNDO, RETRY
language statement when infinite loop protection is disabled (e.g. with the RETRY
function).
Honoring STOP Conditions¶
Stop conditions can be generated by certain runtime errors such as a database disconnection or failures in stream processing. Any runtime code running on a thread which is servicing converted Progress business logic will translate any InterruptedException
into a StopConditionException
. Classes which provide such a service:
Stream
(handles the general cases for all streams)ProcessStream
(handles a case that is specific to reading from a process' standard I/O)InMemoryLockManager
There are unusual cases in the runtime where processing is not occurring on a thread which is servicing converted Progress business logic. For example, if a secondary thread is used to cleanup file system resource (e.g. close pipes or files) or if a secondary thread is used to copy data between two pipes. Note that in these cases, there is no counterpart or proper mapping into a Progress semantic since such cases don't exist in the Progress runtime. Generally, such cases silently exit or fail. Classes which provide such behavior:
ProcessOps
(both theCleaner
inner class and interruptions that occur during a child process' execution results in Progress not seeing such interruptions)StreamConnector
(secondary copying thread)
In addition, the STOP
language statement in Progress allows an explicit stop condition to be raised. In this case the business logic will explicitly throw a StopConditionException
.
Stop condition processing is complicated by the Progress feature that the user may press the CTRL-C key combination at any time and this immediately interrupts the execution of the Progress program (which is single threaded). In the FWD environment this is complicated by the split of UI processing into a separate process accessed via the network. The UI client will monitor for the CTRL-C key combination and this will cause asynchronous processing of the interruption to occur. In particular, if processing is currently occurring on the client side, the interruption will be converted to an interruption of that running thread which will be converted to a StopConditionException
in the UI client. This will then unwind the stack normally until the location in the business logic that handles (catches) the stop condition is reached. If the current thread is processing on the server (such that the client cannot honor the UI since it is not actively running), then the server will be asynchronously notified and this will trigger a call to Thread.interrupt()
. If the currently processing thread on the server (for that user's context) is blocked, an InterruptedException
would be generated and this would be caught by the runtime as noted above and translated into a StopConditionException
. This is a key point: J2SE only generates an InterruptedException
in specific cases where a thread blocks such as Object.wait()
or Thread.sleep()
. If the thread is not blocked, then J2SE will not generate an InterruptedException
, instead it just sets the interrupt status of the thread. The TransactionManager
checks the interrupted status of the current thread at block start, stop and when a block iterates. Any previous interruption will be honored by throwing a StopConditionException
. Please see the Transactions chapter of this book for more details on TransactionManager
.
Infinite Loop Protection¶
Infinite loop protection (ILP) is a hidden feature of the block processing that is designed to modify the block behavior as specified by the 4GL programmer in order to avoid an infinite loop. The idea is that an action that is specified by a 4GL programmer (RETRY
or NEXT
) which could perpetuate an infinite loop, will be silently converted (by the runtime) to a different action (LEAVE
, NEXT
or RETURN
) in order to cause a change in the loop state that will avoid an infinite loop. This silent conversion is done based on runtime state which is affected by the flow of control of the program. So blocks and statements that are conditionally executed will change the state of the runtime and thus modify the conversions that occur. For this reason, the behavior cannot be duplicated by static code modifications. In particular, the following behavior is regulated:
1. RETRY
ON condition UNDO [ label1 ] [ , RETRY [ label2 ] ]
- This is an
ON phrase
for any of the 4 conditions, which has a secondary action ofRETRY
- This is an
UNDO, RETRY
- This is an
UNDO
language statement with a secondary action ofRETRY
.
- This is an
- ILP modifies
RETRY
in all block types.
2. NEXT
ON condition UNDO [ label1 ] [, NEXT [ label2 ] ]
- This is an
ON phrase
for any of the 4 conditions, which has a secondary action ofNEXT
- This is an
UNDO, NEXT
- This is an UNDO language statement with a secondary action of
NEXT
.
- This is an UNDO language statement with a secondary action of
- ILP does NOT modify
NEXT
in blocks that are loops which inherently change the program state in each iteration, as theDO TO
,REPEAT TO
, and all forms ofFOR EACH
. In such cases, the program's data generally changes in each iteration (FOR EACH
technically can be an infinite loop if you edit the index being used to navigate the records, but this is a rare use case). This means that aNEXT
is "safe" in such cases so ILP does not apply.
3. The block to which the RETRY
or NEXT
is targeted (explicitly using a label or implicitly calculated by the 4GL runtime/compiler) determines the kind of conversion that occurs. See the Determining the Target and Meaning of UNDO, LEAVE, NEXT and RETRY section for details on determining the target block for an action.
This conversion will occur unless ILP has been disabled for the targeted block. ILP becomes disabled based on runtime state which is maintained implicitly by particular language statements and by the block processing infrastructure of the runtime.
If ILP is NOT disabled, the following conversions occur:
Block Type | Top Level | Block/Loop | Conversion |
---|---|---|---|
FOR EACH (all forms - even when there is a WHILE or TO/BY present) |
no | loop | RETRY - converted to NEXT NEXT - no conversion, allowed to execute |
DO TO/BY |
no | loop | RETRY - converted to NEXT NEXT - no conversion, allowed to execute |
REPEAT TO/BY |
no | loop | RETRY - converted to NEXT NEXT - no conversion, allowed to execute |
FOR FIRST/LAST (all forms - even when there is a WHILE or TO/BY present) |
no | block | RETRY - converted to LEAVE NEXT - converted to LEAVE |
DO WHILE |
no | loop | RETRY - converted to LEAVE NEXT - converted to LEAVE |
REPEAT WHILE |
no | loop | RETRY - converted to LEAVE NEXT - converted to LEAVE |
REPEAT |
no | loop | RETRY - converted to LEAVE NEXT - converted to LEAVE |
DO |
no | block | RETRY - converted to LEAVE NEXT - converted to LEAVE |
external procedure | yes | block | RETRY - converted to RETURN NEXT - converted to RETURN |
internal procedure | yes | block | RETRY - converted to RETURN NEXT - converted to RETURN |
function | yes | block | RETRY - converted to RETURN NEXT - converted to RETURN |
trigger | yes | block | RETRY - converted to RETURN NEXT - converted to RETURN |
editing | no | loop | Infinite loop protection is always disabled in editing blocks (since they can only be called from UPDATE/SET/PROMPT-FOR as part of a user interaction), so no conversions ever occur in targeted editing blocks. |
There are 2 cases where this ILP is disabled:
- A language statement which blocks for user input is executed before the point in the block where the condition is raised. At the moment that such a statement executes, the infinite loop protection is disabled. Since editing blocks are inherently processed as part of user input, infinite loop protection is meaningless for such loops.
- The
RETRY
built-in function is executed before the point in the block where the condition is raised. At the moment that function executes, the infinite loop protection is disabled.
Until one of these 2 cases is executed, any condition that generates a RETRY
will result in a NEXT
, LEAVE
or RETURN
. Only when the infinite loop protection is disabled will RETRY
result in a RETRY
! Likewise@ NEXT may be allowed, but depending on the block type it may be converted to a
LEAVE or
RETURN.
All block types have retry support. In other words, it is possible to retry any block including non-looping blocks and the top-level blocks (like a built-in function or internal procedure). But whether the RETRY
will be allowed or not is determined by the ILP processing at runtime. It is important to note that the NEXT
language statement is NOT affected by ILP. ILP only affects ON phrases
and the UNDO
language statement.
A note in the UNDO
language statement states that if "nothing changes during a RETRY
of a block" it will convert the RETRY
into a NEXT or LEAVE. As far as can be determined using application code, the 4GL runtime does not actually monitor the state of variables or other resources to detect if "nothing changes". It looks like the disabling of ILP is taken as a proxy for whether things have changed in the current iteration.
The Progress READKEY
documentation has a specific note regarding a special implementation of infinite loop protection. It states that a count is used to convert UNDO, RETRY
into UNDO, NEXT
as well as the conversion of UNDO, NEXT
into UNDO, LEAVE
. While testing has not found any special counter behavior for READKEY
, there is a "stutter quirk" (a counter based difference in ILP behavior) that has been found associated with ON ENDKEY UNDO, RETRY
when it is used on certain block types.
The following code will cause a kind of "stuttering" where RETRY
will sometimes be allowed to occur due to the state of a counter. This stutter quirk only comes into play when an ENDKEY
condition is raised and is handled by the ON ENDKEY UNDO, RETRY
phrase. Consider this code:
def var i as int no-undo. do i = 1 to 5 on endkey undo, retry: message i. apply "endkey". end.
The results:
1 2 3 3 4 4 5 5
The first 2 retries are converted to a NEXT
but the 3rd RETRY
is allowed to go forward as a RETRY
(no conversion occurs). Then the 4th RETRY
is converted to a NEXT
but the 5th RETRY
is a RETRY
. This only occurs with the execution of the actions associated with an ON phrase
and ONLY with ENDKEY
. If any other condition is targeted (e.g. ERROR
), this will not occur. Likewise, if any other action is specified (NEXT
instead of RETRY
), then this behavior does not occur.
Finally, this stutter mode is only seen in ON ENDKEY UNDO, RETRY
phrases for the following block types:
DO TO
REPEAT TO
FOR EACH
(all forms)
Determining the Target and Meaning of UNDO, LEAVE, NEXT and RETRY¶
Each of these actions can explicitly reference a specific labeled block (e.g. LEAVE inner
) or will implicitly reference a block. In each of these cases, the action always operates in regards to a specific block. In the implicit cases (one in which no block label was specified in the 4GL source code), the 4GL compiler/runtime must determine the block which is referenced. There are complex rules by which the UNDO
, LEAVE
, NEXT
and RETRY
actions are "bound" to a specific block as its target. These actions do not always operate on the current block or even on an explicitly defined block target. This target block resolution is complicated by the block structure in which the ON
phrase or language statements are contained. In particular, the target can reference enclosing blocks implicitly or explicitly. The block properties of the enclosing blocks will also affect any implicit target calculation.
There are 3 ways that these actions can be invoked:
- A language statement encountered in the normal flow of control (
LEAVE [ label
] and@ NEXT [ label ] only). - As an
other_action
defined in anUNDO
language statement (UNDO [ label1 ], [ other_action [ label2 ] ]
), encountered in the normal flow of control. - As an
other_action
defined in anON phrase
(ON <condition> UNDO [ label1 ], [ other_action [ label2 ] ]
), in response to a condition being raised inside the block.
The UNDO
language statement and ON phrases
have the ability to specify up to 2 labels (one for the UNDO
and one for the other action).
There are only 4 types of blocks in Progress which can be labeled: DO
, REPEAT
, FOR
and EDITING
. Thus, only these block types can be explicitly targeted by the UNDO, LEAVE, NEXT
or RETRY
actions. But when implicit block targets are calculated by Progress, it is possible for a non-labeled block to be the target of an action. The non-labeled block types are special top-level blocks (external procedure, internal procedure, trigger and user defined function). None of those non-labeled block types are iterating blocks. This means that a NEXT
which targets one of these blocks must be translated into something else (LEAVE
in Progress terms). In Progress, LEAVE
on a top-level block is the same as a RETURN
statement (with no modifiers or returned data). If LEAVE
or NEXT
do implicitly target a top-level block, then the 4GL behavior silently converts them into a RETURN
. RETRY
on a top-level block will be converted to a RETURN
if infinite loop protection is NOT disabled.
All blocks in Progress implicitly have the ERROR
property (see the Conditions section of this chapter for details) except for the DO
and EDITING
(PROMPT-FOR
or SET
forms) blocks. The DO
only has the ERROR
property when it has an ON ERROR phrase
or if it has the TRANSACTION
keyword.
The ERROR
property that exists for DO
blocks and for the UPDATE
form of EDITING
blocks (and for the special "hidden" block in non-EDITING UPDATE
blocks) is special in that it is not in effect in the following case:
- The block does not also have the
ENDKEY
property (EDITING
blocks never have theENDKEY
property andDO
blocks only have it if it is explicitly specified in anON ENDKEY phrase
); AND - The
ERROR
condition was raised by theEND-ERROR
key (or anAPPLY
of that key); AND - The current interactions counter is less than 2
This is runtime state that will modify how the ERROR
property is processed when the ERROR
condition is generated by the END-ERROR
key. This means that an ERROR
condition generated by END-ERROR
key in an UPDATE
block (EDITING
or not) is always forwarded on to the containing block, even though all other ERROR
conditions are handled as an UNDO, RETRY
.
Most importantly, for the purposes of resolving unlabeled actions to blocks with the ERROR
property, EDITING
blocks (even UPDATE EDITING
blocks) are treated as if they do not have the ERROR
property.
Considering this, the following are the possible implicit block target resolution algorithms:
Code | Description |
---|---|
NEAR-ERR | The nearest enclosing block with the ERROR property.Example 1: outer: repeat: inner: do on error undo, retry: <statement> end. end. The nearest enclosing block (to the <statement> ) with the ERROR properly is inner.Example 2: outer: repeat: inner: do: <statement> end. end. The nearest enclosing block (to the <statement> ) with the ERROR property is outer. |
CURRENT | The "current" block. This is the block which directly encloses the UNDO, NEXT or LEAVE language statement. Or in the case of an ON phrase , it is the block upon which the ON phrase is specified.outer: repeat: inner: do on error undo, RETRY: <statement> end. end. In this example, for both the <statement> and the RETRY , the current block is inner. |
AS-UNDO | The other action will be targeted at the same block as the associated UNDO is targeted. If UNDO is targeted (implicitly or explicitly) to inner then the other action will be targeted to inner. |
Assuming that the directly containing block is labeled "inner" and that block is contained inside a block labeled "outer", the following are possible formations of the UNDO
language statement and ON phrases
:
Feature | UNDO Target | Other Action | Other Action Target | Static Conversion Possible | Compiler Error |
---|---|---|---|---|---|
UNDO. |
NEAR-ERR | RETRY (implicit) |
AS-UNDO | n/a |
|
UNDO inner. |
inner | RETRY (implicit) |
AS-UNDO | If inner does not have the ERROR property. |
|
UNDO outer. |
outer | RETRY (implicit) |
AS-UNDO | If outer does not have the ERROR property. |
|
UNDO, RETRY. |
NEAR-ERR | RETRY |
AS-UNDO | |
|
UNDO, LEAVE. |
NEAR-ERR | LEAVE |
AS-UNDO | * | |
UNDO, NEXT. |
NEAR-ERR | NEXT |
AS-UNDO | * | |
UNDO, RETRY inner. |
NEAR-ERR | RETRY |
inner | If inner does not have the ERROR property, then UNDO would choose a different block than inner. Since RETRY is targeted at inner, this is a problem. UNDO and RETRY must always be targeted at the same block. |
|
UNDO, LEAVE inner. |
NEAR-ERR | LEAVE |
inner | If inner does not have the ERROR property, then UNDO would choose a different block (a more enclosing one) than inner. Since LEAVE must be targeted at a block that is the same as the UNDO target or which encloses the UNDO target, this is a problem. |
|
UNDO, NEXT inner. |
NEAR-ERR | NEXT |
inner | If inner does not have the ERROR property, then UNDO would choose a different block (a more enclosing one) than inner. Since NEXT must be targeted at a block that is the same as the UNDO target or which encloses the UNDO target, this is a problem.In addition, if inner is not an iterating block the compiler fails. |
|
UNDO, RETRY outer. |
NEAR-ERR | RETRY |
outer | This is only possible if an inner block (one that more directly encloses the statement) does not have the ERROR property. If inner does have such properties, then it would be implicitly chosen as the UNDO target and that would mismatch with the outer block as the action target, causing the compiler to error. |
|
UNDO, LEAVE outer. |
NEAR-ERR | LEAVE |
outer | The outer block or one of its nested blocks (which still encloses this statement) must have the ERROR property, otherwise the LEAVE will be targeted inside the UNDO target, which is not allowed. |
|
UNDO, NEXT outer. |
NEAR-ERR | NEXT |
outer | The outer block or one of its nested blocks (which still encloses this statement) must have the ERROR property, otherwise the NEXT will be targeted inside the UNDO target, which is not allowed.In addition, if outer is not an iterating block the compiler fails. |
|
UNDO inner, RETRY. |
inner | RETRY |
AS-UNDO | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
UNDO inner, LEAVE. |
inner | LEAVE |
AS-UNDO | * | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
UNDO inner, NEXT. |
inner | NEXT |
AS-UNDO | * | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
UNDO inner, RETRY inner. |
inner | RETRY |
inner | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
UNDO inner, LEAVE inner. |
inner | LEAVE |
inner | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
UNDO inner, NEXT inner. |
inner | NEXT |
inner | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
UNDO inner, RETRY outer. |
n/a | n/a | n/a | Invalid syntax, compiler will always error because RETRY and UNDO must target the same block. |
|
UNDO inner, LEAVE outer. |
inner | LEAVE |
outer | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. (outer has no property dependencies) |
|
UNDO inner, NEXT outer. |
inner | NEXT |
outer | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. (outer has no property dependencies)In addition, if outer is not an iterating block the compiler fails. |
|
UNDO outer, RETRY. |
outer | RETRY |
AS-UNDO | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
UNDO outer, LEAVE. |
outer | LEAVE |
AS-UNDO | * | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
UNDO outer, NEXT. |
outer | NEXT |
AS-UNDO | * | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
UNDO outer, RETRY inner. |
n/a | n/a | n/a | Invalid syntax, compiler will always error because RETRY and UNDO must target the same block. |
|
UNDO outer, LEAVE inner. |
n/a | n/a | n/a | Invalid syntax, compiler will always error because LEAVE must target the same or a more enclosing block as UNDO . |
|
UNDO outer, NEXT inner. |
n/a | n/a | n/a | Invalid syntax, compiler will always error because NEXT must target the same or a more enclosing block as UNDO . |
|
UNDO outer, RETRY outer. |
outer | RETRY |
outer | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
UNDO outer, LEAVE outer. |
outer | LEAVE |
outer | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
UNDO outer, NEXT outer. |
outer | NEXT |
outer | outer must have ANY property (not just ERROR ) otherwise there is a compiler error.In addition, if outer is not an iterating block the compiler fails. |
|
ON <condition> UNDO. |
CURRENT | RETRY (implicit) |
AS-UNDO | CURRENT must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO inner. |
inner | RETRY (implicit) |
AS-UNDO | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO outer. |
outer | RETRY (implicit) |
AS-UNDO | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO, RETRY. |
CURRENT | RETRY |
AS-UNDO | |
|
ON <condition> UNDO, LEAVE. |
CURRENT | LEAVE |
AS-UNDO | * | |
ON <condition> UNDO, NEXT. |
CURRENT | NEXT |
AS-UNDO | * | No error even if the implicit target block is not an iterating block. |
ON <condition> UNDO, RETRY inner. |
CURRENT | RETRY |
inner | CURRENT must be the same block as inner (RETRY and UNDO must always be at the targeted to the same block). |
|
ON <condition> UNDO, LEAVE inner. |
CURRENT | LEAVE |
inner | CURRENT must be the same block as inner or enclosed by inner. The LEAVE target block needs no properties in particular. |
|
ON <condition> UNDO, NEXT inner. |
CURRENT | NEXT |
inner | CURRENT must be the same block as inner or enclosed by inner. The NEXT target block needs no properties in particular.However, if inner is not an iterating block the compiler fails. |
|
ON <condition> UNDO, RETRY outer. |
n/a | n/a | n/a | Invalid syntax. The UNDO will target the current block which is more deeply nested than the RETRY target, which causes the compiler to fail. |
|
ON <condition> UNDO, LEAVE outer. |
CURRENT | LEAVE |
outer | The LEAVE target block needs no properties in particular. |
|
ON <condition> UNDO, NEXT outer. |
CURRENT | NEXT |
outer | The NEXT target block needs no properties in particular.If outer is not an iterating block the compiler fails. |
|
ON <condition> UNDO inner, RETRY. |
inner | RETRY |
AS-UNDO | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO inner, LEAVE. |
inner | LEAVE |
AS-UNDO | * | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
ON <condition> UNDO inner, NEXT. |
inner | NEXT |
AS-UNDO | * | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
ON <condition> UNDO inner, RETRY inner. |
inner | RETRY |
inner | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO inner, LEAVE inner. |
inner | LEAVE |
inner | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO inner, NEXT inner. |
inner | NEXT |
inner | inner must have ANY property (not just ERROR ) otherwise there is a compiler error.If inner is not an iterating block the compiler fails. |
|
ON <condition> UNDO inner, RETRY outer. |
n/a | n/a | n/a | Invalid syntax. The UNDO will target the inner block which is more deeply nested than the RETRY target, which causes the compiler to fail. |
|
ON <condition> UNDO inner, LEAVE outer. |
inner | LEAVE |
outer | inner must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO inner, NEXT outer. |
inner | NEXT |
outer | inner must have ANY property (not just ERROR ) otherwise there is a compiler error.If outer is not an iterating block the compiler fails. |
|
ON <condition> UNDO outer, RETRY. |
outer | RETRY |
AS-UNDO | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO outer, LEAVE. |
outer | LEAVE |
AS-UNDO | * | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
ON <condition> UNDO outer, NEXT. |
outer | NEXT |
AS-UNDO | * | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
ON <condition> UNDO outer, RETRY inner. |
n/a | n/a | n/a | Invalid syntax. The UNDO and RETRY must target the same block, otherwise the compiler fails. |
|
ON <condition> UNDO outer, LEAVE inner. |
n/a | n/a | n/a | Invalid syntax. LEAVE must target the same or a more enclosing block as UNDO is targeting. |
|
ON <condition> UNDO outer, NEXT inner. |
n/a | n/a | n/a | Invalid syntax. NEXT must target the same or a more enclosing block as UNDO is targeting. |
|
ON <condition> UNDO outer, RETRY outer. |
outer | RETRY |
outer | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO outer, LEAVE outer. |
outer | LEAVE |
outer | outer must have ANY property (not just ERROR ) otherwise there is a compiler error. |
|
ON <condition> UNDO outer, NEXT outer. |
outer | NEXT |
outer | outer must have ANY property (not just ERROR ) otherwise there is a compiler error.If outer is not an iterating block the compiler fails. |
In the above table <condition>
can be ERROR
, ENDKEY
, STOP
or QUIT
. The particular condition in question does not matter for the purposes of the behavior documented above.
For details on the implicit targeting of LEAVE
and NEXT
statements, please see the LEAVE Statement and NEXT Statement sections (respectively) of the Control Flow chapter.
The "Static Conversion Possible" column denotes the difference between runtime conversion of actions (infinite loop protection) versus static (compile time) conversion of actions. A static conversion is one that will not change based on runtime state, but instead is based on data known at the time the source code is compiled.
At runtime, a RETRY
action will be converted to a NEXT
, LEAVE
or RETURN
(depending on target block type) if infinite loop protection is NOT disabled. See the Infinite Loop Protection section above for details.
If the action is NEXT
and was caused by an ON phrase
or UNDO
statement AND the target block is a non-iterating inner block (it is not a top-level block), then the NEXT
will be converted to a LEAVE
.
If the action is NEXT
or LEAVE
and the target block matched is a top level block (external procedure, internal procedure, function or trigger), then the action will be converted to a RETURN
.
© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.