OpenEdge Development: Progress 4GL Handbook


Table of ContentsPreviousNextIndex
Using Basic 4GL Constructs

In "Introducing the Progress 4GL," you learned some of the basic characteristics of the Progress 4GL and created a small test procedure. In this chapter you will extend your test procedure by:

Refining the data selection with a WHERE clause

So far you’ve been selecting all the Customer records in the database. Now you’ll refine that selection to show only those Customers from the state of New Hampshire. There is a State field in the Customer table that holds the two-letter state abbreviation for states in the USA.

The Progress 4GL supports a WHERE clause in any statement that retrieves data from the database, which will be familiar to you if you have used SQL or other similar data access languages. The WHERE keyword can be followed by any expression that identifies a subset of the data. You’ll learn a lot more about this in later chapters, but for now a simple expression is all you need.

To refine the data selection in your test procedure:
  1. Add the following WHERE clause to the end of your FOR EACH statement:
  2. FOR EACH Customer WHERE State = “NH”:

  3. Press F2 to see the reduced list of Customers. The list should now use only a bit more than a page.
  4. Add a sort expression to the end of your WHERE clause to sort the Customers in order by their City. The 4GL uses the keyword BY to indicate a sort sequence:
  5. FOR EACH Customer WHERE State = “NH” BY City:

  6. Press F2 to run the procedure again to see the effects of your change.

Comparison operators

The equal sign is just one of a number of comparison operators you can use in 4GL expressions. Table 2–1 provides a complete list.

Table 2–1: Comparison operators
Keyword
Symbol
Explanation
EQ
=
Equal to.
NE
<>
Not equal to.
GT
>
Greater than.
LT
<
Less than.
GE
>=
Greater than or equal to.
LE
<=
Less than or equal to.
BEGINS
Not applicable
A character value that begins with this substring.
MATCHES
Not applicable
A character value that matches this substring, which can include wild card characters.
The expression you use to the right of the MATCHES keyword can contain the wild card characters:
  • An asterisk (*) represents one or more missing characters.
  • A period (.) represents exactly one missing character.
CONTAINS
Not applicable
A database text field that has a special kind of index called a WORD-INDEX.
The WORD-INDEX indexes all the words in a field’s text strings, for all the records of the table, allowing you to locate individual words or associated words in the database records, much as you do when you use an Internet search engine to locate text in documents on the eb.

Using quotation marks

You can use single quotation marks (‘:) and double quotation marks (“) interchangeably to define a string constant. You must balance them properly, using the same type of quotation mark at the beginning and the end of the string. Use double quotes if your string contains a single quote, and vice versa, as in the following example:

DISPLAY “Always using the same type of quotes!”.
DISPLAY ‘:Like this.’.
DISPLAY “But never like this!’.

Creating nested blocks to display related data

To review, the FOR EACH statement in your procedure creates a code block nested inside the implicit main block of the procedure itself. Now you will create yet another block nested inside of that, to display the Order records in the database for each New Hampshire Customer.

To create a nested block, add another FOR EACH block inside the one you have, so that your procedure looks like this:

FOR EACH Customer WHERE State = “NH” BY City:
    DISPLAY CustNum Name City.
    FOR EACH Order OF Customer:
        DISPLAY OrderNum OrderDate ShipDate.
    END.
END.

This example shows the code indented so that the new block is visually nested in the outer block, which helps code readability. The intelligent editor should help you with this; if it doesn’t get it quite right, make the effort to indent the code properly yourself.

First, look at the new FOR EACH statement. The keyword OF is a shorthand for a WHERE clause that joins the two tables together. When you looked at the two tables and their fields in the Dictionary, you saw that both tables have a CustNum field. This is the primary key of the Customer table, which means that each Customer is assigned a unique number for the CustNum field, and this is the primary identifier for the Customer. In the Order table, the OrderNum is the unique Order identifier, and its primary key. The CustNum field in the Order table points back to the Customer the Order is for. It’s a foreign key because it points to a record in another table. To retrieve and display the Orders for a Customer, you have to join the two tables on the CustNum field that they have in common. The full WHERE clause for this join would be: WHERE Customer.CustNum = Order.CustNum. This kind of syntax will be familiar to you if you’ve ever worked with SQL.

The WHERE clause is telling Progress to select those records where the CustNum field in one table matches the CustNum field in the other. In order to tell Progress which field is which, both are qualified by the table name, followed by a period.

