Project

General

Profile

Expressions

A large percentage of business logic in a 4GL program is encoded as expressions. Expression support in FWD covers the majority of the commonly used features.

At its most elemental level, expressions encode calculations in an algebraic-like notation. The code in an expression can be organized in 3 broad categories: data values, operators and invocations of external logic.

The data that can be used in expressions includes all the data types documented in the Data Types chapter. Values can come from sources such as literals, variables and database fields, but all values must be one of the 4GL data types.

Operators are symbols that provide the algebraic-like notation as in the expression 5 + my-variable (the + sign is the operator and there are 2 operands). These encode low level operations on the data (operands).

Invocations of external logic such as function calls or method calls allow more complex processing to be encoded as "building blocks" which can be invoked from within an expression. All such invocation elements share the behavior that they all return a value of one of the possible data types and they all accept a list of 0 or more parameters which must be of the possible data types. This call/return invocation design allows the invocation to be executed and the result substituted into the expression as the expression is evaluated.

Each small expression can be combined using operators into an arbitrarily complex larger expression. Ultimately, each expression evaluates based on the precedence order of the operators in use. When all of the operations and invocations have resolved, every expression will evaluate to a single value of one of the possible data types.

The following is a summary of all valid elements of a 4GL expression and their level of support in FWD.

Element Type Purpose Supported Notes
Attribute Data Allows the expression to get and/or set predefined values (attributes) that exist in system resources (system handles) or in application resources (handles). The data types of attributes match up with the possible data types for variables. Attributes can never be user-defined. Yes  
COM Method Call Invocation Invokes a method on an OCX, ActiveX control or other COM object. Yes  
COM Property Data Allows the get and/or set of predefined values that exist in an OCX, ActiveX control or other COM object. Yes  
Database Field Reference Data When referenced as the left operand of the assignment operator, the field of the currently available record will be set to the value of the right operand. When referenced in any other position of an expression, this is the equivalent of accessing the value of the field for the currently available record. Yes See Part 5 for more details.
Function Call Invocation Progress 4GL provides built-in functions as well as a facility to implement user-defined functions in 4GL code. Both types of function can be called in expressions. These function calls can take a list of parameters and will return a result which from the perspective of the expression is the value to which the call evaluates. Built-in functions are not specific to any given resource (like a widget) but rather are globally defined code. Yes For details on how user-defined functions are emitted, please see the Blocks, Control Flow and Transactions chapter in this book.
Handle Data A handle reference is just a specialized form of a variable reference (see below in this same table). There are special considerations for handles that are not present for normal variable references. In particular, methods and attributes for widgets and other 4GL resources can be accessed via a handle and the : operator. Yes  
Literal Data Some data types can have a literal or constant value directly encoded in a 4GL expression. The value of such a literal is predetermined and is constant. Yes For details, see the Data Types chapter.
Method Call Invocation Allows the expression to invoke behavior/code which is specific to a given widget (or other 4GL resource) instance (methods). The resources are identified by a handle and are system level (system handles) or are application resources (handles). Parameters can be passed and the method call has a return value, just as in function calls. The data types of the parameters and return value match up with the possible data types for variables. Methods can never be user-defined. Yes  
Object Method Call Invocation Object-oriented language enhancements available in Progress 4GL v10 (and above) provide syntax for defining objects in 4GL code. Those objects can have user-defined methods which can be invoked similar to how Methods are supported on handles (see above). Yes For details, see Object Method Call.
Object Property Data Object-oriented language enhancements available in Progress 4GL v10 (and above) provide syntax for defining objects in 4GL code. Those objects can have user-defined data called properties, which can be accessed similar to how Attributes are supported on handles (see above). Yes For details, see the Data Types chapter.
Object Reference Data Object-oriented language enhancements available in Progress 4GL v10 (and above) provide syntax for defining objects in 4GL code. Instances of those objects can be instantiated, can have their methods and properties accessed and can be passed as parameters. Yes  
Operator Operator Operators are special symbols (sometimes glyphs like + or * and sometimes text like MATCHES) which describe operations which should be executed based on the data provided as operands. Using an algebraic style of notation, operators efficiently encode arithmetic, comparison and other operations inside expressions. Yes  
Variable Reference Data Stores data of a known type in a user-named storage location called a variable. References to variables evaluate to the current value being stored at the time of the evaluation. Yes For details, see the Data Types chapter.

In translating 4GL expressions into the semantically identical Java expressions, the behavior of each of the 3 categories of expression elements must be considered for suitability.

Invocation processing does have some differences, but Java has all the facilities needed to duplicate the exact behavior of the different invocation mechanisms of the 4GL. So this is a one to one match.

While there are many similarities between Java primitive types and the Progress data types, the types themselves are not equivalent. For this reason, all of the Java replacement data types have been implemented as wrapper classes which are designed to duplicate the 4GL data type behavior exactly. For details on this, please see the Data Types chapter.

Similar to the problem with data types compatibility, Progress 4GL operators have behaviors that cannot be duplicated using the Java operators. For example, the 4GL operators have very specific (sometimes non-intuitive) behavior regarding how the unknown value is processed. Java operators have no concept of the unknown value. In addition to this problem, since the data types being processed on the Java side are wrapper classes (custom Java classes), Java provides no way to natively extend operators to take instances of custom classes as operands. Another way to say this, is that since Java does not yet provide operator overloading, there is no way to extend the syntax of the language to handle the new (4GL compatible custom) types as if they were true primitive data types. This means that all 4GL operators must be replaced using Java method calls (static or instance methods as may make sense in each case).

In the converted Java code, 4GL literals will usually appear as Java literals (see the Data Types chapter for details). Since Progress 4GL does not differentiate between literals and the core data types, these can be freely intermixed in expressions, wherever a value of a given type can appear. This means that the Java methods that replace operator usage, function calls and other 4GL features must be able to accept a mixture of Java primitive types (e.g. int for a 4GL integer literal) and Java classes (e.g. com.goldencode.p2j.util.integer) interchangeably. Since Java does not expose a mechanism for user-defined type auto-boxing, the solution is to use method overloading to provide all the necessary method signatures to accept the different possible combinations. Note that numeric data has the extra complication that Progress 4GL can usually accept integer and decimal types (and their literals) interchangeably. This means that the Java code must handle additional combinations for numerics. At least both com.goldencode.p2j.util.integer and com.goldencode.p2j.util.decimal both extend com.goldencode.p2j.util.NumberType which often allows that class to be used in method signatures instead of having variants for both child classes. Depending on the case, int and double may still need to be dealt with separately, though Java does provide widening conversions that help here.

Where using method overloading is not convenient or feasible, the converted code will "wrap" or "unwrap" sub-expressions as needed. For example, when the converted version of a user-defined function is called (it is a method in Java), it will have a signature that only accepts the BaseDataType wrappers. It won't accept int in place of integer. For this reason, the sub-expression that is passed for each parameter might have to be wrapped using a constructor of the right type (e.g. the literal 14 would become new integer(14)).

The issues with data type wrappers and operator overloading result in the following core implementation choice for the Java expressions: they cannot retain the algebraic-like syntax (often called "infix" notation). Instead, the expression must be re-factored into a series of nested method calls which will naturally form a more "postfix" notation.

As an example, here is a 4GL expression:

define variable my-variable as int init 14.

function my-doubler-func returns int (input x as int):
   return x * 2.
end.

my-doubler-func((my-variable + 30) / 6) modulo 2.

This expression emits in Java as this code (variable and function definitions are excluded):

modulo(myDoublerFunc(divide(plus(myVariable, 30), 6)), 2);

Until Java gets operator overloading, this will be required. The advantage of this Java version is that the precedence order is very explicit, making the evaluation of the result something that can be easily read. On the 4GL side, the reader must "calculate" the precedence themselves. On the other hand, there is value in the algebraic-like infix syntax which is currently not possible.

The rest of this chapter discusses the details of the conversion for expression elements which are not discussed elsewhere in this book.

Attributes, Methods and Handles

Many of the built-in resources of the 4GL have named values and logic which can be accessed directly in expressions. These resources might best be described as "handle-based objects", to differentiate them from the new object-oriented classes (user-defined and built-in) and from COM objects (non-4GL Windows objects). Handle-based objects include all the various widget types, frames, buffers, queries, temp-tables and other similar resources.

Instances of these resources are sometimes referenced via a user-defined name and sometimes via a handle. A handle is simply a named user-defined variable that can reference another resource. Syntactically, access to attributes and methods on that handle is the same as if the resource was being directly accessed. However, the handle can "point" to different resources at different times, depending on the resource that was last assigned to the handle. This indirect form of access to a resource is where the "handle-based object" description comes from.

Resources in a 4GL program can be static. Static resources have user-defined names and are created at the time the 4GL program is compiled using a DEFINE statement or some implicit definition. Static resources can be accessed by name or by handle. Resources can also be dynamically instantiated at runtime by using a CREATE statement. Dynamic resources can only be accessed by handle (they never have a user-defined name).

Any resource that is not created by the 4GL code is generally a global resource and such resources are accessed using system handles. These are handle instances that are global to the session in which the 4GL program runs. Such resources do have a named handle, but that handle cannot be assigned and does not operate like a normal handle variable.

When the 4GL defines named data values on one of these resources, it is called an attribute. All attributes must be of one of the core data types that are supported in the 4GL (e.g. character). These attributes cannot be user-defined and they cannot be extended. Each resource has a unique list of supported attributes and any instance of that resource can have those attributes read (accessed) and sometimes written (assigned).

When the 4GL defines a named function that is associated with an application or system resource, it is called a method. All methods return a data value which must be one of the core data types that are supported in the 4GL (e.g. character). The method is like a block of logic that can be invoked like a function (a method call). These method calls can take parameters just as function calls take parameters. Methods cannot be user-defined and they cannot be extended. Each resource has a unique list of supported methods and any instance of that resource can have those methods called by any code that has a reference to that resource. The idea is that expression can invoke behavior/code which is specific to a given widget (or other 4GL resource) instance (methods).

These 3 elements are highly related in how they convert into Java, since their behavior is tightly coupled in the 4GL. For this reason, they share a common section in this chapter.

The basic syntax for this construct is as follows.

