OpenEdge Development: Progress 4GL Handbook


Table of ContentsPreviousNextIndex
Running Progress 4GL Procedures

This chapter describes how you can work with and run different types of Progress 4GL procedures. It covers the following topics:

Running a subprocedure

To run a procedure from within another 4GL procedure, you use the RUN statement. Like the COMPILE statement, the RUN statement has a lot of options, some of which you’ll learn about in later chapters. For now a very simple form of the statement will do:

RUN procedure-name [ (parameters) ].

If you are running a procedure file like your h-CustSample.p, then the convention is to include the .p extension in the procedure name. For example, if you bring up a new Procedure Window again, you can enter this statement to run the sample procedure you’ve been working on, then press F2:

This is a little different than just pressing the F2 key in the procedure window where you’re editing h-CustSample.p. When you press F2 or select Compile Run you are compiling and running the current contents of the Procedure Editor window, which might be different from what you have saved to a named file. You can compile and run a procedure without giving it a name at all. Indeed, that’s what you just did: you created a procedure containing a single RUN statement, and then simply compiled and executed it by pressing F2. This temporary procedure in turn ran the named procedure you had already saved and compiled.

Now, why did you put the .p extension on the RUN statement when you have already saved a compiled .r version of the file out to your directory? When you write a 4GL RUN statement with a .p extension on the procedure name, Progress always looks first for a compiled file of the same name, but with the .r extension. If it finds it, it executes it. If it doesn’t, it passes the source file to the Progress compiler, which compiles it on the fly and then executes it. In this way, your application can be a mix of compiled and uncompiled procedures while you’re developing it. If you always use the .p extension in your RUN statements, then your procedures are always found and executed whether they’ve been compiled or not. By contrast, if you specify the .r extension in a RUN statement, then Progress looks for only the compiled .r file, and the RUN fails if it isn’t found.

Using the Propath

Before you continue on to look at the parameters that you can pass to a RUN statement, you should understand a little about how Progress searches for procedures. When you type RUN h-CustSample.p, how does Progress know where to look for it?

Progress uses its own directory path list, called the Propath, which is stored as part of its initialization (progress.ini) file. When you install OpenEdge, you get a default progress.ini file in the bin subdirectory under your install directory. You can also create a local copy of this file if you want to change its contents.

If you want to look at and maintain your Propath, there’s a tool to do that for you. It’s one of a number of useful tools you can access from the Procedure Editor.

To access these tools from the Procedure Editor, select Tools PRO*Tools. The PRO*Tools palette appears:

You can reposition and reshape the PRO*Tools palette.

To retain the palette’s new shape and position permanently:
  1. Right-mouse click on the PRO*Tools palette outside the area where icons are displayed. The Menu Bar pop-up menu item appears:
  2. Select the Menu Bar pop-up item. A File menu appears at the top of the palette:
  3. Select File Customize. The PRO*Tools Customization dialog box appears:
  4. Using this tool you can add more useful tools of your own to the palette. For now, however, all you need to note is that the Save Current Palette Position and Orientation toggle box lets you save these settings permanently, so that the PRO*Tools palette always comes up the way you want each time you start OpenEdge.
  5. Leave the Save Current Palette Position and Orientation toggle box checked on, then choose OK.
To view and edit your Propath, choose the Propath icon from the PRO*Tools palette.

The Propath Editor shows you a list of all the directories in which Progress looks, both to find all its own executable files and other supporting files, and to find your application procedures. Here is the default Propath you get when you install the product:

As you can see, Progress first looks in your current working directory (by default, C:/Program Files/OpenEdge). This is where it expects to find any procedures you have written and are trying to compile or run. After that it looks in the gui directory under your OpenEdge install directory. This is where it expects to find the compiled r-code for all the 4GL procedures that support your development and run-time environments. Remember that most of the OpenEdge development tools, including the Procedure Editor, are themselves written in the 4GL.