In the Progress 4GL, you can use the syntax Order OF Customer as a shorthand for this join if the two tables have one or more like-named fields in common that constitute the join relationship, and those fields are indexed in at least one of the tables (normally they should be indexed in both). You can always use the full WHERE clause syntax instead of the OF phrase if you wish; the effect is the same, and if there is any doubt as to how the tables are related, it makes the relationship your code is using completely clear. In fact, the OF phrase is really one of those beginner shortcuts that makes it easy to write a very simple procedure but which really isn’t good practice in a complex application, because it isn’t clear just from reading the statement which fields are being used to relate the two tables. You should generally be explicit about your field relationships in your applications.

These simple nested FOR EACH statements accomplish something that would be a lot of work in other languages. To retrieve the Customers and their Orders separately, as you really want to do, you would have to define two separate queries using embedded SQL syntax, open them, and control them explicitly in your code. This would be a lot of work. For example, the straight forward single SQL query to retrieve the same data would be:

SELECT Customer.CustNum, Name, City, OrderNum, OrderData, ShipDate FROM Customer, ORDER WHERE State = “NH” AND Customer.CustNum = Order.CustNum;

This code would retrieve all the related Customers and Orders into a single two-dimensional table, which is not very useful: all the Customer information would be repeated for each of the Customer’s Orders, and you would have to pull it apart yourself to display the information as header and detail, which is probably what you want.

By contrast, when you run your very simple Progress 4GL procedure, you get a default display that represents the data properly as master (Customer) and detail (Order) information, as shown in Figure 2–1.

Figure 2–1: Result of running simple sample procedure

Progress automatically gives you two separate display areas, one for the Customer fields showing one Customer at a time, and one for the Orders of the Customer. These display areas are called frames. You’ll learn more about Progress frames in later chapters.

Unlike in the first example, which displays a whole page of Customers, each time you press the SPACE BAR, Progress displays just the next Customer and its Orders. Why did Progress do this? The nested FOR EACH blocks tell Progress that there are multiple Orders to display for each Customer, so it knows that it doesn’t make sense to display more than one Customer at a time. So it creates a small frame just big enough to display the fields for one Customer, and then separately creates another frame where it can display multiple Orders. The latter frame is called a down frame, because it can display multiple rows of data as it moves down the page. The top frame is actually referred to as a one down frame because it displays only a single row of data at a time.

You can control the size of the frames, how many rows are displayed, and many other attributes, by appending a WITH phrase to your statement.

To see how the WITH phrase can affect your test procedure:
  1. Add the words WITH CENTERED to the DISPLAY statement for the Order frame:
  2. FOR EACH Customer WHERE State = “NH” BY City:
        DISPLAY CustNum Name City.
        FOR EACH Order OF Customer:
            DISPLAY OrderNum OrderDate ShipDate WITH CENTERED.
        END.
    END.

  3. Run the procedure again. The Order frame is centered in the default display window:

There are lots of frame attributes you can specify here (for a description of them, read the section on the Frame Phrase in the OpenEdge Development: Progress 4GL Reference). This book doesn’t tell you much more about them, either now or later, because you won’t use most of them in your GUI applications. These attributes are designed to help you define frames for a character mode application, where the interface is basically just a series of 24 or 25 lines of 80 characters each. For this kind of display format, a sequence of frames displayed one beneath the other is an appropriate and convenient way to lay out the screen. But in a GUI application, you instead lay out your display using a visual design tool, such as the OpenEdge AppBuilder, and it generates the code or data needed to create the user interface at run time. So this is another part of the snowplow exercise: this chapter shows you the basics of how the 4GL works and how it was designed, even though you will do most of your work a different way in your new applications.

Changing labels and formats

The default label for the Order Number field is Order Num (you define default labels in the Data Dictionary when you set up your database), and you might prefer that it just say Order. Also, the default display format for the ShipDate field is different from the format for the OrderDate field: one has a four-digit year and the other a two-digit year. You can change labels and default formats in your DISPLAY statement. If you add the LABEL keyword or the FORMAT keyword after the field name, followed by a string for the value you’d prefer, then the display changes accordingly.

To make such changes to your test procedure, change the OrderNum LABEL to Order and the ShipDate FORMAT to 99/99/99.

In the following example, each field has its own line in the code block, to make the code easier to read, and to emphasize doing that doesn’t change how the procedure works. Everything up to the period is one 4GL statement:

