Author(s) |
Greg
Shah Sergey Yevtushenko Eric Faulhaber Nick Saxon Constantin Asofiei Stanislav Lomany |
Date |
November 19, 2009 |
Access
Control |
CONFIDENTIAL |
Progress
Type |
Parser Token Type |
Java
Type(s) |
Initial Value |
Notes |
||||||||||||||||||||||||||||||||||||||||
character |
VAR_CHAR FIELD_CHAR |
com.goldencode.p2j.util.character | "" |
Progress uses a single data
type to handle both single character values and strings. For situations such as event processing (keystrokes), the Java char may need to be used in preference. In addition, the data storage itself could be handled by java.lang.String. See wrapper considerations below. |
||||||||||||||||||||||||||||||||||||||||
integer |
VAR_INT FIELD_INT |
com.goldencode.p2j.util.integer (or int in cases where it is determined that unknown value can never be assigned or compared to this variable) |
0 |
The Progress integer is a
32-bit signed integral value whose maximum value is 2147483647 and
minimum value is -2147483648. Overflows wrap around to the
minimum value and underflows wrap around to the maximum value.
This is an exact match to the Java primitive int, except for the
case where the unknown value is assigned or tested on this variable. The logical operators have a predictable response to having the unknown value as one or both operands. This processing is identical for all data types. See the table below. See wrapper considerations below. |
||||||||||||||||||||||||||||||||||||||||
decimal |
VAR_DEC FIELD_DEC |
com.goldencode.p2j.util.decimal (this class will be implemented using double and NaN to represent the unknown value; BigDecimal will be used as a backing data type in the case where it is required; a default number of significant digits right of the decimal point will be 10 but can optionally be set to a smaller value based on the "decimals" keyword override) |
0 |
The Progress decimal is a
signed floating point value whose maximum value is 50 significant
digits in size (the mantissa or significand is a maximum of
50). Up to 10 of those digits can be to the right of the
decimal point (the exponent is a maximum of 10). Thus the
largest value is 99999999999999999999999999999999999999999999999999
and the minimum value is
-99999999999999999999999999999999999999999999999999. If all of
the 10 possible digits of precision are used to the right of the
decimal point, then only 40 digits can be used on the left.
Overflows and underflows both cause the same runtime error "Decimal
number is too large (536)". The logical operators have a predictable response to having the unknown value as one or both operands. This processing is identical for all data types. See the table below. For more details, please see Decimal Support. |
||||||||||||||||||||||||||||||||||||||||
logical |
VAR_LOGICAL FIELD_LOGICAL |
com.goldencode.p2j.util.logical (or boolean in cases where it is determined that unknown value can never be assigned or compared to this variable) |
false |
The Progress logical has
multiple "modes" yes/no, true/false and a user defined
value1/value2. However, all of these are the exact equivalent
of the Java boolean type which has only 2 states, true or false,
except for the case where the unknown value is assigned or tested on
this variable. The logical operators respond differently to having the unknown value as an operand:
Note that any logical expression that evaluates to the unknown value is always considered false (e.g. for purposes of control flow in an IF statement). See wrapper considerations below. |
||||||||||||||||||||||||||||||||||||||||
date |
VAR_DATE FIELD_DATE |
com.goldencode.p2j.util.date |
null |
A custom wrapper class will be
written to provide the proper semantics of date processing in
Progress. Where needed, an instance of java.util.Calendar
(obtained through Calendar.getInstance() will be used for the
calendar processing however all operators will operate on the day
number directly for speed and simplicity. The custom wrapper
class will provide Progress semantics and will hide the complexities
of dealing with the J2SE Calendar interfaces. The logical operators have a predictable response to having the unknown value as one or both operands. This processing is identical for all data types. See the table below. Note that there are special considerations for handling dates within a database where clause. |
||||||||||||||||||||||||||||||||||||||||
rowid |
VAR_ROWID FIELD_ROWID |
com.goldencode.p2j.util.integer | n/a |
The persistence runtime classes transparently map the ID field for the associated database row to an integer wrapper. | ||||||||||||||||||||||||||||||||||||||||
recid |
VAR_RECID FIELD_RECID |
com.goldencode.p2j.util.integer | n/a | The persistence runtime classes transparently map the ID field for the associated database row to an integer wrapper. | ||||||||||||||||||||||||||||||||||||||||
raw |
VAR_RAW FIELD_RAW |
com.goldencode.p2j.util.raw | 0 size array |
In the Java-based runtime, this is not backed by a pointer to memory. Instead, it is backed by a byte array. | ||||||||||||||||||||||||||||||||||||||||
memptr |
VAR_MEMPTR |
com.goldencode.p2j.util.memptr | uninitialized array |
In the Java-based runtime,
this is not backed by a pointer to memory. Instead, it is
backed by a byte array. |
||||||||||||||||||||||||||||||||||||||||
handle |
VAR_HANDLE FIELD_HANDLE |
com.goldencode.p2j.util.handle | no contained object |
Note that the parser converts
all widget handles into handle types since in Progress these are
equivalent. This class is really just a container that holds a backing object. It provides unknown value processing, comparison/assignment operator support and the valid-handle() builtin function implementation. The specific backing object will need to be determined on a case by case basis. The handle gets dereferenced when used as a referent for methods and attributes. |
||||||||||||||||||||||||||||||||||||||||
com-handle | n/a | n/a | n/a | n/a |
Operator |
Left
Operand NaN |
Right Operand NaN | Both Operands NaN |
== |
false |
false | false |
!= |
true |
true |
true |
< |
false | false | false |
<= |
false | false | false |
> |
false | false | false |
>= |
false | false | false |
Progress
Literal |
Parser Token Type |
Java
Literal/Object |
Notes |
true or yes |
BOOL_TRUE |
true |
This is an exact match. |
false or no |
BOOL_FALSE |
false |
This is an exact match. |
? (unknown value) |
UNKNOWN_VAL |
If used as the right side of
an assignment, this is covnerted to a setUnknown() method call on
the BaseDataType object that is the lvalue. If used as 1 operand of a EQUALS, the other operand will be passed to CompareOps.isUnknown(). If used as 1 operand of a NOT_EQ, the other operand will be passed to CompareOps.notUnknown(). In Progress it isn't possible to compare two instances of the unknown value literal. Other usage converts to an instance of com.goldencode.p2j.util.unknown. |
At first glance, null is an exact
match. However, in order to eliminate the need to place null checks throughout
code and to refactor much of the code, wrappers are being used which
internalize the unknown value. |
integer literal |
NUM_LITERAL |
Java integer literal |
This is an exact match since Java integer literals default to int. |
decimal literal |
DEC_LITERAL |
Java floating point literal | As long as the value is within
the mantissa limits listed above (16 decimal digits), this is an
exact match since Java floating point literals default to
doubles. Otherwise, a BigDecimal must be used. |
date literal |
DATE_LITERAL |
com.goldencode.p2j.util.date | There is no date literal in
Java. An instance of the com.goldencode.p2j.util.date will be
created instead. |
string literal | STRING |
Java string literal |
Any string options will be
removed and any enclosing single or double quotes will be
removed. Escaped characters (using the tilde character) will
be converted to native representations and any characters that need
to be escaped will be so escaped:
|
Progress
Identifier |
Java Identifier | Strategy |
Conflict Resolution |
directory name |
package name |
Package naming will be
somewhat direct (following the Java standard): com.<customer>.<application>.<directory> |
TBD |
file name |
class name |
A set of hints will assist in
class naming. Where common sequences are encountered such
characters might be expanded based on hints. For example, so
and po might be expanded to ServiceOrder and PurchaseOrder
respectively. A file ending in -r or -x might be have
ReportBody or Export appended respectively. |
TBD |
procedure name |
method name |
TBD |
TBD |
function name |
method name |
TBD |
TBD |
variable name (normal variable
that is assigned or passed as a parameter) |
variable name |
Hypens will be removed and the
words will be camelcased. This will result in lowercasing some
letters (including the first char of the identifier) and uppercasing
the first character of subsequent words. Words will be assumed
to be separated at the hyphens. |
TBD |
variable name (never assigned
or passed as a parameter) |
constant name |
All letters will be uppercased
and words will be separated by an underscore (_). |
TBD |
schema name |
TBD |
TBD | TBD |
Language
Statement/Construct |
Conditions |
Options
Are Controllable |
define variable |
always |
yes |
define parameter |
when TABLE-HANDLE, AS or LIKE
keywords are encountered |
yes |
format phrase |
when following an unrecognized
symbol and the AS or LIKE keywords are encountered |
yes |
function parameter |
when TABLE-HANDLE or AS keywords are encountered | no |
message |
when SET or UPDATE clause
occurs with a child of SYMBOL and a child of the SYMBOL which is the
AS keyword (as opposed to a child which is an lvalue that has an
optional AS keyword which is bogus) |
no |
Progress
Function |
Java
Equivalent |
Category |
Token Type |
Nullable |
Optional Parameters |
DBCS Issues |
Supported |
Notes |
_CBIT |
character.testBitAt() |
bit manipulation |
FUNC_LOGICAL |
yes |
no |
yes |
yes |
logical _cbit(string,
integer) The first parameter is a string with 0 or more characters. The second parameter is the bit position to test. The input string is treated as a multibyte bitfield. The first character of the string corresponds to bit positions 0-7 (if it is non-DBCS and presumably in a DBCS character the positions would be 0-15). In a non-DBCS charset, the second char would correspond to bits 8-15. The return is always false if the empty string is input. For any string of > 0 length, the return is true if the bit in the specified position is 1 and false if it is 0. Sample Code: def var i as int. def var j as int. def var b as char. def var c as char. do i = -1 to 257. b = "". c = chr(i). do j = 7 to 0 by -1. if _cbit(c, j) then b = b + "1". else b = b + "0". end. message i " = " b. end. |
ABSOLUTE |
MathOps.abs(integer) or MathOps.abs(decimal) |
math |
FUNC_POLY | yes |
no |
no |
yes |
returns INT or DEC depending
on the type of the input |
ACCUM |
Accumulator (abstract base
class); concrete implementations:
|
database |
FUNC_POLY | no |
yes |
Resides in the com.goldencode.p2j.util
package.Each concrete subclass defines the following methods for results retrieval; each class' implementations of these methods may return different data types, so these method signatures are named by convention only, and are not enforced by a common interface:
|
||
ALIAS |
ConnectionManager.alias() | database | FUNC_CHAR |
yes |
yes |
|||
AMBIGUOUS | RecordBuffer.wasAmbiguous() | database |
FUNC_LOGICAL | no |
yes |
|||
ASC |
character.asc() |
type conversion |
FUNC_INT | yes |
yes |
yes |
yes |
No source or target codepage
support at this time. Result in a DBCS environment may vary
from the Progress implementation. |
AVAILABLE |
RecordBuffer.available() |
database |
FUNC_LOGICAL | no |
yes |
|||
CAN-DO |
character.matchList() |
security |
FUNC_LOGICAL | yes |
yes |
|||
CAN-FIND | RandomAccessQuery.hasFirst() RandomAccessQuery.hasNext() RandomAccessQuery.hasPrevious() RandomAccessQuery.hasLast() RandomAccessQuery.hasAny() |
database |
FUNC_LOGICAL | no |
yes |
|||
CAN-QUERY |
UI |
FUNC_LOGICAL | yes |
no |
||||
CAN-SET |
UI |
FUNC_LOGICAL | yes |
no |
||||
CAPS |
character.toUpperCase() |
string |
FUNC_CHAR |
yes |
no |
partial | yes |
It can contain DBCS but only the SBCS chars are uppercased. |
CHR |
character.chr() |
type conversion |
FUNC_CHAR |
yes |
yes |
yes |
yes |
No source or target codepage support at this time. Result in a DBCS environment may vary from the Progress implementation. |
CODEPAGE-CONVERT |
type conversion |
FUNC_CHAR |
yes |
yes |
yes |
no |
||
COMPARE |
character.compare() |
string |
FUNC_LOGICAL |
yes |
yes |
yes |
yes |
No collation table support at this time. Result in a DBCS environment may vary from the Progress implementation. |
CONNECTED | ConnectionManager.connected() | database | FUNC_LOGICAL | no | no | yes | ||
COUNT-OF |
P2JQuery.size() |
database |
FUNC_INT |
no |
no |
yes |
Implemented by concrete query
classes. |
|
CURRENT-CHANGED |
database |
FUNC_LOGICAL |
no |
no |
no |
|||
CURRENT-LANGUAGE |
EnvironmentOps.getCurrentLanguage() | I18N |
VAR_CHAR |
no |
no |
yes |
||
CURRENT-RESULT-ROW |
AbstractQuery.currentRow() |
database |
FUNC_INT |
yes |
no |
yes |
Implemented by AbstractQuery and overridden where necessary by concrete query implementations. | |
CURRENT-VALUE | database |
FUNC_INT |
yes |
yes |
no |
|||
DATASERVERS | EnvironmentOps.getDataServerList() | database |
VAR_CHAR |
no |
no |
early |
||
DATE |
see constructors for date
class |
date |
FUNC_DATE |
yes |
no |
no |
yes |
|
DAY | date.day() which calls
instance method date.getDayNum() |
date |
FUNC_INT |
yes |
no |
no |
yes |
|
DBCODEPAGE | I18N |
FUNC_CHAR |
yes |
yes |
no |
|||
DBCOLLATION | I18N |
FUNC_CHAR |
yes |
yes |
no |
|||
DBNAME |
EnvironmentOps.getCurrentDatabaseName() | database |
VAR_CHAR |
yes |
no |
yes |
||
DBPARAM | database |
FUNC_CHAR | yes |
yes |
no |
|||
DBRESTRICTIONS | ConnectionManager.dbRestrictions() | database |
FUNC_CHAR | yes |
yes |
yes |
||
DBTASKID | database |
FUNC_INT |
yes |
yes |
no |
|||
DBTYPE | ConnectionManager.dbType() | database |
FUNC_CHAR |
yes |
yes |
yes |
||
DBVERSION | database |
FUNC_CHAR |
yes |
yes |
no |
|||
DECIMAL |
see constructors for decimal
class |
type conversion |
FUNC_DEC |
yes |
no |
no |
yes |
|
DYNAMIC-FUNCTION | function execution |
FUNC_POLY | yes |
yes |
no |
|||
ENCODE |
SecurityOps.encode() |
security |
FUNC_CHAR |
yes |
no |
early |
The interface is completely
supported and functional, however the calculated result is NOT
compatible with the 4GL version. |
|
ENTERED | GenericWidget.isEntered() |
UI |
FUNC_LOGICAL |
no |
no |
yes |
||
ENTRY |
character.entry() |
string |
FUNC_CHAR |
yes |
yes |
yes |
yes |
Delimiter processing is *always* case-sensitive at this time. |
ETIME | date.elapsed() |
time |
FUNC_INT |
no |
yes |
yes |
||
EXP |
MathOps.pow() |
math |
FUNC_DEC |
yes |
no |
yes |
||
EXTENT | 0 if the variable or field is
scalar OR array_var.length |
arrays |
FUNC_INT |
no |
no |
yes |
This is a direct conversion
without any backing method or class. |
|
FILL |
character.fill() | string |
FUNC_CHAR |
yes |
no |
yes |
||
FIRST | PresortQuery.isFirst() | loops/transactions | FUNC_LOGICAL |
no |
no |
yes |
||
FIRST-OF | PresortQuery.isFirstOfGroup() | loops/transactions | FUNC_LOGICAL | no |
no |
yes |
||
FRAME-COL | CommonFrame.frameCol() |
UI |
FUNC_DEC |
no |
yes |
yes |
||
FRAME-DB |
UI |
VAR_CHAR |
no |
no |
no |
|||
FRAME-DOWN | CommonFrame.frameDown() | UI |
FUNC_INT |
no |
yes |
yes |
||
FRAME-FIELD |
LogicalTerminal.getFrameField() | UI |
VAR_CHAR | no |
no |
yes |
||
FRAME-FILE |
UI |
VAR_CHAR | no |
no |
no |
|||
FRAME-INDEX | LogicalTerminal.getFrameIndex() | UI |
VAR_INT |
no |
no |
yes |
||
FRAME-LINE | CommonFrame.frameLine() | UI |
FUNC_INT | no |
yes |
yes |
||
FRAME-NAME |
UI |
VAR_CHAR |
no |
no |
||||
FRAME-ROW |
CommonFrame.frameRow() | UI |
FUNC_DEC |
no |
yes |
yes |
||
FRAME-VALUE | LogicalTerminal.getFrameValue() LogicalTerminal.setFrameValue() |
UI |
VAR_CHAR | no |
no |
yes |
||
GATEWAYS |
EnvironmentOps.getDataServerList() |
database |
FUNC_CHAR |
no |
no |
early |
||
GET-BITS |
bit manipulation | FUNC_INT |
yes |
no |
no |
|||
GET-BYTE |
BinaryData.getByte() |
raw/memory access |
FUNC_INT |
yes |
no |
yes |
||
GET-BYTE-ORDER |
raw/memory access | FUNC_INT |
no |
no |
no |
|||
GET-BYTES |
raw/memory access | FUNC_POLY |
yes |
no |
no |
Returns RAW or MEMPTR. |
||
GET-CODEPAGES |
I18N |
VAR_CHAR |
no |
no |
no |
|||
GET-COLLATIONS |
I18N |
FUNC_CHAR |
yes |
no |
no |
|||
GET-DOUBLE |
raw/memory access | FUNC_DEC |
yes |
no |
no |
|||
GET-FLOAT |
raw/memory access | FUNC_DEC |
yes |
no |
no |
|||
GET-LONG |
raw/memory access | FUNC_INT |
yes |
no |
no |
|||
GET-POINTER-VALUE |
raw/memory access | FUNC_INT | no |
no |
no |
|||
GET-SHORT |
raw/memory access | FUNC_INT |
yes |
no |
no |
|||
GET-SIZE |
memptr.length() | raw/memory access | FUNC_INT |
no |
no |
yes |
||
GET-STRING |
BinaryData.getString() | raw/memory access | FUNC_CHAR |
yes |
yes |
yes |
||
GET-UNSIGNED-SHORT |
raw/memory access | FUNC_INT |
yes |
no |
no |
|||
GO-PENDING | LogicalTerminal.isGoPending() | UI |
VAR_LOGICAL |
no |
no |
yes |
||
IF THEN ELSE |
condition ? expr1 : expr2 |
ternary |
FUNC_POLY |
yes |
no |
yes |
Returns same type as the THEN
and ELSE expressions evaluate to. |
|
INDEX |
character.indexOf() |
string |
FUNC_INT |
yes |
yes |
yes |
||
INPUT |
Converts to a field-level
getter in a custom frame interface OR to the
CommonFrame.getScreenValue(widget) method if this is a character
type override case (see notes). |
UI |
FUNC_POLY |
no |
yes |
yes |
The INPUT built-in function
normally returns the same type as the single parameter (which must
be an lvalue which is found in a frame that is in scope).
However, this function can be used in an expression that requires a
character data type even in the case where the parameter is NOT of
the character type! This is like an override option and it
must be detected from the SURROUNDING expression (above the
FUNC_POLY node). For example,an direct assignment of the INPUT
function's return type to a character lvalue is honored. This
case works differently than an assignment to a non-character type in
the case that the value in the screen buffer is uninitialized (it
will return the empty string like screen-value instead of returning
the default value of the operand type). This override support
is implemented except for one case: the
usage of INPUT as a parameter to another built-in function (or
presumably a method) which expects a character parameter should also
cause this override. At this time there is no support for this case.
The resulting type (if not already character) will cause the wrong
type to be emitted. Note that this only is an issue for
built-in functions (and methods). If the parent is a user defined FUNC_* then the
result is the same as the operand, no override occurs. But a
built-in function that takes a character type will get the result as
a character instead of the operand type. See the SCREEN-VALUE attribute which is a related issue. |
|
INTEGER |
see constructors for integer
class |
type conversion |
FUNC_INT | yes |
no |
yes |
||
IS-ATTR-SPACE |
UI |
FUNC_LOGICAL | yes |
no |
no |
|||
IS-LEAD-BYTE |
I18N |
FUNC_LOGICAL | yes |
no |
no |
|||
KBLABEL |
LogicalTerminal.kbLabel() | UI |
FUNC_CHAR |
yes |
no |
yes |
||
KEYCODE | LogicalTerminal.keyCode() | UI |
FUNC_INT |
yes |
no |
yes |
||
KEYFUNCTION | LogicalTerminal.keyFunction() | UI |
FUNC_CHAR |
yes |
no |
yes |
||
KEYLABEL | LogicalTerminal.keyLabel() |
UI |
FUNC_CHAR |
yes |
no |
yes |
||
KEYWORD |
Progress |
FUNC_CHAR | yes |
no |
no |
|||
KEYWORD-ALL | Progress | FUNC_CHAR | yes |
no |
no |
|||
LAST |
PresortQuery.isLast() | loops/transactions | FUNC_LOGICAL | no |
no |
yes |
||
LAST-OF |
PresortQuery.isLastOfGroup() | loops/transactions | FUNC_LOGICAL | no |
no |
yes |
||
LASTKEY |
KeyReader.lastKey() |
UI |
VAR_INT |
no |
no |
yes |
||
LC |
character.toLowerCase() |
string |
FUNC_CHAR |
yes |
no |
partial |
yes |
It can contain DBCS but only the SBCS chars are lowercased. |
LDBNAME |
ConnectionManager.ldbName() | database |
FUNC_CHAR | yes |
yes |
yes |
||
LEFT-TRIM |
character.leftTrim() | string |
FUNC_CHAR |
yes |
yes |
yes |
yes |
|
LENGTH |
character.length() character.byteLength() BinaryData.length() |
raw/memory access |
FUNC_INT |
yes |
yes |
yes |
No support for "column" based
length at this time. |
|
LIBRARY |
R-code library | FUNC_CHAR |
yes |
no |
no |
|||
LINE-COUNTER |
Stream.getNextLineNum() |
I/O |
FUNC_INT |
no |
yes |
yes |
||
LIST-EVENTS |
UI |
FUNC_CHAR | yes |
yes |
no |
|||
LIST-QUERY-ATTRS |
UI |
FUNC_CHAR | yes |
no |
no |
|||
LIST-SET-ATTRS |
UI |
FUNC_CHAR | yes |
no |
no |
|||
LIST-WIDGETS |
UI |
FUNC_CHAR | yes |
yes |
no |
|||
LOCKED |
RecordBuffer.wasLocked() | database |
FUNC_LOGICAL |
no |
no |
yes |
||
LOG |
MathOps.log() |
math |
FUNC_DEC |
yes |
yes |
yes |
||
LOOKUP | character.lookup() |
string |
FUNC_INT |
yes |
yes |
yes |
yes |
Delimiter processing is
*always* case-sensitive at this time. |
MAXIMUM |
character.maximum() date.maximum() logical.maximum() integer.maximum() decimal.maximum() |
math |
FUNC_POLY |
yes |
yes |
yes |
Warning: variable args!
Numeric operands are widened (int to double) if there are a
mixture. All arguments (other than numerics) can only be
compared against others of the same type. |
|
MEMBER |
R-code library |
FUNC_CHAR |
yes |
no |
||||
MESSAGE-LINES |
LogicalTerminal.getMessageLines() | UI |
VAR_INT |
no |
no |
yes |
||
MINIMUM |
character.minimum() date.minimum() logical.minimum() integer.minimum() decimal.minimum() |
math |
FUNC_POLY | yes |
yes |
yes |
Warning: variable args! Numeric operands are widened (int to double) if there are a mixture. All arguments (other than numerics) can only be compared against others of the same type. | |
MONTH | date.month() which calls
instance method date.getMonthNum() |
date |
FUNC_INT |
yes |
no |
yes |
||
NEW |
RecordBuffer.isNew() | database |
FUNC_LOGICAL |
no |
no |
yes |
||
NEXT-VALUE | database |
FUNC_INT |
yes |
yes |
no |
|||
NOT ENTERED |
GenericWidget.isNotEntered() |
UI |
FUNC_LOGICAL | no |
yes |
yes |
||
NUM-ALIASES |
ConnectionManager.numAliases() |
database | VAR_INT |
no |
no |
yes |
||
NUM-DBS |
ConnectionManager.numDBs() | database | VAR_INT |
no |
no |
yes |
||
NUM-ENTRIES |
character.numEntries() character.numEntriesOf() |
string |
FUNC_INT |
yes |
yes |
yes |
yes |
Delimiter processing is *always* case-sensitive at this time. |
NUM-RESULTS | P2JQuery.size() |
database | FUNC_INT |
yes |
no |
yes |
Implemented by concrete query classes. | |
OPSYS |
EnvironmentOps.getOperatingSystem() | OS environment | VAR_CHAR |
no |
no |
yes |
||
OS-DRIVES |
FileSystemOps.getRootList() |
OS environment | VAR_CHAR |
no |
no |
yes |
||
OS-ERROR |
FileSystemOps.getLastError() | OS environment | VAR_INT |
no |
no |
yes |
||
OS-GETENV | FileSystemOps.getProperty() | OS environment |
FUNC_CHAR |
yes |
no |
yes |
||
PAGE-NUMBER | Stream.getPageNum() |
I/O |
FUNC_INT | no |
yes |
yes |
||
PAGE-SIZE | Stream.getPageSize() |
I/O |
FUNC_INT |
no |
yes |
yes |
||
PDBNAME | ConnectionManager.pdbName() | database |
FUNC_CHAR |
yes |
yes |
yes |
||
PROC-HANDLE |
Progress environment | VAR_INT |
no |
no |
no |
Is this actually polymorphic? |
||
PROC-STATUS |
Progress environment | VAR_INT |
no |
no |
no |
|||
PROGRAM-NAME | EnvironmentOps.getSourceName()
+ a constant (public static final String progressSourceName) in each
generated class. |
Progress environment | FUNC_CHAR |
yes |
no |
yes |
Warning: there are 2 flaws in
this implementation.
|
|
PROGRESS |
EnvironmentOps.getRuntimeType() | Progress environment | VAR_CHAR |
no |
no |
yes |
||
PROMSGS | EnvironmentOps.getMessageSource() | Progress environment | VAR_CHAR | no |
no |
yes |
||
PROPATH | EnvironmentOps.getSourcePath() | Progress environment | VAR_CHAR | no |
no |
yes |
||
PROVERSION | EnvironmentOps.getVersion() |
Progress environment | VAR_CHAR | no |
no |
yes |
||
QUERY-OFF-END |
P2JQuery.isOffEnd() | database |
FUNC_LOGICAL |
yes |
no |
yes |
Implemented by concrete
query classes. |
|
R-INDEX |
character.lastIndexOf() |
string |
FUNC_INT |
yes |
yes |
yes |
||
RANDOM |
MathOps.random() |
math |
FUNC_INT |
yes |
no |
yes |
Warning: it is possible to
force Progress to always generate the same sequence of random
numbers in every session! (This sounds like an awful idea but
perhaps it is being used.) |
|
RAW | database |
FUNC_RAW |
no |
yes |
no |
|||
RECID |
RecordBuffer.recordID | database | FUNC_RECID |
yes |
no |
yes |
The type is mapped to integer. |
|
RECORD-LENGTH |
database |
FUNC_INT |
no |
no |
no |
|||
REPLACE |
character.replaceAll() |
string |
FUNC_CHAR |
yes |
no |
yes |
||
RETRY |
TransactionManager.isRetry() |
loops/transactions |
VAR_LOGICAL |
no |
no |
yes |
||
RETURN-VALUE |
ControlFlowOps.getReturnValue() |
procedure execution |
VAR_CHAR |
no |
no |
yes |
||
RGB-VALUE |
UI |
FUNC_INT |
yes |
no |
no |
|||
RIGHT-TRIM |
character.rightTrim() |
string |
FUNC_CHAR |
yes |
yes |
yes |
||
ROUND |
Math.round() |
math |
FUNC_DEC |
yes |
no |
yes |
||
ROWID |
RecordBuffer.recordID | database | FUNC_ROWID |
yes |
no |
yes |
The type is mapped to integer. | |
SCREEN-LINES |
LogicalTerminal.getScreenLines() |
UI |
VAR_INT |
no |
no |
yes |
||
SDBNAME |
ConnectionManager.sdbName() |
database | FUNC_CHAR |
yes |
yes |
yes |
||
SEARCH |
FileSystemOps.searchPath() |
filesystem |
FUNC_CHAR | yes |
no |
yes |
||
SEEK |
Stream.getPosition() |
filesystem |
FUNC_INT |
yes |
yes |
yes |
||
SETUSERID |
security |
FUNC_LOGICAL |
yes |
yes |
no |
|||
SQRT |
MathOps.sqrt() |
math |
FUNC_DEC |
yes |
no |
yes |
||
STRING |
character.valueOf() which uses
a wrapper-specific toString() |
type conversion |
FUNC_CHAR |
yes |
yes |
yes |
||
SUBSTITUTE |
character.substitute() |
string |
FUNC_CHAR | yes |
yes |
yes |
yes |
The variable length argument
list is handled by an argument of type Object[]. |
SUBSTRING |
character.substring() |
string |
FUNC_CHAR |
yes |
yes |
yes |
yes |
No support for "raw", "fixed" or "column" based substrings at this time. |
SUPER | function execution |
FUNC_POLY |
yes |
yes |
no |
|||
TERMINAL |
LogicalTerminal.getTerminal() |
UI |
VAR_CHAR |
no |
no |
yes |
||
TIME |
date.secondsSinceMidnight() |
time |
VAR_INT |
no |
no |
yes |
||
TODAY |
new date() |
date |
VAR_DATE |
no |
no |
yes |
||
TO-ROWID |
database | FUNC_ROWID |
yes |
no |
no |
|||
TRANSACTION |
TransactionManager.isTransactionActive() | loops/transactions |
VAR_LOGICAL |
no |
no |
yes |
||
TRIM |
character.trim() |
string |
FUNC_CHAR |
yes |
yes |
yes |
||
TRUNCATE |
MathOps.truncate() | math |
FUNC_DEC |
yes |
no |
yes |
||
USERID |
SecurityOps.getUserId() | security |
FUNC_CHAR |
no |
yes |
yes |
||
VALID-EVENT |
UI |
FUNC_LOGICAL |
yes |
yes |
no |
|||
VALID-HANDLE |
handle.isValid() |
data types |
FUNC_LOGICAL | no |
no |
yes |
||
WEEKDAY |
date.weekday() which calls
instance method date.getWeekDayNum() |
date |
FUNC_INT |
yes |
no |
yes |
||
WIDGET-HANDLE |
handle.fromString() |
UI |
FUNC_HANDLE |
yes |
no |
yes |
||
YEAR | date.year() which calls instance method date.getYearNum() | date |
FUNC_INT |
yes |
no |
yes |
Progress
Feature |
Java
Replacement |
Type |
Method
or Attribute |
Supported |
Notes |
DCOLOR |
GenericWidget.getDcolor() GenericWidget.setDcolor() |
widget |
ATTRIBUTE | Yes |
|
DESELECT-ROWS |
GenericWidget.deselectRows() |
browse |
METHOD | Yes |
|
ENTRY |
GenericWidget.entry() | combo-box, selection list |
METHOD |
Yes |
|
ERROR |
ErrorManager.isError() |
ERROR-STATUS system handle |
ATTRIBUTE | Yes |
|
FETCH-SELECTED-ROW |
GenericWidget.fetchSelectedRow() | browse |
METHOD | Yes |
|
FILE-NAME | FileSystemOps.initFileInfo()
to set FileSystemOps.fileInfoGetName() to get |
FILE-INFO system handle | ATTRIBUTE | Yes | |
FILE-SIZE | FileSystemOps.fileInfoGetSize() to get | FILE-INFO system handle | ATTRIBUTE | Yes | |
GET-NUMBER |
Manager.getErrorNum() | ERROR-STATUS system handle | METHOD |
Yes |
|
GET-MESSAGE |
ErrorManager.getErrorText() | ERROR-STATUS system handle | METHOD | Yes |
|
HANDLE |
GenericWidget.getHandle() |
widget |
ATTRIBUTE | Yes | Should this really be
converted into a widget object reference? |
IS-SELECTED |
GenericWidget.isSelected() | combo-box, selection list | METHOD | Yes | |
LIST-ITEM-PAIRS |
GenericWidget.getListItems() GenericWidget.setListItems() |
combo-box, selection list | ATTRIBUTE | Yes | |
LOOKUP |
GenericWidget.lookup() | combo-box, selection list | METHOD | Yes | |
NEXT-TAB-ITEM |
GenericWidget.getNextTabItem() GenericWidget.setNextTabItem() |
widget |
ATTRIBUTE | Yes | |
NUM-MESSAGES |
ErrorManager.numErrors() | ERROR-STATUS system handle | ATTRIBUTE |
Yes |
|
NUM-SELECTED-ROWS |
GenericWidget.getNumSelectedRows() GenericWidget.setNumSelectedRows() |
browse | ATTRIBUTE | Yes | |
PARENT |
GenericWidget.getParent() GenericWidget.setParent() |
frame |
ATTRIBUTE | Yes | |
PERSISTENT |
false |
THIS-PROCEDURE system handle |
ATTRIBUTE |
Yes |
Temporary solution to satisfy
the current application project for which we always know the result
will be false. A real implementation will eventually need to
be made. |
PFCOLOR |
GenericWidget.getPfColor() GenericWidget.setPfColor() |
widget |
ATTRIBUTE | Yes | |
PRIVATE-DATA |
GenericWidget.getPrivateData() GenericWidget.setPrivateData() |
frame |
ATTRIBUTE | Yes | |
READ-ONLY |
GenericWidget.isReadOnly() GenericWidget.setReadOnly() |
widget |
ATTRIBUTE |
Yes |
|
REFRESH |
GenericWidget.refresh() |
browse |
METHOD | Yes | |
SCROLLABLE |
GenericWidget.isScrollable() GenericWidget.setScrollable() |
widget |
ATTRIBUTE | Yes | |
SCREEN-VALUE |
CommonFrame.getScreenValue(widget)
or widget setter method from the current frame interface. |
widget |
ATTRIBUTE |
Yes |
This duality is needed since
the screen-value attribute will always return a character type (the
frame interface getters have the same type as the data being
represented/edited). In the case where the "natural" data type
is not character, this will behave differently when the screen
buffer's version of this data is uninitialized. In particular,
the data will be returned as the empty string rather than as the
default type for the natural data type. So if widget i is an
integer and it has never been initialized (copied into the screen
buffer as a result of a display a screen-value setter call or via UI
editing) then it will return "" instead of 0. See the INPUT built-in function which has a similar issue. |
SELECT-ALL |
GenericWidget.selectAll() |
browse |
METHOD | Yes | |
SELECT-FOCUSED-ROW |
GenericWidget.selectFocusedRow() |
browse |
METHOD | Yes | |
SELECTED |
GenericWidget.isSelected() GenericWidget.setSelected() |
widget |
ATTRIBUTE | Yes | |
SENSITIVE |
GenericWidget.isSensitive() GenericWidget.setSensitive() |
widget |
ATTRIBUTE | Yes | |
SET-REPOSITIONED-ROW |
GenericWidget.setRepositionedRow() |
browse |
METHOD | Yes | |
TITLE |
GenericWidget.getTitle() GenericWidget.setTitle() |
frame |
ATTRIBUTE | Yes | |
VISIBLE |
GenericWidget.isVisible() GenericWidget.setVisible() |
widget |
ATTRIBUTE | Yes |
Handle |
Java
Equivalent |
CURRENT-WINDOW |
LogicalTerminal.currentWindow() |
ACTIVE-WINDOW |
LogicalTerminal.activeWindow() |
THIS-PROCEDURE |
none at this time |
FOCUS |
LogicalTerminal.focus() |
SESSION |
none at this time |
SELF | LogicalTerminal.self() |
Options |
silent
flag |
wait flag |
none (the default) |
false |
true |
KW_SILENT |
true |
true |
KW_NO_WAIT |
false |
false |
Stream Type |
Java Class |
Input |
Output |
Notes |
File or Device |
com.goldencode.p2j.util.FileStream |
Y |
Y |
|
Printer |
com.goldencode.p2j.util.PrinterStream | N |
Y |
On Unix, this will probably be implemented as
a pipe to a child process of "lp". As this type may only be
needed on Windows, it isn't implemented at this time. |
Directory |
com.goldencode.p2j.util.DirectoryStream (not yet implemented) |
Y |
N |
Used for reading directory listings. Not
created at this time since it is not used in the application. |
Terminal |
? |
Y |
Y |
How this is to be implemented is TBD. |
Clipboard |
? |
? |
Y |
Windows-only, not implemented at this
time. Note that via the CLIPBOARD system handle, reading the
clipboard may be possible, though this has not been investigated. |
Child Process |
com.goldencode.p2j.util.PipeStream | Y |
Y |
In Progress the same child process stream can
be created as both input and output (simultaneously), however the
Java class will only handle either input OR output. Thus the
converted code will have 2 streams created in this case and all
operations will need to be properly destined for the correct stream. |
DEFINE
STREAM Type |
Registration |
NEW SHARED |
TransactionManager.registerTopLevelFinalizable(this,
true) |
NEW GLOBAL SHARED | TransactionManager.registerFinalizable(this, true) |
SHARED |
n/a |
(not shared at all) |
TransactionManager.registerTopLevelFinalizable(this, true) |
os-command no-wait "echo
HELLO; sleep 3; echo 'HELLO, AGAIN!'". def var c as char init "Hello World!". display c. enable. os-command silent "echo GOODBYE". |
def var redir as logical. def var i as integer. /* some processing that calculates redir, possibly with UI or database reads */ if redir then input from file.txt. set i. message "i =" i. |
Example
Program |
Output |
def var i as int. input thru "echo 5". output to ./test.txt. prompt-for i. output close. message input i. |
test.txt contains: i ---------- 5 The message line on the terminal contains: 5 |
def var i as int. input thru "echo 5". output to ./test.txt. prompt-for i. message input i. output close. |
test.txt contains: i ---------- 5 5 The message line on the terminal contains nothing. |
def var i as int. input thru "echo 5". prompt-for i. message input i. |
The terminal displays: ┌──────────┐ │ i│ │──────────│ │5 │ └──────────┘ The message line on the terminal contains: 5 |
Example
Program |
Output |
def var txt1 as
character. def var txt2 as character. def var i as integer. do i = 1 to 6: txt1 = txt1 + "Hello World! ". end. txt2 = txt1. display txt1 format "x(78)" txt2 format "x(78)". |
┌──────────────────────────────────────────────────────────────────────────────┐ │txt1 │ │txt2 │ │──────────────────────────────────────────────────────────────────────────────│ │Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! │ │Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! │ └──────────────────────────────────────────────────────────────────────────────┘ |
output to ./test.txt. def var txt1 as character. def var txt2 as character. def var i as integer. do i = 1 to 6: txt1 = txt1 + "Hello World! ". end. txt2 = txt1. display txt1 format "x(78)" txt2 format "x(78)". |
test.txt contains: txt1 txt2 ------------------------------------------------------------------------------ Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! |
Example
Program |
Output |
output to ./test.txt. def var i as integer. do i = 1 to 6 with frame fr: display i with 12 down frame fr. end. |
test.txt contains: i ---------- 1 2 3 4 5 6 |
output to ./test.txt. def var i as integer. do i = 1 to 6 with frame fr: display i with 12 down frame fr. up 1. end. |
test.txt contains: i ---------- 1 2 3 4 5 6 |
output to ./test.txt. def var i as integer. do i = 1 to 6 with frame fr: display i with 12 down frame fr. down 1. end. |
test.txt contains: i ---------- 1 2 3 4 5 6 |
def var i as integer. do i = 1 to 6 with frame fr: display i with 12 down frame fr. end. |
The following is displayed on
the terminal: ┌──────────┐ │ i│ │──────────│ │ 1│ │ 2│ │ 3│ │ 4│ │ 5│ │ 6│ │ │ │ │ │ │ │ │ │ │ │ │ └──────────┘ |
def var i as integer. do i = 1 to 6 with frame fr: display i with 12 down frame fr. up 1. end. |
A series of screens is
displayed on the terminal, with each screen alternating between a
number (1-6) and a blank/empty output. 12 screens are
displayed in all, each in turn. The first looks like: ┌──────────┐ │ i│ │──────────│ │ 1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────┘ |
def var i as int no-undo. |
The result on the terminal: ┌──────────────────┐ │ i│ │ ──────────│ │Count: 1│ │Count: 10│ │Count: 9│ │Count: 8│ │Count: 7│ │Count: 6│ │ │ │ │ │ │ │ │ └──────────────────┘ |
def var i as int no-undo. |
The result in the stream: i ---------- Count: 1 Count: 2 Count: 3 Count: 4 Count: 5 Count: 10 |
def var i as integer. do i = 1 to 6 with frame fr: display i with 12 down frame fr. down 1. end. |
The following is displayed on
the terminal: ┌──────────┐ │ i│ │──────────│ │ 1│ │ │ │ 2│ │ │ │ 3│ │ │ │ 4│ │ │ │ 5│ │ │ │ 6│ │ │ └──────────┘ |
def var iterations as int init 8.This overwrite behavior is not supported at this time.
message "Redirect?" set redir as logical.
if redir then
output thru cat.
message "Iterations?" update iterations.
def var i as int.
def var c as char format "x(64)" init "1".
do i = 1 to 63:
c = c + "0".
end.
i = 0.
repeat while i < iterations:
i = i + 1.
display c with 1 down no-labels no-box.
end.
pause.
Block Type | Looping | Default Transaction Level | TRANSACTION Option | ON Phrases | Retry Support | Parameters | Callable (Via Name) | Labeled | Nestable |
external procedure | no | sub-transaction | no | no | yes | yes | yes (filename in a RUN statement) | no | no |
internal procedure | no | sub-transaction | no | no | yes | yes | yes (RUN statement) | no | no |
function | no | sub-transaction | no | no | yes | yes | yes (in an expression) | no | no |
trigger | no | sub-transaction | no | no | yes | no | no | no | yes |
DO | no | no transaction | yes | yes | yes (depending on options) | no | no | yes | yes |
DO TO | yes | no transaction | yes | yes | yes (depending on options) | no | no | yes | yes |
DO WHILE | yes | no transaction | yes | yes | yes (depending on options) | no | no | yes | yes |
DO TO WHILE | yes | no transaction | yes | yes | yes (depending on options) | no | no | yes | yes |
REPEAT (all forms) | yes | sub-transaction | yes | yes | yes | no | no | yes | yes |
FOR FIRST | no (unless there is an additional table defined with EACH table) | sub-transaction | yes | yes | yes | no | no | yes | yes |
FOR LAST | no (unless there is an additional table defined with EACH table) | sub-transaction | yes | yes | yes | no | no | yes | yes |
FOR EACH | yes | sub-transaction | yes | yes | yes | no | no | yes | yes |
EDITING | yes | sub-transaction | no | no | yes | no | no | yes | yes (editing blocks for other frames can be directly contained and any kind of editing block can be indirectly contained) |
Condition |
Default
Action in Response |
ERROR |
If a transaction is active,
UNDO the closest enclosing block that has the ERROR property (this
will also be a transaction or sub-transaction). If a transaction is not active, the UNDO will be a no operation. Then RETRY the same block (or loop) that has the ERROR property. |
ENDKEY |
If a transaction is active,
UNDO the closest enclosing block that has the ENDKEY property (this
will also be a transaction or sub-transaction). If a transaction is not active, the UNDO will be a no operation. Only blocks with the ENDKEY property will have an interactions counter. The interactions counter is a simple count of the number of input-blocking language statements have executed in the block. Any nested block that does not have an interactions counter of its own will have its interactions counted in the nearest enclosing block's interactions counter. This counter is used to determine the behavior of the END-ERROR event (see below). Then LEAVE the same block (or loop) that has the ENDKEY property. |
STOP |
If a transaction is active,
UNDO the block that defines the transaction (as opposed to a
sub-transaction). If a transaction is not active, the UNDO will be a no operation. Then RETRY the "startup procedure". This means that the *entire* top-level procedure is run again from the top. Note that if this is generated based on a database disconnect, a special form of processing will occur where the default STOP behavior cannot be explicitly overridden until an exit to a scope in which no access is made to the database that was disconnected. At this point the normal STOP processing is re-imposed (see Progress Programming Handbook page 5-24). |
QUIT |
If a transaction is active,
COMMIT the block that defines the transaction (as opposed to a
sub-transaction). If a transaction is not active, the COMMIT will be a no operation. Exit to the operating system. |
END-ERROR |
Technically, this really isn't
a separate condition, but rather a key in the UI which generates 2
different conditions based on context. An END condition is
generated if the key press occurs during the first input-blocking
language statement in the block. An ERROR condition is
generated if the key press occurs during a subsequent input-blocking
language statement in the block. The exception to this processing occurs for blocks that do not have the ENDKEY property (this can happen for some DO blocks and is always true for EDITING blocks). In such a case, the interactions counter is not maintained for the current block but rather is maintained at the level of the nearest enclosing block which has the ENDKEY property. This means that the normal conversion of END-ERROR to ERROR is thus modified in such blocks. In the EDITING case especially, it is likely that an ERROR will be generated (because the EDITING block itself counts as an interaction and thus with a larger interactions counter it is more likely that an ERROR will be generated). What is especially different here is that the ERROR will be propagated to the enclosing block that has the ENDKEY property! So even if the DO or EDITING block has the ERROR property in this case, if they also do not have the ENDKEY property and the interactions counter (for the enclosing ENDKEY block) is < 2 then the ERROR will NOT be processed by the current block. See above for ENDKEY and ERROR respectively. |
Action |
Description |
UNDO |
Rolls back all database,
variable, temp-table and work-table modifications since the beginning
of the target block. This target block must have opened a
transaction or it must be enclosed within a block that opened a
transaction AND must itself be a sub-transaction. In any other
case, the UNDO is a no operation. There is no way of disabling UNDO. Any condition that is raised will always trigger an UNDO (with the exception of the implicit behavior for QUIT which is to COMMIT instead of UNDO). In addition, one of the actions below will also be executed. If not explicitly specified, the implicit action is always a RETRY. |
LEAVE |
Breaks (exits) out of the target block. If the target block is a top-level block, the LEAVE will be converted to a RETURN. |
RETRY |
The target block
(non-loop or loop) is executed from the top with the same state as
when it last executed. Since a retry of a block in which the state never changes from iteration to iteration would yield an infinite loop, Progress provides a feature to detect when an infinite loop could occur. This is called Infinite Loop Protection (ILP). RETRY will be converted to a LEAVE, NEXT or RETURN depending on the block type to which the RETRY is targetted. |
NEXT |
The next iteration of the loop
is executed. If the associated block is not a loop, then this
is automatically treated as a LEAVE or a RETURN (see below). Infinite Loop Protection (ILP) protects the NEXT action (when triggered from an ON phrase or from an UNDO statement). In certain cases, a NEXT will be converted to a LEAVE or RETURN by the infinite loop protection logic depending on the block type to which the RETRY is targetted. |
RETURN |
The current procedure,
function or trigger returns to the caller. There are 5 forms:
|
Block
Type |
ERROR |
ENDKEY |
STOP |
QUIT |
FOR |
Y |
Y |
N* |
N* |
REPEAT |
Y |
Y |
N* |
N* |
DO |
N* (except if the
transaction keyword is present, then there is an implicit ERROR
property) |
N |
N |
N |
procedure |
Y |
Y |
Y* (only for the "startup
procedure") |
N* |
trigger |
Y |
Y |
N* |
N* |
editing |
Y
(for UPDATE except when the ERROR condition is generated by the
END-ERROR key and the interactions counter is < 2) / N (for
PROMPT-FOR and SET) |
N |
N |
N |
function |
Y (always LEAVES the function
with an unknown value return) |
Y (always LEAVES the function with an unknown value return) | N |
N |
Block
Type |
Top Level | Block/Loop |
Conversion |
||||
FOR EACH (all forms - even when there is a WHILE or TO/BY
present) |
no | loop |
|
||||
DO TO/BY |
no | loop |
|
||||
REPEAT TO/BY |
no | loop |
|
||||
FOR FIRST/LAST (all forms - even when there is a WHILE or TO/BY present) | no | block |
|
||||
DO WHILE |
no | loop |
|
||||
REPEAT WHILE | no | loop |
|
||||
REPEAT |
no | loop |
|
||||
DO |
no | block |
|
||||
external procedure | yes | block |
|
||||
internal procedure |
yes | block |
|
||||
function | yes | block |
|
||||
trigger |
yes | block |
|
||||
editing |
no | loop |
Infinite loop protection is always
disabled in editing blocks (since they an only be called from
UPDATE/SET/PROMPT-FOR as part of a user interaction), so no conversions
ever occur in targetted editing blocks. |
Code | Description |
NEAR-ERR | The nearest enclosing block with the ERROR property. Example 1 outer: repeat: inner: do on error undo, retry: <statement> end. end. In example 1, the nearest enclosing block (to the <statement>) with the ERROR properly is inner. Example 2: outer: repeat: inner: do: <statement> end. end. In example 2, the nearest enclosing block (to the <statement>) with the ERROR property is outer. |
CURRENT | The "current" block. This is the block which directly
encloses the UNDO, NEXT or LEAVE language statement. Or in the
case of an ON phrase, it is the block upon which the ON phrase is
specified. outer: repeat: inner: do on error undo, RETRY: <statement> end. end. In this example, for both the <statement> and the RETRY the current block is inner. |
AS-UNDO | The other action will be targetted at the same block as the associated UNDO is targetted. If UNDO is targetted (implicitly or explicitly) to inner then the other action will be targetted to inner. |
Feature | UNDO Target | Other Action | Other Action Target | Static Conversion Possible | Compiler Error |
UNDO. | NEAR-ERR | RETRY (implicit) | AS-UNDO | n/a | |
UNDO inner. | inner | RETRY (implicit) | AS-UNDO | If inner does not have the ERROR property. | |
UNDO outer. | outer | RETRY (implicit) | AS-UNDO | If outer does not have the ERROR property. | |
UNDO, RETRY. | NEAR-ERR | RETRY | AS-UNDO | ||
UNDO, LEAVE. | NEAR-ERR | LEAVE | AS-UNDO | * | |
UNDO, NEXT. | NEAR-ERR | NEXT | AS-UNDO | * | |
UNDO, RETRY inner. | NEAR-ERR | RETRY | inner | If inner does not have the ERROR property, then UNDO would choose a different block than inner. Since RETRY is targetted at inner, this is a problem. UNDO and RETRY must always be targetted at the same block. | |
UNDO, LEAVE inner. | NEAR-ERR | LEAVE | inner | If inner does not have the ERROR property, then UNDO would choose a different block (a more enclosing one) than inner. Since LEAVE must be targetted at a block that is the same as the UNDO target or which encloses the UNDO target, this is a problem. | |
UNDO, NEXT inner. | NEAR-ERR | NEXT | inner | If inner does not have the ERROR property, then UNDO would choose a
different block (a more enclosing one) than inner. Since NEXT must be
targetted at a block that is the same as the UNDO target or which
encloses the UNDO target, this is
a problem. In addition, if inner is not an iterating block the compiler fails. |
|
UNDO, RETRY outer. | NEAR-ERR | RETRY | outer | This is only possible if an inner block (one that more directly encloses the statement) does not have the ERROR property. If inner does have such properties, then it would be implicitly chosen as the UNDO target and that would mismatch with the outer block as the action target, causing the compiler to error. | |
UNDO, LEAVE outer. | NEAR-ERR | LEAVE | outer | The outer block or one of its nested blocks (which still encloses this statement) must have the ERROR property, otherwise the LEAVE will be targetted inside the UNDO target, which is not allowed. | |
UNDO, NEXT outer. | NEAR-ERR | NEXT | outer | The outer block or one of its nested blocks (which still encloses this
statement) must have the ERROR property, otherwise the NEXT will be
targetted inside the UNDO target, which is not allowed. In addition, if outer is not an iterating block the compiler fails. |
|
UNDO inner, RETRY. | inner | RETRY | AS-UNDO | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
UNDO inner, LEAVE. | inner | LEAVE | AS-UNDO | * | inner must have ANY property (not just ERROR) otherwise there is a compiler error. |
UNDO inner, NEXT. | inner | NEXT | AS-UNDO | * | inner must have ANY property (not just ERROR) otherwise there is a compiler error. |
UNDO inner, RETRY inner. | inner | RETRY | inner | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
UNDO inner, LEAVE inner. | inner | LEAVE | inner | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
UNDO inner, NEXT inner. | inner | NEXT | inner | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
UNDO inner, RETRY outer. | n/a | n/a | n/a | Invalid syntax, compiler will always error because RETRY and UNDO must target the same block. | |
UNDO inner, LEAVE outer. | inner | LEAVE | outer | inner must have ANY property (not just ERROR) otherwise there is a compiler error. (outer has no property dependencies) | |
UNDO inner, NEXT outer. | inner | NEXT | outer | inner must have ANY property (not just ERROR) otherwise there is a compiler error. (outer has no property dependencies) In addition, if outer is not an iterating block the compiler fails. |
|
UNDO outer, RETRY. | outer | RETRY | AS-UNDO | outer must have ANY property (not just ERROR) otherwise there is a compiler error. | |
UNDO outer, LEAVE. | outer | LEAVE | AS-UNDO | * | outer must have ANY property (not just ERROR) otherwise there is a compiler error. |
UNDO outer, NEXT. | outer | NEXT | AS-UNDO | * | outer must have ANY property (not just ERROR) otherwise there is a compiler error. |
UNDO outer, RETRY inner. | n/a | n/a | n/a | Invalid syntax, compiler will always error because RETRY and UNDO must target the same block. | |
UNDO outer, LEAVE inner. | n/a | n/a | n/a | Invalid syntax, compiler will always error because LEAVE must target the same or a more enclosing block as UNDO. | |
UNDO outer, NEXT inner. | n/a | n/a | n/a | Invalid syntax, compiler will always error because NEXT must target the same or a more enclosing block as UNDO. | |
UNDO outer, RETRY outer. | outer | RETRY | outer | outer must have ANY property (not just ERROR) otherwise there is a compiler error. | |
UNDO outer, LEAVE outer. | outer | LEAVE | outer | outer must have ANY property (not just ERROR) otherwise there is a compiler error. | |
UNDO outer, NEXT outer. | outer | NEXT | outer | outer must have ANY property (not just ERROR) otherwise there is a compiler error. In addition, if outer is not an iterating block the compiler fails. |
|
ON <condition> UNDO. | CURRENT | RETRY (implicit) | AS-UNDO | CURRENT must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO inner. | inner | RETRY (implicit) | AS-UNDO | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO outer. | outer | RETRY (implicit) | AS-UNDO | outer must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO, RETRY. | CURRENT | RETRY | AS-UNDO | ||
ON <condition> UNDO, LEAVE. | CURRENT | LEAVE | AS-UNDO | * | |
ON <condition> UNDO, NEXT. | CURRENT | NEXT | AS-UNDO | * | No error even if the implicit target block is not an iterating block. |
ON <condition> UNDO, RETRY inner. | CURRENT | RETRY | inner | CURRENT must be the same block as inner (RETRY and UNDO must always be at the targetted to the same block). | |
ON <condition> UNDO, LEAVE inner. | CURRENT | LEAVE | inner | CURRENT must be the same block as inner or enclosed by inner. The LEAVE target block needs no properties in particular. | |
ON <condition> UNDO, NEXT inner. | CURRENT | NEXT | inner | CURRENT must be the same block as inner or enclosed by inner. The NEXT target block needs no properties in particular. However, if inner is not an iterating block the compiler fails. |
|
ON <condition> UNDO, RETRY outer. | n/a | n/a | n/a | Invalid syntax. The UNDO will target the current block which
is more deeply nested than the RETRY target, which causes the compiler
to fail. |
|
ON <condition> UNDO, LEAVE outer. | CURRENT | LEAVE | outer | The LEAVE target block needs no properties in particular. | |
ON <condition> UNDO, NEXT outer. | CURRENT | NEXT | outer | The NEXT target block needs no properties in particular. If outer is not an iterating block the compiler fails. |
|
ON <condition> UNDO inner, RETRY. | inner | RETRY | AS-UNDO | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO inner, LEAVE. | inner | LEAVE | AS-UNDO | * | inner must have ANY property (not just ERROR) otherwise there is a compiler error. |
ON <condition> UNDO inner, NEXT. | inner | NEXT | AS-UNDO | * | inner must have ANY property (not just ERROR) otherwise there is a compiler error. |
ON <condition> UNDO inner, RETRY inner. | inner | RETRY | inner | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO inner, LEAVE inner. | inner | LEAVE | inner | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO inner, NEXT inner. | inner | NEXT | inner | inner must have ANY property (not just ERROR) otherwise there is a compiler error. If inner is not an iterating block the compiler fails. |
|
ON <condition> UNDO inner, RETRY outer. | n/a | n/a | n/a | Invalid syntax. The UNDO will target the inner block which is more deeply nested than the RETRY target, which causes the compiler to fail. | |
ON <condition> UNDO inner, LEAVE outer. | inner | LEAVE | outer | inner must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO inner, NEXT outer. | inner | NEXT | outer | inner must have ANY property (not just ERROR) otherwise there is a compiler error. If outer is not an iterating block the compiler fails. |
|
ON <condition> UNDO outer, RETRY. | outer | RETRY | AS-UNDO | outer must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO outer, LEAVE. | outer | LEAVE | AS-UNDO | * | outer must have ANY property (not just ERROR) otherwise there is a compiler error. |
ON <condition> UNDO outer, NEXT. | outer | NEXT | AS-UNDO | * | outer must have ANY property (not just ERROR) otherwise there is a compiler error. |
ON <condition> UNDO outer, RETRY inner. | n/a | n/a | n/a | Invalid syntax. The UNDO and RETRY must target the same block, otherwise the compiler fails. | |
ON <condition> UNDO outer, LEAVE inner. | n/a | n/a | n/a | Invalid syntax. LEAVE must target the same or a more enclosing block as UNDO is targetting. | |
ON <condition> UNDO outer, NEXT inner. | n/a | n/a | n/a | Invalid syntax. NEXT must target the same or a more enclosing block as UNDO is targetting. | |
ON <condition> UNDO outer, RETRY outer. | outer | RETRY | outer | outer must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO outer, LEAVE outer. | outer | LEAVE | outer | outer must have ANY property (not just ERROR) otherwise there is a compiler error. | |
ON <condition> UNDO outer, NEXT outer. | outer | NEXT | outer | outer must have ANY property (not just ERROR) otherwise there is a compiler error. If outer is not an iterating block the compiler fails. |
Statement
|
Condition |
Notes
|
||||
ERROR |
STOP |
QUIT |
ENDKEY |
END-ERROR* |
||
APPLY | + |
+ |
+ |
+ |
+ |
this statement can generate
any event on a procedure including these special events that match
"conditions" (it can also generate events for widgets but this is
conceptually quite different from generating the conditions listed
in this table) |
ASSIGN | + |
- |
- |
- |
- |
|
BUFFER-COMPARE |
+ |
- |
- |
- |
- |
|
BUFFER-COPY |
+ |
- |
- |
- |
- |
|
CHOOSE | + |
+ |
- |
+ |
+ |
interactive |
COMPILE |
+ |
- |
- |
- |
- |
provided here for completeness |
CONNECT |
+ |
- |
- |
- |
- |
|
COPY-LOB |
+ |
- |
- |
- |
- |
|
CREATE ALIAS automation object CALL DATABASE SAX-READER SERVER-SOCKET SOCKET WIDGET-POOL |
+ |
- |
- |
- |
- |
|
DDE |
+ |
- |
- |
- |
- |
all kinds of DDE statement can
raise ERROR |
DELETE OBJECT PROCEDURE WIDGET-POOL |
+ |
- |
- |
- |
- |
|
DICTIONARY |
+ |
- |
- |
- |
- |
"The DICTIONARY statement is
equivalent to RUN dict.p: it runs the Progress procedure called
dict.p. Progress uses the regular search rules to find the
dictionary procedure. The dictionary procedure is part of the
Progress system software." |
DISCONNECT |
+ |
- |
- |
- |
- |
|
DISPLAY | + |
- |
- |
- |
- |
|
EDITING phrase | + |
+ |
+ |
+ |
+ |
The event processing inside
the phrase must apply all events explicitly, either using APPLY
statement or by using RETURN ERROR/STOP/QUIT statements. Also, this
phrase is active only if input is from terminal. In general case it
is necessary to check phrase body to determine which events can be
generated. |
EMPTY TEMP-TABLE | + |
- |
- |
- |
- |
|
ENTRY |
+ |
- |
- |
- |
- |
function and statement |
EXPORT |
+ |
- |
- |
- |
- |
|
FIND | + |
- |
- |
+ |
- |
|
IMPORT | + |
- |
- |
+ |
- |
|
INPUT FROM | + |
- |
- |
+ |
+ |
|
INSERT | + |
+ |
- |
+ |
+ |
interactive |
LOAD |
+ |
- |
- |
- |
- |
|
OUTPUT TO | + |
- |
- |
- |
- |
|
PROCESS EVENTS | + |
+ |
- |
+ |
+ |
behaves like interactive |
PROMPT-FOR | + |
+ |
- |
+ |
+ |
interactive (equivalent to
ENABLE, WAIT-FOR, DISABLE sequence of statements) |
PUT-KEY-VALUE |
+ |
- |
- |
- |
- |
|
QUIT |
- |
- |
+ |
- |
- |
this statement seems the only
way to generate this event (except APPLY) |
RAW-TRANSFER |
+ |
- |
- |
- |
- |
|
READKEY |
+ |
- |
- |
+ |
- |
Actually, the condition is not
directly raised, instead the
LASTKEY value is set to -1 (on ERROR) or -2 (on ENDKEY). To
raise the condition one then must "APPLY LASTKEY". |
RELEASE |
+ |
- |
- |
- |
- |
all kinds of RELEASE statement can raise ERROR |
REPOSITION |
+ |
- |
- |
- |
- |
|
RUN |
+ |
+ |
- |
all kinds of RUN statement can
raise ERROR and STOP |
||
SAVE CACHE | + |
- |
- |
- |
- |
|
SET | + |
+ |
- |
+ |
+ |
interactive (equivalent to
PROMPT-FOR and ASSIGN) |
STOP |
- |
+ |
- |
- |
- |
|
SUBSCRIBE |
+ |
- |
- |
- |
- |
|
UNLOAD |
+ |
- |
- |
- |
- |
|
UPDATE | + |
+ |
- |
+ |
+ |
interactive |
USE |
+ |
- |
- |
- |
- |
|
VALIDATE |
+ |
- |
- |
- |
- |
|
WAIT-FOR | + |
+ |
- |
+ |
+ |
Waits for specified event to
occur. Effectively this does allow other keyboard-driven events to
occur as well. |
pause_infinite_loop_protection*.p
.Option |
Description |
Pros |
Cons |
explicit hard coding in
generated source |
All control flow, logic and
data processing is explicit in the generated source code. Some
features can be helped by hiding things like undo processing in the
data type wrappers or utility classes like streams (with cleanup
processing). However, the core problem here is that the
generated code still must hook into this processing. This will
take the form of try/catch/finally blocks, instrumentation calls to
push/pop scopes, manufactured labels/bogus outer loops to handle
things like retry and call-backs to utility classes to access or
invoke that logic that can be centralized. |
|
|
indirect call mechanism using a utility class to implement the logic | In this model, each block that
has special properties would be implemented in 2 pieces:
|
|
|
bytecode instrumentation | Generate the Java code just as
one would normally write it by hand coding but after javac creates
the resulting class files, rewrite the class file (statically as a
2nd compile step or dynamically at load time via a custom
classloader) to instrument all the same logic and processing that
would have been implemented explicitly in the source as in the hard
coded option above. |
|
|
TransactionManager.pushScope("label", TransactionManager.TRANSACTION, true, true, false, false); try { label: // if this is a loop, the loop control structure will emit here { TransactionManager.blockSetup(); do { try { // block body is emitted here } catch (ErrorConditionException err) { TransactionManager.honorError(err); TransactionManager.rollback("label"); TransactionManager.triggerRetry(null); } catch (EndConditionException end) { TransactionManager.rollback("label"); break label; } catch (RetryUnwindException ru) { TransactionManager.honorRetry(ru); } } while (TransactionManager.needsRetry()); if (TransactionManager.isBreakPending()) { break label; } TransactionManager.iterate(); } } finally { TransactionManager.popScope(); } |
Aggregator long name | Short name |
AVERAGE | AVG |
COUNT | - |
TOTAL | - |
MINIMUM | MIN |
MAXIMUM | MAX |
SUB-AVERAGE | - |
SUB-COUNT | - |
SUB-TOTAL | - |
SUB-MINIMUM | SUB-MIN |
SUB-MAXIMUM | SUB-MAX |
Aggregator | AVERAGE | COUNT | TOTAL | MINIMUM | MAXIMUM | SUB-AVERAGE | SUB-COUNT | SUB-TOTAL | SUB-MINIMUM | SUB-MAXIMUM |
Value | 0 | 0 | 0 | ? | ? | 0 | 0 | 0 | ? | ? |
repeat:- the retrieved accumulation result will be the total accumulation result for this block (which is kept in the 'bundleStack').
...
accum i (count).
message accum count i.
...
end.
B1:- the retrieved accumulation result will be the accumulation result for block B2. If the body for block B2 did not execute, then the accumulation result will be the default "entry" value. The rule extracted from this example is that, when leaving a block which accumulated some data, it will set the 'reporting' data bundle (the primary data bundle from dataBundle stack) for the parent block (block B1) to be the total accumulated value (from the pendingStack) for block B2. In other words, block B1 will inherit and report the accumulation result from block B2, until a new ACCUM stmt is executed or another block is entered.
repeat:
...
B2:
repeat:
...
accum i (count).
...
end.
message accum count i.
...
end.
B1:Frames f1 and f2 will display the same value. This testcase shows that blocks which do not use the ACCUM statement for a certain accumulator will not affect it in any way. This behavior is also present if B2 has no ACCUM stmt and is executed. Also, any ACCUM function usage in block B2 will return the same value as if it was executed in block B1, as proved in the following testcase:
repeat k = 1 to 2:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat while false: /* no accumulator usage */
...
end.
display accum total i with down column 13 frame f2.
end.
B1:
repeat k = 1 to 2:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 1: /* no accum stmt usage */
display accum total i with down column 13 frame f2.
end.
display accum total i with down column 26 frame f2.
end.
B1:In this example, block B2 is not executed, but the block uses the ACCUM stmt; frames f1 and f2 will display different results:
repeat k = 1 to 2:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat while false:
accum i (total).
end.
display accum total i with down column 13 frame f2.
end.
B1:In this example, block B2 is executed, but the ACCUM stmt is not; frames f1 and f2 will also display different results:
repeat k = 1 to 2:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
do transaction:
if false then
accum i (total).
end.
display accum total i with down column 13 frame f2.
end.
B1:Frames f1 and f2 will display the same value. This testcase shows that blocks which do not use the ACCUM statement for a certain accumulator will not affect it in any way. Also, this proves that, regardless of scoping level, if a block doesn't use the ACCUM statement and is not executed, the accumulator is not reset. This behavior is also present if B2 has no ACCUM stmt and is executed.
repeat k = 1 to 2:
accum i (total).
end.
display accum total i with down column 0 frame f1.
B2:
repeat while false: /* no accumulator usage */
...
end.
display accum total i with down column 13 frame f2.
B1:In this example, block B2 is not executed, but the block uses the ACCUM stmt; frames f1 and f2 will also display different results:
repeat k = 1 to 2:
accum i (total).
end.
display accum total i with down column 0 frame f1.
B2:
repeat while false:
accum i (total).
end.
display accum total i with down column 13 frame f2.
B1:In this example, block B2 is executed, but the the ACCUM stmt is skipped; frames f1 and f2 will also display different results:
repeat k = 1 to 2:
accum i (total).
end.
display accum total i with down column 0 frame f1.
B2:
repeat:
if false then
accum i (total).
end.
display accum total i with down column 13 frame f2.
B1:Although the second accumulation statement does not execute at all, frame f1 and f2 will display the same results.
repeat k = 1 to 2:
accum i (total).
display accum total i with down column 0 frame f1.
if false then accum i (total).
display accum total i with down column 13 frame f2.
end.
B1:Although block B2 does not execute at all (header or body), frame f1 and f2 will display different results:
repeat k = 1 to 2:
accum i (total).
display accum total i with down column 0 frame f1.
if false then
B2:
repeat while false:
accum i (total).
end.
display accum total i with down column 13 frame f2.
end.
B1:Block B2 does not execute and frames f1, f2 and f3 will display different results:
repeat k = 1 to 2:
accum i (total).
display accum total i with down column 0 frame f1.
if true then do:
display accum total i with down column 13 frame f2.
B2:
repeat while false:
accum i (total).
end.
end.
display accum total i with down column 26 frame f3.
end.
B1:Block B2 does not execute at all (header or body) but frames f1 and f2 will display the same result.
repeat k = 1 to 2:
accum i (total).
end.
display accum total i with down column 0 frame f1.
if false then
B2:
repeat while false:
accum i (total).
end.
display accum total i with down column 13 frame f2.
B1:will result in frames f1 and f2 displaying the same values. Experimenting with other statements which do not affect the accumulators (like SET/UPDATE/PROMPT-FOR - the editing blocks) showed the same behavior. The conclusion is that only IF statements which are enclosed within a block will set the accumulator to UNKNOWN.
repeat k = 1 to 2:
accum i (total).
end.
display accum total i with down column 0 frame f1.
do:
if false then
B2:
repeat while false:
accum i (total).
end.
end.
display accum total i with down column 13 frame f2.
B1:In this case, frame f2 will always display the same result as frame f1 (on every B2 iteration) and frame f3 will display the current accumulation result for block B2; frame f4 will display the final accumulation result for block B2. This proves that PROGRESS presents some backup mechanism - used to report the "entry" accumulation value if no ACCUM stmts were executed in the current block before the ACCUM function call.
repeat k = 1 to 1:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 2:
display accum total i with down column 13 frame f2.
accum i (total).
display accum total i with down column 26 frame f3.
end.
display accum total i with down column 39 frame f4.
end.
B1:In this case, frame f2 will not display the same result as frame f1:
repeat k = 1 to 1:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 2:
display accum total i with down column 13 frame f2.
if false then accum i (total). /*1*/
display accum total i with down column 26 frame f3.
accum i (total). /*2*/
display accum total i with down column 39 frame f4.
end.
display accum total i with down column 52 frame f5.
end.
B1:shows this:
repeat k = 1 to 1:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 2:
display accum total i with down column 13 frame f2.
accum i (total). /*1*/
display accum total i with down column 26 frame f3.
if false then accum i (total). /*2*/
display accum total i with down column 39 frame f4.
end.
display accum total i with down column 52 frame f5.
end.
B1:In this case, frame f2 will not display the same result as frame f1:
repeat k = 1 to 1:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 2:
if false then
B3:
repeat while false:
accum i (total).
end.
display accum total i with down column 13 frame f2.
accum i (total).
display accum total i with down column 26 frame f3.
end.
display accum total i with down column 39 frame f4.
end.
B1:In this case, frame f2 will not display the same result as frame f1:
repeat k = 1 to 1:
accum i (total).
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 2:
B3:
repeat while false:
accum i (total).
end.
display accum total i with down column 13 frame f2.
accum i (total).
display accum total i with down column 26 frame f3.
end.
display accum total i with down column 39 frame f4.
end.
B1:In this example, frames f2 and f3 will always display the accumulated result for block B2 and frame f4 will always display the accumulated result for block B3:
repeat k = 1 to 1:
if false then accum i (total). /*1*/
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 3:
accum i (total). /*2*/
end.
display accum total i with down column 13 frame f2.
accum i (total). /*3*/
display accum total i with down column 26 frame f3.
B3:
repeat j = 1 to 4:
accum i (total). /*3*/
end.
display accum total i with down column 39 frame f4.
end.
B1:The ACCUM function used in the two message statements will behave differently:
repeat k = 1 to 1:
if false then accum i (total). /*1*/
display accum total i with down column 0 frame f1.
B2:
repeat j = 1 to 3:
message "B2:" + string(accum total i). /*1*/
accum i (total). /*2*/
end.
display accum total i with down column 13 frame f2.
accum i (total). /*3*/
display accum total i with down column 26 frame f3.
B3:
repeat j = 1 to 4:
message "B3:" + string(accum total i). /*2*/
accum i (total). /*4*/
end.
display accum total i with down column 39 frame f4.
end.
def var i as int.For the testcases above, the ACCUM function will always return the same value. This is because accumulators - which are used inside a procedure/function - are alive and can be used only as along 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. In P2J, instead of scoping the accumulator to the internal procedure, we keep a stack with the "toplevel" status of each entered block. This way, when recursion occurs and the ACCUM statement is called, the accumulation will be performed only until a "toplevel" block is encountered - and so no other blocks from a previous recurssion will be affected.
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).
def var j as int.In this testcase, applying the same trigger as the one being executed will not be possible: in PROGRESS, the trigger will not go into recurssion and so the 'apply "F7" to j in frame f0.' statement inside the trigger definition will be ignored.
def var i as int.
def var k as int init 0.
form j with frame f0 column 39.
view frame f0.
on F7 of j in frame f0 do:
do transaction:
accum i (count).
end.
put screen row k + 1 column 1 string(k) + " " + string(accum count i).
if k < 10 then do:
k = k + 1.
apply "F7" to j in frame f0.
k = k - 1.
end.
put screen row k + 1 column 13 string(k) + " " + string(accum count i).
end.
apply "F7" to j in frame f0.
com.goldencode.p2j.persist
package
documentation
for additional details.
Progress
Type |
P2J
Wrapper Type |
character |
character |
date |
date |
decimal |
decimal |
integer recid |
integer |
logical |
logical |
raw |
raw |
<record> of <table>
construct within a Progress record phrase. Although this is a limited
limited approach, since this construct is simply short-hand for a more verbose
where clause construct, it is the only mechanism currently implemented, because
it is reliable and easily automated (mostly). This analysis and its effect
on schema conversion and DMO creation is described in the
com.goldencode.p2j.schema
package
documentation
.
com.goldencode.p2j.persist
package
documentation
.
persist
package
Progress
WHERE |
HQL |
Notes |
LPARENS |
( |
|
AND |
and |
|
OR |
or |
|
EQUALS |
= |
|
NOT_EQ |
!= |
|
GT |
> |
|
GTE |
>= |
|
LT |
< |
|
LTE |
<= |
|
BEGINS |
like '<criteria>%' |
The 2nd operand's text is converted into a Java string using ExpressionConversionWorker progressToJavaString() and then the '%' is appended. Strings are enclosed in single quotes. |
MATCHES |
like '<criteria>' |
The 2nd operand's text is
converted into a Java string using ExpressionConversionWorker
progressToJavaString() and then special matching characters are
converted into the HQL equivalents using ExpressionConversionWorker
convertToSQLLike(). Strings are enclosed in single quotes. |
CONTAINS |
n/a |
|
NOT |
not ( ) |
The child node of this
operator is wrapped in () to ensure that the Progress precedence is
maintained, since this is different compared to HQL NOT precedence. |
BOOL_TRUE |
true |
|
BOOL_FALSE |
false |
|
NUM_LITERAL |
text is emitted directly |
|
DEC_LITERAL |
text is emitted directly | |
STRING |
'text' |
The text is converted into a Java string using ExpressionConversionWorker progressToJavaString(). Strings are enclosed in single quotes. |
DATE_LITERAL |
'YYYY-MM-DD' |
The text is turned into an
instance of com.goldencode.p2j.util.date and the toStringSQL()
method is used to generate the correct format. The resulting
string is enclosed in single quotes. Note that there are special considerations for handling dates within a database where clause. |
UNKNOWN_VAL |
is null is not null null |
If the parent note is EQUALS
or NOT_EQ one of the two first forms are emitted. Otherwise
the 3rd form is emitted. |
FUNC_RECID and oldtype ==
KW_RECID |
<javaname_of_buffer>.id |
This builtin function is
directly converted to a reference to the special "id" field. |
FUNC_ROWID and oldtype ==
KW_ROWID |
<javaname_of_buffer>.id | This builtin function is directly converted to a reference to the special "id" field. |
FIELD_ in the current buffer |
<buffer_qualifier>.<property_name> |
|
LBRACKET |
[] |
Only FIELD_ extents are
supported. Subscripts are supported using [ ] but only if the index is a NUM_LITERAL. Such an index is decremented by 1 and emitted as text inside the square brackets. |
com.goldencode.p2j.persist
package unless otherwise noted.
Statement |
Java
Equivalent |
Notes |
ACCUM |
Accumulator (abstract base
class); concrete implementations:
|
|
BUFFER COMPARE |
RecordBuffer.compare |
|
BUFFER COPY |
RecordBuffer.copy |
|
CLOSE QUERY |
n/a |
P2J queries are not explicitly
closed; this statement is dropped |
CONNECT |
ConnectionManager.connect |
|
CREATE |
RecordBuffer.create |
No USING support at this time. In Progress, CREATE does not immediately flush the new record to the database, since in most cases the default values assigned to a new record would violate uniqueness or nullability constraints. Instead, the record is not written to the database until the first statement is executed, which would update an index for the record's table. In our implementation, we defer writing the record to the database (or more accurately, persisting the record using Hibernate; actual flushing to the database may be further deferred by Hibernate), until the earliest of the following occurs:
Note that our implementation departs slightly from Progress -- particularly with regard to #1 in the list above -- in that the setting of a property which does NOT participate in a unique constraint (but DOES participate in a non-unique index) WILL NOT trigger a buffer flush. However, we believe the remaining conditions adequately make up for this departure. |
CREATE ALIAS |
ConnectionManager.createAlias |
|
CREATE BUFFER |
TBD; dynamic buffer
creation is not supported in the first release |
|
CREATE DATABASE |
TBD; dynamic database creation is not supported in the first release | |
CREATE QUERY |
TBD; dynamic query creation is not supported in the first release | |
CREATE TEMP-TABLE |
TBD; dynamic temp-table creation is not supported in the first release | |
DEFINE BUFFER |
RecordBuffer.define |
see Record
Buffer Instantiation |
DEFINE QUERY |
AbstractQueryWrapper (abstract
base class); concrete implementations:
|
These wrapper classes are used
to support the DEFINE QUERY semantic of defining a query once and
possibly opening it multiple times. An instance is constructed
once, as an instance variable, corresponding with the DEFINE QUERY
statement. Each corresponding OPEN QUERY is converted as an
instance of a concrete subclass of AbstractQuery, which is set into
the wrapper when opened. |
DEFINE TEMP-TABLE DEFINE WORK-TABLE |
TemporaryBuffer.define | no distinction is made between
temp-tables and work-tables at conversion; both are converted
to DMO class definitions in the <...>.dmo._temp
package (DMO interfaces) and the <...>.dmo._temp.impl
package (DMO implementation classes) in the converted application
hierarchysee also Temporary and Work Table Support |
DELETE |
RecordBuffer.delete |
no VALIDATE support at this
time |
DELETE ALIAS |
ConnectionManager.deleteAlias |
|
DISCONNECT |
ConnectionManager.disconnect |
|
FIND (standalone) |
FindQuery AdaptiveQuery |
|
FIND (as preselect retrieval
mechanism) |
P2JQuery.first P2JQuery.last P2JQuery.next P2JQuery.previous |
|
FOR/DO/REPEAT |
AbstractQuery (abstract base
class); concrete implementations:
|
Which concrete implementation
is chosen at conversion time depends upon the number of tables
involved and upon the nature of the query. For an individual table, PreselectQuery is chosen if it is defined explicitly in Progress using the PRESELECT keyword or if the Progress construct's explicit sort criteria do not match the index selected according to index selection rules. PresortQuery is chosen if client-side sorting is required (i.e., the by clause contains an expression which cannot be converted to an HQL 'order by' clause, but must instead convert to a runtime expression). Both of these types can be applied in a multi-table join situation. AdaptiveQuery is chosen by default for a single table query, if other factors do not force a preselect variant. This query operates in preselect mode until some change to a record forces it to operate in dynamic mode. As soon as it can switch back to preselect mode, it will. RandomAccessQuery is used by an AdaptiveQuery when switching to dynamic mode. This is the most flexible query type (but also has the worst performance); it is necessary to support the Progress "dynamic" record retrieval semantic, where updates to a record made during the loop can change the results found later in the loop (an effect [side effect?] of Progress' index implementation). CompoundQuery is chosen for nested FOR loop conversions, where the component loops would convert to AdaptiveQuery instances or to a mixture of adaptive and preselect query variants. CompoundQuery is a driver to which Joinable (implemented by RandomAccessQuery and PreselectQuery) query instances may be added as components. |
GET |
P2JQuery.first P2JQuery.last P2JQuery.next P2JQuery.previous |
|
RELEASE |
RecordBuffer.release |
|
REPOSITION |
RandomAccessQuery.reposition |
|
OPEN QUERY |
AbstractQuery (abstract base
class); concrete implementations:
|
in the event of an OPEN QUERY
which references a query previously declared by a DEFINE QUERY, one
of these concrete classes is set into the corresponding concrete
wrapper implementation; otherwise, an instance of one of these
classes is used directly |
VALIDATE |
RecordBuffer.validate |
LockType
class. These constants are used by the persist package internally.
They appear in converted application code as arguments to various query
constructors and initialization methods, and in record retrieval methods (e.g.,
first
,
last
,
next
,
previous
,
current
, and
unique
), to correspond to explicitly specified lock requests in Progress record
phrases.
dmo_index.xml
document, which resides at the relative root of the
dmo
sub-package in the converted application's package hierarchy.
def var snippet as character initial "whatever".
for each customer
no-lock,
each invoice
where invoice.cust-num = customer.cust-num
and substring(invoice.category, 10, length(snippet)) = snippet
no-lock:
...
customer.name
reference embedded within the built-in function
substring
causes this code to convert to Java code which executes a client-side where
clause expression.
WhereExpression
. At runtime, the query is executed (at a high level) in two phases:
invoice.cust-num = customer.cust-num
component of the where clause can be expressed in HQL as
invoice.cust-num = ?
, where
?
is substituted at runtime with each
customer.cust-num
value found in the outer loop.
WhereExpression
, as the following code segment illustrates:
character snippet = new character("whatever");
WhereExpression whereExpr0 = new WhereExpression()
{
public Object[] getSubstitutions()
{
return new Object[]
{
new IntegerExpression()
{
public integer execute()
{
return snippet.length();
}
},
snippet
};
}
public logical evaluate(BaseDataType[] args)
{
return CompareOps.equals(character.substring(invoice.getCategory(), 10, (integer) args[0]), (character) args[1]);
}
};
evaluate()
method defined above is invoked for each
invoice
record retrieved by the server-side phase. The
WhereExpression
class and the various query implementations ensure that the
arguments/expressions returned by
getSubstitutions()
are resolved at the appropriate times during execution of the query loops (this
is important, since it preserves the Progress semantic by ensuring that any side
effects of invoking built-in or user functions -- such as state changes -- are
maintained). The resolved results of these arguments/expressions compose
the
args
parameter passed to
evaluate()
. If
evaluate()
returns
true
, the current
invoice
record is retained as a final query result; if it returns
false
, the record is dropped from the query's final results.
Validatable
, implemented as an inner class within
each
business logic class in which the field must be validated.
Validatable
implementation to be emitted in the target business class. It also
triggers an instance of the
Validatable
implementation class to be created and registered with the UI widget associated
with the validatable field. In addition to the above listed statements,
the FORM statement is given special consideration, because it may contain the
first reference in a frame to a validatable field, even though it does not
itself trigger validation. Surprisingly, the SET and UPDATE options of the
MESSAGE statement
do not
trigger field-level validation.
Method/Attribute |
Java
Equivalent |
Notes |
CURRENT-RESULT-ROW |
P2JQuery.currentRow() |
|
GET-FIRST() |
P2JQuery.first() |
|
GET-LAST() |
P2JQuery.last() |
|
GET-NEXT() |
P2JQuery.next() |
|
GET-PREV() |
P2JQuery.previous() |
|
HANDLE |
P2JQuery instance reference |
There is no method equivalent
in Java for this attribute; the "handle" of a P2JQuery object
is the object reference itself. Therefore, the conversion of
Progress code which dereferences the handle attribute from a query
object is simply to emit the Java query object reference. |
QUERY-OFF-END |
P2JQuery.isOffEnd() |
|
QUERY |
N/A |
Use of this attribute of
browse is superfluous in Java, in the customer cases encountered
thus far. Therefore, it is simply removed from the tree in a
customer-specific annotation ruleset. If it is determined that
this attribute is universally superfluous, this processing will be
relocated to a common ruleset. |
the case sensitivity of a
field or variable which is substituted at runtime for an HQL query
substitution marker is only maintained if that field or variable is a direct
operand of the comparison. For instance, the Progress where clause customer.name
= invoice.cust-name
might convert to an HQL expression upper(?)
= upper(invoice.cust-name)
, if both fields are case-insensitive.
However, the case sensitivity of customer.name
may not be
considered in the HQL generation if the field reference is more deeply
nested. For example, the Progress where clause substring(customer.name,
0, 5) = invoice.cust-name
would convert to the same HQL phrase shown earlier
if invoice.cust-name
is case-insensitive,
even if customer.name
was case-sensitive. This is considered
to be a relatively obscure case; it will be addressed as needed.where
myBuf.myField[?] = ...
), P2J diverges from Progress in the type of error
raised for a subscript out of bounds condition. Because the query
substitution is handled on the database server using this notation,
Hibernate simply comes back with an empty result set in the case where the
subscript is out of bounds, which will result in an error condition with
text ** <XXXX> record not on file. (138)
when a null DMO
is set as the current record in a record buffer. Progress raises a
stop condition with text ** Array subscript <X> is out of
range. (26)
.Statement |
Java
Equivalent |
Notes |
APPLY |
CommonFrame.apply() LogicalTerminal.apply() |
|
ASSIGN (input buffer form) | Rewritten using the INPUT
function such that this is converted as an explicit assignment
statement that directly reads the field's getter method in the
screen buffer. |
|
BELL |
LogicalTerminal.bell() |
|
CHOOSE |
CommonFrame.choose() | |
CLEAR |
CommonFrame.clear() CommonFrame.clearAll() |
|
COLOR |
LogicalTerminal.setColors() |
|
CREATE WIDGET |
not converted at this time | |
CREATE WIDGET-POOL |
not converted at this time | |
DEFINE BROWSE |
new BrowseWidget() | |
DEFINE BUTTON |
new ButtonWidget() |
|
DEFINE FRAME |
new FrameWidget() | |
DEFINE RECTANGLE |
not converted at this time | |
DELETE OBJECT |
not converted at this time | |
DELETE WIDGET |
not converted at this time |
|
DISABLE |
GenericWidget.disable() CommonFrame.disable() CommonFrame.disableExcept() |
|
DISPLAY |
CommonFrame.display() | |
DOWN |
CommonFrame.down() | |
ENABLE |
GenericWidget.enable() CommonFrame.enable() CommonFrame.enableExcept() |
|
FORM |
Converted into a frame
definition class. |
|
FRAME-VALUE |
LogicalTerminal.setFrameValue() |
|
HIDE |
LogicalTerminal.hideMessage() LogicalTerminal.hideAll() CommonFrame.hide() |
|
INPUT CLEAR |
LogicalTerminal.inputClear() |
|
MESSAGE |
LogicalTerminal.message() LogicalTerminal.messageBox() |
|
NEXT-PROMPT |
CommonFrame.nextPrompt() |
|
ON |
LogicalTerminal.registerRunnable() LogicalTerminal.deregisterRunnable() LogicalTerminal.remapKey() |
|
PAUSE |
LogicalTerminal.pauseBeforeHide() LogicalTerminal.pause() |
|
PROCESS-EVENTS |
LogicalTerminal.processEvents() |
|
PROMPT-FOR |
CommonFrame.promptFor() |
|
PUT-CURSOR | LogicalTerminal.putCursor() |
|
PUT-SCREEN | LogicalTerminal.putScreen() | |
READKEY |
KeyReader.readKey() |
|
SCROLL |
CommonFrame.scroll() |
|
SET |
CommonFrame.set() |
|
STATUS |
LogicalTerminal.statusDefault() LogicalTerminal.statusInputOff() LogicalTerminal.statusInput() |
|
TERMINAL |
LogicalTerminal.setTerminal() |
|
UNDERLINE |
LogicalTerminal.underline() CommonFrame.underline() |
|
UP |
CommonFrame.up() |
|
UPDATE |
CommonFrame.update() |
|
VIEW |
CommonFrame.view() |
|
WAIT-FOR |
LogicalTerminal.waitFor() |
Progress
Code |
Java
Equivalent |
Notes |
repeat i = 1 to 10: |
for (i = 1; i <= 10; i+= 1) |
The code after LEAVE is not
reachable. |
repeat i = 1 to 10: |
for (i = 1; i <= 10; i+= 1) |
The code after IF statement is
unreachable. |
label_xx: |
labelXx: |
The code after CASE statement
is unreachable. |
P2JIndexComponent
needs type information, or at
least we need to know when a component is a text component (JPRM watch
point @32647). Currently, this class' getNameCaseAware()
method does not have enough information to wrap a text component in the
rtrim()
SQL function, unless that component also happens to
be case-insensitive. This hurts us with temp table indexes,
because this method is used to create indexes on temp table at runtime,
and these indexes will not be usable by the database, because they will
not match the SQL we submit, which embeds the rtrim()
function to match Progress' behavior.PreselectQuery
)
during conversion. Currently, PreselectQuery.addComponent()
disallows adding cross-database query components at runtime as a
last-ditch safety check, but by then this is too late. This does
not appear to be an issu, but should be addressed for the long
term.rtrim()
SQL
function to match Progress' behavior, and for case-insensitive text
components, we further wrap that expression inside the upper()
SQL function.initdb
step. It may not be changed thereafter.F
when optimizing queries
such as select ... from ... where F is null...
Instead of
returning an empty result set immediately (since it is impossible
for F
to ever
be null based upon the NOT NULL constraint), it does a sequential
table scan (which can be very expensive for a large table, since
every row must be visited to determine there is no such
record). There may be a way to tune the query planner to be
smarter about this. Another alternative is to create a limited
range index (i.e., create index i on table t where id
is null
). This will result in a very fast response to this
type of query, but it has drawbacks: it adds another index to
maintain and is not portable, since not all databases will support a
where clause in a create index
statement.true
, are included in that index bracket.
We have determined that this must be an index bracketing defect
rather than an expression processing defect, because the same
sub-expression on a stand-alone basis (i.e., outside of a where clause)
behaves normally. That is, it matches the normal expression
processing rules for comparison with unknown value. An
illustration of this defect is provided with P2J uast testcase where-clause-index-unknown-value-bug.p
.
This defect was found using Progress v9.1C. We do not know
if it is present in other versions. At the time of this writing,
there are no plans to mimic this obviously incorrect behavior, since it
seems unlikely that there are legitimate use cases in production
code which would rely upon it. If any cases are found which
inadvertently rely upon this behavior, it is more likely that the
Progress source code should be fixed.?
symbol), and variable references (i.e., a direct comparison between a
database field and a variable set to unknown value at runtime).
"Inlined" here means these comparisons are replaced with the
appropriate {database_field} is [not] null
clause in the
HQL which is submitted to Hibernate. In the static case, the
replacement is done at conversion time; in the variable case, at
runtime (by the HQLPreprocessor). However, several corner cases
remain unaddressed:{database_field}
is [not] null
idiom). These cases can be addressed by expanding our
replacement/inlining coverage in both the conversion and in the
persistence runtime. Examples:where ... some-database-field >= ?
is handledwhere ...
some-database-field >= some-var
(where some-var
is set to unknown value at runtime) is handledwhere ...
some-database-field >= (? + 5)
(however unlikely) is not handledwhere ...
some-database-field >= (some-var + 5)
(where some-var
is set to unknown value at runtime) is not handledNULL
)
is passed from the database into a server-side (e.g., PL/Java)
function. It is unclear whether this is truly a problem, or
whether the server-side expression processing within a database already
correctly handles this situation. If truly an issue,
this problem potentially could be addressed by wrapping every
such instance with case...when...
handling logic, though
the impact on performance would have to be considered carefully.
I suspect this would add noticeable overhead (and could provide
for some very tortuous HQL), but since embedding the database
field within the PL function presumably already precludes any
index-based optimizations, the incremental hit may be acceptable.
Note that this is only an issue for nullable database columns
(non-mandatory fields in Progress-speak), so we may elect to at least
flag potential problem areas during conversion by recognizing the
condition during where clause conversion and issuing a warning to the
conversion log. Example:where ...
field-1 = dec(field-2)
(where field-2
is not mandatory and potentially represents unknown value) is not handled; although the implementation of the dec()
function correctly returns unknown value, this leaves us with an
equality comparison between a database column and NULL inside the SQL
database server, when it should really be processing field1 is null
. TODO: test whether such processing in an RDBMS really differs from Progress.testcases/uast/uselect2.p
testcase. When using a 2 DOWN
frame, the selection
list is shown as uninitialized for the iteration 1 and 3, and as
initialized for the iterations 2 and 4. This is expected. However,
starting with the iteration 5, all subsequent iterations show the
selection list as initialized. This does not happen when using 3
down
frame, for instance.[Router]SessionManager.connectVirtual(InetSocketAdress
address, String certAlias, SessionListener notify)
in the net
package. Currently, this method disables this check if a null certAlias
is provided, which is the common case. But there currently is no
mechanism for the caller of this method to easily determine what to pass
for a remote server's alias. Currently, the directory contains a default/runtime/port_services
path to enable lookups of port number by service name (since Java offers
no standard way to do this). We would probably want to merge this
into a larger table that took hostnames/IP addresses and certificate
aliases into account as well.AdaptiveQuery
should be more restrictive in its
decision to invalidated a preselected result set in the case of record
deletes and insertions. Specifically, these cases should only
invalidate the result set if the deleted/inserted record would actually
affect result set traversal.init()
method of the inner subclass of Block
which is emitted inside the method which represents the converted
function, so long as those top-level instance variables are never
needed. There is some scenario where these are
needed (not sure what that is at the time of writing), but in other cases, they are declared
and initialized, but never subsequently read. The testcase uast/query-test-for-scrolling.p
, for instance, has a function updateRecords(input valOld as int, input valNew as int)
. The valOld
parameter generates both an instance variable in the top-level class QueryTestForScrolling
, as well as an instance variable in the anonymous inner subclass of Block
within the updateRecords()
method. In the init()
method of that inner class, QueryTestForScrolling.this.valOld
is initialized to the value of the anonymous inner class' valOld
variable, but then the top-level valOld
is never accessed. Both the top-level class' valOld
instance variable, and the initialization of it in the inner class' init()
method (and in fact the entire init()
method, since this is all that is done there) could be eliminated as a code cleanup optimization.testcases/ui/revert1.p
testcase.testcases/ui/revert2.p
testcase.