In the gui directory are a number of procedure libraries, each with a .pl filename extension. These are collections of many .r files, gathered together into individual operating system files. The adecomm.pl file, for example, is a library of many dozens of procedures that are common to the development tools, called the Application Development Environment (ADE). It’s more efficient to store them in a single library because it takes up less space on your disk and it’s faster to search.

Following these procedure library files is the install directory itself. This holds a few startup files as well as the original versions of the sports2000 database and other sample databases shipped with the OpenEdge products.

Next is the bin directory. This is where all the executable files are located, including all the supporting procedures for compiling and running your application, which are not written in the 4GL. Finally, there are a couple of directories that support building a custom OpenEdge executable.

Do not modify any of the directories below the current directory. If you try to, Progress resets the Propath, because it recognizes that the tools will stop running if it can’t find all the pieces it needs. But you can add directories above, below, or in place of your current working directory. For example, if you save your r-code into a different directory using the SAVE INTO option on the COMPILE statement, then you must add this directory to your Propath.

When you COMPILE or RUN a procedure, you can specify a partial pathname for it relative to some directory that is in your Propath. For example, if you save something called NextProc.p into a subdirectory called morecode, you can run it using this statement:

RUN morecode/NextProc.p.

Note: You should always use forward slashes in your Progress code, even though the DOS and Windows standard is backslashes. Progress does the necessary conversions to make the statement work on a PC, but if some of your Progress 4GL code is written for another platform, such as UNIX, then it requires the forward slashes.

You can modify your Propath by using the Add, Modify, and Remove buttons in the Propath Editor.

If Progress is having difficulty locating a procedure that you’ve written, or if you have different versions of a procedure in different directories you can check to see how Progress searches for a particular procedure.

To verify which file Progress finds:
  1. In the Propath Editor, choose the Search button. The Propath File Search dialog box appears:
  2. Enter the name of the procedure, including the relative pathname if that’s part of your RUN statement. Also include the same filename extension you are using in your code.
  3. Choose the Search button. Progress displays all the versions of that file it can find, in the order in which it uses them. Thus the one at the top of the list is always the one Progress tries to execute. In this example, the display confirms that when you run h-CustSample.p, it chooses the .r file (if there is one) in preference to the source procedure:

If you save your r-code to a different directory (or set of directories) than you use for the source procedures, remember to construct your Propath in such a way that the r-code is found first if it is there, but that the source procedures are also found, at least when you are in development mode, without requiring a special relative pathname in your code to locate them.

In the OpenEdge install directories, for example, all the compiled 4GL procedures are under the gui directory. The corresponding source procedures are in an equivalent directory tree, but under an src directory. If you were to add the src directory to your Propath, then Progress could locate source procedures to compile and execute on the fly as needed. If you were to place the src directory above the gui directory in your Propath, then Progress would use all the source procedures and compile them on the fly because it finds them first. This would slow down your program execution dramatically. This is just an example of how, as you develop complex applications, you must be careful to arrange your Propath so that Progress can find the procedures it needs efficiently, without doing a lot of unnecessary searching or running uncompiled procedures.

Using external and internal procedures

A separate source procedure, such as h-CustSample.p, is known as an external procedure. This is to distinguish it from another kind of the procedure that you’ll write as the next step in your sample. you can define one or more named entry points within an external procedure file, and these are called internal procedures. You run internal procedures in almost exactly the same way that you run external procedures, except that there is no .p filename extension on the internal procedure.

For your sample, you will run a procedure called calcDays that calculates the number of days between the ShipDate for each Order and today’s date. In order for calcDays to do the calculation, you need to pass the ShipDate to the procedure, and get the count of days back out. Progress supports parameters for its procedures to let you do this. Follow these guidelines:

To run the calcDays procedure from your sample procedure:
  1. Add the following RUN statement to your h-CustSample.p procedure, inside the FOR EACH Order OF Customer block, just before the END statement. Pass in the ShipDate field from the current Order and get back the calculated number of days, which uses the iDays variable you defined earlier when you learned to use the intelligent editor’s aliases:
  2. RUN calcDays (INPUT ShipDate, OUTPUT iDays).

  3. Following this, still inside the FOR EACH Order block, write another statement to display the value you got back along with the rest of the Order information:
  4. DISPLAY iDays LABEL "Days" FORMAT "ZZZ9".