FOR EACH Customer WHERE State = "NH" BY City:
    DISPLAY CustNum NAME City.
    FOR EACH Order OF Customer:
        DISPLAY OrderNum LABEL "Order"
            OrderDate
            ShipDate FORMAT "99/99/99" WITH CENTERED.
    END.
END.

To view the results of these changes, Run your test procedure again:

Using program variables and data types

Like most programming languages, the Progress 4GL lets you define program variables for use within a procedure. Here is the basic syntax:

DEFINE VARIABLE varname AS datatype.

In this syntax, varname is the name of the variable, which must conform to the same rules as other names in the 4GL. (See the "Variable naming conventions" for details.)

Progress supports a range of data types. Table 2–2 lists the basic ones. There are some other special data types available for more advanced programming, but these are enough to get you started.

Table 2–2: Basic supported data types
Data type name
Default display format
Default initial value
CHARACTER
X(8)
“” (the empty string)
DATE
99/99/99
? (the Unknown value, which displays as blank for dates)
DECIMAL
->>,>>9.99
0
HANDLE
>>>>>>9
? (the Unknown value)
INTEGER
->,>>>,>>9
0
LOGICAL
yes/no
no

Here are a few notes on Progress data types:

Defining formats

The list of default formats for different data types introduced you to some of the format characters supported by Progress. Table 2–3 provides a quick summary of the format symbols you’re most likely to use.

Table 2–3: Common format symbols
This format character . . .
Represents . . .
X
Any single character.
N
A digit or a letter. A blank is not allowed.
A
A letter. A blank is not allowed.
!
A letter that is converted to uppercase during input. A blank is not allowed.
9
A digit. A blank is not allowed.
(n)
A number that indicates how many times to repeat the previous format character.
>
A leading digit in a numeric value, to be suppressed if the number does not have that many digits.
Z
A leading digit in a numeric value, to be replaced by a blank if the number does not have that many digits.
*
A leading digit in a numeric value, to be displayed as an asterisk if the number does not have that many digits.
,
A comma in a numeric value greater than 1,000. This is replaced by a period in European format. It is suppressed if it is preceded by a Z, *, or >, and the number does not have enough digits to require the comma.
.
A decimal point in a numeric value. This is replaced by a comma in European format.
+
A sign for a positive or negative number. It is display as + for a positive number, and a – for a negative number.
A sign for a negative number. It is displayed as a – for a negative number. For a positive number it is suppressed if it is to the left of the decimal point in the format, and replaced by a blank if it is to the right of the decimal point.

You can insert other characters as you wish into formats, and they are displayed as literal values. For example, the INTEGER value 1234 with the FORMAT $>,>>>ABC is displayed as $1,234ABC.

Other variable qualifiers

You can qualify a variable definition in a number of ways to modify these defaults. To do this, append one of the keywords listed in Table 2–4 to the end of your variable definition.

:

Table 2–4: Variable qualifiers
Keyword
Followed by
INITIAL
The variable’s initial value.
DECIMALS
The number of decimal places for a DECIMAL variable.
FORMAT
The display format of the variable, enclosed in quotation marks.
LABEL
The label to display with the variable. (The default is the variable name itself.)
COLUMN-LABEL
The label to display with the variable when it is displayed as part of a column of values. (The default is the LABEL if there is one, otherwise the variable name.)
EXTENT
An integer constant. This qualifier allows you to define a variable that is a one-based array of values. You can then reference the individual array elements in your code by enclosing the array subscript in square brackets, as in myVar[2] = 5.

As an alternative to specifying all of this, you can use the following syntax form to define a variable that is LIKE another variable or database field you’ve previously defined:

DEFINE VARIABLE varname LIKE fieldname

In this case it inherits the format, label, initial value, and all other attributes of the other variable or field. This is another strength of the Progress 4GL. Your application procedures can inherit many field attributes from the database schema, so that a change to the schema is propagated to all your procedures automatically when they are recompiled. You can also modify those schema defaults in individual procedures.

There’s one more keyword that you should almost always use at the end of your variable definitions, and that is NO-UNDO. This keyword has to do with how Progress manages transactions that update the database. When you define variables in your 4GL programs, Progress places them into two groups. Those that don’t have the NO-UNDO qualifier are treated as though they were a database record with those variables as fields. If any of the variable values are modified during the course of a database transaction, and then the transaction is backed out, changes to the variable values made during the transaction are backed out as well, so that they revert to their values before the transaction started. This can be very useful sometimes, but in practice, most variables do not need this special treatment, and because Progress has to do some extra work to manage them, it is more efficient to get into the habit of appending NO-UNDO to the end of your variable definitions unless the variable is one that should be treated as part of a transaction. This places them into a second group where Progress manages them without the transaction support.

