Project

General

Profile

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 the DO block definition.
  • Any ON phrase is included in the DO block definition.
  • A frame phrase (WITH FRAME) is included in the DO block definition.
  • A FOR or PRESELECT phrase is included in the DO block definition.
  • DO loop anomaly: the DO block is a loop (there is a TO and/or WHILE 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 an integer, decimal or date 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 step k is negative), the loop ends. Since expression1 is compared to expression2 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 of date 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 the BY option is not specified or is set to 1.
  • <variable>.decrement() is emitted in cases when the BY option is specified or is set to -1.
  • <variable>.assign(plus(<variable>, <k>)) is emitted in cases when the BY 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 a date variable is used and the BY 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 of integer, decimal or date 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 the ToClause constructor. Expressions are defined in a more complicated way. Because, unlike the expression1, 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, by IntegerExpression, DecimalExpression or DateExpression. Instances of these classes allow the expression evaluation to be deferred and to be delegated to the BlockManager. The expression is defined by overriding the execute() 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 simple DO ... WHILE ... loops);
  • as a part of cycle completion condition with a for Java loop (for simple DO ... TO ... WHILE ... loops);
  • with non-simple blocks, a LogicalExpression object passed to a block API provided by the BlockManager.
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 - each value is an expression that evaluates to a possible value for expression. It should evaluate to a value of the data type which is comparable with the data type of the value that expression 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:

  1. If there is an explicit label, that block is targeted.
  2. 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 the DO is a loop)
    • REPEAT
    • FOR (all forms, not just the EACH loop, including the FOR FIRST/LAST blocks)
    • EDITING
  3. 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 the LEAVE is implicitly treated as a RETURN statement (no labels are required).

During conversion, the LEAVE is converted to:

  1. A BlockManager.leave(String label) function call, if the target is a managed block. A “managed block” is one that is processed by the BlockManager APIs.
  2. 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.
  3. A combination of a BlockManager.leave, BlockManager.deferredLeave and a Java break statement is used for cases when the LEAVE targets a non-managed block but there is a managed block between the non-managed and the LEAVE 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.
  4. A BlockManager.returnNormal() method call if this it is being silently converted to a RETURN 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 a FOR block (in which all record phrases are qualified using default (i.e. unique), FIRST or LAST type). In this case, NEXT is automatically converted to LEAVE.
  • If a NEXT statement explicitly targets a simple DO block (without TO or WHILE), 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 one EACH component
    - EDITING
    If one of these blocks is found, then the NEXT 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 enclosing FOR block with unique, FIRST or LAST components only. If such a block is found, then the NEXT will be statically converted to LEAVE and targeted at that nearest enclosing FOR [ FIRST | LAST ] block.
    Note that the looping blocks will always take precedence over the FOR FIRST/LAST. This means that a more deeply nested FOR 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 when BEFORE-HIDE option is not specified;
  • LogicalTerminal.pauseBeforeHide - if BEFORE-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:

  1. 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 the RETURN-VALUE global character variable is set to the empty string "" which can be read by the caller using the RETURN-VALUE function.
    If this statement is executed from a function then an unknown value is returned by this function.
    NEXT and LEAVE statements implicitly targeted to a top-level block are treated as RETURN statements. See the sections related to these statements for more information.
    The conversion rules will emit a BlockManager.returnNormal(), with no parameters.
  2. RETURN <character expression> can be used in order to return from an external or internal procedure and set the RETURN-VALUE global character variable to the value of the given expression. It can be read by the caller using the RETURN-VALUE built-in function. The conversion rules will emit a BlockManager.returnNormal(<character expression>) function call.
  3. 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 the BlockManager.returnNormal(<expression>) function.
  4. RETURN ERROR [<character expression>] can be used in an external or internal procedure in order to raise an ERROR 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 the RETURN 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 of RETURN ERROR from a trigger, so that code does not have to convert. The converted code will include a BlockManager.returnError([< character expression>]) call whenever this statement is encountered.
  5. 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. The NO-APPLY option can also be specified in other block types, but it will be ignored by the conversion rules. It is always converted to a BlockManager.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 the UNDO [ label1 ], LEAVE [ label2 ] or UNDO [ label 1 ] form is used.
  • undoNext - emitted when the UNDO [ label1 ], NEXT [ label2 ] form is used.
  • undoRetry - emitted when the UNDO [ label1 ], RETRY [ label1 ] form is used, and the targeted block is an inner block. The explicitly targeted RETRY block must be the same as the block targeted by the UNDO. Otherwise the Progress 4GL compiler will generate an error.
  • undoRetryTopLevel - emitted when the UNDO [ label1 ], RETRY [ label1 ] or UNDO [ label 1 ] form is used, and the targeted block is a top-level block.
  • undoReturnNormal - emitted when the UNDO [ label1 ], RETURN [ return-value ] form is used, and the targeted block is not a top-level block.
  • undoReturnNormalTopLevel - emitted when the UNDO [ label1 ], RETURN [ return-value ] form is used, and the targeted block is a top-level block.
  • undoReturnConsume - emitted when the UNDO [ label1 ], RETURN NO-APPLY [ return-value ] form is used, and the targeted block is not a top-level block.
  • undoReturnConsumeTopLevel - emitted when the UNDO [ label1 ], RETURN NO-APPLY [ return-value ] form is used, and the targeted block is a top-level block.
  • undoReturnError - emitted when the UNDO [ label1 ], RETURN ERROR [ return-value ] form is used, and the targeted block is not a top-level block.
  • undoReturnErrorTopLevelemitted when the UNDO [ 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.