Accumulators¶
An accumulator is a 4GL structure which collects one piece of data upon each pass through an iterative loop and calculates a particular statistic upon the collected data. For instance, an accumulator might calculate an average numeric value or determine a lexicographical maximum value from a list of strings. Its data may be derived from a mutable variable, a data source or from a complex expression which uses a combination of both variables and data source.
During conversion, FWD will identify each accumulator and convert it to a specialized instance, depending on the kind of the accumulated statistics; this instance, in some cases, will be explicitly used to trigger the accumulation or in other circumstances will be triggered automatically.
The conversion rules try to simplify the converted accumulator code to the maximum extent possible; although in some terms this simplification might indicate that writing compatible 4GL code by hand was made easy, we don't recommend using accumulators outside the 4GL source code. Also, note that there are some accumulator properties which are not fully supported at the time of this writing, which includes:
- accumulation expressions can be used as "variables" and can be referenced directly in Progress without the
ACCUMULATE
statement or theACCUM
function. This poses a parse-time problem but it also has consequences that are harder to implement since such vars can be assigned! - For
COUNT
accumulators, if the expression contains a field reference and the buffer currently does not reference any records (i.e. it was not yet retrieved or it was not found), this 'not-existent' field will be counted. So, accumulation must be triggered before retrieving the record. - In the
DISPLAY with aggregate-phrase
case, the clause which sets the aggregator's label is not currently supported. - The short name for the
AVERAGE
accumulator (AVG
) is not yet supported by the conversion rules.
This chapter will focus on describing how the accumulators (and their sub-accumulator versions) are converted and how the conversion rules have addressed all found behavior, discovered during extensive testing.
Accumulator Expression¶
The expression used by an accumulator to aggregate data can be one of the following:
- simple expressions: a variable or a a buffer field. For buffer field cases, it is required for the referenced buffer to be in an active scope.
- a constant value.
- complex expressions, which combine variables, constants and a field.
The accumulation will be performed in the same manner for all expression types: the expression will be evaluated and will be aggregated (to the primary result and to the sub-result, if needed). Beside providing the actual aggregated data, the accumulated expression (together with the aggregate type) is used to uniquely identify a certain accumulator. So, to implement the 4GL behavior, the conversion rules will compare each expression using its syntax as it appears in the 4GL sources: expressions as n + 1
and 1 + n
will be treated as two distinct expressions, even though they are semantically equivalent.
When complex expressions are used, they need to be evaluated each time the accumulation is performed (as the variable's or field's value might be change). So, the conversion rules will emit a distinct field (at the same block as the accumulator's field) which instantiates an anonymous class. This anonymous class is an extension of some FWD utility classes which extend the com.goldencode.2j.util.Resolvable
interface and will evaluate the expression each time accumulation performs. The following table shows which internal FWD class is used, depending on the expression's type (all classes are part of the com.goldencode.2j.util
package):
Expression Type | integer | decimal | character | date | logical |
---|---|---|---|---|---|
FWD Class | IntegerExpression | DecimalExpression | CharacterExpression | DateExpression | LogicalExpression |
Once a complex expression is used to define an accumulator, the converted name for the field which defines this expression will follow the accumExpr#
pattern, where #
is a counter used to index all complex expressions which define accumulators.
Aggregate Phrase¶
The aggregate phrase is responsible for determining the type of the accumulator. For each accumulator, when used with the ACCUMULATE
statement, ACCUM
function or with a DISPLAY
statement, its aggregate phrase, together with the accumulated expression are responsible for identifying the accumulator across the 4GL code.
The aggregate phrase in some cases can have a short and a long form - the conversion rules will handle both of them in the same way; in this chapter, only the short form will be used when referring to a certain accumulator. Even if the aggregate phrase contains multiple aggregators, the conversion rules will emit a distinct accumulator instance for each combination of aggregator type and expression.
For all aggregators, 4GL supports sub-accumulators, which present majors differences when they are used in a DISPLAY
with aggregate phrase (please see the DISPLAY Statement with Aggregate Phrase section) or their aggregate phrase has a BY break-group
clause (see the ACCUMULATE Statement section for details). If the BY break-group
clause is not specified at their definition, the sub-accumulators behave the same as their full versions.
The conversion process will use the aggregate phrase to convert each accumulator to a sub-class of the p2j.com.goldencode.util.Accumulator
class. The following table presents all aggregators supported by 4GL, their mapping to the FWD implementation class and the type of the expression which can be used with such accumulators:
Long Name | Short Name | Converted Class | Expression 4GL Type | Description |
---|---|---|---|---|
AVERAGE |
AVG |
util.AverageAccumulator | integer decimal recid |
Computes the average value of the given expression. |
COUNT |
- | util.CountAccumulator | any type | Counts the number of loop iterations performed. |
TOTAL |
- | util.TotalAccumulator | integer decimal recid |
Performs a numeric total across all iterations of a loop. |
MINIMUM |
MIN |
util.MinimumAccumulator | any type | Determines the minimum value from among all expressions' values across all iterations of a loop. |
MAXIMUM |
MAX |
util.MaximumAccumulator | any type | Determines the maximum value from among all expressions' values across all iterations of a loop. |
SUB-AVERAGE |
SUB-AVG |
util.AverageAccumulator | integer decimal recid |
Same as AVG , with the difference that it provides the average value for each break group. |
SUB-COUNT |
- | util.CountAccumulator | any type | Same as COUNT , with the difference that it provides the count for each break group. |
SUB-TOTAL |
- | util.TotalAccumulator | integer decimal recid |
Same as TOTAL , with the difference that it provides the total value for each break group. |
SUB-MINIMUM |
SUB-MIN |
util.MinimumAccumulator | any type | Same as MIN , with the difference that it provides the minimum value for each break group. |
SUB-MAXIMUM |
SUB-MAX |
util.MaximumAccumulator | any type | Same as MAX , with the difference that it provides the maximum value for each break group. |
From the conversion's point of view, the aggregator type and the accumulated expressions are only used to map the accumulator to the appropriate FWD class and to create an distinct accumulator for each unique (aggregator type, expression) pair. The conversion differences are related to how and where the accumulator is used - inside the ACCUMULATE
statement, the ACCUM
function, with a DISPLAY
statement, inside a nested block or other cases - each of them will be explained in detail in the next sections. The point of this notice is that the next sections will not focus on repeating the same example or detail for each and every accumulator; instead, it will use only one or few aggregator types and the reader must understand that, unless noted otherwise, the conversion rules apply to all of them.
During conversion, the following APIs will be emitted in the converted code depending on how the accumulator was used. As they reside in the accumulator's base class util.Accumulator
, they are common for all aggregator types. For more details about why each API is emitted, please see their associated sections.
API | Details | Section |
---|---|---|
void addBreakGroup(Resolvable) | Track a new break group category within this sub-accumulator. Takes as parameter the key which will be used to manage and retrieve results for the specified break group. | ACCUMULATE Statement |
void accumulate() | This method may be called one or more times per loop iteration, either directly by client code, or indirectly by a query with which this accumulator is associated. Upon invocation, it checks whether accumulation is enabled or not. If so, it evaluates the expression and then performs an implementation-specific action to update the accumulator's internal state. If a the result must be reset, because the result set being iterated entered a new break group, this is done before the new data item is accumulated. |
ACCUMULATE Statement |
void setSubOnly() | Marks this accumulator as a sub-only accumulator. This call will be emitted only when aggregate phrase contains also a BY break-group clause. |
ACCUMULATE Statement DISPLAY Statement with Aggregate Phrase |
void reset() | When a block contains a nested accumulator reference, a call to this method will be emitted to notify the block that it uses this accumulator, so that the accumulation value for all parents block will be updated accordingly. | Block Behavior for Accumulators |
void ifEnded() | This method is used to notify the accumulator that an IF statement which contains the ACCUM statement or a DISPLAY with aggregate phrase has ended. |
Block Behavior for Accumulators |
void postponeReset() | When an IF statement which contains the ACCUM statement or DISPLAY with aggregate phrase is started (and the accumulation statement is not also enclosed within a block with an implicit looping property), the accumulator must be reset when the enclosed accumulation did not execute. |
Block Behavior for Accumulators |
void postponeUnknown() | When an IF statement which contains the ACCUM statement or DISPLAY with aggregate phrase is started (and also the accumulation statement is not also enclosed within a block with an implicit looping property), the accumulator must be set to UNKNOWN when the enclosed accumulation did not execute. |
Block Behavior for Accumulators |
When instantiating an accumulator of a certain type, the conversion rules will emit constructors depending on the aggregator and expression type, as presented in the next table. The c'tors are grouped together by their usage, as a c'tor signature may apply to more than one aggregator type, with the same semantics.
API | Details |
---|---|
AverageAccumulator(Class extends BaseDataType>) TotalAccumulator(Class extends BaseDataType>) MinimumAccumulator(Class extends BaseDataType>) MaximumAccumulator(Class extends BaseDataType>) |
This c'tor is emitted only when a DISPLAY with aggregate phrase statement is encountered. Each c'tor takes as parameter the type of the accumulated expression. For more details, please see the DISPLAY Statement with Aggregate Phrase section of this chapter. |
AverageAccumulator(int) AverageAccumulator(double) TotalAccumulator(int) TotalAccumulator(double) MinimumAccumulator(int) MinimumAccumulator(double) MinimumAccumulator(String) MinimumAccumulator(boolean) MaximumAccumulator(int) MaximumAccumulator(double) MaximumAccumulator(String) MaximumAccumulator(boolean) |
If the accumulated expression is a constant value, then this constant will be accumulated on each iteration. Depending on the aggregator's type, it can receive either only numeric constants or also string and logical constants. |
AverageAccumulator(BaseDataType) TotalAccumulator(BaseDataType) MinimumAccumulator(BaseDataType) MaximumAccumulator(BaseDataType) |
When the accumulated expression is a simple variable, a reference to this variable is saved by the accumulator. On each accumulation, the variable will be evaluated (to retrieve its value) and this value will be accumulated. |
AverageAccumulator(Resolvable) TotalAccumulator(Resolvable) MinimumAccumulator(Resolvable) MaximumAccumulator(Resolvable) |
In cases of complex expressions or when a buffer field is used, a Resolvable instance is used to encapsulate the expression - this forces the expression or field to be evaluated each time the accumulation is performed. |
CountAccumulator() | The COUNT accumulator is a special aggregator which doesn't rely on the expression when the accumulator is performed. The expression is used only during conversion, to uniquely distinguish between two COUNT accumulators. Each time the accumulation is performed, the internal counter for this accumulator will be incremented. |
Following are a few cases which show when these c'tors are generated by the conversion rules. At this time, these examples want to prove only how the converted c'tors and the accumulator's expression gets converted, so other details will be ignored, as they will be treated in the next sections.
Example 1:
def var i as int. ... repeat i = 1 to 3: accum i (total). end.
Converted code:
... ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... }); ...
Details:
The TotalAccumulator(BaseDataType)
c'tor is emitted, as the accumulated expression is a plain 4GL variable.
Example 2:
def var i as int. ... repeat i = 1 to 3: accum i (average). end.
Converted code:
... ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final AverageAccumulator accumAvg0 = new AverageAccumulator(i); ... }); ...
Details:
The AverageAccumulator(BaseDataType)
c'tor is emitted, as the accumulated expression is a plain 4GL variable.
Example 3:
def var i as int. ... repeat i = 1 to 3: ... accum 1 (total). ... end.
Converted code:
... ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(1); ... }); ...
Details:
The TotalAccumulator(int)
c'tor is emitted, as the accumulated expression is an integer constant.
Example 4:
def var i as int. ... repeat i = 1 to 3: ... accum 1.0 (total). ... end.
Converted code:
... ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(1.0); ... }); ...
Details:
The TotalAccumulator(double)
c'tor is emitted, as the accumulated expression is a decimal constant.
Example 5:
def var i as int. ... repeat i = 1 to 3: ... accum “test” (min). ... end.
Converted code:
... ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final MinumumAccumulator accumMin0 = new MinimumAccumulator(“test”); ... }); ...
Details:
The MinimumAccumulator(String)
c'tor is emitted, as the accumulated expression is a string constant.
Example 6:
def var i as int. ... repeat i = 1 to 3: accum i + 1 (total). end.
Converted code:
ToClause toClause5 = new ToClause(i, 1, 3); repeatTo("loopLabel5", toClause5, new Block() { IntegerExpression accumExpr0 = new IntegerExpression() { public integer execute() { return plus(i, 1); } }; final TotalAccumulator accumTotal0 = new TotalAccumulator(accumExpr0); ... });
Details:
The TotalAccumulator(Resolvable)
c'tor is emitted, as the accumulated expression is a complex expression. Note how the accumExpr0
defines an anonymous class by extending the IntegerExpression
class (as this is an integer
expression). Each time accumulation is performed, accumExpr0.execute()
will be called to evaluate the expression.
Example 7:
def var i as int. ... repeat i = 1 to 3: accum i + 1 (count). end.
Converted code:
ToClause toClause6 = new ToClause(i, 1, 3); repeatTo("loopLabel6", toClause6, new Block() { final CountAccumulator accumCount0 = new CountAccumulator(); ... });
Details:
The CountAccumulator()
default c'tor is emitted regardless of how the expression looks. When a complex expression is involved, it will be ignored, as it doesn't need to be evaluated.
Example 8:
for each book: ... accum book.sold-qty (total). ... end.
Converted code:
forEach("loopLabel8", new Block() { ... FieldReference fieldRef0 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal0 = new TotalAccumulator(fieldRef0); ... });
Details:
When the accumulated data comes from a buffer field, the generated c'tor will take as parameter a field reference (similar to how the DISPLAY
statement displays a field). Each time accumulation is performed, the field's current value will be retrieved and accumulated.
Example 9:
def var i as int. ... for each book break by book.book-title: ... accum book.cost (average by book.book-title). ... end.
Converted code:
forEach("loopLabel10", new Block() { ... FieldReference fieldRef0 = new FieldReference(book, "cost"); final AverageAccumulator accumAvg0 = new AverageAccumulator(fieldRef0); ... });
Details:
When accumulating data from a buffer field, the AverageAccumulator(Resolvable)
c'tor is emitted, as the field's value needs to be evaluated on each iteration. This example computes the average cost for each book.
Example 10:
for each book break by book.publisher: ... display book.publisher book.sold-qty (total). ... end.
Converted code:
AverageAccumulator accumDisp0 = new AverageAccumulator(decimal.class); ... forEach("loopLabel11", new Block() { ... });
Details:
When defining the accumulator using the aggregate phrase with a DISPLAY
statement, the conversion rules must emit an instance field for the external procedure; in this case, the field must not have access to the actual buffer field being accumulated (as the buffer is not yet in scope), so the TotalAccumulator(decimal.class)
c'tor is emitted. This example displays the publishers, the quantity of sold books and the total number of sold books.
Naming and Scoping¶
As mentioned in the previous sections, an accumulator is determined to be unique using its type and the accumulated expression. Beside this, 4GL keeps separate scopes for all accumulators defined in the external procedures, internal procedures, functions or triggers - if an accumulator is defined for the same expression and aggregator type in one or more such blocks, 4GL will treat each occurrence as being in a distinct scope and they will be treated as distinct accumulators by the conversion rules. Note that the EDITING
blocks (with the PROMPT-FOR
, UPDATE
or SET
statements) do not have isolated scopes, so when used inside an editing block, the accumulator will scope to the editing block's parent.
During conversion, an accumulator is converted to an instance field for the external procedure class or for the block which encloses all references of this accumulator, depending on some rules. The name of the instance field will be determined using the aggregator's type and a counter.
The accumulator's converted name starts with the accum
prefix followed by a string depending on the aggregator type and ends with an unique counter, to distinguish between two accumulators of the same type. The name of the accumulator is determined based on rules found in the following table. The #
character represents an unique counter which counts all accumulators of the given type.
Aggregator Type | Converted Name | Details |
---|---|---|
AVG SUB-AVG |
accumAvg# | Uniquely identifies AVG and SUB-AVG accumulators. |
COUNT SUB-COUNT |
accumCount# | Uniquely identifies COUNT and SUB-COUNT accumulators. |
TOTAL SUB-TOTAL |
accumTotal# | Uniquely identifies TOTAL and SUB-TOTAL accumulators. |
MIN SUB-MIN |
accumMin# | Uniquely identifies MIN and SUB-MIN accumulators. |
MAX SUB-MAX |
accumMax# | Uniquely identifies MAX and SUB-MAX accumulators. |
DISPLAY with aggregate phrase |
accumDisp# | This distinct name is used for all accumulators first defined in a DISPLAY with aggregate phrase. They are not split into accumulator types, instead the # counter will count and uniquely identify all such accumulators. |
Note that if an accumulator is used in both the ACCUMULATE
statement and in a DISPLAY
with aggregate phrase statement, the accumulator's converted name will be determined based on where the accumulator is first used: if it's the ACCUMULATE
statement, then the converted name will be accum<type>#;
else, the converted name will be accumDisp#
.
The accumulator will be converted to an instance field for the external procedure or for an inner block, following these rules:
- the accumulator will be promoted to an instance field for the external procedure if the
ACCUM
function is used with aFORM
,PUT
orEXPORT
statement. - the accumulator will be promoted to an instance field for the external procedure if the accumulator is used in a
DISPLAY
with aggregate phrase statement. - the accumulator will be promoted to an instance field for the external procedure if the
ACCUM
function is used directly in the external procedure, internal procedure, function or trigger. - the nearest block which encloses all references of this accumulator, in all other cases.
The following examples will use the ACCUMULATE
statement (using the accum <expression> (<type>)
syntax) and the DISPLAY
with aggregate phrase to define an accumulator and explain how the instance field is named and scoped; please see the ACCUMULATE Statement and the DISPLAY Statement with Aggregate Phrase sections for details about how these statements get converted or, for cases where the ACCUM
function is used (using the ACCUM <type> <expression>
syntax), please see the ACCUM Function section for details. The examples will show only how the accumulator is named and scoped, without details about how the other statements get converted.
Example 1:
def var i as int init 0. ... repeat i = 1 to 3: accum i (total). end. ...
Converted code:
ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... });
Details:
The TOTAL
accumulator for the i
variable is converted to the accumTotal0
field. As the accumulator was used only in the REPEAT
block, it was converted to an instance field for this block.
Example 2:
def var i as int init 0. ... repeat i = 1 to 3: accum i (count). end. ...
Converted code:
ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final CountAccumulator accumCount0 = new CountAccumulator(); ... });
Details:
The COUNT
accumulator for the i
variable is converted to the accumCount0
field. As the accumulator was used only in the REPEAT
block, it was converted to an instance field for this block.
Example 3:
def var i as int init 0. ... repeat i = 1 to 3: accum i (max). end. ...
Converted code:
ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final MaximumAccumulator accumMax0 = new MaximumAccumulator(i); ... });
Details:
The MAX
accumulator for the i
variable is converted to the accumMax0
field. As the accumulator was used only in the REPEAT
block, it was converted to an instance field for this block.
Example 4:
def var i as int init 0. ... repeat i = 1 to 3: accum i (min). end. ...
Converted code:
ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final MinimumAccumulator accumMin0 = new MinimumAccumulator(i); ... });
Details:
The MIN
accumulator for the i
variable is converted to the accumMin0
field. As the accumulator was used only in the REPEAT
block, it was converted to an instance field for this block.
Example 5:
def var i as int init 0. ... repeat i = 1 to 3: accum i (average). end. ...
Converted code:
ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { final AverageAccumulator accumAvg0 = new AverageAccumulator(i); ... });
Details:
The AVERAGE
accumulator for the i
variable is converted to the accumAvg0
field. As the accumulator was used only in the REPEAT
block, it was converted to an instance field for this block.
Example 6:
def var i as int init 0. ... repeat i = 1 to 3: display i (total). end. ...
Converted code:
TestFrame0 frame0 = GenericFrame.createFrame(TestFrame0.class, ""); TotalAccumulator accumDisp0 = new TotalAccumulator(integer.class); ... ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { public void init() { frame0.openScope(); ... accumTotal0.reset(); } ... });
Details:
The TOTAL
accumulator for the i
variable, as it was used in an aggregate phrase for the DISPLAY
statement, is converted to the accumDisp0
field for the external procedure class (it follows the frame definition).
Example 7:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: accum i (total). B2: repeat j = 1 to 3: display i (total). end. end. ...
Converted code:
... repeatTo("B1", toClause1, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... public void body() { accumTotal0.accumulate(); ... repeatTo("B2", toClause2, new Block() { public void init() { frame0.openScope(); ... accumTotal0.reset(); } ... }); } });
Details:
As the accumulator is first used with the ACCUMULATE
statement, the converted name will be accumTotal0
and not accumDisp0
. See how the accumulator is determined to be unique using only the expression and its type, so the inner block accumulates data for the same accumulator as the parent block. The accumulator is converted to instance field for the block which encloses all accumulator and frame references, so in this case will be block B1
.
Example 8:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: display i (total). B2: repeat j = 1 to 3: accum i (total). end. end. ...
Converted code:
AccumCas5b2Frame0 frame0 = GenericFrame.createFrame(AccumCas5b2Frame0.class, ""); TotalAccumulator accumDisp0 = new TotalAccumulator(j); ... ... repeatTo("B1", toClause0, new Block() { public void init() { frame0.openScope(); ... accumDisp0.reset(); } public void body() { ... repeatTo("B2", toClause1, new Block() { public void init() { accumDisp0.reset(); } public void body() { accumDisp0.accumulate(); } }); } });
Details:
If the order is reversed and the accumulator is first used with in the DISPLAY
statement, the converted name will be accumDisp0
. Also, as the accumulator was used with the DISPLAY
statement first, it was converted to an instance field for the external procedure.
Example 9:
def var i as int init 0. ... repeat i = 1 to 3: accum 1 + i (total). accum i + 1 (total). end. ...
Converted code:
ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { IntegerExpression accumExpr0 = new IntegerExpression() { public integer execute() { return plus(1, i); } }; final TotalAccumulator accumTotal0 = new TotalAccumulator(accumExpr0); IntegerExpression accumExpr1 = new IntegerExpression() { public integer execute() { return plus(i, 1); } }; final TotalAccumulator accumTotal1 = new TotalAccumulator(accumExpr1); ... });
Details:
Even if the 1 + i
and i + 1
are semantically equivalent, this doesn't mean that they are the same accumulator; the conversion rules will emit two distinct accumulators, for each expression - these will be accumTotal0
and accumTotal1
, with their associated expression, accumExpr0
and accumExpr1
. In this case, both accumulators are converted to instance field for the REPEAT
block, as they are not referenced outside of it.
Example 10:
def var i as int. procedure proc1. B1: repeat i = 1 to 3: accum i (total). end. end. B2: repeat i = 1 to 3: accum i (total). end.
Converted code:
integer i = new integer(0); TotalAccumulator accumTotal1 = new TotalAccumulator(i); ... ToClause toClause1 = new ToClause(i, 1, 3); repeatTo("B2", toClause1, new Block() { ... }); ... public void proc1() { internalProcedure(new Block() { public void body() { ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("B1", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... }); } }); }
Details:
The two accumulators, even if they are for the same variable, they are in different scopes - this will result in two accumulators, one defined in the proc1
internal procedure and one defined in the external procedure.
Example 11:
def var i as int. function func1 return int. B1: repeat i = 1 to 3: accum i (total). end. return 0. end. B2: repeat i = 1 to 3: accum i (total). end.
Converted code:
integer i = new integer(0); TotalAccumulator accumTotal1 = new TotalAccumulator(i); ... ToClause toClause1 = new ToClause(i, 1, 3); repeatTo("B2", toClause1, new Block() { ... }); ... public integer func1() { return integerFunction(new Block() { public void body() { ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("B1", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... }); returnNormal(0); } }); }
Details:
The two accumulators, even if they are for the same expression and of the same type, they are in different scopes - this will result in two accumulators, one defined in the func1
function and one defined in the external procedure.
Example 12:
def var i as int. on endkey anywhere do: B1: repeat i = 1 to 3: accum i (total). end. end. B2: repeat i = 1 to 3: accum i (total). end.
Converted code:
integer i = new integer(0); TotalAccumulator accumTotal1 = new TotalAccumulator(i); ... ToClause toClause1 = new ToClause(i, 1, 3); repeatTo("B2", toClause1, new Block() { ... }); ... public class TriggerBlock0 extends Trigger { public void body() { ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("B2", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... }); } }
Details:
The two accumulators, even if they are for the same expression and of the same type, they are in different scopes - this will result in two accumulators, one defined in the trigger and one defined in the external procedure.
Example 13:
def var i as int. update j editing: B1: repeat i = 1 to 3: accum i (total). end. end. B2: repeat i = 1 to 3: accum i (total). end.
Converted code:
integer i = new integer(0); TotalAccumulator accumTotal0 = new TotalAccumulator(i); updateEditing(frame0, elementList0, "editingLabel0", new Block() { public void body() { ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("B2", toClause0, new Block() { ... }); } }); ToClause toClause1 = new ToClause(i, 1, 3); repeatTo("B2", toClause1, new Block() { ... });
Details:
In this case, as the EDITING
block does not affect the accumulator scope, the two accumulator statements reference the same accumulator. So, a single accumulator will be instantiated for the external procedure class.
Example 14:
def var i as int. ... repeat i = 1 to 3: accum i (total). end. message string(accum total i).
Converted code:
integer i = new integer(0); TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel1", toClause0, new Block() { ... }); message(valueOf((integer) accumTotal0.getResult()));
Details:
As the accumulator was used with the ACCUM
function in the external procedure, it will be emitted as an instance field for the external procedure.
Example 15:
def var i as int. procedure proc1. repeat i = 1 to 3: accum i (total). end. message string(accum total i). end.
Converted code:
integer i = new integer(0); TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... public void proc1() { internalProcedure(new Block() { public void body() { ToClause toClause0 = new ToClause(i, 1, 3); repeatTo("loopLabel0", toClause0, new Block() { ... }); message(valueOf((integer) accumTotal0.getResult())); } }); }
Details:
As the accumulator was used with the ACCUM
function in the proc1
procedure, it will be emitted as an instance field for the external procedure (note that the accumulator still remains scoped only to the proc1
procedure in 4GL terms).
ACCUMULATE Statement¶
The only way to define an accumulator in 4GL is by using the ACCUMULATE
statement or an aggregate phrase with a DISPLAY
statement; without first being defined by one of these two statements, the accumulator does not exist and can't be used in the ACCUM
function.
The ACCUMULATE
statement is used to calculate one or more aggregate values from a certain expression. Its short form is ACCUM
and must not be confused with the ACCUM
function. The syntax of this statement is:
ACCUMULATE { expression (aggregate-phrase) } . . .
where the expression
can be any valid 4GL expression with its type compatible with the specified aggregate phrase(s). For this case, the aggregate phrase has the following form:
{ AVG | COUNT | TOTAL | MIN | MAX | SUB-AVG | SUB-COUNT | SUB-TOTAL | SUB-MIN | SUB-MAX } . . . { BY break-group } . . .
For each expression, 4GL allows the declaration of multiple aggregators, via the aggregate phrase. The conversion process will take into consideration that each expression may have more than one aggregator specified and, in these cases, will trigger the generation of an unique accumulator instance for each combination of (expression, aggregator).
The BY break-group
can be used only with query loops. For a certain accumulated expression, multiple break groups can be defined - the only constraint is that for each break group specified with the aggregate phrase, there must be a BREAK BY
entry defined at the query loop.
When using the BY break-group
clause there are a few cases which do not impact the converted code, but they are worth mentioning:
- if the
BY break-group
clause is set for a full accumulator, it will be possible to retrieve both the aggregated result for the current group and the grand total (when theBY break-group
clause is not specified for theACCUM
function). - If the
ACCUMULATE
statement does not specify aBY break-group
clause for this sub-accumulator, then when retrieving its result using theACCUM
function theBY break-group
clause must not be set - in this case, the sub-accumulator is not a real sub-accumulator and it will behave as a full accumulator. - if the
ACCUMULATE
statement does specify aBY break-group
clause and this is a sub-accumulator, the data will be aggregated only for each group (the aggregated result for the entire data set will not be available in this block, will be available only after this block has finished). - the sub-only properties of a sub-accumulator are scoped starting only with the block where the sub-accumulator is first defined. So, if its full version is used in a parent block and its sub-accumulator version is used in a child block, the accumulator will maintain sub-accumulator properties only in the child block.
In 4GL, accumulation can be performed only inside blocks which have the loop property; thus, the ACCUMULATE
statement and the DISPLAY
with aggregate phrase can be used only when they are nested in one of the following blocks: REPEAT
, DO ... {TRANSACTION | ON END-KEY UNDO, LEAVE}
, FOR {EACH | LAST | FIRST}
. If it does not have as parent (direct or not) a block with the loop property, the compilation and the conversion of the 4GL source code will fail. Each time a block which uses (on any nesting level) a certain accumulator is encountered, an Accumulator.reset()
call is added in the block's init()
method - see the Block Behavior for Accumulators section for details about why this call is added.
When accumulating data, if there is more than one ACCUMULATE
statement (as a direct child) for the same accumulator in certain block, 4GL will accumulate data only for the first encountered statement. FWD solves this by leaving in the converted code, for a certain accumulator, only the first ACCUMULATE
statement's occurence - all subsequent direct references will discarded. Also, in 4GL the accumulators are undoable, but this doesn't affect the converted code; instead, the FWD runtime will take care of rolling back the accumulated data, when an iteration is rolled back.
Example 1:
def var i as int. ... repeat i = 1 to 3: accum i (total). end.
Converted code:
... repeatTo("loopLabel0", toClause0, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } });
Details:
This example shows how the statement gets converted with the REPEAT
block. See the accumTotal0.accumulate()
call added in the REPEAT
's body()
method.
Example 2:
def var i as int. ... do i = 1 to 3 transaction: accum i (total). end.
Converted code:
... doTo(TransactionType.FULL, "loopLabel0", toClause0, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } });
Details:
When the DO ... TRANSACTION
block is found as parent, the conversion rules emit the same accumulator code as for the REPEAT
case.
Example 3:
def var i as int. ... do i = 1 to 3 on end-key undo, leave: accum i (total). end.
Converted code:
... doTo(TransactionType.SUB, "loopLabel0", toClause0, onPhrase0, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } });
Details:
When the DO ... ON END-KEY UNDO, LEAVE
block is found as parent, the converted rules emit the same accumulator code as for the REPEAT
case.
Example 4:
for each book: accum book.sold-qty (total). end.
Converted code:
forEach("loopLabel4", new Block() { AdaptiveQuery query0 = null; FieldReference fieldRef0 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal0 = new TotalAccumulator(fieldRef0); public void init() { RecordBuffer.openScope(book); query0 = new AdaptiveQuery(book, (String) null, null, "book.bookId asc"); query0.addAccumulator(accumTotal0); accumTotal0.reset(); } public void body() { query0.next(); accumTotal0.accumulate(); } });
Details:
This example shows how the statement gets converted with the FOR EACH
block. In this case, the accumulator must be registered with the query, so that the next time a new record is retrieved, the accumTotal0.accumulate()
call will know that a new iteration is in effect and it will trigger accumulation.
Example 5:
for first book: accum book.sold-qty (total). end.
Converted code:
forBlock("blockLabel0", new Block() { RandomAccessQuery query1 = null; FieldReference fieldRef1 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal2 = new TotalAccumulator(fieldRef1); public void init() { RecordBuffer.openScope(book); query1 = new RandomAccessQuery(book, (String) null, null, "book.bookId asc"); accumTotal2.reset(); } public void body() { query1.first(); accumTotal2.accumulate(); } });
Details:
In the FOR FIRST
block case, the accumulator does get registered with the query, all accumulator-related converted code is the same as with the REPEAT
block.
Example 6:
for last book: accum book.sold-qty (total). end.
Converted code:
forBlock("blockLabel1", new Block() { RandomAccessQuery query2 = null; FieldReference fieldRef2 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal3 = new TotalAccumulator(fieldRef2); public void init() { RecordBuffer.openScope(book); query2 = new RandomAccessQuery(book, (String) null, null, "book.bookId asc"); accumTotal3.reset(); } public void body() { query2.last(); accumTotal3.accumulate(); } });
Details:
In the FOR LAST
block case, the accumulator does get registered with the query, all accumulator-related converted code is the same as with the REPEAT
block.
Example 7:
define query qBook for book. open query qBook for each book. get first qBook. do while available(book) transaction: accum book.sold-qty (total). get next qBook. end.
Converted code:
RecordBuffer.openScope(book); query0.assign(new AdaptiveQuery(book, (String) null, null, "book.bookId asc")); query0.open(); query0.first(); LogicalExpression whileClause0 = new LogicalExpression() { public logical execute() { return RecordBuffer.isAvailable(book); } }; doWhile(TransactionType.FULL, "loopLabel0", whileClause0, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); query0.next(); } });
Details:
This example is just a more complicated example of the DO ... TRANSACTION
case: it shows how the statement gets converted when the DO
block iterates over a query - the code added for the accumulator is the same as in the previous examples.
Example 8
def var i as int. ... repeat i = 1 to 3: accum i (total). ... accum i (total). end.
Converted code:
... repeatTo("loopLabel1", toClause1, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); i.assign(plus(i, 1)); } });
Details:
The conversion rules will emit only the first ACCUMULATE
statement call, and will remove all subsequent ones. Note how the code is emitted only for the first ACCUMULATE
statement.
Example 9:
def var i as int. def var j as int. ... B1: repeat i = 1 to 3: accum i (total). ... B2: repeat j = 1 to 3: accum i (total). end. end.
Converted code:
repeatTo("B1", toClause0, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); ... repeatTo("B2", toClause1, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } });
Details:
This examples proves that 4GL ignores only multiple accumulator references which are directly nested in a block. As the second ACCUMULATE
call is not a direct child of the B1
block, it will not be removed.
Example 10:
for each book break by book.publisher: accum book.sold-qty (total by book.publisher). end.
Converted code:
forEach("loopLabel1", new Block() { PresortQuery query1 = null; FieldReference byExpr0 = new FieldReference(book, "publisher"); public void init() { query1 = new PresortQuery(book, (String) null, null, "book.publisher asc"); query1.enableBreakGroups(); query1.setNonScrolling(); query1.addSortCriterion(byExpr0); query1.addAccumulator(accumTotal0); accumTotal0.addBreakGroup(byExpr0); accumTotal0.reset(); } public void body() { query1.next(); accumTotal0.accumulate(); } });
Details:
A BY
clause can be used with the ACCUMULATE
statement only if it is nested in a query block and it contains a BREAK BY
clause for the same group. In this case, see how the break group is registered with the accumulator (via the addBreakGroup
call) and also how it is registered with the query (via the addAccumulator
call), so that it will be notified when a new break group is entered. Note that this is not a sub-accumulator, so using the ACCUM
function to retrieve the result will give either the grand total or the group total, depending on whether the BY
clause was specified or not.
Example 11:
for each book: accum book.sold-qty (sub-total). end.
Converted code:
forEach("loopLabel8", new Block() { AdaptiveQuery query0 = null; FieldReference fieldRef0 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal0 = new TotalAccumulator(fieldRef0); public void init() { RecordBuffer.openScope(book); query0 = new AdaptiveQuery(book, (String) null, null, "book.bookId asc"); query0.addAccumulator(accumTotal0); accumTotal4.reset(); } public void body() { query0.next(); accumTotal0.accumulate(); } });
Details:
If a sub-accumulator is used without a BY break-group
clause, it will behave as a full-accumulator and the conversion rules will emit the same code as for its full version.
Example 12:
for each book break by book.publisher: accum book.sold-qty (sub-total by book.publisher). end.
Converted code:
forEach("loopLabel2", new Block() { PresortQuery query2 = null; FieldReference byExpr0 = new FieldReference(book, "publisher"); public void init() { query2 = new PresortQuery(book, (String) null, null, "book.publisher asc"); query2.enableBreakGroups(); query2.setNonScrolling(); query2.addSortCriterion(byExpr0); query2.addAccumulator(accumTotal0); accumTotal0.setSubOnly(); accumTotal0.addBreakGroup(byExpr0); accumTotal0.reset(); } public void body() { query2.next(); accumTotal0.accumulate(); } });
Details:
When using a sub-accumulator and a BY break-group
clause, the accumulator will be marked as sub-accumulator for this block (see the setSubOnly()
call added in the block's init()
method). The cumulative aggregated result will be available only after the block has finished.
Example 13:
def var i as int. def var j as int. repeat i = 1 to 3: accum i (total min max average count). repeat j = 1 to 3: accum i (total). end. end.
Converted code:
... repeatTo("loopLabel2", toClause2, new Block() { final TotalAccumulator accumTotal0 = new TotalAccumulator(i); final MinimumAccumulator accumMin0 = new MinimumAccumulator(i); final MaximumAccumulator accumMax0 = new MaximumAccumulator(i); final AverageAccumulator accumAvg0 = new AverageAccumulator(i); final CountAccumulator accumCount0 = new CountAccumulator(); public void init() { accumTotal0.reset(); accumMin0.reset(); accumMax0.reset(); accumAvg0.reset(); accumCount0.reset(); } public void body() { accumTotal0.accumulate(); accumMin0.accumulate(); accumMax0.accumulate(); accumAvg0.accumulate(); accumCount0.accumulate(); ... repeatTo("loopLabel3", toClause3, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } });
Details:
As the aggregate phrase can contain multiple aggregators for the same expressions, the conversion rules will emit distinct accumulator instances for each aggregator type.
DISPLAY Statement with Aggregate Phrase¶
4GL provides a shortcut to display the accumulated expression's value and its aggregated (sub-)result, by using an aggregate phrase with the DISPLAY
statement. The syntax of this case is:
DISPLAY { expression (aggregate-phrase) } . . .
where the aggregate phrase has a syntax similar with the ACCUMULATE
statement case:
{ AVG | COUNT | TOTAL | MIN | MAX | SUB-AVG | SUB-COUNT | SUB-TOTAL | SUB-MIN | SUB-MAX } [ LABEL aggr-label ] { BY break-group }
Here, the aggr-label
is an optional attribute which sets the label for the row where the aggregated result will be displayed (note that this clause is not supported at the time of this writing).
When multiple aggregators are specified for a certain expression, they will be displayed always in the same order, regardless of the order specified in the 4GL code; this order comes from a hard-coded weight set internally by 4GL for each aggregator. When the aggregated result is displayed, the row containing this data will have a default label set to it, which can be seen in the next table. Note that the weight and default label for the sub-accumulators is the same as the weight and default label for their full versions.
Aggregator | Weight | Default Label |
---|---|---|
TOTAL SUB-TOTAL |
1 | TOTAL |
COUNT SUB-COUNT |
2 | COUNT |
MAX SUB-MAX |
3 | MAX |
MIN SUB-MIN |
4 | MIN |
AVG SUB-AVG |
5 | AVG |
When an accumulator is used in an aggregate phrase with the DISPLAY
statement, there are some special cases which need to be considered, depending on how the aggregate phrase was defined. Although some of them are handled by the FWD runtime, they are worth mentioning, to understand why these were not handled in the converted code:
- If the
DISPLAY
statement is never executed (thus no accumulation is performed) but the block is iterated at least once, the frame will still display the total or sub-total results, once the block is finished. This is handled by the FWD runtime, as each accumulator is registered with the frame and the frame is notified when the block is finished. - A certain accumulator can be used with different
DISPLAY
statements, within the same block, but the accumulation is triggered only for the firstDISPLAY
call. Note that this is currently not supported by FWD. - The accumulators registered with a
DISPLAY
statement may never accumulate. Thus, as access to the actual expression may not be available, their data type will be set on instantiation by the conversion rules and this type will be identical to the result type of the accumulated expression. More, this type is identical to the type of the widget with which this accumulator is associated in the frame . - If a
BY break-group
clause is specified when a full accumulator is used with aDISPLAY
statement, it will display the accumulation result for each group and a final result for the entire data set. If it does not have a group specified, this will display only the final result. The conversion rules will emit the break group for theDISPLAY
's aggregate phrase, but the FWD runtime will be responsible of displaying the final or group result. - If a
BY break-group
clause is specified when the sub-accumulator is used with aDISPLAY
statement, it will display the accumulation result for each group, but it will not display the final result for the entire data set. If it does not have a group specified, this will display only the final result. The conversion rules will emit the break group for theDISPLAY
's aggregate phrase, but the FWD runtime will be responsible of displaying the final result. - No explicit
Accumulator.accumulate()
calls will be emitted, as the accumulation will be performed only when the frame will be displayed.
As the aggregate phrase belongs to this frame, in 4GL terms this means that the accumulators defined by such aggregate phrases must be registered with the frame, when the frame enters its scope. For this, the conversion process will emit a special registration code in the init()
method for the same block as where the frame's openScope()
call is emitted:
... public void init() { frame.openScope(); FrameElement[] aggrList = new FrameElement[] { new AggregatorElement(<expression0>, frame.widget(), <accum0>), new AggregatorElement(<expression1>, frame.widget(), <accum1>) ... }; frame.registerAccumulators(aggrList); ... <accum0>.reset(); <accum1>.reset(); ... }
When registering or displaying an accumulator, a special FrameElement
subclass is used - com.goldencode.2j.ui.AggregatorElement.
This class has specialized constructors similar to the Accumulator
subclasses, so that it will take as parameters:
- the expression (
<expression>
in the code snippet above), which may be a constant, variable, field or complex expression. - the frame's widget where the accumulated data will be displayed (this is the same widget as where the expression's values are displayed).
- the
Accumulator
instance,<accum>
in the code snippet above.
The conversion rules will emit in the aggrList
array all accumulators defined with a certain frame.
Following examples will show how the accumulators are converted when their accumulation is triggered by the display of a frame:
Example 1:
def var i as int. ... repeat i = 1 to 3: display i (total) with frame fa. end.
Converted code:
TotalAccumulator accumDisp0 = new TotalAccumulator(integer.class); ... repeatTo("loopLabel0", toClause0, new Block() { public void init() { faFrame.openScope(); FrameElement[] aggrList0 = new FrameElement[] { new AggregatorElement(i, faFrame.widgeti(), accumDisp0) }; faFrame.registerAccumulators(aggrList0); accumDisp0.reset(); } public void body() { FrameElement[] elementList0 = new FrameElement[] { new AggregatorElement(i, faFrame.widgeti(), accumDisp0) }; faFrame.display(elementList0); } });
Details:
When using the aggregate phrase with a DISPLAY
statement, the converted accumulator will follow the frame definition - so, it will be an instance field for the external procedure class (named accumDisp0
in this case). Also, the accumulator will be registered with the frame in the block's init()
method (via the frame.registerAccumulators(...)
call) so that the frame will know to display the final result, once the block has finished.
Example 2:
def var i as int. ... repeat i = 1 to 3: display i (average) i (count) with frame fa. end.
Converted code:
AverageAccumulator accumDisp0 = new AverageAccumulator(integer.class); CountAccumulator accumDisp1 = new CountAccumulator(); ... repeatTo("loopLabel1", toClause1, new Block() { public void init() { faFrame.openScope(); FrameElement[] aggrList1 = new FrameElement[] { new AggregatorElement(i, faFrame.widgeti(), accumDisp1), new AggregatorElement(i, faFrame.widgeti(), accumDisp0) }; faFrame.registerAccumulators(aggrList1); accumDisp1.reset(); accumDisp0.reset(); } public void body() { FrameElement[] elementList1 = new FrameElement[] { new AggregatorElement(i, faFrame.widgeti(), accumDisp1), new AggregatorElement(i, faFrame.widgeti(), accumDisp0) }; faFrame.display(elementList1); } });
Details:
This example shows how multiple aggregators can be specified for the same expression - although the i
variable appears two times, it will be displayed in only one widget and the result for the two aggregators will be displayed at the end of the REPEAT
block, on distinct rows, by their weight. Also, see how the two aggregators are registered with the same widget.
Example 3:
for each book: display book.publisher book.book-title book.sold-qty (total) with frame fa. end.
Converted code:
TotalAccumulator accumDisp2 = new TotalAccumulator(integer.class); ... forEach("loopLabel2", new Block() { AdaptiveQuery query0 = null; public void init() { faFrame.openScope(); FrameElement[] aggrList2 = new FrameElement[] { new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp2) }; faFrame.registerAccumulators(aggrList2); RecordBuffer.openScope(book); query0 = new AdaptiveQuery(book, (String) null, null, "book.bookId asc"); query0.addAccumulator(accumDisp2); accumDisp2.reset(); } public void body() { query0.next(); FrameElement[] elementList2 = new FrameElement[] { new Element(new FieldReference(book, "publisher"), faFrame.widgetPublisher()), new Element(new FieldReference(book, "bookTitle"), faFrame.widgetBookTitle()), new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp2) }; faFrame.display(elementList2); } });
Details:
This example shows each publisher, the sold quantity for each book and a grand total of sold books. When query loops are involved, the accumulator still needs to be registered with the query, via the query.addAccumulator()
call.
Example 4:
for each book: display book.publisher book.sold-qty (sub-total) with frame fa. end.
Converted code:
TotalAccumulator accumDisp3 = new TotalAccumulator(integer.class); ... forEach("loopLabel3", new Block() { AdaptiveQuery query1 = null; public void init() { faFrame.openScope(); FrameElement[] aggrList3 = new FrameElement[] { new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp3) }; faFrame.registerAccumulators(aggrList3); RecordBuffer.openScope(book); query1 = new AdaptiveQuery(book, (String) null, null, "book.bookId asc"); query1.addAccumulator(accumDisp3); accumDisp3.reset(); } public void body() { query1.next(); FrameElement[] elementList3 = new FrameElement[] { new Element(new FieldReference(book, "publisher"), faFrame.widgetPublisher()), new Element(new FieldReference(book, "bookTitle"), faFrame.widgetBookTitle()), new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp3) }; faFrame.display(elementList3); } });
Details:
This example is the same as the previous one and its goal is to show that sub-accumulators without a BY break-group
clause are not real sub-accumulators - instead, they act as their full version. So, in this case, no accum.setSubOnly()
call is emitted in the block's init()
mehtod.
Example 5:
for each book break by book.publisher: display book.publisher book.sold-qty (total by book.publisher) with frame fa. end.
Converted code:
TotalAccumulator accumDisp4 = new TotalAccumulator(integer.class); ... forEach("loopLabel4", new Block() { PresortQuery query2 = null; FieldReference byExpr0 = new FieldReference(book, "publisher"); public void init() { faFrame.openScope(); FrameElement[] aggrList4 = new FrameElement[] { new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp4) }; faFrame.registerAccumulators(aggrList4); RecordBuffer.openScope(book); query2 = new PresortQuery(book, (String) null, null, "book.publisher asc"); query2.enableBreakGroups(); query2.setNonScrolling(); query2.addSortCriterion(byExpr0); query2.addAccumulator(accumDisp4); accumDisp4.addBreakGroup(byExpr0); accumDisp4.reset(); } public void body() { query2.next(); FrameElement[] elementList4 = new FrameElement[] { new Element(new FieldReference(book, "publisher"), faFrame.widgetPublisher()), new Element(new FieldReference(book, "bookTitle"), faFrame.widgetBookTitle()), new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp4) }; faFrame.display(elementList4); } });
Details:
Full accumulators with BY break-group
clause will display sub-totals for each break group and a final grand total. Note that accum.setSubOnly()
is not emitted in this case either.
Example 6:
for each book break by book.publisher: display book.publisher book.sold-qty (sub-total by book.publisher) with frame fa. end.
Converted code:
TotalAccumulator accumDisp5 = new TotalAccumulator(integer.class); ... forEach("loopLabel5", new Block() { PresortQuery query3 = null; FieldReference byExpr1 = new FieldReference(book, "publisher"); public void init() { faFrame.openScope(); FrameElement[] aggrList5 = new FrameElement[] { new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp5) }; faFrame.registerAccumulators(aggrList5); RecordBufaer.openScope(book); query3 = new PresortQuery(book, (String) null, null, "book.publisher asc"); query3.enableBreakGroups(); query3.setNonScrolling(); query3.addSortCriterion(byExpr1); query3.addAccumulator(accumDisp5); accumDisp5.setSubOnly(); accumDisp5.addBreakGroup(byExpr1); accumDisp5.reset(); } public void body() { query3.next(); FrameElement[] elementList5 = new FrameElement[] { new Element(new FieldReference(book, "publisher"), faFrame.widgetPublisher()), new Element(new FieldReference(book, "bookTitle"), faFrame.widgetBookTitle()), new AggregatorElement(new FieldReference(book, "soldQty"), faFrame.widgetSoldQty(), accumDisp5) }; faFrame.display(elementList5); } });
Details:
Finally, this is a real sub-accumulator - in this case, the setSubOnly()
call in the init()
method will mark it as a sub-accumulator in this block, so that it will display only group results.
Example 7
B1: repeat i = 1 to 3: display i (total) with frame f1. B2: repeat j = 1 to 3: display i (total) with frame f2. end. end.
Converted code:
... repeatTo("B1", toClause2, new Block() { final TotalAccumulator accumDisp6 = new TotalAccumulator(integer.class); public void init() { f1Frame.openScope(); FrameElement[] aggrList6 = new FrameElement[] { new AggregatorElement(i, f1Frame.widgeti(), accumDisp6) }; f1Frame.registerAccumulators(aggrList6); accumDisp6.reset(); } public void body() { FrameElement[] elementList6 = new FrameElement[] { new AggregatorElement(i, f1Frame.widgeti(), accumDisp6) }; f1Frame.display(elementList6); ... repeatTo("B2", toClause3, new Block() { public void init() { f2Frame.openScope(); FrameElement[] aggrList7 = new FrameElement[] { new AggregatorElement(i, f2Frame.widgeti(), accumDisp6) }; f2Frame.registerAccumulators(aggrList7); accumDisp6.reset(); } public void body() { FrameElement[] elementList7 = new FrameElement[] { new AggregatorElement(i, f2Frame.widgeti(), accumDisp6) }; f2Frame.display(elementList7); } }); } });
Details:
The goal of this example is to show that, if the same accumulator is used in the aggregate phrase for multiple DISPLAY
statements, then in 4GL there is only one accumulator. The accumulation performed in the inner block will be seen by the parent block and, when the parent block is finished, it will display the aggregated result from both blocks.
ACCUM Function¶
The ACCUM
function is used to retrieve the data previously aggregated with the ACCUMULATE
statement or DISPLAY
with aggregate phrase, for a certain expression. Note that Progress restricts this function so it can be used only if there is an ACCUMULATE
statement (or DISPLAY
with aggregate phrase) for this accumulator in the 4GL code prior to the function call; if there is no such call, the 4GL will fail with a compile error. As only 4GL code which passes compilation can be converted, the conversion rules will fail if, when an ACCUM
function call is encountered, there is no found accumulator defined using the ACCUMULATE
statement or a DISPLAY
with aggregate phrase, for the same expression and aggregator tyep.
For each call, this function can return the aggregated result for only one expression and aggregator. Thus, its syntax is:
ACCUM { (aggregate-phrase) expression }
where the aggregate phrase must be under this form:
{ AVG | COUNT | TOTAL | MIN | MAX | SUB-AVG | SUB-COUNT | SUB-TOTAL | SUB-MIN | SUB-MAX } { BY break-group }
As mentioned before, the expression
used with the ACCUM
function must be in the exact form as when it was used with the ACCUMULATE
statement or with the DISPLAY
with aggregate phrase, even if they are semantically equivalent - i.e. price * qty-sold
is not the same as qty-sold * price
. The aggregate phrase can specify only one aggregator (with its associated BY break-group
clause, if required). If a sub-accumulator is used and is missing the BY break-group
clause, then it will behave as a full accumulator only if its definition also has no BY break-group
clause. Else, if its definition has a BY break-group
clause and this is a sub-accumulator, calling the ACCUM
function without the BY break-group
clause will return the reset value, as the aggregated result for the entire data set is not available yet (will be available once the block is finished). This does not affect converted code, this behavior is implemented in the FWD runtime.
When retrieving the accumulated result using the ACCUM
function, the conversion rules will emit a distinct method call, depending on whether it needs only a result for a certain group (when a BY
clause is used) or the current result, when no group is specified. For sub-results, the converted code will call the accum.getResult(Resolvable breakGroupKey)
method, where the breakGroupKey
is the key for which the sub-result is needed; for the full result, the converted code will call the accum.getResult()
method. These methods are not defined in the p2j.com.goldencode.util.Accumulator
, because their returned type depends on the type of the used aggregator; in both cases, the accum
represents the name of the field which references the converted accumulator. The next table shows the returned type of the getResult
methods, for each Accumulator
sub-class in the FWD runtime:
Class | AverageAccumulator | CountAccumulator | TotalAccumulator | MinimumAccumulator | MaximumAccumulator |
---|---|---|---|---|---|
Returned Type | NumberType | integer | NumberType | BaseDataType | BaseDataType |
Note that there are special cases when the ACCUM
function returns some specific values, depending on whether accumulation was performed or not - this section will not treat these cases, but will explain only how this statement gets converted; please see the next section, Block Behavior for Accumulators, for details on how the aggregated result gets updated in different phases of block execution.
Example 1:
def var i as int init 0. ... repeat i = 1 to 3: message string(accum total i). accum i (total). end. ...
Conversion result:
EXPRESSION EXECUTION ERROR: --------------------------- throwException("Unmatched expression in ACCUM function", this) ^ { Unmatched expression in ACCUM function [KW_TOTAL id <12884901944> 4:15] }
Details:
In this case, even if the ACCUM
function and ACCUMULATE
statement are in the same block, the accumulator is not used in an ACCUMULATE
statement or DISPLAY
with aggregate-phrase prior the ACCUM
function call - thus, conversion fails with the noted errors. 4GL compilation error is not known at the time of this writing.
Example 2:
def var i as int init 0. ... repeat i = 1 to 3: accum i (total). message string(accum total i). end. ...
Converted code:
TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... repeatTo("loopLabel0", toClause0, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); message(valueOf((integer) accumTotal0.getResult())); } });
Details:
In this example, accumulation is performed on each iteration of the REPEAT
block. The MESSAGE
statement will display the aggregated result, on each iteration. Note how the ACCUM
function converts to a getResult()
method call.
Example 3:
def var i as int init 0. ... repeat i = 1 to 3: accum i (total). display i accum total i with frame f1. end. ...
Converted code:
TotalAccumulator accumTotal0 = new TotalAccumulator(i); ... repeatTo("loopLabel1", toClause1, new Block() { public void init() { f1Frame.openScope(); accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); FrameElement[] elementList0 = new FrameElement[] { new Element(i, f1Frame.widgeti()), new Element((integer) accumTotal0.getResult(), f1Frame.widgetExpr2()) }; f1Frame.display(elementList0); } });
Details:
When the ACCUM
function is used with the DISPLAY
statement, the result will be displayed in a special widget, for which the default label will be determined using the same rules as described in the DISPLAY with Aggregate Phrase section. As in the previous case, the conversion rules emit the same construct for the ACCUM
function call - a getResult()
method call. Also, the widget to which the accumulated result is displayed via the ACCUM
function call follows the same rules as the a DISPLAY
statement's normal widgets - see the DISPLAY Statement chapter for details.
Example 4:
... for each book break by book.publisher: accum book.sold-qty (total by book.publisher). if last-of(book.publisher) then display book.publisher accum total by book.publisher book.sold-qty. end. ...
Converted code:
forEach("loopLabel2", new Block() { PresortQuery query0 = null; FieldReference byExpr0 = new FieldReference(book, "publisher"); FieldReference fieldRef0 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal1 = new TotalAccumulator(fieldRef0); public void init() { frame0.openScope(); RecordBuffer.openScope(book); query0 = new PresortQuery(book, (String) null, null, "book.publisher asc"); query0.enableBreakGroups(); query0.setNonScrolling(); query0.addSortCriterion(byExpr0); query0.addAccumulator(accumTotal1); accumTotal1.addBreakGroup(byExpr0); accumTotal1.reset(); } public void body() { query0.next(); accumTotal1.accumulate(); if ((query0.isLastOfGroup(byExpr0)).booleanValue()) { FrameElement[] elementList1 = new FrameElement[] { new Element(new FieldReference(book, "publisher"), frame0.widgetPublisher()), new Element((integer) accumTotal1.getResult(byExpr0), frame0.widgetExpr4()) }; frame0.display(elementList1); } } });
Details:
When the BY break-group
clause is used, the ACCUM
function will retrieve the current aggregated result only for the specified break group - see how the getResult
method gets as parameter the break group reference byExpr0
. In this case, the FOR EACH
loop will display the sold quantity for each publisher (and each publisher will appear only once, as the LAST-OF
condition is used). Also, as a full accumulator is used, no setSubOnly
call is emitted.
Example 5:
... for each book break by book.publisher: accum book.sold-qty (total by book.publisher). display book.publisher accum total book.sold-qty. end. ...
Converted code:
forEach("loopLabel3", new Block() { PresortQuery query1 = null; FieldReference byExpr1 = new FieldReference(book, "publisher"); FieldReference fieldRef1 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal2 = new TotalAccumulator(fieldRef1); public void init() { frame1.openScope(); RecordBuffer.openScope(book); query1 = new PresortQuery(book, (String) null, null, "book.publisher asc"); query1.enableBreakGroups(); query1.setNonScrolling(); query1.addSortCriterion(byExpr1); query1.addAccumulator(accumTotal2); accumTotal2.addBreakGroup(byExpr1); accumTotal2.reset(); } public void body() { query1.next(); accumTotal2.accumulate(); FrameElement[] elementList2 = new FrameElement[] { new Element(new FieldReference(book, "publisher"), frame1.widgetPublisher()), new Element((integer) accumTotal2.getResult(), frame1.widgetExpr6()) }; frame1.display(elementList2); } });
Details:
This is similar as the previous example, except that, even if the ACCUMULATE
statement has a BY
clause, the ACCUM
function does not - in this case, the ACCUM
function will return the current aggregated result. The goal of this example is to prove that if the BY
clause appears with the ACCUMULATE
statement, it is not mandatory to be used with the ACCUM
function too, when a full accumulator is used.
Example 6:
... for each book break by book.publisher: accum book.sold-qty (sub-total by book.publisher). if last-of(book.publisher) then display book.publisher accum sub-total by book.publisher book.sold-qty. end. ...
Converted code:
forEach("loopLabel4", new Block() { PresortQuery query2 = null; FieldReference byExpr2 = new FieldReference(book, "publisher"); FieldReference fieldRef2 = new FieldReference(book, "soldQty"); final TotalAccumulator accumTotal3 = new TotalAccumulator(fieldRef2); public void init() { frame2.openScope(); RecordBuffer.openScope(book); query2 = new PresortQuery(book, (String) null, null, "book.publisher asc"); query2.enableBreakGroups(); query2.setNonScrolling(); query2.addSortCriterion(byExpr2); query2.addAccumulator(accumTotal3); accumTotal3.setSubOnly(); accumTotal3.addBreakGroup(byExpr2); accumTotal3.reset(); } public void body() { query2.next(); accumTotal3.accumulate(); if ((query2.isLastOfGroup(byExpr2)).booleanValue()) { FrameElement[] elementList3 = new FrameElement[] { new Element(new FieldReference(book, "publisher"), frame2.widgetPublisher()), new Element((integer) accumTotal3.getResult(byExpr2), frame2.widgetExpr8()) }; frame2.display(elementList3); } } });
Details:
Here, as the SUB-TOTAL
version of the TOTAL
aggregator is used and also there is a BY break-group
clause in both the ACCUM
statement and function, this means this is a real sub-accumulator so a setSubOnly
call is emitted. Also, note that the converted ACCUM
function call gets as parameter the break group instance, byExpr2
.
Example 7:
... for each book break by book.publisher: accum book.sold-qty (total). if last-of(book.publisher) then display book.publisher accum total by book.publisher book.sold-qty. end. ...
Converted code:
n/a
Details:
4GL does not allow the BY
clause in the ACCUM
function if there is no BY
clause in the ACCUMULATE
statement - in such cases, the 4GL code compilation fails. Note that even if the conversion of these cases passes, this will result in runtime errors as the accumulator is not aware of the break group specified with the ACCUM
function, when the result is retrieved - so, the getResult
call for such cases will fail at runtime with an exception.
Block Behavior for Accumulators¶
4GL restricts the ACCUMULATE
statements to be used only in blocks which have the loop property, as specified in the ACCUMULATE Statement section. The good news is that 4GL follows the same rules when an ACCUMULATE
statement or DISPLAY
with aggregate phrase is used, regardless of the kind of block used (only restriction is to be one of the blocks mentioned above). It gets complicated in cases when an accumulator aggregates its data in both a parent and a nested block, as the child block is not aware (to some extent) of the accumulation performed in the parent block. When retrieving the accumulated result in the child block, this will not include the aggregated data in the parent block and once the child block is finished, the accumulated result in the parent block will be updated with the accumulated data from the child block. Although this behavior is straightforward at a first glance, extensive testing proved that there are other hidden rules when an accumulator aggregates data in nested blocks, which affect the aggregated result reported after a nested block was executed (or not).
To understand how the accumulators behave, first we will introduce a few rules. In these rules, when a block is mentioned, it is assumed that the block has the loop property and has a direct reference to the accumulator (i.e. the accumulator reference is not nested within a child block). Also, when ACCUMULATE
statement (or accumulation) is mentioned, it is to be assumed that the same behavior applies to the DISPLAY
with aggregate phrase too.
- if the aggregated result is retrieved inside the external procedure but before any block which accumulates data is initialized (i.e. the block's
init()
method is never executed), the result will be the unknown value. This value will be returned by theACCUM
function until (at least) a block'sinit()
method is executed. - if the
init()
method was executed and the aggregated result is retrieved before any accumulation was performed in this block or in any inner blocks which accumulate data for the same accumulator, the aggregated result will be set to the accumulated value as it was before executing the block'sinit()
method call. This applies for all iterations - if no accumulation is yet performed in this iteration, the aggregated result returned by theACCUM
function will be the value as it was prior this block's execution. - once the block in which the accumulation is executed is finished, the reported accumulated result will be the total accumulated result (for this block and all inner blocks). Here there are some sub-cases, depending on how the block was finished:
- if the block's
init()
method was executed (the block was initialized) but it'sbody()
method was never executed (thus no accumulation was performed), the reported result will be a distinct, reset value, depending on the aggregator type:
table{margin-left:auto;margin-right:auto}.Aggregator AVG,SUB-AVG COUNT,SUB-COUNT TOTAL,SUB-TOTAL MIN,SUB-MIN MAX,SUB-MAX Reset Value 0 0 0 ? ?
- if the block's
body()
was executed and accumulation was performed, then the accumulated value will be the aggregated result. - if the block's
body()
was executed but no accumulation was performed, then the accumulated value will be the reset value.
- once a child block which had accumulated data for this accumulator is finished, any
ACCUM
function call (after the child block has finished) will return the accumulated result from the child block, even if the parent block has accumulated data between the child block end and theACCUM
function call time frame.
For all these cases, the conversion rules will emit an Accumulator.reset()
call in the Block.init()
method for the block which directly references an ACCUMULATE
statement and for all its parent blocks which also have the loop property. This will reset the accumulator upon block entrance, leaving the other rules (related to backing up and returning the accumulated value prior the entrance of the block, inner block updates aggregated result for all parent blocks, etc) to the FWD runtime. For this, the FWD runtime will register the accumulator for scope notification (it will be notified each time a new block is entered, iterated, retried or finished) and also for undo notification (to roll back the accumulated data in this iteration, if the iteration is rolled back).
The following examples are split into three sections:
- examples from 1 to 11 show how the accumulators behave when accumulation is performed while in a nested block and what happens if a nested block (which contains an
ACCUMULATE
statement) does not execute - examples from 12 to 23 show how the accumulated result is affected when the
IF
statement stops the execution of a nested block which contains anACCUMULATE
statement. - example 24 shows how the accumulator behave in recursive calls.
Example 1:
def var i as int init 0. ... if i < 0 then do transaction: accum i (total). end. message accum total i. repeat i = 1 to 3: message accum total i. accum i (total). end. ...
Converted code:
... if (_isLessThan(i, 0)) { doBlock(TransactionType.FULL, "blockLabel0", new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } message((integer) accumTotal0.getResult()); ... repeatTo("loopLabel0", toClause0, new Block() { public void init() { accumTotal0.reset(); } public void body() { message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); } }); ...
Details:
In this example, the accumulator's value before any accumulated result will be unknown - so the first MESSAGE
statement will display the unknown value, ?
. Once it enters the REPEAT
block, although the accumulator is reset, the actual reset will not be executed until the first accumulation or the block is exit - its reset is marked as “pending”. Thus, the second MESSAGE
statement within the REPEAT
block will always display the unknown value (as this was the accumulator's value prior the entrance of this block).
The i < 0
statement was added because 4GL doesn't allow the ACCUM
function to be executed if the accumulator is not yet defined (there is no ACCUMULATE
statement in the code prior to the ACCUM
function call) - this is just a dummy code to define the accumulator.
Example 2:
def var i as int init 0. def var n as int. ... n = 0. repeat i = 1 to n: accum i (total). ... end. message accum total i. ...
Converted code:
... n.assign(0); ... repeatTo("loopLabel1", toClause1, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); ... } }); message((integer) accumTotal0.getResult()); ...
Details:
Here, the REPEAT
block is initialized, but its body is never executed - in this case, the block initialization will reset the accumulator, so the MESSAGE
statement will display its reset value.
Example 3:
def var i as int init 0. ... repeat i = 1 to 3: accum i (total). ... end. message accum total i. ...
Converted code:
... ToClause toClause2 = new ToClause(i, 1, 3); repeatTo("loopLabel2", toClause2, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); ... } }); message((integer) accumTotal0.getResult()); ...
Details:
The difference between this and example №2 is that the REPEAT
block's body and accumulation actually gets executed - is such case, the result shown by the MESSAGE
statement will be the actual aggregated result.
Example 4:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... B2: repeat j = 1 to 3: accum i (total). end. end. ...
Converted code:
... repeatTo("b1", toClause3, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... repeatTo("b2", toClause4, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } }); ...
Details:
Although block B1
has a nested ACCUMULATE
statement, this reference is not directly enclosed in block B1
; instead, is directly enclosed in block@ B2 , but, as
B1 nests
B2 , the
Accumulate.reset() call will be emitted for both blocks.
Example 5:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: accum i (total). ... message accum total i. B2: repeat j = 1 to 3: ... /* this accumulator is not used here (others may be used) */ end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause5, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); ... message((integer) accumTotal0.getResult()); repeatTo("b2", toClause6, new Block() { public void body() { ... /* this accumulator is not used here (others may be used) */ } }); message((integer) accumTotal0.getResult()); } }); ...
Details:
Both MESSAGE
statements will display the same result, as block B2
(regardless if it's executed or not) does not use this accumulator, so it will not reset it - no Accumulator.reset()
call is emitted for block B2
.
Example 6:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: accum i (total). ... message accum total i. B2: repeat j = 1 to 3: ... /* no accumulation is performed in this block */ message accum total i. ... end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause7, new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); ... message((integer) accumTotal0.getResult()); repeatTo("b2", toClause8, new Block() { public void body() { ... /* no accumulation is performed in this block */ message((integer) accumTotal0.getResult()); ... } }); message((integer) accumTotal0.getResult()); } }); ...
Details:
All MESSAGE
statements will display the same result, as block B2
(regardless if it's executed or not) does not perform any accumulation for this accumulator (only the ACCUM
function is used), so it will not reset it - no Accumulator.reset()
call is emitted for block B2
.
Example 7:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... accum i (total). B2: repeat j = 1 to 3: message accum total i. accum i (total). end. end. message accum total i. ...
Converted code:
... repeatTo("b1", toClause9, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... accumTotal0.accumulate(); ... repeatTo("b2", toClause10, new Block() { public void init() { accumTotal0.reset(); } public void body() { message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); } }); } }); message((integer) accumTotal0.getResult()); ...
Details:
Here, both B1
and B2
blocks accumulate data for the i (total)
accumulator, thus Accumulate.reset()
calls were added to the init()
method of both blocks. The first MESSAGE
statement will always display the accumulated result as it was before B2
's init()
method was executed (which is the accumulated result as in current B1
iteration), while the last MESSAGE
statement will display the accumulated result from both blocks (as any data accumulated in a child block will be accumulated in all parent blocks which reference the same accumulator).
Example 8:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... accum i (total). B2: repeat j = 1 to 3: message accum total i. accum i (total). end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause11, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... accumTotal0.accumulate(); ... repeatTo("b2", toClause12, new Block() { public void init() { accumTotal0.reset(); } public void body() { message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); } }); message((integer) accumTotal0.getResult()); } }); ...
Details:
This example is the same as №8, except the second MESSAGE
statement will always display the accumulated result from block B2
.
Example 9:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... accum i (total). message accum total i. B2: repeat j = 1 to 3: ... accum i (total). ... end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause13, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... accumTotal0.accumulate(); message((integer) accumTotal0.getResult()); ... repeatTo("b2", toClause14, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... accumTotal0.accumulate(); ... } }); message((integer) accumTotal0.getResult()); } }); ...
Details:
In this case, first MESSAGE
statement will display the accumulated result for the data in the current and all prior B1
and B2
iterations, while the second MESSAGE
statement will always display the accumulated result from block B2
.
Example 10:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... B2: repeat j = 1 to 3: ... accum i (total). ... end. message accum total i. accum i (total). message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause15, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... repeatTo("b2", toClause16, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... accumTotal0.accumulate(); ... } }); message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); message((integer) accumTotal0.getResult()); } }); ...
Details:
Both MESSAGE
statements will always display the accumulated result from block B2
since, once an inner block accumulated data for this accumulator, calling the ACCUM
function in the parent block will result in reporting the accumulated result from the child block, even if accumulation is performed in the parent block. The accumulated result reported in the parent block will change only if another inner block which uses the accumulator is executed.
Example 11:
def var i as int init 0. def var j as int init 0. def var n as int init 0. ... B1: repeat i = 1 to 3: ... B2: repeat j = 1 to n: ... accum i (total). ... end. message accum total i. accum i (total). message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause17, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... repeatTo("b2", toClause18, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... accumTotal0.accumulate(); ... } }); message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); message((integer) accumTotal0.getResult()); } }); ...
Details:
This example is similar to №10, with the single difference that the B2
's body is never executed; regardless, the accumulated result displayed by the two MESSAGE
statements is the same - they will display the accumulator's reset value, as B2
's header was executed.
IF Statement¶
The examples above proved how accumulation works when data is aggregated in single or nested blocks or when the block which is aggregating data is not executed. But, there are cases in which the nested block is not initialized at all, but the aggregated result is affected. This includes cases when the block is not executed because, after the condition of an IF
statement has been evaluated, the code path which had a nested accumulation reference did not get executed, i.e. an ACCUMULATE
statement nested in block/IF/THEN/[block]/ACCUMULATE
structure did not executed because the IF
condition evaluated to false (here, block
is a block with the loop property - this excludes the external procedure, internal procedure, function or trigger). In these cases, when the IF
's enclosed accumulation is not executed, the accumulator has its result set to either the reset or the unknown value, depending on some rules:
- when the
ACCUMULATE
statement is nested in a code path likeblock/IF/THEN/ACCUMULATE
orblock/IF/ELSE/ACCUMULATE
(there is no block with loop property between theIF
and the innerACCUMULATE
statement) and the accumulation is never performed (as after theIF
condition is evaluated, the branch which has noACCUMULATE
statement is executed), the result of this accumulator will be set to its reset value, when retrieved after theIF
(and itsTHEN/ELSE
branch) has finished. - when the
ACCUMULATE
statement is called in cases likeblock/IF/THEN/block/ACCUMULATE
block/IF/ELSE/block/ACCUMULATE
(there is a block with loop property between theIF
and the innerACCUMULATE
statement) and the accumulation is never performed (as after theIF
condition is evaluated, the branch which doesn't have the innerACCUMULATE
statement is executed), the result of this accumulator will be set to unknown, when retrieved after theIF
(and itsTHEN/ELSE
branch) has finished. More, once the runtime decided that it needs to report the unknown value for this accumulator as this rule was applied, the unknown value will be reported for the remainder part of this block, until the next iteration - regardless if accumulation will be performed or not in this or any child blocks.
For these two cases, the converted code will enclose the IF
statement between try ... finally
statements and will set some special flags within the accumulator, to let it know it will enter a block which may accumulate data and it should act accordingly if it does not accumulate the data. The converted code for such IF
statements looks like (assuming either the IF
's THEN
or ELSE
branch contain at least one reference to the accum
accumulator):
4GL code:
if (condition) then ... else ...
Converted code:
try { accum.postpone...(); if (condition) { ... } else { ... } } finally { accum.ifEnded(); }
where accum.postpone...()
will be either accum.postponeReset()
(when the accumulator's result must be set to its reset value if no accumulation was performed, once the IF
has finished - the first condition above) or accum.postponeUnknown()
(when the accumulator's result must be set to unknown if no accumulation was performed, once the IF
has finished - the second condition above). Following examples will explain in more detail how the IF
statement affects the accumulators:
Example 12:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... B2: repeat j = 1 to 3: ... if i < 0 then accum i (total). ... end. message accum total i. accum i (total). message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause0, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... repeatTo("b2", toClause1, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... try { accumTotal0.postponeReset(); if (_isLessThan(i, 0)) { accumTotal0.accumulate(); } } finally { accumTotal0.ifEnded(); } ... } }); message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); message((integer) accumTotal0.getResult()); } }); ...
Details:
Here, both MESSAGE
statements still display the same result because, even block B2
is executed, the inner ACCUMULATE
statement is not - so, the accumulation result will be the reset value (done when executing B2
's header).
Example 13:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... if i < 0 then accum i (total). ... end. message accum total i. ...
Converted code:
... repeatTo("b1", toClause2, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... try { accumTotal0.postponeReset(); if (_isLessThan(i, 0)) { accumTotal0.accumulate(); } } finally { accumTotal0.ifEnded(); } ... } }); message((integer) accumTotal0.getResult()); ...
Details:
Although this is similar to previous example, this proves that if the block is executed but no accumulation is performed, then the accumulator is still reset even if the block is a child of the external procedure (thus the MESSAGE
statements will display the reset value).
Example 14:
def var i as int init 0. def var j as int init 0. ... if i < 0 then accum i (total). ... message accum total i.
Converted code:
EXPRESSION EXECUTION ERROR: --------------------------- exprNames.addEntry(false, eKey, javaname) ^ { java.util.NoSuchElementException }
Details:
This is not a valid 4GL
code, as the ACCUMULATE
statement is scoped directly to the external procedure (which doesn't have loop property).
Example 15:
def var i as int init 0. def var j as int init 0. ... if i < 0 then do transaction: accum i (total). end. ... message accum total i.
Converted code:
... if (_isLessThan(i, 0)) { doBlock(TransactionType.FULL, "blockLabel0", new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } message((integer) accumTotal0.getResult()); ...
Details:
In this case, even if the 4GL code is valid, the conversion rules do not bracket the converted IF
statement between a try ... finally
clause, as the IF
is not also scoped to a block with loop property. The MESSAGE
statement will display the unknown value.
Example 16:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... message accum total i. accum i (total). ... if i < 0 then accum i (total). message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause3, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); ... try { accumTotal0.postponeReset(); if (_isLessThan(i, 0)) { accumTotal0.accumulate(); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); } }); ...
Details:
As the first “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try ... finally
block and a postponeReset()
call is emitted. Also, this case proves that the accumulated result reported by the last MESSAGE
statement will be not be the reset value, as accumulation was already performed.
Example 17:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... if i < 0 then accum i (total). message accum total i. accum i (total). message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause4, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... try { accumTotal0.postponeReset(); if (_isLessThan(i, 0)) { accumTotal0.accumulate(); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); message((integer) accumTotal0.getResult()); } }); ...
Details:
As the first “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try ... finally
block and a postponeReset()
call is emitted. Also, this case proves that even if no accumulation is performed, once the IF
statement has passed, the ACCUM
function will return the reset value until accumulation is performed.
Example 18:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... message accum total i. accum i (total). if i > 0 then do: message accum total i. accum i (total). end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause5, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); try { accumTotal0.postponeReset(); if (_isGreaterThan(i, 0)) { message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); } }); ...
Details:
As the first “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try ... finally
block and a postponeReset()
call is emitted. Also, this case proves that the IF
statement doesn't always reset the accumulator, as the second MESSAGE
statement will display the current accumulated result.
Example 19:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... message accum total i. accum i (total). ... if i < 0 then B2: do transaction: accum i (total). end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause6, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); ... try { accumTotal0.postponeUnknown(); if (_isLessThan(i, 0)) { doBlock(TransactionType.FULL, "b2", new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); } }); ...
Details:
As the second “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try … finally
block and a postponeUnknown()
call is emitted. Also, this case proves that even if no accumulation is performed by the IF
's inner block, once the IF
statement has passed, the ACCUM
function will not return the unknown value, as accumulation was already performed.
Example 20:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... if i < 0 then B2: do transaction: accum i (total). end. message accum total i. accum i (total). message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause7, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... try { accumTotal0.postponeUnknown(); if (_isLessThan(i, 0)) { doBlock(TransactionType.FULL, "b2", new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); message((integer) accumTotal0.getResult()); } }); ...
Details:
As the second “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try ... finally
block and a postponeUnknown()
call is emitted. This example also shows that the first MESSAGE
statement will never display the accumulated value as before the B1
block has initialized, but instead will always display the unknown value.
Example 21:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... message accum total i. accum i (total). ... if i > 0 then B2: do transaction: accum i (total). end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause8, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); ... try { accumTotal0.postponeUnknown(); if (_isGreaterThan(i, 0)) { doBlock(TransactionType.FULL, "b2", new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); } }); ...
Details:
As the second “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try ... finally
block and a postponeUnknown()
call is emitted. This example also shows that the last MESSAGE
statement will not display the unknown value, but instead will display the accumulated value from block B2
(as the IF
condition evaluated to true
and block B2
was executed).
Example 22:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... if i > 0 then B2: do transaction: accum i (total). end. message accum total i. accum i (total). message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause9, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... try { accumTotal0.postponeUnknown(); if (_isGreaterThan(i, 0)) { doBlock(TransactionType.FULL, "b2", new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); message((integer) accumTotal0.getResult()); } }); ...
Details:
As the second “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try ... finally
block and a postponeUnknown()
call is emitted. This example also shows that the first MESSAGE
statement will not display the unknown value, but instead will display the accumulated value from block B2
(as the IF
condition evaluated to true
and block B2
was executed).
Example 23:
def var i as int init 0. def var j as int init 0. ... B1: repeat i = 1 to 3: ... message accum total i. accum i (total). if i > 0 then do: message accum total i. B2: do transaction: accum i (total). end. end. message accum total i. end. ...
Converted code:
... repeatTo("b1", toClause10, new Block() { public void init() { accumTotal0.reset(); } public void body() { ... message((integer) accumTotal0.getResult()); accumTotal0.accumulate(); try { accumTotal0.postponeUnknown(); if (_isGreaterThan(i, 0)) { message((integer) accumTotal0.getResult()); doBlock(TransactionType.FULL, "b2", new Block() { public void init() { accumTotal0.reset(); } public void body() { accumTotal0.accumulate(); } }); } } finally { accumTotal0.ifEnded(); } message((integer) accumTotal0.getResult()); } }); ...
Details:
As the second “postpone on IF
” condition is encountered, the IF
statement is bracketed with a try ... finally
block and a postponeUnknown()
call is emitted. This example also shows that the second MESSAGE
statement will display the current accumulated result (thus the IF doesn't always set the accumulator to unknown).
Recursive Calls¶
One behavior which was not yet presented is how accumulators behave during recursions; testing showed that the accumulator's value is not inherited from recursive calls (be it procedure, function or trigger). Instead, the accumulator is isolated in each recursive call and, once the recursive call has ended, any accumulated data in the current call is not propagated down the stack, as shown in the following example:
Example 24:
def var i as int init 0. ... procedure proc1. def input parameter p as int. do transaction: accum i (count). if p <> 0 then run proc1(p - 1). end. put screen row p + 1 string(p) + " " + string(accum count i). end. run proc1(10). ...
Converted code:
integer i = new integer(0); CountAccumulator accumCount0 = new CountAccumulator(); ... proc1(new integer(10)); ... public void proc1(final integer _p) { internalProcedure(new Block() { integer p = new integer(_p); public void init() { TransactionManager.registerUndo(p); } public void body() { doBlock(TransactionType.FULL, "blockLabel0", new Block() { public void init() { accumCount0.reset(); } public void body() { accumCount0.accumulate(); if (_isNotEqual(p, 0)) { proc1(minus(p, 1)); } } }); putScreen(concat(valueOf(p), " ", valueOf(accumCount0.getResult())), plus(p, 1), 1); } }); }
Details:
In this case, the ACCUM
function will always return the same value and there is no special construct emitted by the conversion rules. Also, this proves that accumulators which are used inside a procedure/function are alive and can be used only as long as the procedure is executed; this means that the accumulator is not scoped to the root external procedure, but is scoped to the internal procedure or function.
© 2004-2017 Golden Code Development Corporation. ALL RIGHTS RESERVED.