Variable naming conventions

There are no specific naming requirements for variables, but there are some recommended guidelines that will bring your own variables into line with the standards used in the OpenEdge development tools and their support code.

You should begin a variable with a lowercase letter (or sometimes two) to indicate the data type of the variable. This can help readers of your code to understand at a glance how a variable is being used. When you start doing more dynamic programming later on, it is very important to differentiate between a variable that represents a value directly, and one that is a handle to an object that has a value. Here are some recommended data type prefixes:

The rest of the name should be in mixed case, where capital letters are used to identify different words or subparts of a name, as in cCustName or iOrderCount.

Placement of variable definitions

Where you place variables in your code also makes a difference. Progress does a single pass through the statements in a procedure to build the intermediary code that it uses to execute the procedure. Because a variable is a definitional element of a procedure and not a statement that is executed in sequence, it does not really matter where the variable definition appears. However, because of the one-pass nature of the Progress syntax analyzer, the definition has to appear before the variable is used in the procedure. By convention, it is usually best for you to define all your variables at the top of a procedure, to aid in readability and to make sure that they’re all defined before they’re used.

For the next change to the test procedure, you will put to work several of the concepts you’ve just learned about. You will display a special value for each Order record. This task involves defining a variable with an initial value, writing an IF-THEN construct with a check for the Unknown value, and then using one of the many built-in Progress 4GL functions to extract a value to display. The value you will display is the ShipDate month of an Order expressed as a three-character abbreviation, such as JAN or FEB.

To build up the list of possible values for the month, you need to define a CHARACTER variable to hold the list. Add the following variable definition to the top of your procedure:

DEFINE VARIABLE cMonthList AS CHARACTER NO-UNDO
    INIT "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC".

Defining an IF-THEN-ELSE decision point

You’ll notice when you run your procedure that some of the ship dates aren’t defined. Some of these Orders apparently have been on hold for a very long time! Let’s construct a statement to branch in the code depending on whether the ShipDate field is defined or not. The 4GL, like most languages, has an IF-THEN construct for such decision points. It can also have an ELSE branch:

IF condition THEN { block | statement } [ ELSE { block | statement } ]

The condition is any expression that evaluates to true or false. Following the THEN keyword, you can place a single 4GL statement, or a whole set of statements that are all to be executed if the condition is true. The way to represent a block that simply groups a series of statements together that are all executed together is a DO-END block:

IF condition THEN
DO:
    statement
    .
    .
    .
END.

If you include the ELSE keyword followed by a statement or a block of statements, that branch is taken if the condition evaluates to false.

Using the Progress Unknown value

Although the ShipDate is displayed as being blank in these cases, the value stored in the database isn’t a blank value, because this is a date, not a character value. If you were to add an expression to your code to compare the ShipDate to a blank value, you would get your very first Progress 4GL compiler error:

FOR EACH Customer WHERE State = "NH" BY City:
    DISPLAY CustNum NAME City.
    FOR EACH Order OF Customer:
        IF ShipDate NE "" THEN
        DISPLAY OrderNum LABEL "Order"
            OrderDate
            ShipDate FORMAT "99/99/99" WITH CENTERED.
    END.
END.

Whenever you press the F2 key to run your procedure, Progress performs a syntax validation. If it can’t properly process the code you’ve written, you get an error such as this one. In this case, Progress sees that you’re trying to compare a field defined to be a date with a character string constant, and these don’t match. To correct this, you need to change the nature of your comparison.

The value stored in the database to represent a value that isn’t defined is called the Unknown value. In 4GL statements you represent the Unknown value with a question mark (?). Note that you don’t put quotation marks around the question mark; it acts as a special symbol on its own. It is not equal to any defined value. In the 4GL, you write a statement that looks as though you are comparing a value to a question mark, such as IF ShipDate = ?, but in fact the statement is asking if the ShipDate has the Unknown value, which means it has no particular value at all. The Unknown value is like the NULL value in SQL, and the expression IF ShipDate = ? is like the SQL expression IF ShipDate IS NULL.