Writing internal procedures

Now it’s time to write the calcDays procedure. Put it at the end of h-CustSample.p, following all the code you’ve written so far.

Each internal procedure starts with a header statement, which is just the keyword PROCEDURE followed by the internal procedure name and a colon.

Following this you need to define any parameters the procedure uses. The syntax for this is very similar to the syntax for the DEFINE VARIABLE statement. In place of the keyword VARIABLE, use the keyword PARAMETER, and precede this with the parameter type—INPUT, OUTPUT, or INPUT-OUTPUT. Note that the keyword INPUT is not optional in parameter definitions. Here’s the declaration for the calcDays procedure and its parameters. The parameter names start with the letter p to help identify them, followed by a prefix that identifies the data type as DATE or INTEGER:

PROCEDURE calcDays:
DEFINE INPUT PARAMETER pdaShip AS DATE NO-UNDO.
DEFINE OUTPUT PARAMETER piDays AS INTEGER NO-UNDO.

Now you can write 4GL statements exactly as you can for an external procedure. If you want to have variables in the subprocedure that aren’t needed elsewhere, then define them following the parameter definitions. Otherwise you can refer freely to variables that are defined in the external procedure itself. You’ll take a much closer look at variable scope and other such topics later. Be cautious when you use variables that are defined outside a procedure unless you have a good reason for using them, because they compromise the modularity and reusability of your code. For example, if you pull your procedures apart later and put the calcDays procedure somewhere else, it might break if it has a dependency on something declared outside of it. For this reason, you pass the calculated number of days back as an OUTPUT parameter, even though you could refer to the variable directly.

Assigning a value to a variable

The calcDays procedure just has a single executable statement, but it’s one that demonstrates a couple of key new language concepts—assignment and unknown value.

You’re probably familiar with language statements that assign values by using what looks like an equation, where the value or expression on the right side is assigned to the field or variable on the left. Progress does this too, and uses the same equal sign for the assignment that is used for testing equality in comparisons. However, you can use the keyword EQ only in comparisons, not in assignments.

In this example, you want to subtract the ShipDate (which was passed in as the parameter pdaShip) from today’s date (which, as you have seen, is returned by the built-in function TODAY) and assign the result to the OUTPUT parameter piDays.

To make this change in your sample procedure, add the following statement:

piDays = TODAY - pdaShip.

This is simple enough, but it won’t work all the time. Remember that some of the Orders have not been shipped, so their ShipDate has the Unknown value. If you subtract an Unknown value from TODAY (or use an Unknown value in any other expression), the result of the entire expression is always the Unknown value. In this case you want the procedure to return 0. To do this you can use a special compact form of the IF-THEN-ELSE construct as a single 4GL expression, appearing on the right side of an assignment. The general syntax for this is:

result-value = IF logical-expression
THEN value-if-true ELSE value-if-false.

This syntax is more concise than using a series of statements of the form:

IF logical-expression THEN result-value = value-if-true.
ELSE result-value = value-if-false.

For your purposes you can refine the assignment statement in this way to allow for Unknown values:

piDays = IF pdaShip = ? THEN 0 ELSE TODAY - pdaShip.

Finally, you end each internal procedure with an END PROCEDURE statement. The keyword PROCEDURE is optional, but is always a good idea as it improves the readability of your code:

END PROCEDURE.

To see the calculated days displayed, Run the procedure once more:

In summary, you define an internal procedure just as would an external procedure, with the exception that each internal procedure must start with a PROCEDURE header statement and end with its own END PROCEDURE statement. An external procedure uses neither of these.

You RUN an internal procedure just as you would an external procedure, except that there is no filename extension on the procedure name. Here are a couple of important related points:

First, it is possible for you to run an external procedure by naming it in a RUN statement without any filename extension, for example, RUN CustSample. In this case, Progress searches for these forms of the procedure in this order:

  1. An internal procedure named CustSample.
  2. An external compiled file named CustSample.r.
  3. An external source procedure named h-CustSample.p.

To do this, however, would be considered very bad form. When a person reading Progress code sees a RUN statement with no filename extension, it is natural for that person to expect that it is an internal procedure. There is rarely a good reason for violating this expectation.

Second, and even more exceptional, is that because a period is a valid character in a procedure name, it would be possible to have an internal procedure named, for example, calcDays.p, and to run it under that name. You should never do this. Always avoid confusing yourself or others who read your code.

When to use internal and external procedures

The decision as to when to use a separate external procedure file versus when to make an entry point for an internal procedure in a larger file is largely a matter of style and application organization. It’s a good idea to group together in a single procedure file related small procedures that call one another or that are typically called by multiple other procedures. On the other hand, large procedures that perform complex operations on their own should probably be independent external procedure files. Keep in mind that when you need to run an internal procedure, Progress first needs to load the entire procedure file that contains it, and this can consume excessive memory if you make your procedure files extremely large. Later in "Advanced Use of Procedures in Progress," you’ll learn about persistent procedures and how you can preload a file that contains many internal procedures that other procedures need to call.

Adding comments to your procedure

The final step in this exercise is to add some comments to your procedure to make sure you and everyone else can follow what the code does. In Progress you begin a comment with the characters /* and end it with */. A comment can appear anywhere in your procedure where white space can appear (that is, anywhere except in the middle of a name or other token). You can put full-line or multi-line comments at the top of each section of code, and shorter comments to the right of individual lines. Just make sure you use them, and make them meaningful to you and to others. The intelligent editor colors comments in green by default. The editor can also type the comment symbols for you, if you type CMT and press the SPACEBAR in the editor window. Here’s the final procedure with a few added comments:

h-CustSample.p
/* h-CustSample.p— shows a few things about the Progress 4GL */
DEFINE VARIABLE cMonthList AS CHARACTER NO-UNDO
INIT "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC".
DEFINE VARIABLE iDays AS INTEGER NO-UNDO. /* used in calcDays proc */
/* First display each Customer from New Hampshire: */
FOR EACH Customer WHERE state = "NH" BY City:
DISPLAY CustNum NAME City.
/* Show the Orders unless the Credit Limit is less than
twice the balance. */
IF CreditLimit < 2 * Balance THEN
DISPLAY "Credit Ratio:" CreditLimit / Balance .
ELSE FOR EACH Order OF Customer:
DISPLAY OrderNum LABEL "Order"
OrderDate ShipDate FORMAT "99/99/99" WITH CENTERED.
/* Show the month as a three-letter abbreviation, along with the
number of days since the order was shipped. */
IF ShipDate NE ? THEN
DISPLAY ENTRY(MONTH(ShipDate), cMonthList) LABEL "Month".
RUN calcDays (INPUT ShipDate, OUTPUT iDays).
DISPLAY iDays LABEL "Days" FORMAT "ZZZ9".
END.
END.
PROCEDURE calcDays:
/* This calculates the number of days since the Order was shipped. */
DEFINE INPUT PARAMETER pdaShip AS DATE NO-UNDO.
DEFINE OUTPUT PARAMETER piDays AS INTEGER NO-UNDO.
piDays = IF pdaShip = ? THEN 0
ELSE TODAY - pdaShip.
END PROCEDURE.

You’ve learned a tremendous amount about the Progress 4GL in the course of writing a procedure barely over twenty lines long, which retrieves and merges data from two different database tables, and performs a number of extra calculations and formatting operations along the way. These first chapters have tried to provide you with some insight into the power of the Progress 4GL. Feel free to experiment with the language statements and functions you’ve encountered.

In the next chapters, you’ll look at another major OpenEdge development tool, the AppBuilder, and use it to generate the code you need to create an application window that looks a lot closer to the kind of graphical interface you might expect in your applications. Later chapters then introduce you to a world of creating applications with almost no code at all!


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