In Part 1 of this series (MC, December 1995), I explained what the planned enhancements to ILE RPG will allow programmers to do: use a free-form syntax to call programs and procedures, call procedures that return values, call procedures with parameters passed by value, and prototype calls to ensure correct parameter passing. If you haven't read Part 1, I encourage you to do so.
This month, I'll explain what the new RPG subprocedure feature will allow programmers to do: define procedures that can be called like RPG built-in functions, declare local variables, declare automatic storage, and create cycle-free RPG modules.
In addition to what you've already seen, there are some more-practical benefits you can realize with the planned enhancements. For example, if an application currently has 10 ILE RPG programs, each program contains all the code that the RPG cycle requires. You may have used the /COPY compiler directive extensively to try to control how you use files or how you call your programs. You probably have the same F specs coded in most of your programs; if so, your compiled programs each contain all the I specs and O specs needed for processing your externally described files. With the new enhancements, you'll be able to eliminate the redundant code. Let me explain.
In V3R1 ILE RPG, a module could only contain one procedure. That one procedure would always have the code required for the RPG cycle. Think of this procedure as a main procedure. With the enhancements, the single procedure per module restriction will be removed, and you'll be able to create a subprocedure, a new type of procedure that offers several advantages, one being the elimination of the RPG cycle code. If you change your application so that what was formerly ten modules is now one module with ten procedures, you will have only one copy of the RPG cycle. Furthermore, you will have only one copy of your F specs, I specs, and O specs. This considerably reduces your application's storage consumption and total compile time.
There may also be some performance benefits. The performance of the actual calls is no different, but a subprocedure should run a bit faster than a main procedure since it doesn't have to go through the cycle.
Let's take a closer look at this concept of subprocedures.
I have to talk about ILE for a moment to explain what a subprocedure really is. For more information about ILE, see IBM's Integrated Language Environment Concepts manual; "V3R1 Announcement Follow-up: An Introduction to Program Binding" (MC, August 1994); or "The Integrated Language Environment (ILE)" (MC, May 1993). An ILE program consists of one or more modules; each module contains one or more procedures. 1 illustrates the elements of an ILE program. There are several types of procedures:
I have to talk about ILE for a moment to explain what a subprocedure really is. For more information about ILE, see IBM's Integrated Language Environment Concepts manual; "V3R1 Announcement Follow-up: An Introduction to Program Binding" (MC, August 1994); or "The Integrated Language Environment (ILE)" (MC, May 1993). An ILE program consists of one or more modules; each module contains one or more procedures. Figure 1 illustrates the elements of an ILE program. There are several types of procedures:
Main procedure: A main procedure is one that is eligible to be the first procedure called when a program is called. The module's main procedure is always exported from the module.
In ILE RPG, the main procedure consists of everything after the H spec, up to the first subprocedure. The main procedure always has the same name as the created module. In other languages, this is not necessarily true. For example, C requires that its main procedures be called main, and they can be coded anywhere in the module.
Prior to the multiple-procedure enhancements, ILE RPG modules had only one procedure, and it was always a main procedure. With the multiple procedure enhancements, ILE RPG modules can have zero or one main procedure and possibly several subprocedures. We'll look at how to code a module with no main procedure later.
User entry procedure: The user entry procedure is the first procedure called when the program is called.
In any program, there is exactly one user entry procedure, regardless of how many modules have a main procedure. If your program has several modules that have a main procedure, you select the user entry procedure using the Program entry procedure module (ENTMOD) parameter of the Create Program (CRTPGM) command.
You may already be familiar with this if you create programs using several ILE RPG modules.
Program entry procedure (PEP): This is a special procedure added by the compiler. When a program is called, the system calls the PEP, which in turn calls the user entry procedure for the program. You do not need to be concerned with this procedure, but it shows up in the list of procedures when you use the Display Module (DSPMOD) command.
Exported subprocedure: This subprocedure is a nonmain procedure in a module; it can be called by any procedure in the program, either in the same module or other modules.
Local subprocedure: This subprocedure is a nonmain procedure in a module; it can only be called by other procedures in the same module.
If you're interested, you can see the procedures in a module using the DSPMOD command and specifying DETAIL(*EXPORT *PROCLIST).
In the following discussions, the term procedure refers to main procedures, user entry procedures, and subprocedures. The term subprocedure refers to both exported and local subprocedures, but does not include main procedures.
When a module has several procedures, the issue of name scope arises. The name scope defines the locations in the module where the name can be used. If you declare a variable in a subprocedure, that name can be used only within the calculations of that subprocedure. If you declare a variable in the main procedure, it is global to the module and can be used anywhere in the module.
In ILE RPG, any file, record format, variable, KLIST, or PLIST declared in the main procedure is global to the module. Any subroutine or TAG in the main procedure is local to the main procedure. Anything declared in a subprocedure is local to the subprocedure.
You do not have to ensure that local names do not conflict with global names. For example, if you declare a file called F1 in the main procedure, you could declare a data structure called F1 in a subprocedure. As long as you don't want to access file F1 by name in the subprocedure, hiding the global name F1 should not cause you any problems.
Previously, every RPG module contained exactly one procedure, and that procedure was designated as a possible main procedure. Now, you'll be able to define as many procedures as you like in a module. You'll even be able to say that you don't want the module to have a main procedure (more about this in a later section, "A No-cycle RPG Module").
An RPG subprocedure differs from a main procedure in the following ways:
o The main procedure doesn't have P specs; the subprocedures begin and end with a P spec.
o The calculations in the main procedure are controlled by the RPG cycle, so they are done repeatedly until terminated either by a RETURN operation or by setting on *INLR, *INRT, or a halt indicator. A subprocedure doesn't use the RPG cycle, so the calculations are done once only. The calculations begin at the first calculation of the subprocedure, and end at the last, although they can be terminated early by a RETURN operation.
Because the subprocedure doesn't use the cycle, exceptions are handled differently. The main difference in exception handling is that inquiry messages are not displayed for unhandled exceptions in a subprocedure; instead, the procedure ends immediately in error.
o A main procedure cannot be called recursively; a subprocedure can be called recursively. When this happens, any information about the old call is saved by the system until the new call is complete.
o The default storage type for a main procedure is static, and, for a subprocedure, it is automatic. For a variable in static storage,
there is one single storage location that is accessed by every call to the procedure. Even if a procedure returns, when the procedure is called again, the static storage still has its old value. For a variable in automatic storage, a new copy of the storage is created each time a procedure is called. If the procedure is called recursively, the old value is part of the information that is saved until the new call is complete.
Even though the default storage type for subprocedures is automatic, you can define variables in static storage using the STATIC keyword on the D spec for the variable.
o Anything you define in the main procedure, except subroutines and TAGs, is global to the module. Anything you define in a subprocedure is local to the subprocedure.
You code subprocedures after output specs and before compile-time data. Each subprocedure begins and ends with a P (Procedure) spec. The body of the subprocedure consists of D specs and C specs, including local subroutines. Each subprocedure can have its own *PSSR subroutine to handle errors in the subprocedure.
The C specs can refer to any definitions-including files, PLISTs, and KLISTs-from the main procedure as well as, of course, any of its definitions from its own D specs. The P spec is similar to the D spec. It contains the following components:
Name: (positions 7-21).This is the name of the prototype that describes this procedure.
Type: (position 24). The type is B for the P spec that begins a procedure or E for the P spec that ends a procedure.
Keywords: Currently, there is only one keyword-EXPORT. Coding the EXPORT keyword means that you want the procedure to be visible to other modules (as illustrated in 2).
Keywords: Currently, there is only one keyword-EXPORT. Coding the EXPORT keyword means that you want the procedure to be visible to other modules (as illustrated in Figure 2).
In last month's article, I showed how the compiler uses the prototype to ensure that a caller is passing the correct parameters and using the return value correctly. The compiler also uses the prototype to ensure that the procedure is expecting the correct parameters and returning the correct type of value.
The compiler does this by comparing the prototype to another new kind of definition: the procedure interface. The procedure interface looks very similar to a prototype, except that it has PI as the type rather than PR. The compiler does not allow any differences between the prototype and the procedure interface.
The procedure interface is also used to define the entry parameters for the subprocedure. The name you give to the parameter on the D spec is the name you use to refer to the parameter inside the subprocedure.
3 contains an example of a subprocedure in which a prototype and a procedure interface are defined. As you can see, the prototype and the procedure interface match.
Figure 3 contains an example of a subprocedure in which a prototype and a procedure interface are defined. As you can see, the prototype and the procedure interface match.
Notice all the places that contain the name MyProc. The name is required on the prototype and the begin-procedure specs. It's optional on the procedure-interface spec and the end-procedure spec.
4 contains another example of a subprocedure. Notice that there is no procedure interface. For procedures that don't return a value and have no parameters, the procedure interface is optional.
Figure 4 contains another example of a subprocedure. Notice that there is no procedure interface. For procedures that don't return a value and have no parameters, the procedure interface is optional.
In Part I of this series, you learned how to define a prototype for a procedure to return a value, allowing you to call the procedure in an expression.
To return a value from a subprocedure, you must code a new form of the RETURN op code within the body of the subprocedure. The expression in the extended factor 2 is the value that is returned from the subprocedure. The old form of RETURN, without factor 2, is still used to return from a main procedure or from a procedure without a return value.
When a subprocedure is prototyped to return a value, you must have a RETURN op code in the subprocedure. Additionally, you must ensure that the RETURN operation is executed at run time. If you have several places in your subprocedure that return a value, you will probably have some conditioning logic to determine which RETURN statement to use; if it is not guaranteed that a RETURN operation will be found on every path through this logic, you should code a default RETURN as the last statement in the subprocedure.
If a subprocedure that returns a value ends without returning a value, run-time exception RNX0224 will be issued to the caller of the subprocedure.
In 5 on page 103, you can see an example of a subprocedure that returns the square root of its parameter if the parameter is valid, or zero if the parameter is not valid.
In Figure 5 on page 103, you can see an example of a subprocedure that returns the square root of its parameter if the parameter is valid, or zero if the parameter is not valid.
1. Every subprocedure in the module must have a prototype. Normally, the prototypes for any exported subprocedures would be kept in a /COPY member, and the prototypes for local subprocedures would be in the source itself. The prototypes for all the subprocedures in a module must be defined in the D specs of the main procedure, so you would place the /COPY statement in the main D specs, along with any prototypes for local subprocedures.
2. You can't specify the external name of an RPG subprocedure using the EXTPROC keyword on the prototype for the subprocedure. The external name for a subprocedure is the name (keyed in upper- or lowercase) in positions 7-12 of the prototype. RPGsubprocedures are always exported in uppercase. For the procedure in 5, the name is MY_SQRT.
2. You can't specify the external name of an RPG subprocedure using the EXTPROC keyword on the prototype for the subprocedure. The external name for a subprocedure is the name (keyed in upper- or lowercase) in positions 7-12 of the prototype. RPGsubprocedures are always exported in uppercase. For the procedure in Figure 5, the name is MY_SQRT.
Defining the Procedure Interface for the Main Procedure
You may be wondering how you code the main procedure if it is prototyped. There are no P specs for the main procedure; it begins after the H spec and ends at the first subprocedure. You are still free to use the *ENTRY PLIST for your entry parameters, but, if the main procedure is prototyped, you should define the entry parameters using a procedure interface.
The procedure interface for the main procedure must have a name. This name must match a prototype that has already been defined.
A program cannot have a return value or parameters passed by value. Since the main procedure of any module may be selected as a program's user entry procedure, the main procedure must conform to these rules.
6 on page 103 contains an example in which the main procedure's entry parameters are defined with a prototype and procedure interface.
Figure 6 on page 103 contains an example in which the main procedure's entry parameters are defined with a prototype and procedure interface.
You could use an *ENTRY PLIST instead of a prototype and procedure interface for this program. If you did, it would look like the illustration in 7.
You could use an *ENTRY PLIST instead of a prototype and procedure interface for this program. If you did, it would look like the illustration in Figure 7.
If you want a module that only contains subprocedures, you can code NOMAIN on the H spec for the module. A NOMAIN module does not have a PEP, so you can't create a program using only this module. You can use a NOMAIN module in a service program, or you can bind the module directly to other modules as part of a program. If the procedures are very specific to one program, binding the module is the best choice, but if the procedures in the modules will be used by several programs, it's best to use the module in a service program. That way, you can reduce the amount of storage used by your module, since you need only one copy of the service program on your system.
The part of the code that precedes the subprocedures is called the main source section. If there is a main procedure, it is part of the main source section. In a NOMAIN module, the main source section contains only global definitions.
You can define files, data areas, KLISTs, PLISTs, and variables in the global definitions. Your files that would normally get opened as part of *INIT get opened when the first subprocedure is called (during module initialization). They never get closed unless you explicitly close them. The same applies to UDS data areas-they get locked during module initialization, but they never get unlocked unless you explicitly unlock them.
Let's look at a meaningful example. I'll develop a module that has two general-purpose procedures, and then I'll use those procedures to write a more specific subprocedure for another module.
The general-purpose procedures are DayOfWeek and FormatDate. DayOfWeek gives the name (e.g., Monday, Tuesday) for a given date. FormatDate gives a readable form of a given date (e.g., Monday, June 5, 1985).
One of the most important tasks is defining the prototypes for these procedures. This is the caller's view of the procedures; ideally, it should never change except in an upward compatible way.
Both these procedures have at least one input parameter: the date. I have a choice for the value the procedure is calculating; I can return that value in a parameter passed by reference, or it can be the return value from the procedure. For DayOfWeek, I'll have the procedure return a value, and, for FormatDate, I'll use a parameter passed by reference. For convenience, I should allow this parameter to be any length, so I'll use the *VARSIZE option. I'll use another parameter to indicate the passed length of this parameter.
For exported procedures, the prototypes should be in a /COPY member. I'll call the module FMTDATE and the /COPY member FMTDATEI (see 8 on page 104).
For exported procedures, the prototypes should be in a /COPY member. I'll call the module FMTDATE and the /COPY member FMTDATEI (see Figure 8 on page 104).
Now, I'm ready to code the FMTDATE module that exports these two subprocedures, FormatDate and DayOfWeek (see 9 on page 104).
Now, I'm ready to code the FMTDATE module that exports these two subprocedures, FormatDate and DayOfWeek (see Figure 9 on page 104).
Notice these points:
o There is nothing for a main procedure to do, so I've made this a NOMAIN module.
o The module is copyrighted.
o The source for the module copies in the prototypes for the module. The compiler will ensure that the exported procedures match the prototypes.
o The local procedure AbsVal uses the new data types I (integer) and U (unsigned integer). Since the input parameter can be either positive or negative, it cannot be unsigned, but the return value is never negative, so unsigned integer is a good choice. (The next section contains more information about these new data types.)
10 contains the source code for a module that uses these procedures. This module calls a program called REPORTS that writes a report to printer file QSYSPRT.
Figure 10 contains the source code for a module that uses these procedures. This module calls a program called REPORTS that writes a report to printer file QSYSPRT.
Note these things about this example:
o Even though the EXCEPT op code is used in the subprocedure, the output specs are part of the main procedure. This means that the names used in the output specs are the global names. If the name PrtLine was defined locally in the DateMsg subprocedure, the EXCEPT operation would not work as expected. The value printed out would be whatever value PrtLine had before the call to DateMsg.
o The new %PARMS built-in function is used to determine how many parameters were passed to the subprocedure.
o For the Override with Printer File (OVRPRTF) to work as expected, the program REPORTS must be in the same activation group as PRTRPT.
Other enhancements to RPG in the next release include new numeric data types: integer and unsigned integer. They are available in two lengths: 10 and 5 digits.
They are similar to the binary data type in the way the numbers are stored, but, unlike the binary data type, when the value is used, it is not necessarily converted to a packed value first. Even though a two-byte binary field can hold values up to 32,767, RPG does not allow two-byte binary fields to be defined to have more than four digits. If a four-digit RPG binary field has a value larger than 9,999, the value is truncated whenever you try to use the value.
Integer and unsigned integer fields do not have this problem. Integer fields can have both positive and negative values. Unsigned integer fields can have only positive values, but they can have larger positive values than an integer field.
The *CYMD date format enhancement is only available for the MOVE, MOVEL, and TEST op codes. It allows you to move or test a character or numeric date in the format CYYMMDD.
The next release of RPG even allows you to copyright the modules and programs that you send to your customers by using a new H spec keyword, COPYRIGHT. You can see the copyright value of an ILE module, program, or service program by using the DETAIL(*COPYRIGHT) parameter of the DSPMOD, Display Program (DSPPGM), and Display Service Program (DSPSRVPGM) commands.
These enhancements give you much more freedom when designing an application in RPG. You will be able to write with a greater degree of modularity and, at the same time, gain more control over the interaction of the modules and probably realize better performance. If you find this freedom overwhelming, look to the principles of software engineering to guide you. There are many excellent books on this topic. Some topics you will find useful to read about include information hiding, module interfaces, data abstraction, and functional abstraction. (See references listed below). Applying those principles was possible using RPG before, but now it is much easier.
Barbara Morris is a staff development analyst for RPG at the IBM Laboratory in Toronto, Canada.
REFERENCES
Integrated Language Environment Concepts (SC41-3606).
Bell, Doug; Ian Morrey, and John R. Pugh. Software Engineering: A Programming Approach. Hemel-Hempstead, Hertfordshire, 1992.
Lamb, David Alex. Software Engineering: Planning for Change. Prentice-Hall, 1988.
Pressman, Roger S. Software Engineering: A Practitioner's Approach. New York: McGraw-Hill, 1992.
ILE RPG 1996 & Beyond: Part 2
Figure 1: The Elements of an ILE Program
ILE RPG 1996 & Beyond: Part 2
Figure 2: Defining a Subprocedure
PName+++++++++++..B...................Keywords+++++++++++++++++++++++++ P MyProc B EXPORT D .... C .... P E
ILE RPG 1996 & Beyond: Part 2
Figure 3: Subprocedure with Procedure Interface
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ * The prototype for the subprocedure D MyProc PR D 5P 0 D 10A VALUE D D DATFMT(*MDY/) * The subprocedure P MyProc B * The procedure interface D MyProc PI D num1 5P 0 D charparm 10A VALUE D date D DATFMT(*MDY/) * The subprocedure body C IF num1 > 14 C *MDY/ MOVEL charparm date C ENDIF P MyProc E
ILE RPG 1996 & Beyond: Part 2
Figure 4: Subprocedure with No Procedure Interface
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ * The prototype for the procedure D OpenFile PR * The procedure P OpenFile B C OPEN CUSTFILE P E
ILE RPG 1996 & Beyond: Part 2
Figure 5: Subprocedure that Returns a Value
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ * The prototype for the subprocedure D my_sqrt PR 10P 0 D 30P 0 VALUE * The subprocedure P my_sqrt B 30P 0 EXPORT * The procedure interface D my_sqrt PI 10P 0 D num 30P 0 VALUE * The subprocedure body C IF num <= 0 C RETURN 0 C ELSE C RETURN num ** .5 C ENDIF P my_sqrt E
ILE RPG 1996 & Beyond: Part 2
Figure 6: A Main Procedure's Entry Parameters with a Prototype and Procedure Interface
FMASTER IF E DISK FQSYSPRT O 80 PRINTER DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ * The prototype for the program (would be normally in a /COPY member) D PrtRpt PR EXTPGM('PRTRPT') D Title 80A OPTIONS(*NOPASS) * The procedure interface, defining entry parameter list D PrtRpt PI D RptTitle 80A CL0N01Factor1+++++++Opcode&ExtFactor2+++++++Result++++++++Len++D+HiLoEq * The main procedure C IF %PARMS = 1 C EXCEPT Title C ENDIF C EXSR Process C RETURN C Process BEGSR C .... print report C ENDSR OQSYSPRT E Title O RptTitle OQSYSPRT E Line O Company O Balance
ILE RPG 1996 & Beyond: Part 2
Figure 7: Using an Entry PLIST Instead of a Prototype and Procedure Interface
CL0N01Factor1+++++++Opcode&ExtFactor2+++++++Result++++++++Len++D+HiLoEq C *ENTRY PLIST C PARM RptTitle 80
ILE RPG 1996 & Beyond: Part 2
Figure 8: /COPY Source Member FMTDATEI
*----------------------------------------------------------------- * DayOfWeek - return the name of the day for the input date * eg "Tuesday" *----------------------------------------------------------------- DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ D DayOfWeek PR 10A D InDate D VALUE DATFMT(*ISO) *----------------------------------------------------------------- * FormatDate - format the input date into a string of the form * "Monday, December 4, 1994" *----------------------------------------------------------------- D FormatDate PR D InDate D VALUE DATFMT(*ISO) D OutString 100A OPTIONS(*VARSIZE) D OutLen 5P 0 VALUE
ILE RPG 1996 & Beyond: Part 2
Figure 9: Source Member for Module FMTDATE
H NOMAIN COPYRIGHT('Copyright: My Company 1995') *----------------------------------------------------------------- * FMTDATE - procedures to format dates into readable strings * * FormatDate - format a date into a string of the form * "Monday, December 4, 1994" *----------------------------------------------------------------- DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++ * Global variables D DayNames S 10A DIM(7) PERRCD(7) CTDATA D MonthNames S 10A DIM(12) PERRCD(6) CTDATA * Local subprocedure to calculate the absolute value of a number D AbsVal PR 10U 0 D Val 10I 0 VALUE /COPY FMTDATEI *----------------------------------------------------------------- * FormatDate - format a date into a string of the form * "Monday, December 4, 1994" * * Uses procedure DayOfWeek to get the "Monday" part *----------------------------------------------------------------- P FormatDate B EXPORT DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++ * Parameters D FormatDate PI D InDate D VALUE DATFMT(*ISO) D OutString 100A OPTIONS(*VARSIZE) D OutLen 5P 0 VALUE * Local variables D MonthNum S 5P 0 D DayChar DS D Day 2S 0 D DayFirst 1A OVERLAY(Day) D YearChar DS D Year 4S 0 * Calculate the month C EXTRCT InDate:*M MonthNum * Get day in char form with no leading zero * Will be in data structure DayChar C EXTRCT InDate:*D Day C IF DayFirst = '0' C EVAL DayFirst = ' ' C ENDIF * Get the year in character form, in data structure YearChar C EXTRCT InDate:*Y Year * Calculate the output string, being sure only to change as * much of the output parameter as we have access to. C EVAL %subst(OutString:1:OutLen) = C %trim(DayOfWeek(InDate)) + ', ' C + %trim(MonthNames(MonthNum)) + ' ' C + %trim(DayChar) + ', ' + YearChar P FormatDate E *----------------------------------------------------------------- * DayOfWeek - return the name of the day for the input date * eg "Tuesday" *----------------------------------------------------------------- P DayOfWeek B EXPORT * Parameters D DayOfWeek PI 10A D InDate D VALUE DATFMT(*ISO) * Local variables D DayNum S 5P 0 D Sunday C CONST(D'1994-10-02') D Days S 30P 0 * Calculate the day of the week using a date known to be Sunday C InDate SUBDUR Sunday Days:*D C Days DIV 7 DayNum C MVR DayNum C RETURN DayNames(AbsVal(DayNum) + 1) P DayOfWeek E *----------------------------------------------------------------- * AbsVal - return the absolute value of the input number * eg AbsVal(-5) = 5 *----------------------------------------------------------------- P AbsVal B * Parameters D AbsVal PI 10U 0 D Val 10I 0 VALUE C IF Val < 0 C RETURN - Val C ELSE C RETURN Val C ENDIF P AbsVal E **CTDATA DayNames Sunday Monday Tuesday Wednesday Thursday Friday Saturday **CTDATA MonthNames January February March April May June July August September October November December
ILE RPG 1996 & Beyond: Part 2
Figure 10: Source Member for Module PRTRPT Using FMTDATE Procedures
H FQSYSPRT O F 100 PRINTER USROPN *--------------------------------------------------------- * Prototypes *--------------------------------------------------------- /COPY FMTDATEI DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ D QCMDEXC PR EXTPGM('QCMDEXC') D Cmd 100A OPTIONS(*VARSIZE) CONST D Len 15P 5 CONST * DateMsg - prints a message followed by a date, followed * optionally by another message D DateMsg PR D Msg1 50A VALUE D Date D VALUE D Msg2 50A VALUE OPTIONS(*NOPASS) *--------------------------------------------------------- * Global definitions *--------------------------------------------------------- D OvrPrtf C 'OVRPRTF QSYSPRT + D SHARE(*YES) OVRSCOPE(*JOB)' D Today S D D DS D TimeVal 12S 0 D CurDate 6S 0 OVERLAY(TimeVal:7) D PrtLine S 100A *--------------------------------------------------------- * Main procedure *--------------------------------------------------------- * Get today's date C TIME TimeVal C *JOBRUN MOVEL CurDate Today * Print the report header C OPEN QSYSPRT C CALLP DateMsg('Report printed on' C : Today) * Override the printer file to be shared and call the program * to print the report to QSYSPRT C CALLP QCMDEXC(OvrPrtf : %size(OvrPrtf)) C CALL 'REPORTS' * Print the report footer C ADDDUR 7:*DAYS Today C CALLP DateMsg('Next report is due on ' C : Today C : 'at the earliest.') C CLOSE QSYSPRT C RETURN OQSYSPRT E O PrtLine * Subprocedure DateMsg *--------------------------------------------------------- P DateMsg B DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ D PI D Msg1 50A VALUE D Date D VALUE D Msg2 50A VALUE OPTIONS(*NOPASS) D DateString S 100A D*PrtLine S 100A * Build a string of Msg1 + the 'Monday, June 5, 1995' form of * the date C CALLP FormatDate(Date : DateString : C %size(DateString)) C EVAL PrtLine = %TRIMR(Msg1) + ' ' C + %TRIMR(DateString) * If the third parameter was passed, add the second part of the * message C IF %PARMS = 3 C EVAL PrtLine = %TRIMR(PrtLine) + ' ' C + %TRIMR(Msg2) C ENDIF * Print out the line C EXCEPT P DateMsg E
LATEST COMMENTS
MC Press Online