Using built-In 4GL functions

To complete this latest change to the procedure, you need to define the statement that checks whether the ShipDate is Unknown, and then picks out the month from the date, using the cMonthList variable you defined just above, and converts it to one of the three-letter abbreviations in the list. Here is the whole statement:

IF ShipDate NE ? THEN
DISPLAY ENTRY(MONTH(ShipDate), cMonthList) LABEL "Month".

Now take a closer look at the elements in the DISPLAY statement. First there is a new keyword, ENTRY. This is the name of a built-in function in the 4GL. There are many such functions to do useful jobs for you, to save you the work of writing the code to do it yourself. The ENTRY function takes two arguments, and as you can see, those arguments are enclosed in parentheses. The first is an INTEGER value, which identifies an entry in a comma-separated list of character values. In this case it represents the month of the year, from 1 to 12. The second argument is the list that contains the entry the function is retrieving. In this case it is the variable you just defined.

Looking closer, you can see that the first of the two arguments to the function, MONTH(ShipDate), is itself another function. This function takes a Progress DATE value as an argument, extracts the month number of the date, and returns it. The returned value is an INTEGER from 1 to 12 that the ENTRY function then uses to pick out the right entry from the list of months in cMonthList. So if the month is May, the MONTH function returns 5 and the ENTRY function picks out the fifth entry from the list of months and returns it to the DISPLAY statement.

Here are some general observations about built-in functions:

To display the month along with each Order:
  1. Add the new statement with the function references into your procedure, inside the block of code that loops through the Orders:
  2. DEFINE VARIABLE cMonthList AS CHARACTER NO-UNDO
        INIT "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC".
    FOR EACH Customer WHERE State = "NH" BY City:
        DISPLAY CustNum NAME City.
        FOR EACH Order OF Customer:
            IF ShipDate NE "" THEN
            DISPLAY OrderNum LABEL "Order"
                OrderDate
                ShipDate FORMAT "99/99/99" WITH CENTERED.
            IF ShipDate NE ? THEN
                DISPLAY ENTRY(MONTH(ShipDate), cMonthList) LABEL "Month".
        END.
    END.

  3. To see the effect of the new code, rerun the procedure:
  4. Note: Several separate 4GL DISPLAY statements contribute to the display of fields in a single line for each Order. This is one of the powerful and flexible characteristics of the 4GL. Progress can gather together a number of different statements in a procedure, some of which might be executed conditionally, and combine them together into a single operation such as this. This feature is generally not possible with other programming languages.
  5. To save your procedure, press F6.

Progress 4GL Functions

This section provides a quick summary of some of the most useful functions available to you in the 4GL:

Other sections of the book look at functions that convert data values and otherwise operate as part of data retrieval.

Date functions

Table 2–5 describes functions that return or operate on data values.

Table 2–5: Date functions
Function
Arguments
Returned value
DAY
DATE
INTEGER — The day of the month.
MONTH
DATE
INTEGER — The month of the year.
YEAR
DATE
INTEGER — The year.
WEEKDAY
DATE
INTEGER — The day of the week, starting with 1 for Sunday.
TIME
none
INTEGER — The number of seconds since midnight.
TODAY
none
DATE — Today’s date.

Functions that convert data are discussed in later chapters. An example of one for now is the STRING function.

To see how you can use the STRING function to convert the time into a meaningful display:
  1. In the Procedure Editor, select File New Procedure to open a new procedure window.
  2. Enter the following code, which applies the STRING function to the result of the TIME function, along with the special formatting characters HH (hour), MM (minute), and SS (second):
  3. DISPLAY STRING(TIME, “HH:MM:SS”).

  4. Press F2:
List functions

The functions described in Table 2–6 operate on lists. All the list functions are one-based, that is, the first element in a list is considered to be element 1, not 0.

Table 2–6: List functions
Function
Arguments
Returned value
ENTRY
element AS INTEGER, list AS CHAR, delimiter AS CHAR
CHARACTER — The nth element in a delimited list, where n is the value of the element argument. The delimiter is optional and defaults to comma.
LOOKUP
element AS CHAR, list AS CHAR, delimiter as CHAR
INTEGER — The numeric (one-based) location of the element within the list. The delimiter is optional and defaults to a comma. The function returns 0 if the element is not in the list.

4GL string manipulation functions

The functions described in Table 2–7 operate on character strings.

