Control Flow¶
- Control Flow
This chapter documents the conversion of language statements that are used to control the path or flow of processing through the 4GL code. Examples of this processing is IF THEN ELSE
and loops like REPEAT
. While some of these statements include block-related processing, the next chapter (entitled Blocks) will provide more detailed coverage of the properties and conversion of blocks. Only the basic control-flow features will be covered in this chapter. Likewise, there are many complex transaction processing features (such as UNDO
or database commit/rollback) which are detailed in the Transactions chapter, rather than here. All three topics are highly interrelated, but they have been split up to make the content easier to consume.
It is important to note that the converted code shown in this chapter is highly dependent upon statically importing features from the BlockManager, CompareOps and other important classes in the FWD runtime. For example, most converted programs will have something like this at the top:
import static com.goldencode.p2j.util.BlockManager.*; import static com.goldencode.p2j.util.CompareOps.*;
These allow the converted code to more compactly reference the BlockManager
APIs, comparison operators and other commonly used features. For example, instead of calling BlockManager.doBlock()
in the converted code, the result will be simply doBlock()
. This greatly improves readability and reduces the amount of code. However, the reader of this chapter must remember that these imports are necessary to make that idiom work.
This is the list of control-flow features documented in this chapter:
4GL Feature | Purpose | Java Implementation | Supported |
---|---|---|---|
CASE |
Branch to one of an arbitrary number of blocks of code based on the evaluation of a single expression. | This converts as either a Java switch statement or as the Java if/else if/else statement. |
Yes |
DO |
Groups statements into a single block, optionally specifying processing block options. | Blocks without properties (simple DO blocks) emit using curly braces {} . Loops without properties (simple DO loops that have TO and/or WHILE clauses) emit using the Java while or Java for statements.Non-simple DO blocks or loops emit as calls to the BlockManager doBlock, doTo, doWhile or doToWhile APIs. |
Yes |
FOR |
Groups statements into a block (non-EACH ) or an iterating loop (EACH ) that reads a record from one or more tables at the start of each block execution. |
All forms of the FOR block (non-EACH ) are converted using BlockManager.forBlock[To][While] APIs.All forms of the FOR EACH loop are converted using BlockManager.forEach[To][While] APIs. |
Yes |
IF THEN ELSE |
Branch to one of two possible blocks of code based on the evaluation of a logical expression. |
Converted using the Java if () {} else {} . |
Yes |
LEAVE |
Normally exit the current (or an enclosing) block. | Converts to a BlockManager.leave() , if the target is a managed block (a block that is processed by the BlockManager ).Converts to a Java break statement, if the target is a non-managed block and there is no managed block between the LEAVE statement and the targeted non-managed block.Converts to a combination of a BlockManager.leave() , a BlockManager.deferredLeave() and a Java break statement when the LEAVE targets a non-managed block but there is a managed block between the non-managed and the LEAVE statement.In the case where the LEAVE is treated by the 4GL as an implicit RETURN , it is converted to BlockManager.returnNormal() . |
Yes |
NEXT |
Normally iterate the current (or an enclosing) loop. | Converts to a BlockManager.next() , if the target is a managed block (a block that is processed by the BlockManager ).Converts to a Java continue statement, if the target is a non-managed block and there is no managed block between the NEXT statement and the targeted non-managed block.In the case where the NEXT is treated by the 4GL as an implicit LEAVE , it is converted to BlockManager.leave() .In the case where the NEXT is treated by the 4GL as an implicit RETURN , it is converted to BlockManager.returnNormal() . |
Yes |
PAUSE |
Halt processing of the program until the user presses a key. | This converts to LogicalTerminal.pause() or to LogicalTerminal.pauseBeforeHide() . |
Yes |
QUIT |
Raise the QUIT condition, which by default will cause the current user session to end. |
This converts to BlockManager.quit() . |
Yes |
REPEAT |
Declares a block of statements that are processed repeatedly until the block ends based on options, conditions being raised or contained language statements. | All forms of the REPEAT loop are converted using BlockManager.repeat[To][While] APIs. |
Yes |
RETRY |
Detect if the nearest enclosing block is being retried. | This converts to TransactionManager.isRetry() (when the result must be a logical ) or TransactionManager._isRetry() (when the result needs to be a boolean ). |
Yes |
RETURN |
Exit from the nearest enclosing top-level block. | This converts to the BlockManager.return*() family of methods. |
Yes |
RUN |
Execute and internal or external procedure. | If this is the execution of an external procedure whose name is known at conversion time (the name is hard coded in the 4GL source), then this will emit as a constructor that instantiates an instance of the business logic class and then a call to that instance's execute() method.If this is the execution of an internal procedure in the containing business logic class, then the method of the this instance will be called directly.All other forms which use dynamic names (e.g. RUN VALUE ) or which call procedures indirectly (e.g. RUN IN handle ) will call the ControlFlowOps.invoke*() family of methods. |
Yes |
STOP |
Raise the STOP condition. |
This converts to BlockManager.stop() . |
Yes |
UNDO |
Abnormally end processing of the current block (or an enclosing block), causing a rollback and then the execution of a programmer-specified control flow action. | This converts to the BlockManager.undo*() family of methods. |
Yes |
Inner Blocks¶
Progress 4GL supports multiple structures for organizing code. Broadly, these features can be classified as top-level blocks or inner blocks.
A top-level block is a code that is structured in a manner that can be called or invoked. External procedures, internal procedures, user-defined functions and triggers are all top-level blocks.
Inner blocks are structures that group code inside of top-level blocks. Inner blocks can be nested, thay can loop, they can be labeled and through labels (or by implicit control flow behavior) they can be targets of changes in the flow of control of the program.
There are four types of inner block: DO
, REPEAT
, FOR
and EDITING
. The first three blocks are documented in this chapter. The EDITING
block is special, in that it is really a loop that is nested inside a user-interface statement, rather than a generic flow control block. For details of how EDITING
blocks convert, please see the EDITING Block section of the Editing chapter in Part 6.
DO Block/Loop¶
The following is the 4GL syntax for the DO
statement:
[ label : ] DO [ variable = expression1 TO expression2 [ BY k ]] [ WHILE expression ] [ TRANSACTION ] [ other-options ]
This chapter documents the conversion of the above syntax. A summary of support for the other-options is provided in the Block Options section below, but full details of these other options must be found elsewhere in this reference.
The 4GL DO
block is special in that, by default, there are no block properties provided by the Progress 4GL runtime. At the programmer's option, many properties (see above) can be added. The result means that the DO block can be lightweight (it will perform better), unless options are added that cause block properties to be attached to the block.
For this reason, the DO
block can be converted in several ways. In some cases, when the block doesn't have deep 4GL behavior and is just used to group code together or is used as a looping block, it can be directly converted to compatible Java statements. This is often called a “simple DO
block” and it occurs when there are no 4GL properties which need to be duplicated by the runtime processing.
A DO
block cannot be emitted as a simple DO
block if any of the following scenarios are true:
- The
TRANSACTION
keyword is used in theDO
block definition. - Any
ON
phrase is included in theDO
block definition. - A frame phrase (
WITH FRAME
) is included in theDO
block definition. - A
FOR
orPRESELECT
phrase is included in theDO
block definition. DO
loop anomaly: theDO
block is a loop (there is aTO
and/orWHILE
clause) and inside that loop there is use of:- the
RUN
statement; OR - the
WAIT-FOR
statement; OR - a call to a user-defined function.
When the simple DO
block cannot be used, the DO
block will be converted using one of the BlockManager
doBlock
, doTo,
doWhile
or doToWhile
APIs. Details about all the parameters for these APIs can be find in the DO Block section of the Blocks chapter of this book.
The usage of BlockManager
APIs does not presuppose the transaction level of the block being processed. The transaction level will default as appropriate for each block type but can also be explicitly specified. For details, see the Transactions chapter.
It is always safe to emit a DO
block (or loop) using the BlockManager
APIs. These APIs provide a fully compatible (with the original 4GL) implementation of block processing. That compatibility comes with the cost of a more complex runtime implementation and the performance cost of having multiple layers and delegated block processing. For that reason, where the above scenarios are not present, the resulting code will be emitted as direct Java block processing.
The DO
statement is also special because it can be used as both a block (grouping related statements together) and as a loop. By default it is a block, but when the TO
or WHILE
clauses are used, it becomes a loop.
The DO
loop anomaly was found to be necessary at one point in the development of the runtime, but it has not been checked in recent runtime versions. It may be unnecessary in the current implementation. If further testing confirms this is unnecessary, then it will be removed. The resulting block will not have full-transaction or sub-transaction semantics unless the normal transaction level rules are met as specified in the Transactions chapter.
This section shows how the DO
block gets converted to Java statements, including examples of the looping constructs. It does not provide full details on how the WHILE
and TO
clauses get converted. For this, please see the TO Clause and WHILE Clause sections of this chapter.
Simple DO Block¶
4GL code:
do: message "message 1". end.
Converted code:
blockLabel0: { message("message 1"); }
Details:
If it is a simple DO
block without any specifications (only label is allowed) then it is converted to a plain Java block, annotated by a label.
Simple DO WHILE Loop¶
4GL code:
do while x < 5: x = x + 1. end.
Converted code:
loopLabel0: while (_isLessThan(x, 5)) { x.assign(plus(x, 1)); }
Details:
If it is a DO WHILE
block without any other specifications, it is converted using Java while
statement, annotated with a label.
Since this is a direct mapping to the flow control statements of Java, the conditional expression which is evaluated will generally return a logical
type. This value will then be “unpacked” into a boolean
(often using logical.booleanValue()
) that is passed to the Java while
. In many places in FWD runtime, there are dual versions of a given method, one which returns a logical
and another (usually prefixed by the _
character) which directly returns a boolean
. Where possible, the converted code will use these special versions (to avoid the unpacking step) and directly return the boolean
. This example of is the replacement less than operator which leads to emitting _isLessThan(x, 5)
instead of emitting isLessThan(x, 5).booleanValue()
.
Simple DO TO Loop¶
4GL code:
do x = 0 to 10 by 2: x = x + 1. end.
Converted code:
loopLabel0: for (x.assign(0); _isLessThanOrEqual(x, 10); x.assign(plus(x, 2))) { x.assign(plus(x, 1)); }
Details:
If it is a DO ... TO
block without any other specifications, it is converted using Java for
statement, annotated with a label.
Simple DO TO WHILE Loop¶
4GL code:
do x = 0 to 10 while x < 5: x = x + 1. end.
Converted code:
loopLabel1: for (x.assign(0); _and(_isLessThanOrEqual(x, 10), () -> isLessThan(x, 5)); x.increment()) { x.assign(plus(x, 1)); }
Details:
If it is a DO ... TO ...
WHILE
block without any other specifications, it is converted using Java for
statement, annotated with a label. When both WHILE
and TO
conditions are specified, the for
termination expression is the conjunction of the WHILE
condition with the loop finishing condition of the TO
condition. The test of these logical expressions is conjoined with the AND
operator.
Non-Simple DO Block¶
4GL code:
do transaction: message "message 1". end.
Converted code:
doBlock(TransactionType.FULL, "blockLabel0", new Block((Body) () -> { message("message 1"); }));
Details:
All other cases (with TRANSACTION
option, PRESELECT
or ON
phrases) are converted using BlockManager.do*
functions. In this example, a full transaction is associated with the block, causing the BlockManager
APIs to be required.
Non-Simple DO TO Loop¶
4GL code:
do x = 1 to 10 on error undo, retry: ... end.
Converted code:
ToClause toClause0 = new ToClause(x, 1, 10); OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.ERROR, Action.RETRY, "loopLabel0") }; doTo(TransactionType.SUB, "loopLabel0", toClause0, onPhrase0, new Block((Body) () -> { ... }));
Details:
This loops 10 times, starting with x
as 1
and incrementing x
on each iteration, ending with x
as 10
. When the TO
clause is present, the BlockManager.doTo
API call is emitted.
Non-Simple DO WHILE Loop¶
4GL code:
do while x < 5 on error undo, leave: x = x + 1. end.
Converted code:
LogicalExpression whileClause0 = () -> isLessThan(x, 5); OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.ERROR, Action.LEAVE, "loopLabel0") }; doWhile(TransactionType.SUB, "loopLabel0", whileClause0, onPhrase0, new Block((Body) () -> { x.assign(plus(x, 1)); }));
Details:
This loops so long as x
is less than 5. When the WHILE
clause is present, the BlockManager.doWhile
API call is emitted.
Non-Simple DO TO WHILE Loop¶
4GL code:
do y = 1 to 10 while x < 5 on error undo, leave: x = x + 1. end.
Converted code:
ToClause toClause0 = new ToClause(y, 1, 10); LogicalExpression whileClause0 = () -> isLessThan(x, 5); OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.ERROR, Action.LEAVE, "loopLabel0") }; doToWhile(TransactionType.SUB, "loopLabel0", toClause0, whileClause0, onPhrase0, new Block((Body) () -> { x.assign(plus(x, 1)); }));
Details:
This loops with two constraints, meaning that it will iterate 10 times, starting with y
as 1
and incrementing y
on each iteration, ending with y
as 10
BUT only so as long as x
is less than 5. If x
becomes 5 or above before the 10 iterations are complete, then the loop will exit early. The test of these logical expressions is conjoined with the AND
operator.
When both the TO
and WHILE
clauses are present, the BlockManager.doToWhile
API call is emitted.
REPEAT Loop¶
The following is the 4GL syntax for the DO
statement:
[ label : ] REPEAT [ variable = expression1 TO expression2 [ BY k ]] [ WHILE expression ] [ TRANSACTION ] [ other-options ]
This chapter documents the conversion of the above syntax. A summary of support for the other-options is provided in the Block Options section below, but full details of these other options must be found elsewhere in this reference.
A REPEAT
block is a looping block which will terminate on the condition indicated by the TO
or WHILE
clauses, until a statement is executed that reroutes control flow out of the loop (e.g. LEAVE
, RETURN
, UNDO
) or until some condition is raised (i.e. the user presses the END
key) which causes the loop to end.
By default (with no limiting TO
or WHILE
clauses), the REPEAT
is an infinite loop.
Unlike the DO
block, REPEAT
loops always have block properties. This means that a REPEAT
cannot be directly converted to Java control flow statements since the runtime code must duplicate the block/loop properties of the 4GL. All forms of the REPEAT
loop are converted using BlockManager.repeat[To][While]
APIs. In cases when either the TO
or WHILE
clause is present, the clauses are converted following the rules presented in the TO Clause and WHILE Clause sections of this chapter.
REPEAT Loop¶
4GL code:
/* simple REPEAT */ repeat: ... end.
Converted code:
/* simple REPEAT */ repeat("loopLabel0", new Block((Body) () -> { ... }));
Details:
This simple REPEAT
will loop infinitely until a LEAVE
statement is encountered or some other condition is raised. When neither the TO
nor the WHILE
clause is present, the BlockManager.repeat
API call is emitted.
REPEAT TO Loop¶
4GL code:
/* REPEAT with TO */ repeat x = 1 to 10: ... end.
Converted code:
/* REPEAT with TO */ ToClause toClause0 = new ToClause(x, 1, 10); repeatTo("loopLabel1", toClause0, new Block((Body) () -> { ... }));
Details:
This loops 10 times, starting with x
as 1
and incrementing x
on each iteration, ending with x
as 10
. When the TO
clause is present, the BlockManager.repeatTo
API call is emitted.
REPEAT WHILE Loop¶
4GL code:
/* REPEAT with WHILE */ repeat while y > 0 : ... end.
Converted code:
/* REPEAT with WHILE */ LogicalExpression whileClause0 = () -> isGreaterThan(y, 0); repeatWhile("loopLabel1", whileClause0, new Block((Body) () -> { ... });
Details:
This loops so long as y
is a positive number. When the WHILE
clause is present, the BlockManager.repeatWhile
API call is emitted.
REPEAT TO WHILE Loop¶
4GL code:
/* REPEAT with TO and WHILE */ repeat x = 1 to 10 while y > 0 : ... end.
Converted code:
/* REPEAT with TO and WHILE */ ToClause toClause0 = new ToClause(x, 1, 10); LogicalExpression whileClause0 = () -> isGreaterThan(y, 0); repeatToWhile("loopLabel1", toClause0, whileClause0, new Block((Body) () -> { ... });
Details:
This loops with two constraints, meaning that it will iterate 10 times, starting with x
as 1
and incrementing x
on each iteration, ending with x
as 10
BUT only so as long as y
is a positive number. If y
becomes 0 or negative before the 10 iterations are complete, then the loop will exit early. The test of these logical expressions is conjoined with the AND
operator.
When both the TO
and WHILE
clauses are present, the BlockManager.repeatToWhile
API call is emitted.
REPEAT PRESELECT Loop¶
4GL code:
/* REPEAT PRESELECT */ repeat preselect each book: ... end.
Converted code:
/* REPEAT PRESELECT */ PreselectQuery query0 = new PreselectQuery(); repeat("loopLabel2", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { ... }));
Details:
When queries are involved, the conversion of the REPEAT
block will still use the BlockManager.repeat
API call. As detailed in Part 5 of this book, the database processing is added, but there are no differences in the BlockManager
API usage. Details on the use of the init()
method of the Block
class can be found in the Blocks chapter.
FOR Block/Loop¶
The following is the 4GL syntax for the FOR
statement:
[ label : ] FOR { [EACH | FIRST | LAST] record-phrase } [, [ EACH | FIRST | LAST ] record-phrase ] ... [ variable = expression1 TO expression2 [ BY k ]] [ WHILE expression ] [ TRANSACTION ] [ other-options ]
This chapter documents the conversion of the above syntax. A summary of support for the other-options is provided in the Block Options section below, but full details of these other options must be found elsewhere in this reference.
The FOR
block is the most common statement for iterating through records (from one or more tables) which satisfy a certain condition. As with the DO
and REPEAT
statements, the TO
and WHILE
clauses, if used, get converted following the rules presented in the TO Clause and WHILE Clause sections of this chapter.
From the control flow perspective, the FOR
block will either execute its body at most once (for the FOR
, FOR FIRST
or FOR LAST
cases) or will execute it for every record returned by a FOR EACH
query. This means that the FOR
can be either a block or a loop. Where the DO
loop iteration is driven by the use of TO
or WHILE
clauses, the looping in a FOR
statement is driven by the database query that is associated with the statement. The use of TO
or WHILE
clauses can limit the looping behavior, but such usage will not create a loop where the FOR
would normally be a block.
FOR Unique Block¶
4GL code:
for book where book-id = 0: ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); forBlock("blockLabel0", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, "book.bookId = 0", null, "book.bookId asc"); }, (Body) () -> { query0.unique(); ... }));
Details:
A FOR
statement which has no FIRST
, LAST
or EACH
qualifier before the record phrase is a block (not a loop) in which the record phrase must identify one and only one record in the referenced table. This is referred to as a FOR
unique block. In this case, any code inside the block will execute zero or one times, depending on whether the record is found. An error is raised if more than one record matches the criteria.
The conversion processing will insert the query initialization into the init()
method and in the absence of a TO
or WHILE
clause, the unique()
method call on the query (which will return the record or raise an error) will be emitted as the first statement in the body()
method. This is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
When neither the TO
or WHILE
clauses are present, the BlockManager.forBlock
API call is emitted.
FOR Unique TO Block¶
4GL code:
for book where book-id = 0 x = 1 to 5: ... end.
Converted code:
RandomAccessQuery query2 = new RandomAccessQuery(); ToClause toClause4 = new ToClause(x, 1, 5); forBlockTo("blockLabel3", toClause4, new Block((Init) () -> { RecordBuffer.openScope(book); query2.initialize(book, "book.bookId = 0", null, "book.bookId asc"); }, (Enter) () -> { query2.unique(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has no FIRST
, LAST
or EACH
qualifier before the record phrase is a block (not a loop) in which the record phrase must identify one and only one record in the referenced table. This is referred to as a FOR
unique block. In this case, any code inside the block will execute zero or one times, depending on whether the record is found. An error is raised if more than one record matches the criteria.
The conversion processing will insert the query initialization into the init()
method and with the presence of a TO
clause, the unique()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. This is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the TO
clause DOES NOT make this a loop. Instead, when the block is executed, if a record is found, then x
would be set to 1
and it would never be incremented.
When the TO
clause is present, the BlockManager.forBlockTo
API call is emitted.
FOR Unique WHILE Block¶
4GL code:
for book where book-id = 0 while y < 10: ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); LogicalOp whileClause0 = () -> isLessThan(y, 10); forBlockWhile("blockLabel0", whileClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, "book.bookId = 0", null, "book.bookId asc"); }, (Enter) () -> { query0.unique(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has no FIRST
, LAST
or EACH
qualifier before the record phrase is a block (not a loop) in which the record phrase must identify one and only one record in the referenced table. This is referred to as a FOR
unique block. In this case, any code inside the block will execute zero or one times, depending on whether the record is found. An error is raised if more than one record matches the criteria.
The conversion processing will insert the query initialization into the init()
method and with the presence of a WHILE
clause, the unique()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. This is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the WHILE
clause DOES NOT make this a loop. Instead, before the block is executed, if a record is found, then y
would be compared to 10
and if not less than that, the block would exit without executing.
When WHILE
clause is present, the BlockManager.forBlockWhile
API call is emitted.
FOR Unique TO WHILE Block¶
4GL code:
for book where book-id = 0 x = 1 to 5 while y < 10: ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); ToClause toClause0 = new ToClause(x, 1, 5); LogicalOp whileClause0 = () -> isLessThan(y, 10); forBlockToWhile("blockLabel0", toClause0, whileClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, "book.bookId = 0", null, "book.bookId asc"); }, (Enter) () -> { query0.unique(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has no FIRST
, LAST
or EACH
qualifier before the record phrase is a block (not a loop) in which the record phrase must identify one and only one record in the referenced table. This is referred to as a FOR
unique block. In this case, any code inside the block will execute zero or one times, depending on whether the record is found. An error is raised if more than one record matches the criteria.
The conversion processing will insert the query initialization into the init()
method and with the presence of both TO
and WHILE
clauses, the unique()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. This is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the TO
and WHILE
clauses DOES NOT make this a loop. Instead, before the block is executed, if a record is found, then y
would be compared to 5
and if not less than that, the block would exit without executing. At the same time (after a record is found but before the block is executed), x
would be set to 1
and it would never be incremented.
When both the TO
and WHILE
clauses are present, the BlockManager.forBlockToWhile
API call is emitted.
FOR FIRST/LAST Block¶
4GL code:
for first book: ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); forBlock("blockLabel0", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { query0.first(); ... }));
Details:
A FOR
statement which has a FIRST
or LAST
qualifier before the record phrase is a block (not a loop) in which the record phrase always identifies (at most) one and only one record in the referenced table. The WHERE
clause can refine the criteria but it is also valid to just request the first or last record for a table. In this case, any code inside the block will execute zero or one times, depending on whether the record is found.
The conversion processing will insert the query initialization into the init()
method and in the absence of a TO
or WHILE
clause, either the first()
or last()
method call on the query (which will return the record or raise an error) will be emitted as the first statement in the body()
method. Other than the query retrieval method that is called, there is no other difference between a FOR FIRST
and a FOR LAST
. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
When neither the TO
or WHILE
clauses are present, the BlockManager.forBlock
API call is emitted.
FOR FIRST/LAST TO Block¶
4GL code:
for first book x = 1 to 10: ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); ToClause toClause0 = new ToClause(x, 1, 10); forBlockTo("blockLabel0", toClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.first(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has a FIRST
or LAST
qualifier before the record phrase is a block (not a loop) in which the record phrase always identifies (at most) one and only one record in the referenced table. The WHERE
clause can refine the criteria but it is also valid to just request the first or last record for a table. In this case, any code inside the block will execute zero or one times, depending on whether the record is found.
The conversion processing will insert the query initialization into the init()
method and with the presence of a TO
clause, either the first()
or last()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. Other than the query retrieval method that is called, there is no other difference between a FOR FIRST TO
and a FOR LAST TO
. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the TO
clause DOES NOT make this a loop. Instead, when the block is executed, if a record is found, then x
would be set to 1
and it would never be incremented.
When a TO
clause is present, the BlockManager.forBlockTo
API call is emitted.
FOR FIRST/LAST WHILE Block¶
4GL code:
for last book while y > 0 : ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); LogicalOp whileClause0 = () -> isGreaterThan(y, 0); forBlockWhile("blockLabel0", whileClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.last(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has a FIRST
or LAST
qualifier before the record phrase is a block (not a loop) in which the record phrase always identifies (at most) one and only one record in the referenced table. The WHERE
clause can refine the criteria but it is also valid to just request the first or last record for a table. In this case, any code inside the block will execute zero or one times, depending on whether the record is found.
The conversion processing will insert the query initialization into the init()
method and with the presence of a WHILE
clause, either the first()
or last()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. Other than the query retrieval method that is called, there is no other difference between a FOR FIRST WHILE
and a FOR LAST WHILE
. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the WHILE
clause DOES NOT make this a loop. Instead, before the block is executed, if a record is found, then y
would be compared to 0
and if not greater than that, the block would exit without executing.
When a WHILE
clause is present, the BlockManager.forBlockWhile
API call is emitted.
FOR FIRST/LAST TO WHILE Block¶
4GL code:
for last book x = 1 to 10 while y > 0: ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); ToClause toClause0 = new ToClause(x, 1, 10); LogicalOp whileClause0 = () -> isGreaterThan(y, 0); forBlockToWhile("blockLabel0", toClause0, whileClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.last(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has a FIRST
or LAST
qualifier before the record phrase is a block (not a loop) in which the record phrase always identifies (at most) one and only one record in the referenced table. The WHERE
clause can refine the criteria but it is also valid to just request the first or last record for a table. In this case, any code inside the block will execute zero or one times, depending on whether the record is found.
The conversion processing will insert the query initialization into the init()
method and with the presence of both the TO
and WHILE
clauses, either the first()
or last()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. Other than the query retrieval method that is called, there is no other difference between a FOR FIRST TO WHILE
and a FOR LAST TO WHILE
. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the TO
and WHILE
clauses DOES NOT make this a loop. Instead, before the block is executed, if a record is found, then y
would be compared to 0
and if not greater than that, the block would exit without executing. At the same time (after a record is found but before the block is executed), x
would be set to 1
and it would never be incremented.
When both the TO
and WHILE
clauses are present, the BlockManager.forBlockToWhile
API call is emitted.
FOR FIRST/LAST Multi-Table Block¶
4GL code:
for first book, last person: ... end.
Converted code:
CompoundQuery query0 = new CompoundQuery(); forBlock("blockLabel0", new Block((Init) () -> { RecordBuffer.openScope(book, person); query0.initialize(false, false); query0.addComponent(new RandomAccessQuery().initialize(book, ((String) null), null, "book.bookId asc"), QueryConstants.FIRST); query0.addComponent(new RandomAccessQuery().initialize(person, ((String) null), null, "person.siteId asc, person.empNum asc"), QueryConstants.LAST); }, (Body) () -> { query0.iterate(); ... }));
Details:
A FOR
statement which references multiple tables where none of the qualifiers before the record phrase is an EACH
, is a block (not a loop) in which each record phrase always identifies (at most) one and only one record in the referenced table. In the above example, this is a FOR FIRST table1, LAST table2
statement. The number of record phrases can be arbitrarily large. So long as there is no EACH
qualifier, then this is a block that executes (at most) one time. The WHERE
clause can refine the criteria but it is also valid to just request the first or last record for a table. In this case, any code inside the block will execute zero or one times, depending on whether the records are found.
The conversion processing will insert the query initialization into the init()
method and in the absence of a TO
or WHILE
clause, the iterate()
method call on the query (which will return the records or raise an error) will be emitted as the first statement in the body()
method. The result of the single iterate()
method call is that each referenced buffer will have a record in it if all of the record phrases successfully identified a record. Although the retrieval method is called iterate()
, that method is only ever called once in the FOR
block case. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
When neither the TO
or WHILE
clauses are present, the BlockManager.forBlock
API call is emitted.
From a block management and control flow perspective, the multi-table case is no different than a regular FOR FIRST/LAST
. The conversion process does emit the database processing differently, but the block management is unaware of this fact and the control flow does not change based on this.
FOR EACH Loop¶
4GL code:
for each book: ... end.
Converted code:
AdaptiveQuery query0 = new AdaptiveQuery(); forEach("loopLabel0", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { query0.next(); ... }));
Details:
A FOR
statement which has an EACH
qualifier before the record phrase is a loop in which the record phrase identifies zero or more records in the referenced table. The WHERE
clause refines the criteria and the BY
clause largely determines the order the records are returned. Any code inside the block will execute once for every record that is found, except if conditions are raised or flow control statements are executed that cause the loop to be exited.
The conversion processing will insert the query initialization into the init()
method and in the absence of a TO
or WHILE
clause, the next()
method call on the query (which will return the record or raise an error) will be emitted as the first statement in the body()
method. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
When neither the TO
or WHILE
clauses are present, the BlockManager.forEach
API call is emitted.
FOR EACH TO Loop¶
4GL code:
for each book x = 1 to 10: ... end.
Converted code:
AdaptiveQuery query0 = new AdaptiveQuery(); ToClause toClause0 = new ToClause(x, 1, 10); forEachTo("loopLabel0", toClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.next(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has an EACH
qualifier before the record phrase is a loop in which the record phrase identifies zero or more records in the referenced table. The WHERE
clause refines the criteria and the BY
clause largely determines the order the records are returned. Any code inside the block will execute once for every record that is found, except if conditions are raised or flow control statements are executed that cause the loop to be exited.
The conversion processing will insert the query initialization into the init()
method and in the presence of a TO
clause, the next()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the TO
clause limits the loop processing. Each time the loop is entered (the first time and then after each iteration), if a record is found, then x
would increment from 1
to 10
. After the 10th iteration, the loop will exit, even if there are more records. Likewise, if there are less than 10
records, the loop will exit before the TO
clause can complete.
When the TO
clause is present, the BlockManager.forEachTo
API call is emitted.
FOR EACH WHILE Loop¶
4GL code:
for each book while y > 0: ... end.
Converted code:
AdaptiveQuery query0 = new AdaptiveQuery(); LogicalOp whileClause0 = () -> isGreaterThan(y, 0); forEachWhile("loopLabel0", whileClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.next(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has an EACH
qualifier before the record phrase is a loop in which the record phrase identifies zero or more records in the referenced table. The WHERE
clause refines the criteria and the BY
clause largely determines the order the records are returned. Any code inside the block will execute once for every record that is found, except if conditions are raised or flow control statements are executed that cause the loop to be exited.
The conversion processing will insert the query initialization into the init()
method and in the presence of a WHILE
clause, the next()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the WHILE
clause limits the loop processing. Each time the loop is entered (the first time and then after each iteration), if a record is found, then y
is compared to 0
and the loop will exit if it is not greater than that value. The first time the expression evaluates false, the loop will exit, even if there are more records. Likewise, if the records run out before the WHILE
clause evaluates false, the loop will still exit.
When the WHILE
clause is present, the BlockManager.forEachWhile
API call is emitted.
FOR EACH TO WHILE Loop¶
4GL code:
for each book x = 1 to 10 while y > 0: ... end.
Converted code:
AdaptiveQuery query0 = new AdaptiveQuery(); ToClause toClause0 = new ToClause(x, 1, 10); LogicalOp whileClause0 = () -> isGreaterThan(y, 0); forEachToWhile("loopLabel0", toClause0, whileClause0, new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Enter) () -> { query0.next(); }, (Body) () -> { ... }));
Details:
A FOR
statement which has an EACH
qualifier before the record phrase is a loop in which the record phrase identifies zero or more records in the referenced table. The WHERE
clause refines the criteria and the BY
clause largely determines the order the records are returned. Any code inside the block will execute once for every record that is found, except if conditions are raised or flow control statements are executed that cause the loop to be exited.
The conversion processing will insert the query initialization into the init()
method and in the presence of both the TO
and WHILE
clauses, the next()
method call on the query (which will return the record or raise an error) will be emitted in the enter()
method. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
The presence of the TO
clause limits the loop processing. Each time the loop is entered (the first time and then after each iteration), if a record is found, then x
would increment from 1
to 10
. After the 10th iteration, the loop will exit, even if there are more records. Likewise, if there are less than 10
records, the loop will exit before the TO
clause can complete.
The presence of the WHILE
clause limits the loop processing. Each time the loop is entered (the first time and then after each iteration), if a record is found, then y
is compared to 0
and the loop will exit if it is not greater than that value. The first time the expression evaluates false, the loop will exit, even if there are more records. Likewise, if the records run out before the WHILE
clause evaluates false, the loop will still exit.
The evaluation of the TO
and WHILE
clauses is done using the AND
operator, which means that both limits must be met for the loop to continue.
When both the TO
and WHILE
clauses are present, the BlockManager.forEachToWhile
API call is emitted.
FOR EACH Multi-Table Loop¶
4GL code:
for each person, last address: ... end.
Converted code:
CompoundQuery query0 = new CompoundQuery(); forEach("loopLabel0", new Block((Init) () -> { RecordBuffer.openScope(person, address); query0.initialize(false, false); query0.addComponent(new AdaptiveQuery().initialize(person, ((String) null), null, "person.siteId asc, person.empNum asc")); query0.addComponent(new RandomAccessQuery().initialize(address, ((String) null), null, "address.addrId asc"), QueryConstants.LAST); }, (Body) () -> { query0.iterate(); ... }));
Details:
A FOR
statement which references multiple tables where at least one of the qualifiers before the record phrase is an EACH
, is a loop in which each record phrase identifies zero or more records in the referenced tables. In the above example, this is a FOR EACH table1, LAST table2
statement. The number of record phrases can be arbitrarily large. The WHERE
clause refines the criteria and the BY
clause largely determines the order the records are returned. Any code inside the loop will execute so long as the records are being retrieved, except if conditions are raised or flow control statements are executed that cause the loop to be exited.
The conversion processing will insert the query initialization into the init()
method and in the absence of a TO
or WHILE
clause, the iterate()
method call on the query (which will return the records or raise an error) will be emitted as the first statement in the body()
method. The result of the single iterate()
method call is that each referenced buffer will have a record in it if all of the record phrases successfully identified a record. Every time the loop is entered (the first time in and on every iteration), the iterate()
method will be called and the loop body will process so long as records are retrieved. The placement of the query retrieval method call is required to fully duplicate the 4GL condition handling and flow control behavior. Please see the Blocks chapter for full details.
When neither the TO
or WHILE
clauses are present, the BlockManager.forEach
API call is emitted.
From a block management and control flow perspective, the multi-table case is no different than a regular FOR EACH
. The conversion process does emit the database processing differently, but the block management is unaware of this fact and the control flow does not change based on this.
Labels¶
Inner blocks (all forms of DO
, REPEAT
, FOR
and EDITING
) can be labeled and that label can then be referenced in language statements that explicitly change the flow of control such as UNDO
, LEAVE
or NEXT
statements and in ON
phrases. In 4GL labels are specified before a block, as in: label: <BLOCK>
. If a block is converted using a Java statement then a standard Java label is used, as in: label: [statement] { ... }
.
Example 1:
label1: do: message "message 1". end. label2: do while true: message "message 2". leave. end.
Converted code:
label1: { message("message 1"); } label2: while (true) { message("message 2"); break label2; }
Details:
The first block is converted to a simple Java block, to which the label is associated (label1
in this case).
The second block converts to a while
statement, which also has an associated label (label2
in this case). The LEAVE
statement, though unlabeled in the 4GL is explicitly matched to the same label that would be chosen at runtime in the 4GL. For details on how this implicit label reference is resolved, please see the section entitled Determining the Target and Meaning of UNDO, LEAVE, NEXT and RETRY in the Blocks chapter.
Example 2:
some-label: repeat: message "message 1". leave. end.
Converted code:
repeat("someLabel", new Block((Body) () -> { message("message 1"); leave("someLabel"); }));
Details:
When a block is converted to an API call provided by the BlockManager
, then the label is specified as one of the parameters of this API.
The label name gets converted following the same conversion rules as the 4GL variable names. For details about how they get converted, please see the Naming section of the Other Customization chapter from the FWD Conversion Handbook.
During conversion, each block which doesn't have set an explicit label will have an implicit label added. Depending on the type of block (looping or not), the implicit label names will follow these rules:
Block Type | Implicit Label Name | Details |
---|---|---|
Looping block: • DO [ TO ] [ WHILE ] • FOR EACH • REPEAT |
loopLabel<index> |
All looping blocks without an explicit label will be implicitly labeled. The <index> starts from zero and counts, from top to bottom in the procedure file, all looping blocks without an explicit label.The DO block is considered a looping block only if it contains at least one of the TO or WHILE clauses. |
A non-looping block: • DO • FOR • FOR FIRST • FOR LAST |
blockLabel<index> |
All non-top level blocks which do not loop will be implicitly labeled. The <index> starts from zero and counts, from top to bottom in the procedure file, all non- top level and non-looping blocks without an explicit label. |
EDITING block |
editingLabel<index> |
All EDITING blocks without an explicit label will be implicitly labeled. The <index> starts from zero and counts, from top to bottom in the procedure file, all editing blocks without an explicit label. |
Example 3:
do: x = x + 1. end.
Converted code:
blockLabel0: { x.assign(plus(x, 1)); }
Details:
A simple DO
without any of the TO
or WHILE
clauses is implicitly labeled using the blockLabel<index>
rule.
Example 4:
repeat: x = x + 1. leave. end.
Converted code:
repeat("loopLabel0", new Block((Body) () -> { x.assign(plus(x, 1)); leave("loopLabel0"); }));
Details:
A non-labeled REPEAT
block will be implicitly labeled using the loopLabel<index>
rule, as this is a looping block.
TO Clause¶
The TO
clause adds a looping variable to a DO
, FOR
or REPEAT
block. If this variable reaches its specified boundary value, the block exits. Its syntax is:
variable = expression1 TO expression2 [ BY k ]
where the 4GL parameters are:
variable
- identifies the name of a variable whose value you are changing in a loop. It can be of aninteger
,decimal
ordate
type.expression1
- an expression which is evaluated on the first iteration of the loop and initializes the looping variable. The type of the expression must be assignment compatible with the variable.expression2
- expression which represents the boundary value for the variable. It gets evaluated on each iteration. When the variable exceeds the value of this expression (or is less than it if the stepk
is negative), the loop ends. Sinceexpression1
is compared toexpression2
at the start of the first iteration of the block, the block can be executed 0 times. The type of the expression must be comparable with the variable.k
- loop step, the amount to add to the variable after each iteration. This must be a constant. It can be either positive or negative. The default value is 1 (if the variable is ofdate
type that means 1 day).
For the simple DO ... TO ...
block, the TO
is converted using a for
Java loop.
When non-simple DO blocks are being converted, the TO
clause is converted using a ToClause
object which is passed to a the BlockManager
API.
for Java Loop¶
When a simple DO
block has a TO
clause, it gets converted using the Java for
loop, which will have the following format:
<label>: for (<variable>.assign(<expression1>); _is<Less/Greater>ThanOrEqual(<variable>, <expression2>); <increment_logic>) { ... }
where <increment_logic>
can be:
<variable>.increment()
is emitted in cases when theBY
option is not specified or is set to 1.<variable>.decrement()
is emitted in cases when theBY
option is specified or is set to -1.<variable>.assign(plus(<variable>, <k>))
is emitted in cases when theBY
option is specified and is set to a value which is not 1 or -1.<variable>.assign(plusDays(<variable>, <k>))
is emitted in cases when adate
variable is used and theBY
option is specified with a value which is not 1 or -1.
Example 4:
/* example with constants */ do x = 1 to 10 by 5: ... end.
Converted code:
/* example with constants */ for (x.assign(1); _isLessThanOrEqual(x, 10); x.assign(plus(x, 5))) { ... }
Details:
As constants are used for an integer variable, the Java's for
statement will have as clauses the converted code which will set the initial value for the loop variable (x.assign(1)
), the expression for the termination value (_isLessThanOrEqual(x, 10)
) and the increment logic (x.assign(plus(x, 5))
).
The use of _isLessThanOrEqual()
is required because the increment value is positive.
Example 5:
/* example with expressions */ do x = y + 4.4 to (y + z) / 1.5 by -1: ... end.
Converted code:
/* example with expressions */ for (x.assign(plus(y, decimal.fromLiteral("4.4"))); _isGreaterThanOrEqual(x, divide(plus(y, z), 1.5)); x.decrement()) { ... }
Details:
For complex expression cases the converted code is the same, except that the initial value and the end value will be determined by evaluating the corresponding expression.
The use of _isGreaterThanOrEqual()
is required because the increment value is negative.
Example 6:
/* example with dates */ do dat = 01/01/2012 to date2 by -2: ... end.
Converted code:
/* example with dates */ for (dat.assign(date.fromLiteral("01/01/2012")); _isGreaterThanOrEqual(dat, date2); dat.assign(date.plusDays(dat, -2))) { ... }
Details:
When date
values are used and the step is not 1 or -1, the date.plusDays
is used to change the loop variable after each iteration.
The use of _isGreaterThanOrEqual()
is required because the increment value is negative.
ToClause¶
If a block is converted to a block function provided by the BlockManager
class, a ToClause
helper object is created. It is passed to as a parameter to the BlockManager.doTo[While]
, BlockManager.forBlockTo[While]
, BlockManager.forEachTo[While]
or BlockManager.repeatTo[While]
APIs, as the to
parameter .
The ToClause
constructor takes the following parameters, each corresponding to 4GL parameters:
variable
- variable ofinteger
,decimal
ordate
data type.expression1
- arbitrary expression (e.g.plus(x, 1)
) or constant value.expression2
- constant, variable or arbitrary expression. If it is a constant or a variable then it is directly passed to theToClause
constructor. Expressions are defined in a more complicated way. Because, unlike theexpression1
, which is evaluated once and before the first iteration,expression2
should be re-evaluated on each iteration and therefore it is represented, depending on the type of the looping variable, byIntegerExpression
,DecimalExpression
orDateExpression
. Instances of these classes allow the expression evaluation to be deferred and to be delegated to theBlockManager
. The expression is defined by overriding theexecute()
function of the appropriate*Expression
class. This allows the expression to access the latest version of the business logic's state (e.g. fields or other variables), as this state may be modified during the block iteration.k
- this is an optional, integer or decimal constant, representing the step used to adjust the variable, after each iteration.
The constructors defined by the ToClause
class are a bit wider than it is described in this section, but the converted code fits into the described set of parameters.
In cases when the boundary expression (expression2
) is not a constant or logical variable, then it is converted using an anonymous implementation of the DecimalExpression, IntegerExpression or DateExpression
classes, following this format:
[Decimal|Expression|Integer]Expression resExpr<index> = new [Decimal|Expression|Integer]Expression() { public <type> execute() { return <expression2>; } };
where the <index>
is a counter for all complex expression encountered associated with a TO
clause boundary in the current 4GL program, from top to bottom (starting from 0) and the <type>
depends on the type of the looping variable (is either decimal, integer or date).
Each ToClause
instance will be named using the toClause<index>
format, where <index>
is a counter for all ToClause
objects emitted in this 4GL program, from top to bottom.
Example 1:
repeat x = 1 to 10 by 5: ... end.
Converted code:
ToClause toClause0 = new ToClause(x, 1, 10, 5); repeatTo("loopLabel0", toClause0, new Block((Body) () -> { ... }));
Details:
Constants are used to specify the initial value, the boundary and the step. So, the ToClause constructor will have as parameters a variable and constants for the initial value, boundary and step.
Example 2:
repeat x = y + 4.4 to (y + z) / 1.5: ... end.
Converted code:
DecimalExpression resExpr0 = new DecimalExpression() { public decimal execute() { return divide(plus(y, z), 1.5); } }; ToClause toClause0 = new ToClause(x, plus(y, decimal.fromLiteral("4.4")), resExpr0); repeatTo("loopLabel0", toClause0, new Block((Body) () -> { ... }));
Details:
As a complex expression is used for the loop termination condition, a DecimalExpression
object is constructed, with its execute()
method overridden so that it will be evaluated before each iteration; the result of this method will be compared against the variable's current value.
Example 3:
repeat dat = 01/01/2012 to date2 by -2: ... end.
Converted code:
ToClause toClause0 = new ToClause(dat, date.fromLiteral("01/01/2012"), date2, new integer(-2)); repeatTo("loopLabel0", toClause0, new Block((Body) () -> { ... }));
Details:
When date
values are used, the converted code looks similar as when integer
or decimal
values are used, except the conversion of the literals will be done as appropriate for the date
type.
WHILE Clause¶
A WHILE
clause indicates the condition which allows a DO
, FOR
or REPEAT
loop to continue execution of its next iteration. The loop iterates as long as the condition specified by the expression
evaluates to true
. The expression can be arbitrary, but should evaluate to a logical value. Its syntax is:
WHILE expression
and it can be converted using:
- a
while
Java cycle (for simpleDO ... WHILE ...
loops); - as a part of cycle completion condition with a
for
Java loop (for simpleDO ... TO ... WHILE ...
loops); - with non-simple blocks, a
LogicalExpression
object passed to a block API provided by theBlockManager.
WHILE as a while Java Loop¶
The Java while
statement is emitted only in cases when a simple DO
block has a WHILE
clause. Even when complex expressions are encountered, in this case the 4GL expression will be emitted directly in the converted while
loop's expression, instead of using a LogicalExpression
delegate.
Since the Java flow control statements cannot interpret an expression of type logical
, the result of the expression evaluation must be “unwrapped” to a boolean
type using booleanValue()
or by using a version of a runtime method that directly returns a boolean
instead of a logical
.
Example 1:
do while l: ... end.
Converted code:
while ((l).booleanValue()) { ... }
Details:
As the loop condition is a simple variable, the Java's WHILE
statement is emitted, with the expression emitted directly as the converted while
's clause.
Example 2:
do while x + 5 < y - 2: ... end.
Converted code:
while (_isLessThan(plus(x, 5), minus(y, 2))) { ... }
Details:
Even with a complex expression, the loop termination condition is emitted directly in the converted WHILE
statement. In this case, the method that implements the less than operator has a version that directly returns a boolean
instead of a logical
.
WHILE as a for Java Loop¶
In cases when a simple DO
statement has both the TO
and WHILE
clauses, it will be converted using Java for
loop, with the converted WHILE
expression joined with the converted boundary condition of the TO
clause using the _and
operator.
Example 3:
do x = 1 to y + 1 while l: ... end.
Converted code:
for (x.assign(1); _and(_isLessThanOrEqual(x, plus(y, 1)), l); x.increment()) { ... }
Details:
In this case, the WHILE
condition is represented by a simple variable, which gets joined with the TO
termination expression using the _and
operator.
Example 4:
do x = y to 10 while x + 5 < y - 2: ... end.
Converted code:
for (x.assign(y); _and(_isLessThanOrEqual(x, 10), () -> isLessThan(plus(x, 5), minus(y, 2))); x.increment()) { ... }
Details:
Even in cases when the WHILE
condition is represented by a complex expression, it still gets joined with the TO
termination expression using the _and
operator.
WHILE as a LogicalExpression¶
If a block was converted using a BlockManager
API, then a LogicalExpression
is created in order to represent the WHILE
expression. It is passed to the BlockManager.do[To]While
, BlockManager.forBlock[To]While
, BlockManager.forEach[To]While
or BlockManager.repeat[To]While
APIs as the expr
parameter.
In cases when the expression is not a constant or a logical
variable, then it is converted using an anonymous implementation of the LogicalExpression
class, following this format:
LogicalExpression whileClause<index> = new LogicalExpression() { public logical execute() { return <expression>; } };
where the <index>
is a counter for all complex logical expressions encountered in the current 4GL program, from top to bottom (starting from 0) and the <expression>
is the converted 4GL WHILE
clause, which gets evaluated on first entry and after each loop iteration completes.
In cases where both a ToClause
and a WHILE
LogicalExpression
instance are passed to a BlockManager
API, the testing of the ToClause
boundary expression and WHILE
expression will be combined using the AND operator (_and
). This is hidden inside the delegation logic of the BlockManager
, but it is important to know so that the behavior of the flow control logic can be predicted.
Example 5:
repeat while l: ... end.
Converted code:
repeatWhile("loopLabel0", () -> l, new Block() { ... });
Details:
If the target expression is a logical
constant or a logical
variable, then it is directly passed to the LogicalExpression
constructor instead of emitting as an inner class. This is a minor optimization since the variable can be directly de-referenced by the delegate.
Example 6:
repeat while x + 5 < y - 2: ... end.
Converted code:
LogicalOp whileClause0 = () -> isLessThan(plus(x, 5), minus(y, 2)); repeatWhile("loopLabel0", whileClause0, ew Block((Body) () -> { ... }));
Details:
If the target expression is more complex than a single variable or constant, it is defined by overriding the execute()
method of the LogicalExpression
anonymous implementation. The whileClause0.execute()
method will be invoked on first entry and after each iteration and if it returns a true value, the loop body will be executed.
Block Options¶
The following is a summary of the support for DO
, REPEAT
and FOR
inner block options. EDITING
blocks have no block options in the 4GL or Java.
Option | Blocks | Description | Supported |
---|---|---|---|
[BREAK] BY { expression | COLLATE (string , strength [, collation ]) } [DESCENDING] ... |
FOR |
Implements a sorting specification for the query and optionally associates break-groups. See the Query Sorting chapter in Part 5. | Yes, except for the COLLATE option. |
FOR record [ , record ] ... |
DO , REPEAT |
Causes a strong buffer scope to be associated with the given block. See the Record Buffer Definition and Scoping chapter in Part 5. | Yes |
ON { ENDKEY | ERROR | QUIT | STOP } spec |
all | Specifies how the inner block is to react to the given condition. See the Blocks chapter for full details. | Yes |
preselect-phrase |
DO , REPEAT |
Creates a preselect query that can retrieve records from one or more tables and associates it with the given block. See the Queries chapter in Part 5 for more details. | Yes |
query-tuning-phrase |
all | Provides some control over queries backed by a Progress DataServer. FWD supports all databases with the same converted application code. As a general principle, there is no need to convert DataServer-specific code. At this time no reason has been found to convert this. | No |
variable = expression1 TO expression2 [BY k] |
all | See the TO Clause section above. | Yes |
TRANSACTION |
all | Causes the given block to have full transaction support. See the Transactions chapter for more details. | Yes |
WHILE expression |
all | See the WHILE Clause section above. | Yes |
WITH frame-phrase |
all | Scopes the given frame to the associated block. For more details, see the Frame Scoping section of the Frames chapter in Part 6. | Yes |
CASE Statement¶
Branch to one of an arbitrary number of blocks of code based on the evaluation of a single expression. 4GL syntax:
CASE expression : { WHEN value1 [ OR WHEN value2 ] ... THEN { block | statement } } ... [ OTHERWISE { block | statement } ] END [ CASE ]
Where its parameters are:
expression
- the expression that determines which branch of code to execute. It can be an arbitrary expression which can yield a value of any data type.value
- eachvalue
is an expression that evaluates to a possible value forexpression
. It should evaluate to a value of the data type which is comparable with the data type of the value thatexpression
yields.
The CASE
statement can be converted in 2 possible ways.
The Java switch
statement is the optimal way to convert CASE
since this is directly equivalent. However, due to limitations in the Java language, it can only be used if the expression
is of type integer expression and ALL values are integer constants. The conversion rules will use the following format:
switch((<expression>).intValue()) { case <value1>: [case <value2> :] ... { ... break; } ... [ default: { ... break; } ] }
As a general purpose fallback, any CASE statement that cannot be implemented using the Java switch
statement will be converted using the Java if
/ else if
/@ else statement. This will occur if the expression is not of integer type or if at least one value is not a constant. The conversion rules will use the following format:
<expression_type> condition<index> = <expression>; if (condition<index>.equals(<value1>) [ || condition<index>.equals(<value1>) ] ...) { ... } ... [ else { ... } ]
where the <index>
is a counter for all CASE
statements converted to compatible IF/ELSE
Java statements, starting from 0 and from top to bottom in the external procedure. This local condition variable is used so that the expression is only evaluated once (it is faster and avoids unexpected side-effects). The captured valid is referenced in each comparison test.
Multiple WHEN
clauses are converted in order and included with the Java ||
(logical OR operator).
In all cases, the block or statement following a certain WHEN
branch will be converted following the same rules as the IF
's THEN/ELSE
branches get converted: the code will be enclosed in a Java { ... }
block, with a Java break
statement added last.
Example 1:
case x + 1: when 1 or when 2 then message "message 1". when 3 then do: message "message 2". message "message 3". end. otherwise message "message 4". end.
Converted code:
switch (plus(x, 1)).intValue()) { case 1: case 2: { message("message 1"); break; } case 3: { message("message 2"); message("message 3"); break; } default: { message("message 4"); break; } }
Details:
As the condition evaluates to an integer value and all WHEN
clauses test for integer constants, the statement block is converted to a compatible Java switch
statement. For each WHEN
clause a break
statement is added in the converted code. Where multiple tests result in executing the same code (the when 1 or when 2
test), consecutive case
clauses are emitted in the converted code, which choose the same block.
The OTHERWISE
branch converts to the default
branch for the converted switch
statement.
Example 2:
case x * 2: when 1 or when y then message "message 1". when (y + 1) * 2 then do: message "message 2". message "message 3". end. otherwise message "message 4". end.
Converted code:
integer condition0 = multiply(x, 2); if (condition0.equals(new integer(1)) || condition0.equals(y)) { message("message 1"); } else if (condition0.equals(multiply(plus(y, 1), 2))) { message("message 2"); message("message 3"); } else { message("message 4"); }
Details:
In this case even if the condition evaluates to an integer value, the WHEN
doesn't test for constants - so, IF/ELSE
statements are emitted to handle each WHEN
condition. For compound WHEN
tests (as in when 1 or when y
), the converted if
statement's condition will contain both tests, joined with the Java ||
operator. The OTHERWISE
block is emitted as the else
block for the last converted if
.
Example 3:
case x: when 1 then do while x < 5: x = x + 1. end. otherwise repeat: x = x + 1. end. end.
Converted example:
switch ((x).intValue()) { case 1: { loopLabel0: while (_isLessThan(x, 5)) /* nested block inside CASE */ { x.assign(plus(x, 1)); break; /* break statement emitted incorrect */ } break; } default: { repeat("loopLabel1", new Block() /* nested block inside DEFAULT */ { public void body() { x.assign(plus(x, 1)); } }); break; } }
Details:
Progress 4GL allows the CASE
statement nested blocks to be more than simple DO
blocks. This is called the “integrated complex block” and it is fully supported. To properly support this feature, the code blocks are enclosed in an extra set of Java curly braces { ... }
such that the normal switch
statement semantics are maintained.
The WHEN
and OTHERWISE
branches that lead to integrated complex blocks get converted to BlockManager
APIs and are nested inside the enclosing simple Java { ... }
block.
The FWD conversion engine has a bug which emits an extra Java break
statement in cases when the 4GL CASE
statement is converted to a Java SWITCH
statement. This bug applies to all WHEN
branches, except when the WHEN
branch contains a simple DO
block. In the converted example above the incorrect break
is marked in red. This is considered a bug.
Example 4:
case x: when 1 then do: x = x + 1. end. end.
Converted code:
switch ((x).intValue()) { case 1: { x.assign(plus(x, 1)); break; } }
Details:
If a simple DO
block is used with a WHEN
test, then the statements are placed directly into body of the converted CASE
clause, without any intermediate blocks.
IF/THEN/ELSE Statement¶
This makes the execution of a statement or block of statements conditional. If the value of the expression following an IF
or ELSE IF
statement is TRUE
, then the statements following the corresponding THEN
keyword are processed. Otherwise, the statements following the ELSE
keyword are processed.
4GL syntax:
IF expression THEN { block | statement } [ ELSE IF expression THEN { block | statement } ] ... [ ELSE { block | statement } ]
This statement should not be confused with the IF THEN ELSE
built-in function. The IF
function can be used within expression because it returns a value. This IF
function is the same as the Java ternary operator, condition ? value1 : value2
. Since it does not alter the logical flow of the program (any more than do any other sub-expressions), it is not documented in this chapter. For complete details about how it gets converted, please see the Expressions chapter.
The IF
statement is the same as the Java if
statement, which is a control flow mechanism to conditionally branch. The match is exact, such that 4GL IF
statements are completely converted as Java if
/@ else statements.
At this time the usage of an ELSE IF
idiom is converted as else { if { ... } }
rather than the more optimal else if { ... }
. This will be improved in a subsequent version.
Since this is a direct mapping to the flow control statements of Java, the conditional expression which is evaluated will generally return a logical
type. This value will then be “unpacked” into a boolean
(often using logical.booleanValue()
) that is passed to the Java if
. In many places in FWD runtime, there are dual versions of a given method, one which returns a logical
and another (usually prefixed by the _
character) which directly returns a boolean
. Where possible, the converted code will use these special versions (to avoid the unpacking step) and directly return the boolean
. A good example of this is the replacement AND operator (CompareOps.and()
versus CompareOps_and()
). This might lead to emitting _and(x, y)
Instead of emitting and(x, y).booleanValue()
.
Example 1:
if x = 0 then message "value is 0". else if x = 1 then message "value is 1". else if x = 2 then do: message "value". message "is 2". end. else message "other value".
Converted code:
if (_isEqual(x, 0)) { message("value is 0"); } else { if (_isEqual(x, 1)) { message("value is 1"); } else { if (_isEqual(x, 2)) { message("value"); message("is 2"); } else { message("other value"); } } }
Details:
This example shows how nested if/then/else
statements get converted. The converted code will automatically group and indent the THEN
and ELSE
branches, even in cases when the branch contains a simple statement, and not a block.
Example 2:
if x = 1 then do while x < 5: x = x + 1. end. if x = 2 then repeat: x = x + 1. leave. end.
Converted example:
if (_isEqual(x, 1)) { loopLabel0: while (_isLessThan(x, 5)) /* nested block inside IF */ { x.assign(plus(x, 1)); } } if (_isEqual(x, 2)) { repeat("loopLabel1", new Block((Body) () -> /* nested block inside IF */ { x.assign(plus(x, 1)); leave("loopLabel1"); })); }
Details:
Progress 4GL allows the THEN
and ELSE
nested blocks to be more than simple DO
blocks. This is called the “integrated complex block” and it is fully supported. To properly support this feature, the code blocks are enclosed in an extra set of Java curly braces { ... }
such that the normal if
statement semantics are maintained.
The THEN
and ELSE
branches that lead to integrated complex blocks get converted to BlockManager
APIs and are nested inside the enclosing simple Java { ... }
block.
Example 3:
if x = 1 then do: message "message 1". message "message 2". end.
Converted code:
if (_isEqual(x, 1)) { message("message 1"); message("message 2"); }
Details:
For a simple DO
block (the most common case for an IF
statement), the statements are placed directly into the if
body without any intermediate blocks.
LEAVE Statement¶
The LEAVE
statement is used to exit a block and transfer control to the statement immediately following the targeted block. When it is used with a top-level block it is converted to a RETURN
statement.
4GL syntax:
LEAVE [ label ]
To implement it, the conversion process will use the following rules to decide how the handle the statement:
- If there is an explicit label, that block is targeted.
- If there is no label, then the implicit target block must be found. The nearest enclosing inner block which is NOT a simple
DO
block (DO
loops are OK) will be chosen. One of the following types will be the target:DO { TO | WHILE | TO ... WHILE }
(the presence or absence of transaction, error or other properties is ignored in this search, the only criteria that matters is whether theDO
is a loop)REPEAT
FOR
(all forms, not just theEACH
loop, including theFOR FIRST/LAST
blocks)EDITING
- If no inner block is found with the above process, then the
LEAVE
is considered to be targeting the nearest top-level block (external procedure, internal procedure, user-defined function or trigger). In that case theLEAVE
is implicitly treated as aRETURN
statement (no labels are required).
During conversion, the LEAVE
is converted to:
- A
BlockManager.leave(String label)
function call, if the target is a managed block. A “managed block” is one that is processed by theBlockManager
APIs. - A Java
break
statement, if the target is a non-managed block and there is no managed block between theLEAVE
statement and the targeted non-managed block. - A combination of a
BlockManager.leave
,BlockManager.deferredLeave
and a Javabreak
statement is used for cases when theLEAVE
targets a non-managed block but there is a managed block between the non-managed and theLEAVE
statement. Rather than force all blocks to be converted to managed blocks, it was decided to accept this slightly more complex implementation in the rare cases where this is an issue. - A
BlockManager.returnNormal()
method call if this it is being silently converted to aRETURN
statement.
If LEAVE
is supposed to cause exit of a block (i.e. is not treated as RETURN
action), it is converted using BlockManager.leave
function or, if the block is converted using Java for
or while
cycle or as a simple { ... }
block, it is converted using Java break
statement. Even if a block was implicitly targeted by a LEAVE
statement, FWD always explicitly specifies in the break
statement the label of the converted target block.
Example 1:
/* explicit label */ someLabel: repeat: ... if l then leave someLabel. ... end. /* implicit targeting */ repeat: ... if l then leave. ... end.
Converted code:
repeat("someLabel", new Block((Body) () -> { ... if ((l).booleanValue()) { leave("someLabel"); } ... })); repeat("loopLabel0", new Block((Body) () -> { ... if ((l).booleanValue()) { leave("loopLabel0"); } ... }));
Details:
Both LEAVE
statements target a managed block, the first is explicit and the second is implicit. The conversion process will emit BlockManager.leave
calls to handle the block exit. The first case has the matching converted label (someLabel
) and the second case has the manufactured label (loopLabel0
). In both cases, the “labels” are passed as a String
. The BlockManager
tracks all block labels as String
values.
Example 2:
/* explicit label from nested block */ someLabel: repeat: repeat: leave someLabel. end. end. /* implicit targeting from a nested block */ repeat: do on error undo, retry: leave. end. end.
Converted code:
repeat("someLabel", new Block((Body) () -> { repeat("loopLabel0", new Block((Body) () -> { leave("someLabel"); })); })); repeat("loopLabel1", new Block((Body) () -> { OnPhrase[] onPhrase0 = new OnPhrase[] { new OnPhrase(Condition.ERROR, Action.RETRY, "blockLabel0") }; doBlock(TransactionType.SUB, "blockLabel0", onPhrase0, new Block((Body) () -> { leave("loopLabel1"); })); }));
Details:
This shows both explicit and implicit targeting of more enclosing blocks by LEAVE
statements in nested blocks. The implicit case shows the fact that an implicit LEAVE
cannot target non-looping DO
blocks, so it targets the nearest enclosing looping block.
Example 3:
/* explicit label */ someLabel: do while x > 0: ... if x < 10 then leave someLabel. end. /* implicit targeting */ do while x > 0: ... if x < 10 then leave. end
Converted code:
/* explicit label */ someLabel: while (_isGreaterThan(x, 0)) { ... if (_isLessThan(x, 10)) { break someLabel; } } /* implicit targeting */ loopLabel0: while (_isGreaterThan(x, 0)) { ... if (_isLessThan(x, 10)) { break loopLabel0; } }
Details:
In cases when the LEAVE
targets non-managed blocks, the Java break
statement is used, unless there is a managed block between the non-managed and the LEAVE
statement (see the next example). Although one of the two labels in this example was implicit in the 4GL, they are explicitly specified on the break
statement in the converted code.
Just as with the managed blocks cases, it is valid to explicitly and implicitly reference outer blocks from more deeply nested blocks. Those examples are not shown here.
Example 4:
b1: do: b2: do for book preselect each book: ... leave b1. end. end.
Converted code:
b1: { PreselectQuery query0 = new PreselectQuery(); String[] enclBlocks0 = new String[] { "b1" }; doBlock(enclBlocks0, "b0", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, ((String) null), null, "book.bookId asc"); }, (Body) () -> { leave("b0"); })); if (deferredLeave("b10)) break b0; }
Details:
In this case, between the LEAVE
and the non-managed block there is the interim doBlock
managed block. The code in that managed block will be processed on a call-back basis by the BlockManager
. To accomplish this, the code is emitted as an anonymous inner class instance. This means that code inside that managed block is not in the same lexical scope as the enclosing non-managed block. The Java break
statement cannot “see” the enclosing block, so using that statement is not possible. In this case the LEAVE
is converted to a BlockManager.leave
call. To make it work inside the BlockManager
, the conversion process will pass the labels of all parent, non-managed, blocks to any enclosed managed blocks, via the enclosing
parameter (which is available for all block-related APIs in BlockManager
- see the Blocks chapter of this book for block-related API details). When the BlockManager.leave
is executed, it will cause the managed blocks to exit up to the point at which the enclosing non-managed blocks start, then it will set a flag noting that a deferred leave is in effect. The exit from that last managed block will leave it in code that checks for a deferred leave flag that matches the target block. Once deferredLeave
notices that there is a pending break for the block with the received label, it will return true
and the business logic will execute the Java break
statement.
This case is a bit messy, but it is also rare.
Example 5:
someLabel: do: ... if l then leave someLabel. ... end.
Converted code:
someLabel: { ... if ((l).booleanValue()) { break someLabel; } ... }
Details:
A LEAVE
statement explicitly targets the DO
block (which gets converted to a Java block); a Java break
statement is used to exit it. Implicit targeting of a simple DO
block is not possible, but as this example shows, an explicit label is honored even on a non-looping DO
.
Example 6:
procedure proc: ... leave. end.
Converted code:
@LegacySignature(type = Type.PROCEDURE, name = "proc") public void proc() { internalProcedure(new Block((Body) () -> { returnNormal(); })); }
Details:
When LEAVE
targets a top-level block it is converted to a RETURN
statement. In this case, the BlockManager.returnNormal
is used in the converted code. This targeting can only occur using an unlabeled LEAVE
. That unlabeled LEAVE
can be directly inside the top-level block (not enclosed within any inner blocks) or it can be enclosed only within nested simple DO
blocks.
NEXT Statement¶
The NEXT
statement is used to prematurely end the current iteration of a block and start the next iteration. In cases when it is not used with a looping block, it has the same behavior as a LEAVE
or RETURN
statement.
NEXT [ label ]
The conversion process uses the following rules to decide how the handle the statement:
1. If there is an explicit label:
- If a block is a looping block, then the next iteration of the targeted block is invoked.
- The only non-looping block that can be explicitly targeted by a
NEXT
statement is aFOR
block (in which all record phrases are qualified using default (i.e. unique),FIRST
orLAST
type). In this case,NEXT
is automatically converted toLEAVE
. - If a
NEXT
statement explicitly targets a simpleDO
block (withoutTO
orWHILE
), it will cause a 4GL compiler error, so this case doesn't convert.
2. If there is no label, then the implicit target block must be found. This is a 2 phase process.
- The nearest enclosing looping block type will be the target:
- DO { TO | WHILE | TO ... WHILE }
- REPEAT
- FOR
with at least oneEACH
component- EDITING
If one of these blocks is found, then theNEXT
iteration of that block will be invoked. - If there is no enclosing looping block between the
NEXT
statement and the nearest enclosing top-level block, then a search will be made to find the nearest enclosingFOR
block with unique,FIRST
orLAST
components only. If such a block is found, then theNEXT
will be statically converted toLEAVE
and targeted at that nearest enclosingFOR [ FIRST | LAST ]
block.
Note that the looping blocks will always take precedence over theFOR FIRST/LAST
. This means that a more deeply nestedFOR FIRST/LAST
block will NOT have preference to an outer looping block.
3. If no inner block is found with the above process, then the NEXT
is considered to be targeting the nearest top-level block (external procedure, internal procedure, user-defined function or trigger). In this case, the NEXT
is converted to a RETURN
statement.
The NEXT
statement will be converted to either BlockManager.next
API calls or to the compatible Java statement, continue
. The BlockManager.next(String label)
API will be emitted in all cases when the targeted block is managed by BlockManager
APIs too. In cases when the targeted block is converted to the for
or while
compatible Java statement (a simple DO
loop), the NEXT
statement will be converted to the Java continue [ label ]
, statement, with its label
set to target the appropriate block.
The label in 4GL is optional and if missing it defaults to the nearest block which satisfies the previously presented rules, but this does not apply to the converted code. Regardless of whether or not the label is explicitly specified, the converted code for this statement will always explicitly target the block to which it refers to, using the block's label as the parameter.
Example 1:
/* explicit label */ someLabel: repeat: ... if nxt then next someLabel. ... end. /* implicit targeting */ repeat: ... if nxt then next. ... end.
Converted code:
/* explicit label */ repeat("someLabel", new Block((Body) () -> { ... if ((nxt).booleanValue()) { next("someLabel"); } ... })); /* implicit targeting */ repeat("loopLabel0", new Block((Body) () -> { ... if ((nxt).booleanValue()) { next("loopLabel0"); } ... }));
Details:
These two examples show that the converted NEXT
statement will always explicitly specify the targeted block, regardless if the label is explicitly set or not. Since these NEXT
statements are each targeting a managed block, the BlockManager.next()
method is called.
Example 2:
/* explicit label */ someLabel: do while x > 0: ... if x < 10 then next someLabel. ... end. /* implicit targeting */ do while x > 0: ... if x < 10 then next. ... end.
Converted code:
/* explicit label */ someLabel: while (_isGreaterThan(x, 0)) { ... if (_isLessThan(x, 10)) { continue someLabel; } ... } /* implicit targeting */ loopLabel0: while (_isGreaterThan(x, 0)) { ... if (_isLessThan(x, 10)) { continue loopLabel0; } ... }
Details:
In this case, the targeted block is the non-managed Java while
statement is used. In the converted code, the NEXT
is converted to a Java continue
statement which explicitly targets the while
loop even if the 4GL code didn't specify it.
Example 3:
/* explicit label */ someLabel: for first book: if book.book-id < 100 then next someLabel. ... end. /* implicit targeting */ for first book: if book.book-id < 100 then next. ... end.
Converted code:
RandomAccessQuery query0 = new RandomAccessQuery(); /* explicit label */ forBlock("someLabel", new Block((Init) () -> { RecordBuffer.openScope(book); query0.initialize(book, (String) null, null, "book.bookId asc"); }, (Body) () -> { query0.first(); if (_isLessThan(book.getBookId(), 100)) { leave("someLabel"); } ... })); RandomAccessQuery query1 = new RandomAccessQuery(); /* implicit targeting */ forBlock("blockLabel0", new Block((Init) () -> { RecordBuffer.openScope(book); query1.initialize(book, (String) null, null, "book.bookId asc"); } (Body) () -> { query1.first(); if (_isLessThan(book.getBookId(), 100)) { leave("blockLabel0"); } ... }));
Details:
When NEXT
is targeted (explicitly or implicitly) at a FOR FIRST/LAST
block then it is silently converted to act as a LEAVE
in the 4GL. The Java version will convert to a BlockManager.leave()
. Even if a block was implicitly targeted by a NEXT
statement, FWD always specifies the explicit label of the converted target block in the leave()
.
Example 4:
procedure proc: ... next. end.
Converted code:
internalProcedure(new Block((Body) () -> { returnNormal(); }));
Details:
When NEXT
implicitly targets a top-level block, it is is converted to a RETURN
statement. BlockManager.returnNormal
is used in the converted code.
PAUSE Statement¶
Suspends processing for a specified number of seconds or until the user presses any key. The type-ahead buffer is cleared. Of great importance to control flow, the PAUSE
statement allows user to raise ENDKEY
, ERROR
or STOP
condition (using corresponding keys).
PAUSE [ n ] [ BEFORE-HIDE ] [ MESSAGE message | NO-MESSAGE ] [ IN WINDOW window ]
The 4GL parameters of this statement are:
n
- a numeric expression specifying the maximum number of seconds suspend processing. Without this option, processing is suspended until the user presses a key. Even if the number of seconds is specified, the user can resume processing immediately by pressing a key.BEFORE-HIDE
- makes the statement specify the pause action (message and delay) the user must take whenever frames are hidden automatically.
PAUSE
statement is converted to the following APIs:
LogicalTerminal.pause
- for all cases whenBEFORE-HIDE
option is not specified;LogicalTerminal.pauseBeforeHide
- ifBEFORE-HIDE
option was specified.
During the PAUSE
(either kind), if the user presses a key which is associated with raising a condition (e.g. the END-ERROR
key), that condition will be raised. The program's processing will resume, BUT instead of executing the statement that immediately follows the PAUSE
, the block/loop will UNDO
and otherwise execute the expected processing that occurs for that condition (based on default block properties, ON
phrases and so forth).
For full details on the conversion of this statement see the PAUSE Statement section of the Terminal Management chapter of Part 6. The content here is only related to control flow.
QUIT Statement¶
Raises the QUIT
condition. By default, this exits from the application and returns to the operating system. One can change this behavior by including the ON QUIT
phrase in an enclosing block's header.
QUIT
is converted to the BlockManager.quit()
method.
Example:
quit.
Converted code:
quit();
RETRY Function¶
Built-in function which returns true
if the current block is being reprocessed after a previous UNDO, RETRY
statement or in response to a condition that would cause a RETRY
action.
4GL syntax:
RETRY
Using the RETRY
function in a block turns off the default error processing, which results in no infinite loop protection for the block (see the Infinite Loop Protection section of this chapter for more details).
RETRY
is converted using TransactionManager.isRetry
or TransactionManager._isRetry
function. The first version returns a 4GL-compatible logical
value and is emitted when it is used together with some other variables or fields, while the second function returns a Java boolean
value and is emitted when the function is used in a Java flow of control statement such as while
or if
. As usual, the underscore prefix means the API returns a Java-style value, and not a 4GL-compatible value.
Example 1:
repeat: if retry then message "You've entered an invalid value.". set x. if x < 0 then undo, retry. end.
Converted code:
repeat("loopLabel0", new Block((Body) () -> { if (TransactionManager._isRetry()) { message("You've entered an invalid value."); } FrameElement[] elementList0 = new FrameElement[] { new Element(x, frame0.widgetx()) }; frame0.set(elementList0); if (_isLessThan(x, 0)) { undoRetry("loopLabel0"); } }));
Details:
As the RETRY
is being used for control flow branching, the TransactionManager._isRetry()
is emitted in the converted code.
RETURN Statement¶
Exits the nearest top-level block (an internal procedure, external procedure, trigger or user-defined function) and returns to the calling code. When exiting a trigger, this can optionally suppress the default behavior for the current user-interface event.
4GL syntax:
RETURN [ ERROR | NO-APPLY ] [ return-value ]
The 4GL provides 5 forms for the RETURN
statement:
RETURN
without any specifications is used to returns from a top-level block: an external or internal procedure, user-defined function or trigger block and allows normal processing to continue.
If this statement is executed from a procedure, then theRETURN-VALUE
global character variable is set to the empty string "" which can be read by the caller using theRETURN-VALUE
function.
If this statement is executed from a function then an unknown value is returned by this function.NEXT
andLEAVE
statements implicitly targeted to a top-level block are treated asRETURN
statements. See the sections related to these statements for more information.
The conversion rules will emit aBlockManager.returnNormal()
, with no parameters.RETURN <character expression>
can be used in order to return from an external or internal procedure and set theRETURN-VALUE
global character variable to the value of the given expression. It can be read by the caller using theRETURN-VALUE
built-in function. The conversion rules will emit aBlockManager.returnNormal(<character expression>)
function call.-
RETURN <expression>
can be used in order to return a value of one of the basic data types from a user-defined function. This is converted using theBlockManager.returnNormal(<expression>)
function. RETURN ERROR [<character expression>]
can be used in an external or internal procedure in order to raise anERROR
condition in the caller. In a function,RETURN ERROR
can also be used, but the error is ignored (not raised) and function returns the unknown value. The conversion process will actually drop any expression passed in theRETURN ERROR
from a user-defined function, but even if it did not, the runtime will drop any given value. If coded inside a trigger, a warning will be given during conversion and at runtime an IllegalStateException will be thrown. The Progress 4GL compiler will error on the use ofRETURN ERROR
from a trigger, so that code does not have to convert. The converted code will include aBlockManager.returnError([< character expression>])
call whenever this statement is encountered.RETURN NO-APPLY [<character expression>]
can be used in a trigger in order to leave it and suppress the default behavior for the current user-interface event. TheNO-APPLY
option can also be specified in other block types, but it will be ignored by the conversion rules. It is always converted to aBlockManager.returnConsume([<character expression>])
function call.
Example 1:
procedure proc: ... if l then return. ... end.
Converted code:
public void proc() { internalProcedure(new Block((Body) () -> { if ((l).booleanValue()) { returnNormal(); } })); }
Details:
Converted using BlockManager.returnNormal
function, as it is used to exit an internal procedure. Inside the BlockManager
, the RETURN-VALUE
will be set to the empty string (using ControlFlowOps.setReturnValue()
).
Example 2:
procedure proc: ... return "O" + "K". end. run proc. message return-value.
Converted code:
proc(); message(ControlFlowOps.getReturnValue()); ... public void proc() { internalProcedure(new Block((Body) () -> { returnNormal(concat("O", "K")); })); }
Details:
Inside the BlockManager
, the RETURN-VALUE
will be set to the concatenated string (using ControlFlowOps.setReturnValue()
). The calling code can obtain that value via the RETURN-VALUE
function (converted to ControlFlowOps.getReturnValue()
).
Example 3:
function func returns integer: def var x as integer init 1. return 2 * x. end. message string(func()).
Converted code:
message(valueOf(func())); ... return integerFunction(new Block() { integer x_1 = UndoableFactory.integer((long) 1); ... return function(this, "func", integer.class, new Block((Body) () -> { returnNormal(multiply(2, x_1)); })); });
Details:
When exiting a user-defined function, the type of the returned expression must match the function's return type. In all cases, the BlockManager.returnNormal
is used to set the correct return value for the function.
Example 4:
procedure proc: ... if l then return error "Some error". end. run proc no-error. if error-status:error then message "ERROR: " + return-value.
Converted code:
ErrorManager.silentErrorEnable(); proc(); ErrorManager.silentErrorDisable(); if ((ErrorManager.isError()).booleanValue()) { message(concat(new character("ERROR: "), ControlFlowOps.getReturnValue())); } ... public void proc() { internalProcedure(new Block((Body) () -> { if ((l).booleanValue()) { returnError("Some error"); } })); }
Details:
The RETURN ERROR
is converted using the BlockManager.returnError()
function.
Example 5:
form x with frame f1. on "1" of x do: /* do not allow to enter "1" in the x field */ return no-apply. end. update x with frame f1.
Converted code:
FrameElement[] elementList0 = new FrameElement[] { new Element(x, f1Frame.widgetx()) }; f1Frame.update(elementList0); ... registerTrigger(new EventList("1", f1Frame.widgetx()), Start.this, new Trigger((Body) () -> { returnConsume(); }));
Details:
When the trigger is exited via a RETURN NO-APPLY
call, the conversion rules will emit a BlockManager.returnConsume()
function call.
RETURN-VALUE Function¶
Returns the character
value saved by the most recent RETURN
statement. If the RETURN
statement had no expression, this returns the empty string.
4GL syntax:
RETURN-VALUE
Inside the BlockManager
, when processing the RETURN
statement the RETURN-VALUE
will be set to the given string or the empty string. This is done in the runtime code using ControlFlowOps.setReturnValue()
.
Any calling code can obtain that value via the RETURN-VALUE
function which is converted to ControlFlowOps.getReturnValue()
.
Example 1:
procedure proc: ... return "O" + "K". end. run proc. message return-value.
Converted code:
ControlFlowOps.invoke("proc"); message(ControlFlowOps.getReturnValue()); ... public void proc() { internalProcedure(new Block((Body) () -> { ... returnNormal(concat("O", "K")); })); }
Details:
Inside the BlockManager
, the RETURN-VALUE
will be set to the concatenated string (using ControlFlowOps.setReturnValue()
). The calling code can obtain that value via the RETURN-VALUE
function (converted to ControlFlowOps.getReturnValue()
).
RUN Statement¶
4GL Form | Description | Notes |
---|---|---|
RUN { extern-proc-name | VALUE ( extern-expression ) | path-name<<member-name>> } [ PERSISTENT [ SET proc-handle ] ] [ ON [ SERVER ] { server-handle | session-handle } [ TRANSACTION DISTINCT ] [ ASYNCHRONOUS [ SET async-request-handle ] [ EVENT-PROCEDURE event-internal-procedure [ IN procedure-context ] ] ] [( parameter [, parameter ] ...) ] [ preprocessor_argument ] ... [ NO-ERROR ] |
Calls an external Progress procedure. | FWD doesn't support asynchronous calls, remote calls and calls to external libraries. The following options are not supported: • path-name<<member-name>> • PERSISTENT ... • ON ... Although the runtime does not support passing preprocessor_argument values to the Java equivalent of the RUN statement, the conversion can be configured to incorporate preprocessor arguments when converting specific external procedures. Since it is done at conversion time, the result is a single static version of the called external procedure. For details, please see the Runtime Preprocessor Arguments section of the FWD Conversion Handbook. |
RUN missing-file.p |
Attempt to call a missing external procedure. | FWD provides only conversion support for this case. During conversion, the assumption is that the missing-file.p is a missing external procedure, and for all such cases it will use the BogusClassName as the Java class name in the converted code. The converted code will not compile and will look like:BogusClassName bogusclassname0 = new BogusClassName(); bogusclassname0.execute(); To go further with the conversion process, every RUN statement which converts to a BogusClassName invocation needs to be analyzed and fixed as needed. |
RUN { intern-proc-name |VALUE ( intern-expression) } [ IN proc-handle ] [ ASYNCHRONOUS [ SET async-request-handle ] [ EVENT-PROCEDURE event-internal-procedure [ IN procedure-context ] ] [ ( parameter [, parameter] ... ) ] [ NO-ERROR ] |
Calls an internal Progress procedure. | FWD doesn't support asynchronous calls, remote calls and calls to external libraries. The following options are not supported: • IN proc-handle • ASYNCHRONOUS ... |
The RUN
statement is used to execute an external or internal procedure. The control flow is switched to the invoked procedure. Once the called procedure returns, the calling procedure will:
- execute the statement immediately following the
RUN
statement OR - if the procedure exit was triggered by a raised condition (be it an error or something else), this condition will propagate up the call path until a block which handles it is found or the application exits.
When invoking a procedure, its name can be specified either using its name (hard coded in the 4GL source) or using an expression (to be evaluated at runtime), via the VALUE()
clause.
Static Procedure Names¶
Each procedure file (e.g. .p
files) is converted to a separate Java business logic class. The converted code of an external procedure will be emitted into the execute
method of the corresponding Java class.
A RUN statement which executes an external procedure is converted in two steps: a local instance of the class which corresponds the target external procedure is instantiated AND then the execute()
method of this instance is directly called.
Internal procedures are converted to instance methods of the same Java class associated with the external procedure where the 4GL internal procedures were defined. A RUN
statement which executes an internal procedure is converted to a call of the corresponding Java method.
Example 1:
run external-proc.p
Converted code:
ControlFlowOps.invoke("external-proc.p");
Details:
Invoking an external procedure via its explicit name will result in an object instantiation of that type and calling the execute()
method on that object instance.
Example 2:
[test.p] procedure internal-proc: ... end. run internal-proc.
Converted code:
public class Test { public void execute() { externalProcedure(Start.this, new Block((Body) () -> { ControlFlowOps.invoke("internalProc"); // call of the internal procedure })); } @LegacySignature(type = Type.PROCEDURE, name = "internalProc") public void internalProc() { internalProcedure(new Block((Body) () -> { returnNormal(); })); } }
Details:
An internal procedure is converted to an instance method in the class which contains the converted external procedure (from which the internal procedure originated). It is invoked directly, using the converted method's name.
Dynamic Procedure Names¶
It is possible to evaluate the name of the procedure (either external or internal) at runtime by passing an arbitrary expression as the procedure name to the RUN VALUE()
statement. This type of call is converted using the ControlFlowOps.invoke
function, which takes two parameters:
- The first parameter is the caller object. The instance of the class which represents the current external procedure is passed as the caller.
- The second parameter is an array of objects. The first element of the array is the expression of an arbitrary data type which yields the name of the procedure to run. The expression is cast to the character type using
toStringMessage()
function. The following array elements are the parameters that should be passed to the called procedure (you can read about the parameters in the next section).
When using the VALUE()
clause, the conversion rules can't decide which needs to be invoked (an external or an internal procedure), as the name of said procedure is determined at runtime. Therefore, the runtime will first check if the given name is for an external procedure (using the mappings defined in the name_map.xml
file which is dynamically loaded from the converted application's jar file when the server starts); if found, it will instantiate a new object of that type (which is for an external procedure class) and will invoke the execute()
method using reflection.
If it can't find a Java class mapped to the assumed external procedure name, it will check if the mappings in name_map.xml
file to find a Java method mapped to an internal procedure, for the calling external procedure class. If it is found, it will invoke it, passing the given parameters as appropriate. For internal procedure cases, the method is resolved only based on the name, ignoring the parameters - if the given parameters do not match the method's signature, the method invocation will fail.
Reflection is used for lookups and invocations. The SourceNameMapper
provides the class and method name mapping from the Progress legacy names to the new Java names. The source for the name mapping data is the above mentioned name_map.xml
file, which is an output of the conversion process.
From the ControlFlowOps
, the following APIs are emitted during conversion:
ControlFlowOps API | Details |
---|---|
invoke(handle target, String name, Object[] param) |
This method is used to invoke methods of a known signature and name in classes whose type is not known at compile-time. For this purpose Progress uses RUN internal_procedure IN procedure_handle .Currently the IN handle clause is not yet supported by conversion rules, so this method is not currently emitted. |
invoke(Object caller, Object[] param) |
This method is used to invoke classes whose name is not known at compile-time. For this purpose Progress uses the RUN VALUE(expression) construct. The value of the expression is used to determine the procedure name. The procedure name may refer to an external or internal procedure. Since the procedures are translated into different Java entities (an external procedure is translated into a well known method name in a class and internal procedures are translated into instance methods), the processing takes this into account.Initially the method assumes that the procedure name belongs to an external procedure file name (which should map to a class name). If found, a default constructor will be used to obtain an instance and the execute method will be invoked on this class. If there is no mapping for such a file name then an internal procedure is assumed. In this case the calling class is searched for the method that maps to this internal procedure. If found, this method will be invoked. |
invokeMapped(handle target, BaseDataType name, Object[] param) invokeMapped(handle target, String name, Object[] param) |
This method is used to invoke methods of a known signature and name in classes whose type is not known at compile-time. For this purpose Progress uses RUN internal_procedure IN procedure_handle .The method name given is in the form of a Progress 4GL internal procedure name and must be converted into the matching Java name before the invocation can proceed. Currently the IN handle clause is not yet supported by conversion rules, so this method is not currently emitted. |
Example 3:
/* run external procedure external-proc.p */ proc-name = "external-proc". run value(proc-name + ".p").
Converted code:
/* run external procedure external-proc.p */ procName.assign("external-proc"); ControlFlowOps.invoke(Test.this, new Object[] { (concat(procName, ".p")).toStringMessage() });
Details:
Assuming the 4GL program file is named test.p
, the first parameter for ControlFlowOps.invoke
will be Test.this
, to disambiguate between the current instance of the Block
class being executed (see the Blocks chapter about how the 4GL blocks are converted) and the instance associated with the external procedure.
Example 4:
[test.p] /* run internal procedure internal-proc */ proc-name = "internal-proc". run value(proc-name).
Converted code:
/* run internal procedure internal-proc */ procName.assign("internal-proc"); ControlFlowOps.invoke(Test.this, new Object[] { (procName).toStringMessage() });
Details:
For the internal procedure case, the converted code looks the same as the external procedure case. It is the runtime which will decide that it needs to invoke an internal procedure and not an external one.
Procedure Parameters¶
The RUN
statement parameter passing syntax to the is the following:
[ INPUT ] expression | TABLE temp-table-name | TABLE-HANDLE temp-table-handle } OUTPUT { field | variable | TABLE temp-table-name [ APPEND ] | TABLE-HANDLE temp-table-handle [ APPEND ] | param-name AS data-type } INPUT-OUTPUT { field | variable | TABLE temp-table-name [ APPEND ] | TABLE-HANDLE temp-table-handle [ APPEND ] } BUFFER buffer
where:
TABLE-HANDLE
type is not supported.param-name AS data-type clause
is not supported.BUFFER
clause is not supported.
During conversion, the INPUT
, OUTPUT
and INPUT-OUTPUT
options are ignored by FWD, as valid 4GL code doesn't allow the RUN
parameters and the procedure's parameters to be out-of-sync. Each parameter specified with the RUN
statement has an equivalent procedure-level parameter defined using DEFINE PARAMETER
statements (please see the Variable Definitions section of the Data Types chapter of this book for details on how these statements get converted).
The conversion process will use only the expression
, field
, variable
and temp-table-name
to pass the parameters to the procedure invocation code.
Example 5:
def temp-table tt field f1 as integer. def var aDate as date. run proc.p(5, /* integer constant */ "str" + "ing", /* string expression */ aDate, /* date variable */ table tt). /* table */
Converted code:
TempRecord1 tt = TemporaryBuffer.define(TempRecord1.class, "tt", false); ... Proc proc0 = new Proc(); proc0.execute(new integer(5), /* integer constant */ concat("str", "ing"), /* string expression */ aDate, /* date variable */ tt); /* table */
Details:
The parameters of an external procedure with the specified static name are passed to the execute
method for the instantiated object which corresponds with the target external procedure.
Example 6:
def temp-table tt field f1 as integer. def var aDate as date. run internal-proc(5, "str" + "ing", aDate, table tt).
Converted code:
TempRecord1 tt = TemporaryBuffer.define(TempRecord1.class, "tt", false); ... internalProc(new integer(5), concat("str", "ing"), aDate, tt);
Details:
Parameters of an internal procedure with the specified static name are passed to the Java method which corresponds the target internal procedure.
Example 7:
def temp-table tt field f1 as integer. def var aDate as date. run value("internal" + "-proc") (5, "str" + "ing", aDate, table tt).
Converted code:
TempRecord1 tt = TemporaryBuffer.define(TempRecord1.class, "tt", false); ... ControlFlowOps.invoke(Test.this, new Object[] { (concat("internal", "-proc")).toStringMessage(), new integer(5), concat("str", "ing"), aDate, tt });
Details:
Parameters of an external or internal procedure, with the name specified as an expression for the VALUE()
clause, are passed using the second parameter (named param
) of the ControlFlowOps.invoke
function. In the param
array, the first element represents the name of the procedure to run and the following elements are the parameters of the executed procedure which are passed to the invoked method using reflection.
Other Options¶
The NO-ERROR
option is converted by bracketing the procedure-calling code with the ErrorManager.silentErrorEnable
and ErrorManager.silentErrorDisable
calls.
Example 8:
run proc.p no-error.
Converted code:
ErrorManager.silentErrorEnable(); Proc proc0 = new Proc(); proc0.execute(); ErrorManager.silentErrorDisable();
Details:
As the NO-ERROR
clause is specified with the RUN
statement, the converted code brackets the procedure invocation code with the silent-error enable/disable calls. This changes the condition processing in the BlockManager
/@ TransactionManager runtime code, such that the error condition is suppressed.
STOP Statement¶
Raises the STOP
condition. By default, this stops processing of a procedure, backs out the active transaction, and returns to the startup procedure. One can change this behavior by including the ON STOP
phrase in an inner block header.
The user can initiate a STOP
condition by pressing the STOP
key ( which is CTRL-C
by default).
4GL syntax:
STOP
STOP
is converted using the BlockManager.stop()
function.
Example:
stop.
Converted code:
stop();
UNDO Statement¶
This statement backs out all modifications to permanent database fields and variables made during the current iteration of a block, and initiates a control-flow action to follow the rollback.
4GL syntax:
UNDO [ label1 ] [ , LEAVE [ label2 ] | , NEXT [ label2 ] | , RETRY [ label1 ] | , RETURN [ ERROR or NO-APPLY ] [ return-value ] ]
The UNDO
statement is tightly coupled with the condition processing and infinite loop processing implementations. This section will show only how this statement gets converted. For details about these other topics, please see the Conditions and the Infinite Loop Protection sections of the Blocks chapter.
This statement is converted to BlockManager.undo*
API calls, depending on how it is used. If the UNDO
is used without an explicit action to perform, it will default to LEAVE
. To summarize the rules:
undoLeave
- emitted when theUNDO [ label1 ], LEAVE [ label2 ]
orUNDO [ label 1 ]
form is used.undoNext
- emitted when theUNDO [ label1 ], NEXT [ label2 ]
form is used.undoRetry
- emitted when theUNDO [ label1 ], RETRY [ label1 ]
form is used, and the targeted block is an inner block. The explicitly targetedRETRY
block must be the same as the block targeted by theUNDO
. Otherwise the Progress 4GL compiler will generate an error.undoRetryTopLevel
- emitted when theUNDO [ label1 ], RETRY [ label1 ]
orUNDO [ label 1 ]
form is used, and the targeted block is a top-level block.undoReturnNormal
- emitted when theUNDO [ label1 ], RETURN [ return-value ]
form is used, and the targeted block is not a top-level block.undoReturnNormalTopLevel
- emitted when theUNDO [ label1 ], RETURN [ return-value ]
form is used, and the targeted block is a top-level block.undoReturnConsume
- emitted when theUNDO [ label1 ], RETURN NO-APPLY [ return-value ]
form is used, and the targeted block is not a top-level block.undoReturnConsumeTopLevel
- emitted when theUNDO [ label1 ], RETURN NO-APPLY [ return-value ]
form is used, and the targeted block is a top-level block.undoReturnError
- emitted when theUNDO [ label1 ], RETURN ERROR [ return-value ]
form is used, and the targeted block is not a top-level block.undoReturnErrorTopLevelemitted
when theUNDO [ label1 ], RETURN ERROR [ return-value ]
form is used, and the targeted block is a top-level block.
The next table shows UNDO
's different conversion forms. Even if the UNDO
statement target is implicitly specified (by leaving out the label(s)), FWD will always emit in the converted code the explicit label(s) of the converted target block(s). This means that FWD must calculate the target block for the UNDO
and the other actions when the labels are not explicitly specified. To understand the confusing, undocumented and complex rules for how implicitly targeted blocks are determined for the UNDO
statement, please see the section entitled Determining the Target and Meaning of UNDO, LEAVE, NEXT and RETRY in the Blocks chapter.
4GL Form | BlockManager API | Example |
---|---|---|
UNDO [ label1 ], LEAVE [ label2 ] or UNDO [ label1 ] |
undoLeave() undoLeave(label1) undoLeave(label1, label2) |
repeat: ... if l then undo, leave. end. Converted code: repeat("loopLabel0", new Block() { public void body() { ... if ((l).booleanValue()) { undoLeave("loopLabel0"); } } }); |
UNDO [ label1 ], NEXT [ label2 ] |
undoNext() undoNext(label1) undoNext(label1, label2) |
repeat: ... if l then undo, next. End. Converted code: repeat("loopLabel0", new Block() { public void body() { ... if ((l).booleanValue()) { undoNext("loopLabel0"); } } }); |
UNDO [ label1 ], RETRY [ label1 ] |
undoRetry() undoRetry(label1) |
repeat: ... if l then undo, retry. End. Converted code: repeat("loopLabel0", new Block() { public void body() { ... if ((l).booleanValue()) { undoRetry("loopLabel0"); } } }); |
UNDO, RETRY UNDO is targeted to a top-level block. |
undoRetryTopLevel() |
if l then undo, retry. Converted code: if ((l).booleanValue()) { undoRetryTopLevel(); } |
UNDO [ label1 ], RETURN [ return-value ] UNDO is targeted to an inner block. |
undoReturnNormal() undoReturnNormal(label1) undoReturnNormal(label1, returnValue) undoReturnNormal(returnValue) |
repeat: ... if l then undo, return "message". End. Converted code: repeat("loopLabel0", new Block() { public void body() { ... if ((l).booleanValue()) { undoReturnNormal( "loopLabel0", "message"); } } }); |
UNDO, RETURN [ return-value ] UNDO is targeted to a top-level block. |
undoReturnNormalTopLevel() undoReturnNormalTopLevel(returnValue) |
if l then undo, return "message". Converted code: if ((l).booleanValue()) { undoReturnNormalTopLevel( "message"); } |
UNDO [ label1 ], RETURN NO-APPLY [ return-value ] UNDO is targeted to an inner block. |
undoReturnConsume() undoReturnConsume(label1) undoReturnConsume(label1, returnValue) |
Not properly converted by FWD at this time. |
UNDO, RETURN NO-APPLY [ return-value ] UNDO is targeted to a top-level block. |
undoReturnConsumeTopLevel() undoReturnConsumeTopLevel(returnValue) |
Not properly converted by FWD at this time. |
UNDO [ label1 ], RETURN ERROR [ return-value ] UNDO is targeted to an inner block. |
undoReturnError() undoReturnError(label1) undoReturnError(label1, returnValue) |
Not properly converted by FWD at this time. |
UNDO, RETURN ERROR [ return-value ] UNDO is targeted to a top-level block. |
undoReturnErrorTopLevel() undoReturnErrorTopLevel(returnValue) |
Not properly converted by FWD at this time. |
© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.