4GL Syntax Purpose Notes
referent:attribute_name [IN container] Attribute access. This is a sub-expression that can be specified anywhere a value of the given type can be specified. Usually, this is part of a larger expression, but technically it can stand on its own (which is not very useful).
referent:attribute_name [IN container] = expression Attribute assignment. For those attributes which are writable (can be modified under the 4GL programmer's control), this form allows the value of the expression to be assigned to the attribute.
referent:method_name([expression, …]) [IN container] Method call. Allows invocation of resource-specific logic on a specific instance of that resource. The method call is a sub-expression that can be included anywhere that a data value can be specified. Since method calls return a value of one of the core data types, evaluation of the method call can naturally be handled as a sub-expression.

Referent Syntax

The referent can be specified in the following ways:

4GL Syntax Notes
static_object_name … [IN container] User-interface widgets can be directly referenced by name (which is the same as the name of the variable or field from which the widget was derived). This is a special case of the next syntax form below. The IN container syntax is optional and can be used when the same widget name can be found in more than one frame.
object_type static_object_name … [IN container] This form is the general purpose form used to reference static application resources. While widgets have a shorthand form, all other static resources must be qualified by their object type. This is a consequence of the complex and separate namespaces that the 4GL provides for each resource type.

Static resource types include frame, query, stream, browse, menu, sub-menu, menu-item, table-handle, dataset handle, buffer, table, temp-table, dataset, data source and data relation.

As with the widget shorthand, the IN container syntax is optionally used to disambiguate the same static object name when it can be found in multiple containers.
handle_expression This is a sub-expression which evaluates to a handle. The most common form of this is the simple handle variable reference. Among other options it is also possible to call a function that returns a handle or to reference a temp-table field that has the handle data type. A handle reference can be assigned any type of resource (static or dynamically created), including assignment from a system handle.
system_handle_name The name of a valid 4GL system handle. These are all built-in to the Progress 4GL language. System handles cannot be created by application code, although some system handles provide access to application resouces.

Statically Defined Object Referents

In the 4GL, direct references to statically defined objects can only reference application-level resources. These are the first two of the referent syntax types shown above. More specifically, the attributes and methods called on application resources must operate on those specific resource instances. The data values returned or set, the logic invoked must all be instance-specific. In the converted Java code, any attributes and methods accessed on such resources would naturally be exposed as instance methods. This means that the specific Java object instances that represent the application-level resources must be emitted as the referent. So long as the replacement Java methods exist for the 4GL attributes and methods being accessed, those Java methods can be invoked directly on the objects.

The following is a 4GL example:

define variable num  as integer.
display num with frame f0.
num = num:dcolor.

This is how the num:dcolor attribute is emitted in Java (everything else is omitted for the sake of brevity):

f0Frame.widgetNum().getDcolor()

The f0Frame.widgetNum() is the sub-expression that returns the widget named num which is the referent.

Note that the IN container syntax has no obvious affect on the converted output. It allows the intent of the 4GL code to be matched with the right resource instance, but the resulting Java output will look the same. The only difference will be in which widget (or other application resource) is selected as the referent. Since the mechanism for accessing resources in Java is much more explicit, the converted result will look the same in the end (there is no need for anything like the IN container syntax in Java).

Widgets in Java are stored and accessed from the containing frame. But other resources are more directly referenced as instance members of the containing Java business logic class. Here is another 4GL example:

def temp-table tt field i as int.
define query qry for tt.
open query qry for each tt.

num = query qry:num-buffers.

This shows the same direct access to a statically defined object (a query in this case), but with the 2nd syntax form that prefixes the object type in front of the resource name (query qry in this case). Just as with the IN container syntax, the object type is not necessary in the Java code since every Java object has its type predetermined at the time it is instantiated. The type in Java is the class of the instance. Here is a small portion of the converted Java code:

query0.numBuffers()

This is only the portion that replaces the query qry:num-buffers. Since non-widget resources exist as instance variables in the business logic class, they are emitted as direct object references in Java. This yields a clean and simple result.

Handle Referents

This section discusses how handle variables can be referenced to access attributes or methods. For other (more general) details on how the handle data type works, please see the chapter on Data Types.

Handles are an indirect referent mechanism in the 4GL. The most common use case is a variable of type handle which then can be assigned to "point" to any application or system resource. Since the variable has a name, the 4GL syntax for this indirect reference does not make it clear that the reference is indirect. Here is an example:

define variable hndl as handle.
...
hndl = query qry:handle.
num = hndl:num-buffers.

In the converted Java, an instance of com.goldencode.p2j.util.handle class is used as the replacement variable that can contain or "wrap" a reference to some resource. Here is the replacement Java code:

handle hndl = new handle();
...
hndl.assign(query0.asHandle());
num.assign(hndl.unwrapBufferCollection().numBuffers());

Since the hndl variable is not of type BufferCollection, the numBuffers() method is not available. Instead, when handles are used, the contained object reference is obtained using handle.unwrapBufferCollection(), so it is unwrapped into the proper type (BufferCollection in this case) before being used as a referent. The resulting Java code in this case makes it more explicit that the use of handles is an indirect access mechanism.

The type object into which the handle is unwrapped is determined by the class needed to access the requested 4GL attribute or method. The Java implementation of attributes and methods ensures that the same class or interface can always be used to access specific attributes or methods, even if there are multiple different resources that can be used as referents. For example, the num-buffers attribute is found in the BufferCollection interface, which is implemented in all of the different Java query types.

This unwrapping is implemented whenever a handle is used as a referent with the colon : operator used to access attributes or methods.

System Handle Referents

System handles are global names in a 4GL program that usually reference system resources and sometimes can reference application resources. The resource which is referenced is managed by the Progress 4GL runtime and the 4GL programmer cannot assign a different resource to any of the system handles. When a system handle provides access to an application resource, it can be thought of as a special well-known name that provides access that would otherwise be more difficult or not possible. For example, the SELF system handle provides access to the current widget within the context of the current trigger or event procedure. Another example: THIS-PROCEDURE provides access to the currently executing procedure. Other system handles provide access to resources that are global to the 4GL session.

The Java conversion of a system handle differs depending on the kind of resource that is referenced from the system handle.

System handles that represent application resources are just another way to access that same resource (see the other referent types above). This means that application resource system handles should convert to Java code that either returns or instantiates an instance of the application resource being referenced. The following table provides details on the conversion of application resource system handles.

4GL Purpose Java Supported Notes
ACTIVE-WINDOW The last application window to receive an ENTRY event. In CHUI, there is only ever a single global window. LogicalTerminal.activeWindow() Yes  
COLOR-TABLE Accesses the current color-table (depends on the environment being used). ColorTable.asHandle() Yes  
COM-SELF Accesses the current ActiveX control that generated the event being processed. n/a No  
CURRENT-WINDOW Accesses (and allows assignment of) the current default window for the session. LogicalTerminal.currentWindow() Yes  
DEFAULT-WINDOW Accesses the session default window. LogicalTerminal.defaultWindow() Yes  
FOCUS Accesses the current widget that has the input focus. LogicalTerminal.focus() Yes  
FONT-TABLE Accesses the current font table in use (depends on the environment being used). FontTable.asHandle() Yes  
SELF In a trigger or event procedure, this accesses the widget that generated the event being processed. LogicalTerminal.self() Yes  
SOURCE-PROCEDURE Accesses the calling procedure that executed the RUN or function call. ProcedureManager.sourceProcedure() Yes  
TARGET-PROCEDURE Accesses the containing procedure for the currently called internal procedure or function. ProcedureManager.targetProcedure() Yes  
THIS-PROCEDURE Accesses the currently executing procedure. When used in WAIT-FOR, APPLY or ON statements LogicalTerminal.currentWindow() Yes  
THIS-PROCEDURE Accesses the currently executing procedure. Otherwise ProcedureManager.thisProcedure() Yes  

Where static methods of LogicalTerminal are used in the above table, the actual result will have the following form (this example is for CURRENT-WINDOW:SENSITIVE):

import static com.goldencode.p2j.ui.LogicalTerminal.*;
…
currentWindow().isSensitive()

The Java replacement for attribute and method usage uses an instance method (in this case isSensitive()) on the instance that is returned by the static method currentWindow() which is of type com.goldencode.p2j.ui.WindowWidget. This allows the specific state of that instance to be read, written or processed upon. It also leaves a simple and clean Java source code result. See the Supported Attributes and Supported Methods sections below for details on how specific attributes and methods convert.

The rest of available system handles provide access to global session resources. The following table provides details on the conversion of global session system handles.

4GL Purpose Supported Notes
AUDIT-CONTROL Controls audit settings for the session. No FWD just implements stub methods for the AUDIT-CONTROL attributes/methods
AUDIT-POLICY Controls the current audit policies. No FWD just implements stub methods for the AUDIT-POLICY attributes/methods
CLIPBOARD Accesses the system clipboard for copy, cut and paste. No FWD just partially implements the CLIPBOARD attributes/methods
CODEBASE-LOCATOR Accesses the location and authentication data for appserver clients. No  
COMPILER Accessed data on the most recent compile operation. No  
DEBUGGER Accesses the session debugger. No FWD just implements stub methods for the DEBUGGER attributes/methods
DSLOG-MANAGER Accesses the DataServer logging manager. No  
ERROR-STATUS Accesses that error state for the most recently executed statement. Yes  
FILE-INFO Accesses data about a file available in the platform's file system. Yes Only one file can be accessed at a time, so this is best supported using static methods as if it was a true session level resource.
LAST-EVENT Accesses data on the last event the 4GL code received. Yes  
LOG-MANAGER Accesses session level logging settings. Yes  
RCODE-INFO Accesses the R-CODE for a specific file. No Only one file can be accessed at a time, so this is best supported using static methods as if it was a true session level resource.
SECURITY-POLICY Accesses security policy settings. Yes  
SESSION Accesses session level settings. Yes  
WEB-CONTEXT In WebSpeed, this provides access to the context and settings for the current web request being processed. No  

The system handles which access session-level resources convert directly to static method calls that are specific replacements for the given 4GL attribute or method. Consider the following 4GL code:

file-info:file-name = "example.txt".
if file-info:file-size gt 0 then message "It exists!".

This converts to the following Java code:

FileSystemOps.initFileInfo(new character("example.txt"));
if (_isGreaterThan(FileSystemOps.fileInfoGetSize(), 0))
{
   message("It exists!");
}

There is no instance of an object which replaces the referent. Instead, static methods of the FileSystemOps class provides the session-level access to the attributes available in the FILE-INFO system handle.

See the Supported Attributes and Supported Methods sections below for details on how specific attributes and methods convert.

It is also important to point out that no matter how the system handles convert (to an instance of an object upon which methods are called OR as static method calls), there is no case where any casting is needed of the result. Java casts are only used for regular handle referents, not for system handles.

How Attributes Convert

There are 4 possible ways that 4GL attributes can convert to Java. Two of the cases are general purpose conversions and the other two are special cases for the HANDLE and SCREEN-VALUE attributes.

Mechanism Description
Static method call. This is only used for attribute access in session-level system handles.
Method call on a specific object instance. Used for attribute access in application-level resources including all other system handle attributes not handled above.
Simple reference. The HANDLE attribute does not convert to any kind of method call. In Java, the instance of the object that represents an application-level resource needs no special wrapping or container. This 4GL attribute is essentially dropped and instead, the resource itself is left to emit as a simple Java reference.
Custom frame-level method call. The SCREEN-VALUE attribute converts to a custom method call that does not conform to any of the other mechanisms. This method call is made on the containing frame and the widget that is being read (or assigned) to is passed as the first (or only) parameter.

Where an attribute is writable (can be assigned), the method used will be different. This means that assignment uses a setter instance method and reading an attribute value uses a getter instance method. Not all attributes are writable in the 4GL so some attributes only have getters.

The following examples will demonstrate each mechanism.

This 4GL example will convert as a static method.

if error-status:error then
   message "There were " + string(error-status:num-messages) + " errors.".

This is the resulting Java.

if ((ErrorManager.isError()).booleanValue())
{
   message(concat("There were ", valueOf(ErrorManager.numErrors()), new character(" errors.")));
}

As an example, ERROR-STATUS:ERROR converts directly to ErrorManager.isError(). There is no counterpart for the system handle referent since this is a static method.

/* direct widget access, attribute read */
bool = num:visible.

/* direct widget access, attribute assignment */
num:visible = bool.

/* direct widget access, HANDLE attribute read */
hndl = num:handle.

/* handle-based widget access, attribute read */
bool = hndl:visible.

/* handle-based widget access, attribute assignment */
hndl:visible = bool.

The resulting Java looks like this:

/* direct widget access, attribute read */
bool.assign(f0Frame.widgetNum().isVisible());

/* direct widget access, attribute assignment */
f0Frame.widgetNum().setVisible(bool);

/* direct widget access, HANDLE attribute read */
hndl.assign(f0Frame.widgetNum().asWidgetHandle());

/* handle-based widget access, attribute read */
bool.assign(hndl.unwrapVisible().isVisible());

/* handle-based widget access, attribute assignment */
hndl.unwrapVisible().setVisible(bool);

The key here is that an instance of handle is extracted right from the widget (using asWidgetHandle) or the method is applied on a transparently casted resource (HasVisible) By "transparently", FWD allows the handle to be unwrapped only to some predetermined and granular interfaces (like HasVisible).

This is another instance method example. It shows a non-widget resource.

num = query qry:num-buffers.
hndl = query qry:handle.
num = hndl:num-buffers.

This is the resulting Java code.

num.assign(query0.numBuffers());
hndl.assign(query0);
num.assign(hndl.unwrapBufferCollection().numBuffers());

There is no attribute assignment in this case (NUM-BUFFERS is only readable). The key point is that an instance of type BufferCollection is obtained and the numBuffers() method is called to access the attribute's value.

Here is an example where the referent is a system handle that is referencing an application-level resource.

if this-procedure:persistent then delete procedure this-procedure.

This is the resulting Java code.

if ((thisProcedure().unwrapPersistableProcedure().isPersistent()).booleanValue())
{
   ControlFlowOps.deleteProcedure(thisProcedure());
}

The only difference in approach is how the referent instance is obtained. The procedure handle (thisProcedure) is retrieved and then the instance method isPersistent() is called on that object.

Another application-level system handle example:

on end-error of num
do:
   message self:name.
end.

This use of the SELF system handle converts like this:

registerTrigger(new EventList("end-error", f0Frame.widgetNum()), Expr.this, new Trigger((Body) () -> 
{
   message(self().unwrap().name());
}));

The code that registers the END-ERROR event is left out for brevity. The key here is that the SELF system handle translates the referent into a static method call to LogicalTerminal.self() which returns a type of GenericWidget. The name() method is called on that instance. In the code above, a static import of the LogicalTerminal methods makes the method call simply self().

The following shows both the HANDLE and the SCREEN-VALUE special cases.

define variable hndl as handle.
define variable num  as integer.
define variable txt  as character.

display num txt with frame f0.
/* direct widget access, attribute assignment */
num:screen-value = "30".

/* direct widget access, attribute read */
if num:screen-value ne 30 then message "Problem!".

/* direct widget access, HANDLE attribute read */
hndl = num:handle.

/* handle-based widget access, attribute assignment */
hndl:screen-value = "14".

/* handle-based widget access, attribute read */
if hndl:screen-value ne 14 then message "Problem!".

This shows assignment and reading of SCREEN-VALUE from a referent that is a statically defined object name and from a handle. The following is the Java code that results (only the attribute portion is included):

/* direct widget access, attribute assignment */
f0Frame.widgetNum().setScreenValue(new character("30"));

/* direct widget access, attribute read */
if (_isNotEqual(f0Frame.widgetNum().getScreenValue(), 30))
{
   message("Problem!");
}

/* direct widget access, HANDLE attribute read */
hndl.assign(f0Frame.widgetNum().asWidgetHandle());

/* handle-based widget access, attribute assignment */
hndl.unwrapWidget().setScreenValue(new character("14"));

/* handle-based widget access, attribute read */
if (_isNotEqual(hndl.unwrapWidget().getScreenValue(), 14))
{
   message("Problem!");
}

The SCREEN-VALUE attribute reads convert to a general syntax of widget.getScreenValue(). The SCREEN-VALUE attribute assignments convert to widget.setScreenValue(new_character_value). The SCREEN-VALUE attribute is a character attribute, even though the widget's data type may have a type other than character. This is just a quirk of the 4GL.

The num:handle attribute converts to that the f0Frame.widgetNum() accessor in this case. A simpler example is this 4GL:

hndl = query qry:handle.

The converted Java result:

hndl.assign(query0);

In this case, the query qry:handle converts to query0, which is the Java local variable name for the instance of that resource. It is assigned into the hndl local variable using the assign() method. The HANDLE attribute itself is not writable, so there is no corresponding assignment form.

Supported Attributes

Any attribute not listed in the following table is not supported. Since there are many more attributes in the 4GL than are supported in FWD, this decision keeps the size of the table to a manageable level. The first column shows the 4GL syntax, the Java Read column shows the Java code that will emit to access (read) the value of the attribute and the Java Write column shows the Java code that will emit to assign (write to) the attribute (if possible). The Cast Class column indicates the interface to which a handle is unwrapped before calling the instance method OR if it is set to n/a, this indicates that the converted result is a static method call (which is only used for session-level system handle conversion).

4GL Cast Class Java Read Java Write (Assignment)
BATCH-MODE CommonSession isBatchMode() n/a
BGCOLOR CommonWidget getBgColor() setBgColor()
CHARSET CommonSession getCharset() n/a
CODE CommonLastEvent getLastKey() n/a
COLUMN Coordinates getColumn() setColumn()
COM-HANDLE ControlFrame getComHandle() n/a
CURRENT-RESULT-ROW P2JQuery currentRow() n/a
CURRENT-WINDOW PersistentProcedure currentWindow() setProcCurrentWindow()
DATA-TYPE CommonField getDataType() setDataType()
DATE-FORMAT CommonSession getDateFormat() setDateFormat()
DCOLOR CommonWidget getDcolor() setDcolor()
DISPLAY-TYPE CommonSession getDisplayType() n/a
ERROR Errorable error error
FGCOLOR CommonWidget getFgColor() setFgColor()
FILE-CREATE-DATE CommonFileInfo FileSystemOps.fileInfoGetCreationDate() n/a
FILE-CREATE-TIME CommonFileInfo FileSystemOps.fileInfoGetCreationTime() n/a
FILE-MOD-DATE CommonFileInfo FileSystemOps.fileInfoGetModDate() n/a
FILE-MOD-TIME CommonFileInfo FileSystemOps.fileInfoGetModTime() n/a
FILE-NAME CommonFileInfo getFileName() initFileInfo()
FILE-SIZE CommonFileInfo fileInfoGetSize() n/a
FILE-TYPE CommonFileInfo fileInfoGetType() n/a
FIRST-CHILD CommonHandleTree firstChild() n/a
FORMAT CommonField getFormat() setFormat()
FRAME CommonWidget getFrameHandle() setFrameHandle()
FULL-PATHNAME CommonFileInfo fileInfoGetFullPath() n/a
FUNCTION CommonLastEvent getLastFunction() n/a
HEIGHT-CHARS Sizeable getHeightChars() setHeightChars()
HEIGHT-PIXELS Sizeable getHeightPixels() setHeightPixels()
HIDDEN CommonWidget isHidden() setHidden()
ICON CommonWindow getIcon() n/a
IMAGE CommonWidget getImage() n/a
INTERNAL-ENTRIES PersistentProcedure internalEntries() n/a
KEEP-FRAME-Z-ORDER CommonWindow isKeepZOrder() setKeepZOrder()
LABEL CommonField getLabel() setLabel()
LAST-CHILD CommonHandleTree lastChild() n/a
LIST-ITEM-PAIRS CommonListWidget getListItemPairs() setListItemPairs()
MAX-HEIGHT CommonWindow getMaxHeight() setMaxHeight()
MAX-WIDTH CommonWindow getMaxWidth() setMaxWidth()
MESSAGE-AREA CommonWindow isMessageArea() setMessageArea()
MULTIPLE Multiple isMultiple() setMultiple()
NAME CommonHandleChain name() name()
NEXT-SIBLING CommonHandleChain getNextSibling() n/a
NEXT-TAB-ITEM CommonWidget getNextTabItem() n/a
NUM-BUFFERS BufferCollection numBuffers() n/a
NUM-COLUMNS CommonWidget getNumColumns() n/a
NUM-MESSAGES CommonErrorStatus numErrors() n/a
NUM-SELECTED-ROWS CommonWidget getNumSelectedRows() n/a
PARENT CommonWidget getParentHandle() setParentHandle()
PATHNAME CommonFileInfo fileInfoGetPathName() n/a
PERSISTENT PersistableProcedure isPersistent() setPersistent()
PFCOLOR CommonWidget getPfColor() setPfColor()
PREV-SIBLING CommonHandleChain getPrevSibling() n/a
PRIVATE-DATA CommonHandleChain getPrivateData() setPrivateData()
QUERY-OFF-END P2JQuery isOffEnd() n/a
READ-ONLY WriteProtectable isReadOnly() setReadOnly()
RESIZE CommonPane isResize() setResize()
ROW Coordinates getRow() setRow()
SCROLL-BARS CommonWindow isScrollBars() setScrollBars()
SCROLLABLE CommonWidget isScrollable() setScrollable()
SELECTED CommonWidget isSelected() setSelected()
SENSITIVE Sensitive isSensitive() setSensitive()
STATUS-AREA CommonWindow isStatusArea() setStatusArea()
TABLE DatabaseInfo getTable() n/a
TEMP-DIRECTORY CommonSession getTempDirectory() n/a
THREE-D CommonWidget isThreeD() setThreeD()
TITLE CommonWidget getTitle() setTitle()
TOOLTIP CommonWidget getTooltip() setTooltip()
TYPE CommonHandle getResourceType() n/a
VISIBLE HasVisible isVisible() setVisible()
VIRTUAL-HEIGHT-CHARS CommonWidget getVirtHeight() setVirtHeight()
VIRTUAL-WIDTH-CHARS CommonWidget getVirtWidth() setVirtWidth()
WIDTH-CHARS CommonWidget getWidthChars() setWidthChars()
WIDTH-PIXELS CommonWidget getWidthPixels() setWidthPixels()
X Coordinates getX() setX()
Y Coordinates getY() setY()

How Methods Convert

Compared to attribute conversion, method conversion is very simple. The referent processing is identical for both attributes and methods, but methods don't have read versus write modes. 4GL methods are simply translated into a replacement Java method call that has the same signature. In other words, there will be a Java method that returns the same data type as the 4GL method and both methods will accept the same parameter lists (the same types in the same order). There are no special cases like HANDLE and SCREEN-VALUE. For details on the general approach, please see the How Attributes Convert section above.

The following 4GL example will illustrate:

num:move-after-tab-item(txt:handle).

This is the resulting Java code:

f0Frame.widgetNum().moveAfterTab(f0Frame.widgetTxt().asWidgetHandle());

The parameters convert however the sub-expression converts in FWD. There is nothing special done with the parameters. The above example just happens to use the HANDLE attribute, but that is a coincidence due to the choice of the 4GL MOVE-AFTER-TAB-ITEM method.

The only issue to note is that there are some special cases in the 4GL, where parameters are not valid expressions. For example, this 4GL usage:

query qry:get-first(SHARE-LOCK).
hndl:get-next(SHARE-LOCK, NO-WAIT).

FWD can convert this to Java, but the parameter handling is broken at this time:

query0.getFirst(LockType.LT_SHARE);
hndl.unwrapQuery().getNext(LockType.LT_SHARE);

Supported Methods

Any method not listed in the following table is not supported. Since there are many more methods in the 4GL than are supported in FWD, this decision keeps the size of the table to a manageable level. The first column shows the 4GL syntax, the Java column shows the Java code that will emit to invoke the functionality. The class column indicates the class or interface to which a handle is unwrapped to before calling the instance method OR if it is set to n/a, this indicates that the converted result is a static method call (which is only used for session-level system handle conversion).

4GL Cast Class Java
ADD-FIRST CommonListWidget addFirst()
DESELECT-ROWS CommonWidget deselectRows()
ENTRY CommonListWidget entry()
FETCH-SELECTED-ROW GenericWidget fetchSelectedRow()
GET-BROWSE-COLUMN BrowseInterface getBrowseColumn()
GET-FIRST P2JQuery getFirst()
GET-LAST P2JQuery getFirst()
GET-MESSAGE CommonErrorStatus getErrorText()
GET-NEXT P2JQuery getNext()
GET-NUMBER CommonErrorStatus getErrorNumber()
GET-PREV P2JQuery getPrevious()
IS-SELECTED CommonWidget isSelected()
LOAD-ICON CommonWindow loadIcon()
LOAD-IMAGE ImageSupport loadImage()
LOOKUP CommonListWidget lookup()
MOVE-AFTER-TAB-ITEM CommonWidget moveAfterTabItem()
MOVE-TO-BOTTOM CommonWidget moveToBottom()
MOVE-TO-TOP CommonWidget moveToTop()
REFRESH CommonWidget refresh()
SELECT-ALL CommonWidget selectAll()
SELECT-FOCUSED-ROW BrowseInterface selectFocusedRow()
SET-REPOSITIONED-ROW BrowseInterface setRepositionedRow()
VALIDATE CommonWidget validateFields()

Limitations

Many system handles, attributes and methods are not supported. See above for the lists of supported system handles, attributes and methods respectively. In particular, since the list of attributes and methods that are not supported is much longer than the list of supported attributes and methods, the above lists only show the supported attributes and methods. If an attribute or method does not appear in the list, it is not supported.

Method and attribute chaining is not supported at this time. Only non-chained references can be properly converted.

Certain sources of referent are not fully supported at this time. In particular, this affects the use of functions which return a handle and temp-table fields of handle type. Some forms of these referents will emit as noted above, but some forms may not yet work.

System handles that convert to static method calls do not properly convert when accessed via a local handle variable that was assigned from the system handle. This use case is possible in the 4GL, but it is not yet supported in FWD.

Procedure handle support is provisional (not fully functional) and is subject to change.

Wherever method parameters are normal 4GL expressions, they will emit normally and without problems. Method parameters which are not valid 4GL expressions (e.g. the NO-LOCK keyword in a GET-FIRST method call) will require extra conversion processing, which may or may not be fully implemented at this time.

There is experimental support for the use of resources generated using CREATE WIDGET. At this time, the code is not functional and it is subject to change.

COM Method Call

Supported. Documentation TBD.

COM Property

Supported. Documentation TBD.

Function Call

From a logical perspective, there are 2 types of function calls: user-defined functions and built-in functions.

A user-defined function is a named block of code that is implemented in the application and which can be invoked by name as a sub-expression, it can optionally accept parameters of known data types and it returns a value of a known data type. To access a user-defined function, it must be defined using the FUNCTION statement in the local file. Normally, the FUNCTION statement defines the signature as well as the code block that is called on invocation. It is also possible for the FUNCTION statement to be implemented as a FORWARD declaration of the function's signature only (with the code block implementation implemented later) or as an IN procedure_handle clause which declares the function's signature but specifies that the implementation exists in a procedure that can be accessed via a specific procedure handle. Any of the above mechanisms are sufficient to declare the function such that it can be called by all subsequent expressions in the local file. For details on user-defined functions and the conversion of the FUNCTION statement, please see the Blocks chapter.

A built-in function is a feature of the Progress 4GL runtime. It does not need to be defined or forward declared. Most built-in functions are invoked (called) using the same syntax as user-defined functions. As with most things in the Progress 4GL, there are exceptions to this rule. A subset of built-in functions have unique syntax that does not match the standard function call.

The standard function call conversion is as follows:

4GL Java Purpose
user-defined-function-name() userDefinedFunctionName() User-defined function defined in the current procedure without any parameters. User-defined functions are always converted into non-static methods in the business logic class that is the Java result of a 4GL procedure. As such, no referent is needed to call those methods from within that same class. Of course, the 4GL function name is converted into a valid Java method name. Otherwise the syntax is the same.
user-defined-function-name(parm1, parm2) userDefinedFunctionName(parm1, parm2) User-defined function defined in the current procedure with 2 parameters. This is the same approach as the no parameter user-defined function version above, except that the parameter expression list is emitted as a comma-separated list inside the parenthesis. This is the same syntax approach as the 4GL, except that each sub-expression that is a parameter will be converted just as any 4GL to Java expression would be converted.
built-in-function-name() ClassName.builtInFunctionName() OR referent.builtInFunctionName() OR new wrapper_type() Most common built-in function usage without any parameters. There are 3 common cases that will result. The most common Java approach will emit as a static method call to some FWD runtime class that contains the backing method that matches that built-in. The next most common conversion approach is to emit as an instance method call (a non-static call) on some known referent. This is naturally used when the built-in function in the 4GL operates on some specific resource (e.g. accumulators or streams). The last (and least common) conversion approach is to emit a constructor for one of the BaseDataType wrapper classes. This is used for the data type conversion built-ins. Since there are no user-defined names, there is no name conversion here. The input name and the output names are fixed and are known in advance.
built-in-function-name(parm1, parm2) ClassName.builtInFunctionName(parm1, parm2) OR referent.builtInFunctionName(parm1, parm2) OR new wrapper_type(parm1, parm2) Most common built-in function usage with 2 parameters. This is the same approach as the no parameter built-in function version above, except that the parameter expression list is emitted as a comma-separated list inside the parenthesis. This is the same syntax approach as the 4GL, except that each sub-expression that is a parameter will be converted just as any 4GL to Java expression would be converted. In the case where the 4GL built-in supports varying/optional parameter lists, this is naturally supported by method overloading the Java class with all the different method signatures needed (static, instance or constructors).

There is a quirk in the 4GL function call syntax which is not documented, but is occasionally seen in source code. Normally, a function call which takes parameters has a simple comma-separated list of sub-expressions, each one representing one parameter. In the 4GL, each parameter can be optionally prefaced with one of the following keywords: INPUT, INPUT-OUTPUT or OUTPUT. This is the same syntax that can be used in the RUN or PUBLISH statement parameters. Please note that this is syntax without any meaning and since it just adds text without any value, it is not often seen. It is possible that most 4GL programmers don't even know this is possible. The FWD parser just drops these keywords, since they are meaningless. Here is an example:

user-function-name(INPUT-OUTPUT parm1, OUTPUT parm2)

However, the inclusion of this unnecessary complexity in the 4GL does cause an unexpected limitation on how one can write 4GL parameters. The INPUT built-in function is something that can normally be used as a sub-expression, but since this will conflict with the optional use of the INPUT keyword, there are limits to how this can be used. If the function call is to a built-in function that has not been overridden by a user-defined function (the user-defined function of the same name would hide the built-in), the INPUT keyword will be treated as the built-in INPUT function instead of being ignored. That may lead to an error in the case where INPUT was used as the descriptive keyword (this just isn't syntactically valid). For example, the following is not valid:

index(INPUT source, INPUT target)

This differs when dealing with a user-defined function call. To actually use the INPUT built-in function as a parameter in a call to a user-defined function, one must parenthesize the call in Progress! Even this trick only will work for the first parameter in the parameter list. Subsequent parameters simply cannot use the INPUT built-in as the "top-level" expression node, no matter if it is parenthesized or not. This works (but INPUT is a call to the built-in, not a use of the keyword):

user-function-name((INPUT parm1), OUTPUT parm2)

This does not work:

user-function-name(INPUT-OUTPUT parm1, (INPUT parm2))

For built-in functions, the following special syntax cases exist:

Progress provides constructs that look like global variables (e.g. OPSYS). These global variables are termed built-in functions by Progress but they act like read-only variables. Most of these cannot be assigned and they are read by simply referencing their name without any parenthesis. Some global variables can be assigned (e.g. PROPATH). Here is a list of global variables (_CONTROL, _MSG, _PCONTROL, _SERIAL-NUM are undocumented):

_CONTROL
_MSG
_PCONTROL
_SERIAL-NUM
CURRENT-LANGUAGE
DATASERVERS
DBNAME
FRAME-DB
FRAME-FIELD
FRAME-FILE
FRAME-INDEX
FRAME-NAME
FRAME-VALUE
GATEWAYS
GENERATE-PBE-SALT
GENERATE-RANDOM-KEY
GENERATE-UUID
GET-CODEPAGES
GO-PENDING
IS-ATTR-SPACE
LASTKEY
LAST-KEY
MESSAGE-LINES
NUM-ALIASES
NUM-DBS
OPSYS
OS-DRIVES
OS-ERROR
PROC-HANDLE
PROC-STATUS
PROGRESS
PROMSGS
PROPATH
PROVERSION
RETURN-VALUE
SCREEN-LINES
TERMINAL

Note that LAST-KEY is an undocumented synonym for LASTKEY.

Some built-in functions can accept optional parenthesis. This allows these functions to appear as normal functions (with no parameters) or as global variables. This is the list: ETIME, FRAME-COL, FRAME-DOWN, FRAME-LINE, FRAME-ROW, LINE-COUNTER, PAGE-NUMBER, PAGE-SIZE, RETRY (undocumented), SUPER, TIME (undocumented), TODAY (undocumented), TRANSACTION (undocumented), USERID (also USER which is an undocumented synonym; note that USERI is not valid). It is likely that some new functions in v10 (like MTIME, NOW and TIMEZONE) also exhibit this as undocumented behavior, but this has not been tested.

Parenthesis are just syntactic sugar, meant to ease the reading and writing of code by humans. The Progress 4GL's inconsistent approach leads to confusion and complexity for the programmer. Given that the FWD parser has been written with this complexity and ambiguity in mind, the solution to those problems are largely hidden inside the parser. This means that the later phases of the conversion can ignore the quirkiness of whether there are or are not parenthesis used for a given built-in function.

Normally, all parameters are valid sub-expressions. This means that they must follow the 4GL expression syntax and can be evaluated to a single valued result of a supported data type. There are some built-in functions which are special cases that take abnormal parenthesized parameters that aren't expressions.

CAN-FIND( [ FIRST | LAST | NEXT | PREVIOUS | CURRENT ] record_phrase )
CAST( expression, class_name )
CURRENT-VALUE( sequence_name [, database_name] )
DYNAMIC-CURRENT-VALUE( sequence_name [, database_name] )
DYNAMIC-FUNCTION( function_name_expression [IN proc_handle_expr] [, parameter_expression …] )
DYNAMIC-NEXT-VALUE( sequence_name [, database_name] )
FRAME-COLUMN( frame_name )
FRAME-DOWN( frame_name )
FRAME-LINE( frame_name )
FRAME-ROW( frame_name )
NEXT-VALUE( sequence_name [, database_name] )
RECID( record )
ROWID( record )
SEEK( INPUT | OUTPUT | expression )
SUPER( [ INPUT | INPUT-OUTPUT | OUTPUT ] name AS data_type [ , … ] )

Each of the above uses non-expressions such as language keywords, language phrases or symbols that resolve to non-expression resources.

The following list of functions are another special case. They take parameters but do not use parenthesis. Generally, these cases also do not take normal sub-expressions as parameters. This makes for very interesting parsing ambiguity, but again, this is largely hidden in the parser.

ACCUM aggregate_phrase expression
AMBIGUOUS record
AVAILABLE record
CURRENT-CHANGED record
field ENTERED
field NOT ENTERED
IF expression THEN expr1 ELSE expr2
INPUT widget
LOCKED record
NEW record

Actually, the record functions AMBIGUOUS, AVAILABLE, CURRENT-CHANGED, LOCKED and NEW can optionally have the record parameter inside parenthesis. Normal 4GL doesn't use that form, but it is valid.

These non-parenthesized parameter exceptions can be used as sub-expressions so they are certainly implemented as functions even though they don't look like function calls. Language statements in the 4GL cannot be used as sub-expressions.

Two of the above non-parenthesized functions have yet another anomaly. They are coded with postfixed function names: field ENTERED and field NOT ENTERED.

The parser deals with all of these quirks and hides the 4GL madness from the rest of the conversion.

Built-in functions that do take parenthesized parameter lists sometimes can take optional parameters (at the end). The maximum number of variables, their type and order are all fixed (known in advance). Some of the parameters at the end can be left off and when this happens, the function provides well known default behavior. This is a form of polymorphism that is not provided as a tool for user-defined functions.

The MAXIMUM and MINIMUM built-in functions support variable length argument lists. This is not the same as optional parameters. In this case, the number of arguments (parameters) can be arbitrary. Essentially, this is used to provide a list of values to process. Variable arguments is not provided as a tool for user-defined functions. The parser naturally handles variable arguments since it does no signature checking on function calls.

The final quirk of built-in functions is that some of them can have polymorphic return types. This means that the data type of the return value can vary. Usually these are driven by the data type of the input parameters. Here is the list of such functions:

ABSOLUTE
ACCUMULATE
ADD-INTERVAL
DYNAMIC-FUNCTION
GET-BYTES
IF
INPUT
MAXIMUM
MINIMUM
NORMALIZE
SUPER

User-defined functions always take parenthesis even if there are no parameters (empty parenthesis must be coded in this case). Likewise, user-defined functions have a fixed parameter list (the number and type of parameters does not change) and a fixed return type.

The following table documents the mapping between built-in functions and their Java equivalents. References to classes such as decimal, integer, logical, date, character, MathOps, and CompareOps refer to classes in package com.goldencode.p2j.util. If the built-in function is not listed or if the Java column is listed as n/a, then it is not currently supported. For more details on the supported built-in functions, please see the corresponding section of this book (e.g. Part 5 for Database built-ins, Part 6 for User-Interface built-ins...). The conversion result is most often a static method call. This will be obvious from the class referent. If the Java code is "new wrapper()" then it is the constructor approach (wrapper will be one of the BaseDataType classes). Instance methods will have a specific referent in the Java code. Other than these 3 approaches (all documented earlier in this section), there are some exceptions which will be documented in the "Notes" column.

4GL Java Category Notes
_CBIT character.testBitAt() bit manipulation This function is an undocumented built-in with the following signature:
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 multi-byte bit-field. 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 but this is untested by the author). 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.

_CONTROL n/a ?  
_MSG n/a ?  
_PCONTROL n/a ?  
_SERIAL-NUM n/a licensing  
ABSOLUTE MathOps.abs() math Returns an integer or decimal value depending on the type of the input parameter.
ACCUM referent.getResult() database This will resolve to an instance method call on an Accumulator (abstract base class) instance. The concrete implementations of this class:
AverageAccumulator, CountAccumulator, MaximumAccumulator, MinimumAccumulator, TotalAccumulator
Each concrete subclass defines the following methods for results retrieval. Each classes' 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:
getResult() to retrieve the cumulative result getResult(Resolvable breakGroupKey) to retrieve the current break group result
ADD-INTERVAL DateOps.addInterval() date and time  
ALIAS ConnectionManager.alias() database  
AMBIGUOUS RecordBuffer.wasAmbiguous()
OR
RecordBuffer._wasAmbiguous()
database The _wasAmbiguous() version is used in control flow statements since it directly returns a boolean instead of a logical.
ASC character.asc() type conversion There is no source or target codepage support at this time. The result in a DBCS environment may vary from the Progress implementation.
AUDIT-ENABLED() n/a security  
AVAILABLE RecordBuffer.isAvailable()
OR
RecordBuffer._isAvailable()
database The _isAvailable() version is used in control flow statements since it directly returns a boolean instead of a logical.
BASE64-DECODE n/a type conversion  
BASE64-ENCODE n/a type conversion  
BOX n/a type convention  
CAN-DO character.matchList() security Usage of this function within a WHERE clause is processed differently. See Part 5 .
CAN-FIND referent.hasOne()
OR referent.hasAny()
database This will convert to an instance method call where the referent is a FindQuery instance. hasOne() is used for a CAN-FIND( UNIQUE … ) and hasAny() is used for all other cases.
The only exception to this is when a CAN-FIND is embedded in a WHERE clause of a query. For full details on CAN-FIND, please see Part 5.
CAN-QUERY n/a UI  
CAN-SET n/a UI  
CAPS character.toUpperCase() string It can contain DBCS but only the SBCS chars are uppercased.
CAST n/a type conversion  
CHR character.chr() type conversion There is no source or target codepage support at this time. The result in a DBCS environment may vary from the Progress implementation.
CODEPAGE-CONVERT n/a type conversion  
COMPARE character.compare() string There is no collation table support at this time. The result in a DBCS environment may vary from the Progress implementation.
CONNECTED ConnectionManager.connected() database  
COUNT-OF referent.size() database The referent will be an instance of FWDQuery which is implemented by the concrete query classes.
CURRENT-CHANGED RecordBuffer.currentChanged() database  
CURRENT-LANGUAGE EnvironmentOps.getCurrentLanguage() I18N  
CURRENT-RESULT-ROW referent.currentRow() database The referent will be an instance of AbstractQuery, overridden where necessary by concrete query implementations.
CURRENT-VALUE SequenceManager.currentValue()
SequenceManager.setValue()
database The first form is for CURRENT-VALUE function and the second for the CURRENT-VALUE statement.
DATASERVERS EnvironmentOps.getDataServerList() database This is the same as GATEWAYS.
DATA-SOURCE-MODIFIED n/a database  
DATE new date() date The constructors are overloaded to support the parameters for all the different forms of input to the function.
DATETIME new datetime() date and time The constructors are overloaded to support the parameters for all the different forms of input to the function.
DATETIME-TZ new datetimetz() date and time The constructors are overloaded to support the parameters for all the different forms of input to the function.
DAY date.day() date  
DBCODEPAGE n/a I18N  
DBCOLLATION n/a I18N  
DBNAME ConnectionManager.dbName() database  
DBPARAM ConnectionManager.dbParam() database The parameters returned by this method generally will not match those returned by the original 4GL implementation, since we don't support any others than those handled by ConnectionManager.connect().
DB-REMOTE-HOST n/a database  
DBRESTRICTIONS ConnectionManager.dbRestrictions() database  
DBTASKID   database  
DBTYPE ConnectionManager.dbType() database  
DBVERSION n/a database  
DECIMAL new decimal() type conversion The constructors are overloaded to support the parameters for all the different forms of input to the function.
DECRYPT n/a security  
DYNAMIC-CAST n/a type conversion  
DYNAMIC-CURRENT-VALUE SequenceManager.dynamicCurrentValue()
SequenceManager.dynamicSetValue()
database The first form is for DYNAMIC-CURRENT-VALUE function and the second for the DYNAMIC-CURRENT-VALUE statement.
DYNAMIC-FUNCTION n/a function execution  
DYNAMIC-INVOKE n/a method execution  
DYNAMIC-NEXT-VALUE SequenceManager.dynamicNextValue() database  
ENCODE SecurityOps.encode() security The interface is completely supported and functional, however the calculated result is NOT binary compatible with the 4GL version.
ENCRYPT n/a security  
ENTERED referent.isEntered() UI The referent for this instance method call will be an instance of GenericWidget.
ENTRY character.entry() string Delimiter processing is always case-sensitive at this time.
ERROR n/a database  
ETIME date.elapsed() time  
EXP MathOps.pow() math  
EXTENT int literalOR referent.length arrays This is a direct conversion without any backing method or class.
If the 4GL parameter is a variable with an extent > 1, then the referent will be the converted array variable and the result will be a direct reference to the array's length member. If the extent <= 1 (the variable is scalar in 4GL), then the result will be the int literal 0.
If the 4GL parameter is a field with an extent > 1, then the Java result will be an int literal of the extent size as read from the schema. If the extent is <= 1 (the field is scalar), the result will be the int literal 1.
FILL character.fill() string  
FIRST referent.isFirst() loops/transactions The instance method will be called on a referent that will be an instance of PresortQuery.
FIRST-OF referent.isFirstOfGroup() loops/transactions The instance method will be called on a referent that will be an instance of PresortQuery.
FRAME-COL referent.frameCol() UI The instance method will be called on a referent that will be an instance of CommonFrame.
FRAME-DB n/a UI  
FRAME-DOWN referent.frameDown() UI The instance method will be called on a referent that will be an instance of CommonFrame.
FRAME-FIELD LogicalTerminal.getFrameField() UI  
FRAME-FILE n/a UI  
FRAME-INDEX LogicalTerminal.getFrameIndex() UI  
FRAME-LINE referent.frameLine() UI The instance method will be called on a referent that will be an instance of CommonFrame.
FRAME-NAME n/a UI  
FRAME-ROW referent.frameRow() UI The instance method will be called on a referent that will be an instance of CommonFrame.
FRAME-VALUE LogicalTerminal.getFrameValue() LogicalTerminal.setFrameValue() UI  
GATEWAYS EnvironmentOps.getDataServerList() database This is the same as DATASERVERS.
GENERATE-PBE-KEY n/a security  
GENERATE-PBE-SALT n/a security  
GENERATE-UUID n/a security  
GET-BITS n/a bit manipulation  
GET-BYTE referent.getByte() raw/memory access This instance method will be called on an instance of BinaryData.
GET-BYTE-ORDER n/a raw/memory access  
GET-BYTES n/a raw/memory access Returns RAW or MEMPTR.
GET-CODEPAGE n/a I18N  
GET-CODEPAGES n/a I18N  
GET-COLLATION n/a I18N  
GET-COLLATIONS n/a I18N  
GET-DOUBLE n/a raw/memory access  
GET-FLOAT n/a raw/memory access  
GET-INT64 n/a raw/memory access  
GET-LONG n/a raw/memory access  
GET-POINTER-VALUE n/a raw/memory access  
GET-SHORT n/a raw/memory access  
GET-SIZE referent.length() raw/memory access This instance method will be called on an instance of memptr.
GET-STRING referent.getString() raw/memory access This instance method will be called on an instance of BinaryData.
GET-UNSIGNED-LONG n/a raw/memory access  
GET-UNSIGNED-SHORT n/a raw/memory access  
GO-PENDING LogicalTerminal.isGoPending() UI  
GUID n/a security  
HANDLE n/a type conversion  
HEX-DECODE n/a type conversion  
HEX-ENCODE n/a type conversion  
IF THEN ELSE condition ? new wrapper(expr1) : new wrapper(expr2) ternary This converts to the Java ternary ? : operator. The condition will be checked (and unwrapped using booleanValue() if needed). Then the return value will be wrapped into a common BaseDataType for the THEN and ELSE expressions. The wrapper type will always be the same type for both return expressions.
INDEX character.indexOf() string  
INPUT referent.getterMethodName()
OR
referent.getScreenValue(referent.widgetAccessorMethodName())
UI This either 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.
In either case, this is an instance method call and the referent will be a frame. If the widget-specific data getter is called, then the method call will be made to the widget-specific getter method in the frame-specific interface. If getScreenValue() is called, the frame will be used as a CommonFrame and the widget will be passed as a parameter.
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. For example, a 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 function 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.
Also see the SCREEN-VALUE attribute which has a related issue.
INT64 new int64() type conversion The constructors are overloaded to support the parameters for all the different forms of input to the function.
INTEGER new integer() type conversion The constructors are overloaded to support the parameters for all the different forms of input to the function.
INTERVAL DateOps.interval() date and time  
IS-ATTR-SPACE n/a UI  
IS-CODEPAGE-FIXED n/a I18N  
IS-COLUMN-CODEPAGE n/a I18N  
IS-LEAD-BYTE n/a I18N  
ISO-DATE date.isoDate() date and time This static function will call date.getIsoDate() overrided in datetime and datetimetz.
KBLABEL LogicalTerminal.kbLabel() UI  
KEYCODE LogicalTerminal.keyCode() UI  
KEYFUNCTION LogicalTerminal.keyFunction() UI  
KEYLABEL LogicalTerminal.keyLabel() UI  
KEYWORD ProgressAst.keyword() Progress  
KEYWORD-ALL ProgressAst.keywordAll() Progress  
LAST referent.isLast() loops/transactions The instance method will be called on a referent that will be an instance of PresortQuery.
LASTKEY KeyReader.lastKey() UI LAST-KEY is treated as the equivalent.
LAST-KEY KeyReader.lastKey() UI Undocumented synonym for LASTKEY.
LAST-OF referent.isLastOfGroup() loops/transactions The instance method will be called on a referent that will be an instance of PresortQuery.
LC character.toLowerCase() string It can contain DBCS but only the SBCS chars are lowercased.
LDBNAME ConnectionManager.ldbName() database  
LEFT-TRIM character.leftTrim() string  
LENGTH character.length()
OR character.byteLength()
OR BinaryData.length()
raw/memory access If a character value is passed as the first parameter, then the static method call will be made to character.length() by default (or if the "character" type is passed as the 2nd parameter). Otherwise, a static method call to character.byteLength() will be made for "raw" type. There is no support for "column" based length at this time.
If a raw value is passed for the first parameter, a static method call to BinaryData.length() will be made.
There is no support for the BLOB type at this time.
LIBRARY n/a R-code library  
LINE-COUNTER referent.getNextLineNum() I/O This is an instance method call. The referent will be a named stream instance accessible in the current scope (if explicitly provided as a parameter) OR will be the Stream instance returned from UnnamedStreams.safeOutput() for the unnamed stream.
LIST-EVENTS n/a UI  
LIST-QUERY-ATTRS n/a UI  
LIST-SET-ATTRS n/a UI  
LIST-WIDGETS n/a UI  
LOCKED RecordBuffer.wasLocked()
OR
RecordBuffer._wasLocked()
database The _wasLocked() version is used in control flow statements since it directly returns a boolean instead of a logical.
LOG MathOps.log() math  
LOGICAL n/a type conversion  
LOOKUP character.lookup() string Delimiter processing is *always* case-sensitive at this time.
MAXIMUM date.maximum()
datetime.maximum()
datetimetz.maximum() logical.maximum() integer.maximum()
int64.maximum() decimal.maximum()
math This is a static method call using Java's variable argument lists (varargs). The result data type will be the same as the class used for the calculation. If all the numeric operands are of one type, then that type will be used for the calculation. For example, if all operands are integer then integer is used to calculate the result and the result will be of type integer. Numeric operands can be intermixed (decimal and integer), but the decimal class will be used to calculate the result. All arguments (other than numerics) can only be compared against others of the same type.
MD5-DIGEST n/a security  
MEMBER n/a R-code library  
MESSAGE-LINES LogicalTerminal.getMessageLines() UI  
MINIMUM character.minimum() date.minimum()
datetime.minimum()
datetimetz.minimum() logical.minimum() integer.minimum()
int64.minimum() decimal.minimum()
math This is a static method call using Java's variable argument lists (varargs). The result data type will be the same as the class used for the calculation. If all the numeric operands are of one type, then that type will be used for the calculation. For example, if all operands are integer then integer is used to calculate the result and the result will be of type integer. Numeric operands can be intermixed (decimal and integer), but the decimal class will be used to calculate the result. All arguments (other than numerics) can only be compared against others of the same type.
MONTH date.month() date  
MTIME datetime.millisecondsSinceMidnight() time There are two static function in java, for one or for no parameters at all.
NEW n/a object-oriented Used for class instantiation.
NEW RecordBuffer.isNew()
OR
RecordBuffer._isNew()
database The _isNew() version is used in control flow statements since it directly returns a boolean instead of a logical.
NEXT-VALUE SequenceManager.nextValue() database  
NORMALIZE n/a type conversion  
NOT ENTERED referent.isNotEntered() UI This instance method will be called on a referent of type GenericWidget.
NOW datetime.now()
datetimetz.now()
date and time The first form is used only in datetime variable initialization (init clause).
NUM-ALIASES ConnectionManager.numAliases() database  
NUM-DBS ConnectionManager.numDBs() database  
NUM-ENTRIES character.numEntries() string Delimiter processing is *always* case-sensitive at this time.
NUM-RESULTS referent.size() database The referent will be an instance of FWDQuery which is implemented by the concrete query classes.
OPSYS EnvironmentOps.getOperatingSystem() OS environment  
OS-DRIVES FileSystemOps.getRootList() OS environment  
OS-ERROR FileSystemOps.getLastError() OS environment  
OS-GETENV FileSystemOps.getProperty() OS environment  
PAGE-NUMBER referent.getPageNum() I/O This is an instance method call. The referent will be a named stream instance accessible in the current scope (if explicitly provided as a parameter) OR will be the Stream instance returned from UnnamedStreams.safeOutput() for the unnamed stream.
PAGE-SIZE referent.getPageSize() I/O This is an instance method call. The referent will be a named stream instance accessible in the current scope (if explicitly provided as a parameter) OR will be the Stream instance returned from UnnamedStreams.safeOutput() for the unnamed stream.
PDBNAME ConnectionManager.pdbName() database  
PROC-HANDLE n/a Progress environment Is this actually polymorphic?
PROC-STATUS n/a Progress environment  
PROGRAM-NAME EnvironmentOps.getSourceName() Progress environment See the runtime method for implementation limitations.
PROGRESS EnvironmentOps.getRuntimeType() Progress environment  
PROMSGS EnvironmentOps.getMessageSource() Progress environment  
PROPATH EnvironmentOps.getSourcePath() Progress environment  
PROVERSION EnvironmentOps.getVersion() Progress environment  
QUERY-OFF-END referent.isOffEnd() database The referent will be an instance of FWDQuery which is implemented by the concrete query classes.
QUOTER character.quoter() type conversion  
R-INDEX character.lastIndexOf() string  
RANDOM MathOps.random() math 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. The FWD random implementation does not match that behavior.
RAW n/a database  
RECID RecordBuffer.recordID() database  
RECORD-LENGTH n/a database  
REJECTED n/a database  
REPLACE character.replaceAll() string  
RETRY TransactionManager.isRetry()
OR
TransactionManager._isRetry()
loops/transactions The _isRetry() version is used in control flow statements since it directly returns a boolean instead of a logical.
RETURN-VALUE ControlFlowOps.getReturnValue() procedure execution  
RGB-VALUE n/a UI  
RIGHT-TRIM character.rightTrim() string  
ROUND Math.round() math  
ROW-STATE n/a database  
ROWID RecordBuffer.rowID() database  
SCREEN-LINES LogicalTerminal.getScreenLines() UI  
SDBNAME ConnectionManager.sdbName() database  
SEARCH FileSystemOps.searchPath() filesystem  
SEEK referent.getPosition() filesystem This is an instance method call. The referent will be a named stream instance accessible in the current scope (if explicitly provided as a parameter) OR will be the Stream instance returned from UnnamedStreams.safeInput() (if INPUT was specified as the first parameter) or UnnamedStreams.safeOutput() (if OUTPUT was specified as the first parameter) for the unnamed stream options.
SET-DB-CLIENT n/a security  
SETUSERID n/a security  
SHA1-DIGEST n/a security  
SQRT MathOps.sqrt() math  
SSL-SERVER-NAME n/a security  
STRING character.valueOf() type conversion  
SUBSTITUTE character.substitute() string The variable length argument list is handled by an argument of type Object[].
SUBSTRING character.substring() string No support for "raw", "fixed" or "column" based substrings at this time.
SUPER n/a function execution  
TERMINAL LogicalTerminal.getTerminal() UI  
TIME date.secondsSinceMidnight() time  
TIMEZONE date.getDefaultTimeZoneOffset()
date.getOffsetForSpec()
datetimetz.getTimeZoneOffset()
time date.getDefaultTimeZoneOffset() is
used when called without parameters.
date.getOffsetForSpec() is
used with a sharcter parameter.
datetimetz.getTimeZoneOffset() is
used with to extract the timezone from a datetime-tz value.
TODAY date.today() date  
TO-ROWID rowid.rowid() database Length of the parameter must be even (max 18, including 0x prefix). Alpha digits must be in lowercase.
TRANSACTION TransactionManager.isTransactionActive()
OR
TransactionManager.isTransaction()
loops/transactions The isTransaction() version is used in control flow statements since it directly returns a boolean instead of a logical.
TRIM character.trim() string  
TRUNCATE MathOps.truncate() math  
TYPE-OF n/a object-oriented  
UNBOX n/a type conversion  
USERID SecurityOps.getUserId()
OR
SecurityOps.getUserIdFromDB()
security The SecurityOps.getUserId() is used for the no parameter version and SecurityOps.getUserIdFromDB() is used for the version that takes a database name parameter.
VALID-EVENT n/a UI  
VALID-HANDLE referent.isValid() data types This is an instance method called on a referent that is a handle type.
VALID-OBJECT n/a object-oriented  
WEEKDAY date.weekday() date  
WIDGET-HANDLE handle.fromString() UI This is a static method.
YEAR date.year() date  

Object Method Call

Object oriented support for method calls is included in v10. This includes chaining method calls and property references.

Supported. See Object Method Call.

Object Property

Access to object-oriented class data (properties) which can have getter and setter custom logic is included in v10.

Supported. See Data Types.

Object Reference

References to instances of classes (built-in and user-defined) can be included as a sub-expression in v10. This includes usage of the special references SUPER and THIS.

Supported. See Data Types.

Operators

The following is the list of Progress 4GL operators, types (unary or binary) and their precedence levels (highest precedence to lowest precedence):

Precedence Level Operator Type Purpose
9 () Unary Parenthesis is the precedence operator. It allows custom control over the order of evaluation by the programmer.
8 : Binary The colon operator is used for handle-based attribute and method access, COM/ActiveX property and method access and the 4GL object-oriented property and method access.
7 unary -, unary + Unary Unary minus is used to negate the sign of a numeric operand. Unary plus is meaningless.
6 *, /, MODULO Binary Arithmetic multiply, divide and integer remainder. These can be used on any kind of numeric data.
5 binary -, binary + Binary Arithmetic subtraction and addition on numeric or date data. The binary plus also can be used for string concatenation.
4 =, EQ, <>, NE, <, LT, >, GT, <=, LE, >= GE, MATCHES, BEGINS, CONTAINS Binary Relational comparisons, equality/inequality testing and pattern matching in text data.
3 NOT Unary Logical negation (the result is the opposite of how its operand evaluates).
2 AND Binary Logical conjoin (the result is only true If both operands evaluate true).
1 OR Binary Logical disjoin (the result is true If either operand evaluates true).

The higher the precedence level, the more tightly the operator will bind to its operands. A simple example:

4 + 5 * 6

The proper result is 34 since multiply binds more tightly to its operands than arithmetic plus. This means that multiply evaluates first, yielding an intermediate value of 30 which is added to 4 resulting in 34. This binding takes precedence over any left-to-right evaluation. Operators will evaluate left to right when they are within the same precedence level.

Standard Conversion Approach

Java does not support operator overloading (which is appalling in this author's opinion). This means that the custom wrapper classes (see the Data Types chapter) cannot be treated as first class data types in the language (e.g. numbers or boolean values) which can be operands of the built-in language operators. This means that the nice algebraic-like syntax of Java primitives (e.g. int or boolean) cannot be duplicated with the wrapper types. Since the Progress data types need special behavior that cannot be feasibly duplicated using Java primitives, without operator overloading, the resulting expression conversion is not as good as it could be.

A closely related problem is that the Progress 4GL operators differ in their behavior from the Java operators. The most obvious difference is that the 4GL operators are "unknown value aware". That means that there is specific, unique behavior that will occur when unknown value is present as an operand. For example, many Progress operators silently return the unknown value if either or both operands are the unknown value. Operator overloading would also allow an unknown value aware implementation, but that is not available at the time of this writing.

This means that all operators (except for the parenthesis precedence operator) must be converted to method calls instead of to the algebraic-like notation that would be preferable. Each operator is converted to a static Java method call that is implemented in a data type wrapper class (integer, decimal, date, logical, character) or in a common operations class such as MathOps or CompareOps. The static methods implement the exact Progress 4GL behaviors including handling unknown value, handling the proper mixture of Java primitives and wrapper types, handling different wrapper types interchangeably when needed (e.g. decimal and integer), providing deferred evaluation and handling precedence.

By using static imports, the class names can be dropped from the method calls. This means that a call to MathOps.plus(myVar, 3) will actually emit as just plus(myVar, 3), resulting in shorter and more readable code.

Progress 4GL expressions that are used for WHERE clauses or BY clauses are handled differently, since they are most often processed inside the relational database engine and not inside the FWD application server. For details on how WHERE and BY clauses convert, please see Part 5 of this book.

All operators are fully implemented except the highly specialized CONTAINS operator which handles word index searches and is only present in a WHERE clause.

In regards to precedence, the structure of the converted Java expression is an exact duplicate of the Progress source tree structure. This is required to maintain logical correctness in the result. Generally, the Java operator precedence maps closely to the 4GL operator precedence. There are only 2 deviations.

Precedence Issue 1

The Progress logical NOT operator has a very low precedence and the corresponding Java ! operator has a high precedence (in Java this is the same as unary plus and unary minus). If the real Java operator would be used, the logical not would have to have its operand parenthesized to maintain the same result as in Progress. If this would not happen, then the Java ! would bind to the first emitted portion of the operand and this would be evaluated before any subsequent operator. In Progress 4GL, you can write this:

NOT i > 1

To get the same result in Java, you would have to write this:

!(i > 1)

Since static methods are used instead of the ! operator, the expression subtree will automatically be parenthesized properly in the result. This is the actual resulting Java code from the above 4GL example:

not(isGreaterThan(i, 1))

Precedence Issue 2

In Progress 4GL the equality (EQ or =) and inequality (NE or <>) operators have the same precedence level as the comparison operators (GT, >, LT, <, GE, >=, LE, <=) but in Java, these are split into 2 precedence levels. This is resolved in the converted Java code since the static methods have parenthesized "operand" lists that override the precedence order to exactly match the order parsed into the tree structure (AST) of the 4GL expression.

() Precedence Operator

Fully supported.

This is one of the only operators that can directly convert to a Java operator instead of to a static method call. The behavior and code of the precedence operator is identical in both languages. For this reason, wherever the use of parenthesis in the 4GL is deemed essential (by the conversion), parenthesis will be emitted in the corresponding Java code.

Consider this 4GL code:

if my-var then (if another-var then 4 else 9) else 14.

In this case, the parenthesis will emit as in the following Java code (formatted differently for readability in this text):

myVar.booleanValue() ? (anotherVar.booleanValue() ? new integer(4) : new integer(9))
                     : new integer(14);

At the time of this writing, the only exception in the conversion is when parenthesis directly encloses any expression that directly contains a sub-expression whose lowest precedence (outermost) node is a unary or binary operator. In such cases it is known that the converted Java will have the operands and precedence automatically duplicated since the result will be emitted as static method calls. This allows the parenthesis to be dropped. That means the parenthesis are not needed to maintain the precedence and no matching parenthesis will be generated in the converted code in this case.

For example, this 4GL:

(4 + 5) * 6

The resulting Java code will not have parenthesis operators (though the method calls will still use parenthesis):

multiply(plus(4, 5), 6);

The reason this works is because the FWD parser has already created the expression AST to capture the precedence data in the tree structure itself. The result naturally emits with the right precedence. Interestingly, this is a very common case, which means that it is common for the parenthesis to be dropped in the converted output.

: Handle and Object Reference Operator

Use to reference handle attributes or methods is fully supported. As a general rule, this operator is supported using the Java object referencing operator (the . character). For details, see the Attributes, Methods and Handles section of this chapter.

Use for COM/ActiveX and 4GL object-oriented resources is not supported at this time.

Assignment Operator

Fully supported.

The assignment operator = is special in that it cannot exist in arbitrary sub-expressions. Instead, it must only exist at a top-level expression which stands alone or is part of an ASSIGN language statement. An example of the 4GL standalone assignment expression:

my-var = 14 + another-var.

The assignment operator is binary and must exist immediately to the right of the variable or field being assigned (my-var in this case). Since both the assignment operator and the equality operator are represented by the equal character, assignment is differentiated from the equality testing operator by context. For more details on how the assignment operator converts, please see the Assignment section of the Data Types chapter.

AND Operator

Fully supported.

This binary operator returns false if either of the operands are false and true only if both are true. It is converted to the static method character.and() or character._and(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Simple Case

The most simple 4GL example:

bool1 AND bool2

The resulting Java code:

and(bool1, bool2);

A control flow example:

IF bool1 AND bool2 THEN

The resulting Java code:

if (_and(bool1, bool2))
{
   ...

}

Deferred Evaluation Case

This operator supports deferred evaluation of the second operand. Please see the section below entitled Deferred Evaluation of Second Operand, for details.

BEGINS Operator

Fully supported.

This binary operator is used to detect if the first text operand starts with the text in the second operand. It is converted to the static method character.begins() or character._begins(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

4GL example:

varname BEGINS "bogus" 

The resulting Java code:

begins(varname, "bogus");

Another example:

IF varname BEGINS "bogus" THEN

The resulting Java code:

if (_begins(varname, "bogus")
{
   ...
}

Binary Minus Operator

Fully supported.

This binary operator is used for 3 possible purposes.

Numeric Data

When the first operand is numeric, it is used to subtract the second numeric operand from the first numeric operand. It is converted to the static method MathOps.minus().

4GL example:

varname - 14

The resulting Java code:

minus(varname, 14);
Two Date Operands

When both operands are the date type, this operator is used to subtract a second date from the first date, yielding a difference in days. This is converted to the static method date.differenceNum().

4GL example:

my-date - another-date

The resulting Java code:

differenceNum(myDate, anotherDate);
One Date Operand, One Numeric Operand

When the first operand is a date type and the second operand is a numeric type, this operator is used to subtract the numeric value in days from the given date. This will convert to the static method date.minusDays().

This is a 4GL example:

my-date - 14

The resulting Java code:

minusDays(myDate, 14);
Two Datetime (or Datetime-tz) Operands

When both operands are the datetime or datetime-tz type, this operator is used to subtract the second value from the first one, yielding a difference in milliseconds. This is converted to the static method datetime.differenceNum().

4GL example:

my-datetime - another-datetime

The resulting Java code:

datetime.differenceNum(myDate, anotherDate);
One Datetime (or Datetime-tz) Operand, One Numeric Operand

When the first operand is a datetime or datetime-tz type and the second operand is a numeric type, this operator is used to subtract the numeric value in milliseconds from the given datetime value. This will convert to the static method datetime.minusMillis().

This is a 4GL example:

my-date - 600000

The resulting Java code:

datetime.minusMillis(myDate, 60000);

Binary Plus Operator

Fully supported.

This binary operator is used for 3 possible purposes.

Numeric Data

When the first operand is numeric, it is used to add two numeric operands and it is converted to the static method MathOps.plus().

4GL example:

varname + 14

The resulting Java code:

plus(varname, 14);
Date Data

When the first operand is a date, it is used to add a second numeric operand (representing the number of days) to the date. This is converted to the static method date.plusDays().

4GL example:

my-date + 14

The resulting Java code:

plusDays(myDate, 14);
Datetime (or Datetime-tz) Data

When the first operand is a datetime or datetime-tz, it is used to add a second numeric operand (representing the number of milliseconds) to the datetime. This is converted to the static method datetime.plusMillis().

4GL example:

my-datetime + 14

The resulting Java code:

datetime.plusMillis(myDatetime, 14);
String Data

When the first operand is a character type, this is used as a string concatenation operator. This will convert to the static method character.concat(). Of special interest is that the conversion detects when there is more than one consecutive string concatenation operator in an expression and these are collapsed into a single call to concat(). This is possible because concat() takes variable length argument lists (Java varargs).

This is a simple 4GL example:

"Hello " + my-world

The resulting Java code:

concat("Hello ", myWorld);

Here is a more complex 4GL example:

"This is " + my-text1 + " some text " + my-text2 + " more text!" 

The resulting Java code:

concat("This is ", myText1, " some text ", myText2, "more text!");

CONTAINS Operator

This operator handles word index searches and is only present in a WHERE clause. For details of WHERE clause conversion, please see Part 5 of this book.

Divide Operator

Fully supported.

This binary operator is used to divide two numeric operands. It is converted to the static method MathOps.divide().

4GL example:

varname / 14

The resulting Java code:

divide(varname, 14);

EQ Operator

Fully supported.

There are 2 different conversion paths for this operator.

One Operand is the Unknown Value Literal

If either of the operands is the unknown value literal, then a special conversion mode is entered where this binary operator is used to detect if the operand which is not the unknown value literal is equivalent to the unknown value. It is converted to the static method CompareOps.isUnknown() or CompareOps._isUnknown(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

That this conversion allows the unknown value literal to be dropped from the Java side. The conversion is the same no matter which operand is the unknown value literal.

Both 4GL forms (EQ and =) of this operator are supported (they are treated as synonyms).

4GL example:

varname EQ ?

The resulting Java code:

isUnknown(varname);

Another example:

IF ? = varname THEN

The resulting Java code:

if (_isUnknown(varname))
{
   ...
}
Neither Operand is the Unknown Value Literal

If neither of the operands is the unknown value literal, then this binary operator is used to detect if the operands are equivalent to each other. It is converted to the static method CompareOps.isEqual() or CompareOps._isEqual(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Both 4GL forms (EQ and =) of this operator are supported (they are treated as synonyms).

4GL example:

varname EQ 14

The resulting Java code:

isEqual(varname, 14);

Another example:

IF 14 = varname THEN

The resulting Java code:

if (_isEqual(14, varname))
{
   ...
}

GT Relational Comparison Operator

Fully supported.

This binary operator is used to detect if the first operand is greater than the second operand. It is converted to the static method CompareOps.isGreaterThan() or CompareOps._isGreaterThan(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Both 4GL forms (GT and >) of this operator are supported (they are treated as synonyms).

4GL example:

varname GT "bogus" 

The resulting Java code:

isGreaterThan(varname, "bogus");

Another example:

IF varname > "bogus" THEN

The resulting Java code:

if (_isGreaterThan(varname, "bogus")
{
   ...
}

GE Relational Comparison Operator

Fully supported.

This binary operator is used to detect if the first operand is greater than or equal to the second operand. It is converted to the static method CompareOps.isGreaterThanOrEqual() or CompareOps._isGreaterThanOrEqual(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Both 4GL forms (GE and >=) of this operator are supported (they are treated as synonyms).

4GL example:

varname GE "bogus" 

The resulting Java code:

isGreaterThanorEqual(varname, "bogus");

Another example:

IF varname >= "bogus" THEN

The resulting Java code:

if (_isGreaterThanOrEqual(varname, "bogus")
{
   ...
}

LT Relational Comparison Operator

Fully supported.

This binary operator is used to detect if the first operand is less than the second operand. It is converted to the static method CompareOps.isLessThan() or CompareOps._isLessThan(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Both 4GL forms (LT and <) of this operator are supported (they are treated as synonyms).

4GL example:

varname LT "bogus" 

The resulting Java code:

isLessThan(varname, "bogus");

Another example:

IF varname < "bogus" THEN

The resulting Java code:

if (_isLessThan(varname, "bogus")
{
   ...
}

LE Relational Comparison Operator

Fully supported.

This binary operator is used to detect if the first operand is less than or equal to the second operand. It is converted to the static method CompareOps.isLessThanOrEqual() or CompareOps._isLessThanOrEqual(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Both 4GL forms (LE and <=) of this operator are supported (they are treated as synonyms).

4GL example:

varname LE "bogus" 

The resulting Java code:

isLessThanorEqual(varname, "bogus");

Another example:

IF varname <= "bogus" THEN

The resulting Java code:

if (_isLessThanOrEqual(varname, "bogus")
{
   ...
}

MATCHES Operator

Fully supported.

This binary operator is used to detect if the first text operand contains the pattern specified in second operand. It is converted to the static method character.matches() or character._matches(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

4GL example:

varname MATCHES "bog*" 

The resulting Java code:

matches(varname, "bog*");

Another example:

IF varname MATCHES "bog*" THEN

The resulting Java code:

if (_matches(varname, "bog*")
{
   ...
}

MODULO Operator

Fully supported.

This binary operator is used to divide two numeric operands and return the integer remainder. It is converted to the static method MathOps.modulo().

4GL example:

varname MODULO 14

The resulting Java code:

modulo(varname, 14);

Multiply Operator

Fully supported.

This binary operator is used to multiply two numeric operands. It is converted to the static method MathOps.multiply().

4GL example:

varname * 14

The resulting Java code:

multiply(varname, 14);

NE Operator

Fully supported.

There are 2 different conversion paths for this operator.

One Operand is the Unknown Value Literal

If either of the operands is the unknown value literal, then a special conversion mode is entered where this binary operator is used to detect if the operand which is not the unknown value literal is not equivalent to the unknown value. It is converted to the static method CompareOps.notUnknown() or CompareOps._notUnknown(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

That this conversion allows the unknown value literal to be dropped from the Java side. The conversion is the same no matter which operand is the unknown value literal.

Both 4GL forms (NE and <>) of this operator are supported (they are treated as synonyms).

4GL example:

varname NE ?

The resulting Java code:

notUnknown(varname);

Another example:

IF ? <> varname THEN

The resulting Java code:

if (_notUnknown(varname))
{
   ...
}
Neither Operand is the Unknown Value Literal

If neither of the operands is the unknown value literal, then this binary operator is used to detect if the operands are not equivalent to each other. It is converted to the static method CompareOps.isNotEqual() or CompareOps._isNotEqual(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Both 4GL forms (NE and <>) of this operator are supported (they are treated as synonyms).

4GL example:

varname NE 14

The resulting Java code:

isNotEqual(varname, 14);

Another example:

IF 14 <> varname THEN

The resulting Java code:

if (_isNotEqual(14, varname))
{
   ...
}

NOT Operator

Fully supported.

This unary operator returns the opposite logical value of the operand. It is converted to the static method character.not() or character._not(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

4GL example:

NOT varname

The resulting Java code:

not(varname);

Another example:

IF NOT varname THEN

The resulting Java code:

if (_not(varname))
{
   ...
}

OR Operator

Fully supported.

This binary operator returns true if either of the operands are true and false only if both are false. It is converted to the static method character.or() or character._or(). The second form is used where the sub-expression is directly contained within a Java control-flow statement such as an if, since it directly returns a boolean and avoids the need to "unwrap" using booleanValue() on the result.

Simple Case

The most simple 4GL example:

bool1 OR bool2

The resulting Java code:

or(bool1, bool2);

A control flow example:

IF bool1 OR bool2 THEN

The resulting Java code:

if (_or(bool1, bool2))
{
   ...

}

Deferred Evaluation Case

This operator supports deferred evaluation of the second operand. Please see the section below entitled Deferred Evaluation of Second Operand, for details.

Unary Minus Operator

Fully supported.

This operator is used to reverse the sign of the numeric expression given as a postfixed operand. It is converted to the static method MathOps.negate().

4GL example:

-varname

The resulting Java code:

negate(varname);

Unary Plus Operator

Fully supported.

The unary plus operator has no functional affect in the 4GL. It is only used as syntactic sugar. As such, it is dropped in the converted output. Only the postfixed operand will emit.

Deferred Evaluation of Second Operand

The logical OR and logical AND operators support deferred evaluation of the second operand. For the OR operator, if the first operand is true, then the result of the operator will be true regardless of the value of the second operand. For the AND operator, if the first operand is false, then the result of the operator will be false regardless of the value of the second operand. In the 4GL, these operators "short-circuit" the evaluation process and do not evaluate the second operand since the result is already known. This is a common feature for most languages and Java's logical OR (||) and logical AND (&&) operators also provide this feature. But without operator overloading, the current conversion uses static method calls to duplicate the 4GL behavior. Consider this code:

IF bool-var OR buffer.my-bool-field THEN

The result in Java could be expressed as the following:

if (_or(boolVar, buffer.getMyBoolField()))
{
   ...
}

This would generally work just fine until the moment that bool-var is true but the buffer does not have any record in it. In Progress, when code references a field from a buffer that does not have a record in it, an ERROR condition is raised. But in this case, no ERROR condition is ever raised because the 4GL would short-circuit the evaluation of the field. In other words, in Progress the field is never accessed in this case and the ERROR condition is not raised. The problem with the Java static method approach is that both operands are evaluated (in the context of the calling business logic) before the static _or() method is actually dispatched (called). This means that this approach needs special processing to defer the evaluation of the second operand until the static method can determine if it needs to evaluate the operand or not. The evaluation of the operand must be delegated to the static method. This same behavior affects the AND operator and its corresponding Java static methods.

Some second operands are safe to evaluate before the static method is called. Any of the safe cases are emitted as simple sub-expressions that are evaluated ahead of time, since it is known that there are no side effects, state changes or conditions that can be raised. In other words, in a safe case the order of evaluation of the operands (or even whether or not the operand is evaluated) does not matter.

Other sub-expressions are unsafe. The conversion detects the unsafe cases and converts differently (see below). The following is a list of the unsafe cases:

Second Operand Contains
database field access
call to a user-defined function
any call to a built-in function which can change the runtime state or have side-effects (ACCUM, DYNAMIC-FUNCTION, DYNAMIC-NEXT-VALUE, GENERATE-PBE-KEY, GENERATE-PBE-SALT, GENERATE-RANDOM-KEY, GENERATE-UUID, GUID, NEXT-VALUE, RETRY, SET-DB-CLIENT, SETUSERID, SUPER)
any call to a built-in function which is unsafe when the accessed resource is uninitialized or which can otherwise raise an ERROR condition (AMBIGUOUS, CAN-FIND, CAN-QUERY, CAN-SET, CAST, COUNT-OF, CURRENT-CHANGED, CURRENT-RESULT-ROW, DATA-SOURCE-MODIFIED, DATE, DECIMAL, DYNAMIC-CURRENT-VALUE, ERROR, FIRST, FIRST-OF, HANDLE, INTEGER, LAST, LAST-OF, LIST-EVENTS, LIST-QUERY-ATTRS, LIST-SET-ATTRS, LOCKED, NEW, NUM-RESULTS, QUERY-OFF-END, RECID, RECORD-LENGTH, REJECTED, ROWID, ROW-STATE, VALID-EVENT, WIDGET-HANDLE)
call to a built-in function which is unsafe when the accessed resource is a MEMPTR type (BASE64-DECODE, DECIMAL, DECRYPT, ENCRYPT, GET-BYTE, GET-BYTES, GET-DOUBLE, GET-FLOAT, GET-INT64, GET-LONG, GET-POINTER-VALUE, GET-SHORT, GET-STRING, GET-UNSIGNED-LONG, GET-UNSIGNED-SHORT, HEX-ENCODE, INTEGER, LENGTH, MD5-DIGEST, SHA1-DIGEST, STRING)
call to the ETIME built-in function when there is a parameter since this changes state
use of the : operator (to reference a handle or object) since this can change state or raise conditions
use of a subscript to access an element in an array (extent variable)

There are 2 forms in which the unsafe sub-expressions can be converted.

The first case is only used when the second operand is a sub-expression that is a simple database field reference. By simple, it means that the field reference is not part of a more complex sub-expression, but rather that the entire sub-expression is only a reference to that database field.

4GL example:

bool1 AND buffer.bool2

The resulting Java code:

and(bool1, new LogicalExpression(new FieldReference(buffer, "bool2")));

Another example:

IF bool1 AND buffer.bool2 THEN

The resulting Java code:

if (_and(bool1, new LogicalExpression(new FieldReference(buffer, "bool2"))))
{
   ...
}

The idea is that a "pointer" to the field is passed to the method call. The field itself is not dereferenced until such time as the called method (and() or _and() in the example above) decides it is needed. The dereferencing is delegated to the static method that replaces the 4GL operator. Of course, the same example code above can be used for the OR operator by substituting the or() and _or() methods into the generated Java code. Everything else would be the same.

The second case is used for all other unsafe cases. It is more complex, but it is flexible enough to handle all possible cases.

A 4GL example:

bool1 OR user-defined-function()

This is the resulting Java:

or(bool1, new LogicalExpression()
{
   public logical execute()
   {
      return userDefinedFunction();
   }
});

Another 4GL example:

IF bool1 OR user-defined-function() THEN

This is the resulting Java:

if (_or(bool1, new LogicalExpression()
{
   public logical execute()
   {
      return userDefinedFunction();
   }
}))
{
   ...
}

This shows the general case use of a LogicalExpression anonymous inner class that contains the converted sub-expression and ensures that that sub-expression is not evaluated until the static method decides it needs to be evaluated. The evaluation is delegated to the static method.

This works for both AND and OR operators, in all 4 of the possible methods (and(), _and(), or(), _or()). The only problem is that there is extra syntax needed for the anonymous inner class. When Java gets support for closures (anonymous methods passed as simple blocks of code for deferred evaluation), this output will be dramatically improved.


© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.