Table 2–7: 4GL string manipulation functions
Function
Arguments
Returned value
FILL
expression AS CHAR, repeat-count AS INTEGER
CHARACTER — A character string made up of the expression repeated repeat-count times.
INDEX
source AS CHAR, target AS CHAR, starting-point AS INTEGER
INTEGER — The position of the first occurrence of the target string within the source string, relative to the starting-point. The starting-point is optional and defaults to 1 (the beginning of the string).
LEFT-TRIM
string AS CHAR, trim-chars AS CHAR
CHARACTER — The input string, with the trim-chars removed from the beginning of the string. The trim-chars argument is optional, and defaults to any white space (spaces, tabs, carriage returns, and line feeds).
LENGTH
string AS CHAR, type AS CHAR
INTEGER — The number of characters, bytes, or columns in the string. The type is optional and defaults to CHARACTER. Other possible values for this argument are RAW, which makes the function return the number of bytes in the string, and COLUMN, which causes it to return the number of display or print character-columns. These latter two types are useful for manipulating strings that might contain double-byte characters representing characters in non-European languages.
R-INDEX
source AS CHAR, target AS CHAR, starting-point AS INTEGER
INTEGER — The position of the target string within the source string, but with the search starting at the end of the string rather than the beginning. The position, however, is counted starting at the left. This function is useful for returning the right-most (last) occurrence of the source substring. The starting-point is optional and defaults to 1.
REPLACE
source AS CHAR, from-string AS CHAR, to-string AS CHAR
CHARACTER — The source string with every occurrence of the from-string replaced by the to-string.
RIGHT-TRIM
string AS CHAR.trim-chars AS CHAR
CHARACTER — The input string with the trim-chars removed from the end of the string. The trim-chars argument is optional and defaults to all white space.
SUBSTRING
source AS CHAR, position AS INTEGER, length AS INTEGER, type AS CHARACTER
CHARACTER — The substring of the source string beginning at the position. The length is optional and specifies the (maximum) length to return. The default is the remaining length of the string starting at position. The type is also optional and has same values as for the LENGTH function.
TRIM
string AS CHAR. trim-chars AS CHAR
CHARACTER — The input string with the trim-chars removed from both the beginning and the end of the string. The trim-chars argument is optional and defaults to all white space.

Putting a calculation into your procedure

The next change to your sample procedure is to perform a simple calculation and display a value based on the result. This section provides an introduction to representing arithmetic expressions in the Progress 4GL. It also discusses how to use some of the special built-in functions for advanced arithmetic operations.

Arithmetic expressions and operands

The Progress 4GL supports the set of arithmetic operands described in Table 2–8. You can use these operands to define expressions. You might be familiar with them from other programming languages you have used.

Table 2–8: Supported arithmetic operands
Symbol
Explanation
+
Adds numeric values.
Concatenates character strings.
-
Subtracts numeric values or date values.
*
Multiplies numeric values.
/
Divides numeric values.

There’s one special thing you need to know when you’re writing expressions involving these operands. Because the Progress 4GL allows the use of a hyphen as a character in a procedure name, variable name, or database field name, it cannot recognize the difference between a hyphen and a minus sign used for subtraction, which are the same keyboard character. For example, there’s no way for the syntax analyzer to tell whether the string ABC-DEF represents a single hyphenated variable or field name, or whether it represent the arithmetic expression ABC minus DEF, involving two fields or variables named ABD and DEF. For this reason, you have to put a space or other white space characters around the “-” character when you use it as a minus sign for subtraction of one number from another. Note that you don’t have to insert a space after a minus sign that precedes a negative number, such as –25. For consistency, the other arithmetic operands also require white space. If you forget to put it in, you’ll get an error, except in the case of the forward slash character. In the case of the slash, if you leave out the white space, Progress interprets the value as a date! So, for example, 5/6 represents May 6th, not a numeric fraction.

To illustrate how to use arithmetic operands in the sample procedure, you need to determine whether the CreditLimit of the Customer is less than twice the outstanding Balance. If this is true, then you must display the ratio of CreditLimit to Balance. Otherwise you display the Orders for the Customer. Add the following code, just in front of the FOR EACH Order OF Customer statement that’s already there:

IF CreditLimit < 2 * Balance THEN
        DISPLAY "Credit Ratio:" CreditLimit / Balance.
    ELSE FOR EACH Order OF Customer:

You can add parentheses to such an expression to make the grouping of terms explicit. Otherwise, Progress observes the standard rules of precedence. Multiplication and division are performed before addition and subtraction, and all such calculations are performed before a comparison operation.

The expression following the IF keyword compares the CreditLimit field from the current Customer record with two times the value of the Balance field. If the first value is less than the second, then the expression is true and the statement following the THEN keyword is executed, which displays the string expression Credit Ratio: followed by the value of the CreditLimit divided by the Balance.

The ELSE keyword is followed by the entire FOR EACH Order block, so that block of code, which displays all the Orders of the Customer, is skipped if the expression is true, and executed only if it is false.

To see the result of this change, run your procedure again. For a Customer where the ratio is greater than or equal to 2, the Orders display as before. For a Customer where the ratio is less than 2, the new expression is displayed instead:

You might notice a couple of things about this display:

Arithmetic built-in functions

Table 2–9 describes some of the useful built-in functions that extend the basic set of numeric operands.

Table 2–9: Arithmetic built-in functions
Function
Arguments
Returned value
ABSOLUTE
value AS INTEGER or DECIMAL.
INTEGER or DECIMAL — The absolute value of the numeric value.
EXP
base AS INTEGER or DECIMAL, exponent AS INTEGER or DECIMAL.
INTEGER or DECIMAL— The result of raising the base number to the exponent power.
LOG
expression AS DECIMAL, base AS INTEGER or DECIMAL.
DECIMAL— The logarithm of the expression using the specified base. The base is optional; the natural logarithm, base (e), is returned by default.
MAXIMUM
two or more expressions AS INTEGER or DECIMAL.
INTEGER or DECIMAL— The largest value of the expressions.
MINIMUM
two or more expressions AS INTEGER or DECIMAL.
INTEGER or DECIMAL— The smallest value of the expressions.
MODULO
This function has the special syntax: expression MODULO base.
INTEGER— The remainder after division. The expression and base must be INTEGER values.
RANDOM
low-value AS INTEGER, high-value AS INTEGER.
INTEGER — A random INTEGER value between the low-value and the high-value (inclusive). There is a Random (–rand) Progress startup option that determines whether a different set of values is returned for each OpenEdge session.
ROUND
expression AS DECIMAL, precision AS (positive) INTEGER.
DECIMAL — The DECIMAL expression rounded to the number of decimal places specified by the precision. The rounding is down for all values beyond the precision that are less than .5, and up for all higher values.
SQRT
expression AS INTEGER or DECIMAL.
DECIMAL — The square root of the expression.
TRUNCATE
expression AS DECIMAL, precision AS (non-negative) INTEGER.
DECIMAL — The expression truncated to the specified precision.

Using the Intelligent Edit Control and its shortcuts

The first step in the next part of your sample procedure involves defining another program variable. You won’t do too many of these before you might begin to get tired of typing the words DEFINE VARIABLE and NO-UNDO and so forth over and over again. In fact, you don’t have to do all that typing at all.

You’ve already seen that when you use the Progress Procedure Editor to edit your Progress 4GL code, you are using an intelligent syntax editor that can perform many tasks for you. One of these is syntax completion. Using this feature can save you many keystrokes in your programming, helps to prevent typographical errors, and helps to impose a standard form to your code.

To define a new INTEGER variable for your procedure:
  1. Position the cursor to the beginning of the line where you want the definition to go, following the variable definition you have already, then enter the letters DVI, or DVIN, which stand for Define Variable INteger.
  2. Press the SPACE BAR. The following code appears:
  3. The text DEFINE VARIABLE AS INTEGER NO-UNDO is inserted for you, and the cursor is placed right where you need it so you can enter the variable name.
  4. Type iDays as the variable name. You’ll use this variable later.

Here are the abbreviations for other data types that you’ve learned:

To see all the abbreviations (also called aliases) and other editing options available to you, select Options Editing Options from the Procedure Editor menu. The Editing Options dialog box appears:

Among other things, this dialog box specifies the following default settings:

To see a list of all the aliases (also called shortcuts) that are defined, choose the Alias button. The Alias Editor dialog box appears:

In this dialog box you see all the aliases for the DEFINE VARIABLE statement, along with a number of other useful ones, including one that automatically respells the (apparently) common typographical error DIPSLAY as DISPLAY. Aliases can be further abbreviated to any unique substring of the alias, which is why DVI works as well as DVIN. Note that you can also define new aliases of your own. They are stored in a file called p4gl.als.

To peruse another set of options that you can customize, choose OK to return to the main Editing Options dialog box, then choose the Options button. The Progress 4GL Options dialog box appears:

This dialog box includes, among other options, the automatic uppercasing of keywords that you’ve seen in action. If this isn’t your preference, you can turn it off.

To customize which keywords you want colored and what colors to use, choose OK to return to the Editing Options dialog box, then choose the Color Coding button. The Color Coding Setup dialog box appears:

If you choose the Help button on any of these dialog boxes, you can see detailed information on all the options available to you.

Getting to online help

You can access the OpenEdge Online Help system at any time just by pressing the F1 key. Help is context-sensitive, so it always tries to come up initialized to a section of the help files that seems relevant to what you’re doing. For example, if you press F1 from within the Editing Options dialog box, you get help on the options in that dialog box:

In a Progress editor, if you highlight one or more keywords in a procedure and press F1, you get help for that type of statement.

To try accessing online help:
  1. In your test procedure, highlight the keywords DEFINE VARIABLE, then press F1:
  2. The online help window appears showing the help keyword index:

  3. Choose Display to see the help text for this entry:
  4. Choose the Contents button to access the available online help for all the OpenEdge tools, or choose the Find button to specify one or more key words to search for in the help text.

Saving and compiling your test procedure

You’ve written your first Progress 4GL procedure. As you make changes to it, you should resave it by selecting File Save from the Procedure Editor menu or by just pressing F6.

This section examines what happens when you press the F2 key or select Compile Run from the menu to run your procedure:

  1. When you tell Progress to run your procedure in this way, the Procedure Editor creates a temporary file from the 4GL code currently in the editor window and then passes this file to the Progress compiler.
  2. The compiler performs a syntax analysis of the procedure and stops with one or more error messages if it detects any errors in your 4GL statements.
  3. If your procedure is error-free, the compiler then translates or compiles your procedure into an intermediate form that is then interpreted by the Progress run-time interpreter to execute the statements in the procedure. This intermediate form is called r-code, for run-time code. It is not a binary executable but rather a largely platform-independent set of instructions (independent except for the user interface specifics, that is).
  4. The Progress run-time interpreter reads and executes the r-code.

This ability to compile and run what’s in the editor on the fly makes it easy for you to build and test procedures in an iterative way, making a change as you have done and then simply running the procedure to see the effects. Naturally, you want to save the r-code permanently when you have finished a procedure so that Progress can skip the compilation step when you run the procedure and simply execute the r-code directly. Generally it is just the compiled r-code that you deploy as your finished application.

To save the source procedure itself, use the standard File Save As menu option in the Procedure Editor, which saves the contents of the editor out to disk and gives it a filename with a .p extension.

To compile a procedure that you have saved, you need to use the COMPILE statement in the 4GL. There’s a COMPILE statement rather than just having a single key to press because the COMPILE statement in fact has a lot of options to it, including where the compiled code is saved, whether a cross-reference file is generated to help you debug your application, and so forth. For now, it is sufficient to know just the simple form of the statement you use to save your compiled procedure.

To compile your test procedure:
  1. From the Procedure Editor, select File New Procedure Window.
  2. Another editor window appears that has all the same capabilities as the one with which you started out, except that not all the menu options are available from it. You can be working with any number of procedure windows at one time, and save them independently as separate named procedures. Or you can bring up a window just to execute a statement, as you are doing now.
  3. In the new editor window, enter the following statement:
  4. COMPILE h-CustSample.p SAVE.

  5. Press F2 or select Compile Run. The procedure window disappears and almost immediately returns.

The compilation of h-CustSample.p is complete. If the compiler had detected any syntax errors in the procedure, you would have seen them and the procedure would not have compiled.

If you check your working directory you can see the compiled procedure. It has the .r extension and is commonly referred to as a .r file.

One option for the COMPILE statement is worth mentioning at this time. You can specify a different directory for your .r files. This is a good idea, especially when you get to the point where you are ready to start testing completed parts of your application. Use this syntax:

COMPILE source-procedure SAVE INTO directory.

If you need to recompile a large number of procedures at once, you can use a special tool to do that for you. This is available through the Tools Application Compiler option on the Procedure Editor.

Now you’ve compiled your first procedure! In the next chapter, you learn how to run one procedure from another.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095
Table of ContentsPreviousNextIndex