Copyright © 2003 Bob’s Best Corporation
Charles River Ma.
-All rights reserved-
Company and product names are trademarks or registered trademarks
of their respective owners.
Mention of these products does not constitute a recommendation of those
products, nor an endorsement. Bob’s Best Corporation does not assume
responsibility
with regard to the performance these products.
Intended Audience
This
document is directed toward programmers who want to understand how to use
PL/I
or see what another programmer has done.
This manual documents:
· General information about the PL/I language.
· Sample programs that are useful for reviewing PL/I statement syntax and programming style, and that may be worth including in your own work.
· CICS information about the Transaction Server, techniques to log error information, and tips to avoid programming errors.
· An introduction to DB2 with a few programming tips and basic error-return value information.
· A description of the more frequently used PL/I built-in functions.
· Some of the frequently returned PL/I error codes.
· Some pretty neat programs for CICS, EXCI and MQSeries.
This manual is the product of many years' learning the hard way. It's also a labor of love for a language that is much richer and easier to code in than any other I have worked with. I hope you find your time well spent in going through the material.
Heed this advice or be prepared to work long weekends:
1. KEEP THY PROGRAM SIMPLE.
2. FANCY STUFF WITH POINTER VARIABLES LEADS TO ETERNAL DAMNATION.
3. COVET THY NEIGHBOR'S CODE - BUT ONLY IF IT WORKS.
4. ASSIGN TEXT TO TEXT AND NUMBER TO NUMBER BECAUSE GOD WANTS IT THAT WAY.
5. READ THE COMPILER WARNINGS - IT COULD SAVE YOUR LOVE LIFE!
6. WHEN ALL ELSE FAILS, ASK FOR HELP PRIOR TO THE PRODUCTION DATE.
7. A GOOD COMPILE WILL NOT OVERRIDE HOW MACHINE INSTRUCTIONS WORK.
· YOU CANNOT DIVIDE BY 0.
· THERE ARE ONLY 8 BITS IN A BYTE.
· YOU CAN ONLY MODIFY STORAGE WITHIN YOUR OWN PROGRAM.
8. BLASPHEMY OF THE OPERATING SYSTEM RARELY WORKS. LOOK AT THINE OWN EFFORTS.
9. HONOR THY ERROR CONDITIONS OR REMAIN IN PURGATORY FOREVER.
When reading this, please only use the program examples for exact syntax. While I have tried to make all the program statements correct, they have not been compiled. There are sample programs throughout that you can review, remembering Free Advice #3. If you find code you like or want to modify, cut and paste it to a TSO screen.
ISQUARE: PROC OPTIONS(MAIN); GET DATA(I); I=I**2; PUT DATA(I); END ISQUARE;
ß program name & beginning àß program statements àß end à
Every program has beginning and ending statements. In between, each program has all the elements found in any language: variable definitions, functions, subroutines, and other logic. The sample shown above is about the simplest PL/I program that can be written. It presents the most basic elements of the language, and includes input, logic, and output. It's pretty much self-documenting and does not have a lot of wasted text.
There are coding examples plus some insight on good and bad usage. Again, the ordering of topics and lack of explanation of basic concepts (such as opening files) assumes an experienced reader. If you don't know:
· How to design and write a program in some language, and
· How to use MVS JCL or TSO
…this is not the place to start. If you have a great deal of PL/I experience, focus on the comments about technique.
YOURNAME: PROC OPTIONS(MAIN); START OF PROGRAM
.
.
.
END
YOURNAME; END OF PROGRAM
The coding workspace comprises columns 2-71, and is completely free-form. Column 1 is reserved for printer control. Programs start and end as shown above (YOURNAME is the 7‑character program name, of which the first two characters represent the business area at Bob’s Best).
%[anything] is a preprocessor option - an option that's used during the source-code setup phase preceding the actual compile. Preprocessor statements are covered under Preprocessor Options.
PL/I
has very few reserved words. In general, the compiler can figure out
the
reserved words from your usage, but avoid
using reserved words as variable names.
Here are a few syntax rules you should be aware of:
· Beware of the compiler defaults! PL/I tries to make everything work its way.
· You can mix data definitions and logic but don't do it! The programmer who maintains the code may know where you live.
· Statements end in a semicolon ( ; ).
· Comments start with /* and end with */.
· Names start with a letter. AvOiD mixed case as a matter of practice, although the PL/I compiler accepts mixed case just fine.
· Use underscores ( _ ) in names. Do not use hyphens ( - ) in names.
· Use a single apostrophe ( ' ) to start and end character-string values.
A program's style and visual appearance are important. No matter how perfect it is, someone will change it or throw it away. Balance clarity, brevity, and performance in your work
Some programmers like to have a lot of white space in their program's source listing. Others, like me, try to keep logical blocks to a size that fits on a screen. I will do things that appall the white space programmers. My reason is simple: when I page back and forth in TSO, I forget what was on the previous screen. The fewer instructions I write, and the less white space I leave, the easier it is to follow the one-screen rule. You are not paid by the line, how fast the program runs, or any other single measurement.
A number of different programming styles are illustrated in this manual. Try to take the best of each. Often more than one statement is placed on a single line, to make reading faster. This was not done as a style endorsement. Moderation and common sense will never get you in trouble. Trying to show you know every form of PL/I statement, and writing logic only a rocket scientist can follow, will not improve your advancement opportunities.
Data definitions can be explicit or implicit:
· Explicit (with DECLARE (DCL) keyword) - DCL A_NUM FIXED BINARY(31).
· Implicit (like Fortran BINARY or FLOAT) - Use without an explicit definition.
The explicit definitions take this form:
DCL variable-name data-type INIT(init-value) [storage-class] [alignment]
Where:
· DCL is followed by the name of the variable you're defining. With VisualAge PL/I names may be up to 100 characters long. This is to make programs more readable. Who would find names this long more readable I have no idea.
· data-type indicates the type of data field you're defining. Data types that can be used as dates can be given the DATE attribute and then are eligible for the neat date arithmetic instructions that are available. They may also be given a VALUE instead of an initial value when they are being used as a constant in the program. This will help the compiler generate faster running code. Be careful with both of these and read the fine print from IBM before using.
· The keyword INIT initializes the field to the specified init-value (where that value must be appropriate to the specified data type). The initialization may be performed by a restricted expression. Restricted expressions are run time functions that the compiler can resolve such as using the address or length of another variable.
· The storage-class is almost never used. The default, AUTOMATIC, is generally fine.
· The alignment controls whether fields align on byte boundaries, as described with the data-type values below.
Each data definition requires that you specify the field's data type. The possible values are described below:
|
Data Type |
Example and Notes |
|
BIT |
BIT (count-of-bits) - Little switches all in a row. DCL YES BIT(1) INIT('1'B); use in IF YES THEN DO or to set up hexadecimal fields, as in: DCL LOWERCASE_A BIT(8) INIT('10000001'B); BIT is a way to avoid using lowercase or to set up non-printable characters. It's also good for switches. Don't set up a complex bit-driven logic where the logic flow depends on combinations of BIT switches being on or off. That is a 60s style of doing things. It is just too hard to follow or read in a storage printout. Never start or end
a
data structure with a bit string. |
|
CHAR |
CHAR(99) - Any of 256 eight-bit combinations . VisualAge PL/I allows the use of * in the name field. This is the “filler” field syntax. It may be used with all data types. |
|
PIC |
PIC'99' The field length is the length inside the quotes, specified as a series of Zs (for alphabetic, as in PIC 'ZZZ') or 9s (for numeric, as in PIC '999.99'). The number of Z and 9 characters determines the size of the numeric field. You can use any of the following symbols as described: $ and the decimal point ( . )
are cosmetic. Up to 31 digits of precision permitted with VisualAge PL/I. |
|
FIXED BINARY |
FIXED BIN(15) - Careful, it's limited to 32767. Use FIXED BINARY(31). Be a big spender - always use 31 bits. With VisualAge PL/I fixed binary fields may be up to 63 bits of precision Also UNSIGNED is supported as in UNSIGNED FIXED BIN(8) to talk to the C languages. |
|
FIXED DECIMAL |
FIXED DEC(total-digits, decimal-digits) an OS/390 packed decimal number such as
+123 becomes hex'123C' = +123 in two bytes inside the
machine The total-digits should be an odd number as in the examples
below: DCL BY_100s FIXED DEC(5,-2); specifies a scaling factor of -2. BY_100s can be from -99999*100 to 99999*100, in increments of 100. Up to 31 digits of precision are available with VisualAge PL/I |
|
FLOAT |
Floating point decimal and binary numbers. |
|
POINTER |
Addressing variable (must point to the right place!). |
Place BINARY fields together at the beginning of a data structure, or learn about slack bytes. Slack bytes are a vestige of the S/360, a computer from the far distant past. Binary fields had to start on even-byte storage boundaries for the binary math operations to work, so PL/I will place them there. If BINARY fields are in the middle of a data structure, PL/I adds slack bytes for free whenever you don't consider alignment. This can kill in two ways:
· If the copybook used for a file happens to align correctly, there is no slack in program one. Program two in a set may not be as lucky. You get to find the problem.
· The same can happen with overlay definitions in CICS programs sharing a common communications area definition.
The compiler will tell if it has added slack bytes to structures. Look for "/*PADDING*/" in the output listing to see if this occurred.
Define structures as:
· ALIGNED so the BINARY fields are on even-byte boundaries, or
· UNALIGNED where fields are placed without alignment consideration. This is seen most often in CICS maps. For an UNALIGNED structure, make sure any structure that's redefined over it uses BASED addressing also, and is defined as UNALIGNED.
Read the section on POINTER variables to learn about the use of BASED addressing in variable definitions. Incorrect use of BASED addressing is a leading cause of storage protection errors. With a storage protection error, the program dies and you get a late night phone call.
OFFSET is another form of addressing variable, and is the relative distance from a specific POINTER.
AREA is a defined area of storage in which to use pointers and offsets. It's the playpen.
ALLOCATE and FREE are the statements to buy and sell real estate within the AREA. Explain the Theory of Relativity and use OFFSET, but only in programs supported by other programmers who also understand it and have a PhD in physics. Most application programmers do not see or use absolute and relative addressing. It represents a level of abstraction that takes experience to deal with and may be difficult to debug. For every ALLOCATE statement have a FREE to return the storage to MVS. ALLOCATE in a DO loop is a debugging opportunity waiting to happen especially if the amount of storage available to request is not bounded by an AREA definition.
DATA STRUCTURES DCL 1 I_AM_A_STRUCTURE,
2 IN_THE_STRUCTURE,
3
I_AM_AN_ELEMENT CHAR(10);
Refer to an element in the data structure like this (for example): I_AM_A_STRUCTURE.IN_THE_STRUCTURE.I_AM_AN_ELEMENT. You only need to qualify to a level that avoids confusing the compiler.
ARRAYS(9,8,7,6,5) FIXED DEC(7,4); a multi-dimensional
array
Do not overlay arrays on top of arrays by using BASED addressing - unless you really like reading manuals and working weekends. Business programs that make extensive use of multi-dimensional arrays can be difficult to maintain. Nested DO loops with incrementing indexes are hard to follow, and should be avoided (or kept to a minimum).
DCL ALPHABET CHAR(26) INIT('ABC');
DCL BIN_NUMBER FIXED BIN(31) INIT(99);
DCL PIC_NUMBER PIC'99V.99' INIT(99.9);
Note the V in the PIC DECLARE statement above for the logical placement of the decimal point. The period is for "looks" only.
By the end of this document, be inspired to read the PL/I manuals before undertaking assignments that have to do with money or space flight.
|
A neat
trick: Define a variable FIXIT [DCL
FIXIT PIC'99999' INIT(9999);].
Then
use FIXIT if there is an incomplete definition of what should be done to a
data element [WHOKNOWS=FIXIT].
This
will always compile and the target can be alphabetic or numeric. Come back later and remove the FIXITs
when
fleshing out the logic. |
DCL FIX_NUMBER FIXED DEC(7,2)
INIT(99.9);
DCL BIT_ON BIT(1) INIT('1'B); DCL BIT_YES BIT(1) DEF BIT_ON;
DCL
YES BIT(1) DEF BIT_YES Incorrect!
DEF and POSITION are not used very often. Do not define over a DEF as is attempted with YES above. Refer to the original variable in the DEF. In this case, BIT_ON. POSITION allows defining a variable starting at a position in a string.
DCL 1 MY_STRUCT, 2 J FIXED, 2 A
CHAR(2);
UNION is another syntax key work to redefine the same storage. The advantage it has over the traditional method of redefining using ‘ Bstruc BASED(ADDR(Astruc)), ‘ syntax is it allows all the redefines to be done in the original definition so you don’t hunt through the listing for the true meaning of a variable. The syntax is as follows:
DCL 1 EXAMPLE,
2 NAME_GAME UNION, the whole
3 AS_1_FIELD,
5 TOTAL_NAME CHAR(32),
3 IN_PARTS,
is
the sum of the parts
5 FIRST CHAR(10),
5 MIDDLE CHAR(10),
5 LAST CHAR(10)
5 * CHAR(2), can’t use the last two characters
Needless to say overlaying character fields on floating point fields or other aberrations is not recommended.
MY_STRUCT = ' '; will not work to initialize numeric fields. Use '' to initialize both numeric and alphabetic fields in a structure. '' is the absence of a data value.
By the way - J in this case will be a FIXED DECIMAL field, not BINARY. Be explicit in element definitions.
DCL STRING_OF_SOME_LENGTH CHAR(100)
VARYING;
This is a field to hold a variable-length string of data. PL/I always knows how long the string is because it's defining a two-byte counter field for PL/I's internal instruction use in front of the string which reserves 100 bytes of storage. Think of it as a structure with a FIXED BIN(15) field and a CHAR(100) field. Do not attempt to change or address the length portion of the definition. Use VARYINGZ to get a string with a one byte counter.
Refer to the examples later in the document before using variable-length character strings.
Never use a variable-length character string in a structure, or have it defined BASED on the location of another variable. The address of the variable-length string is the length portion of the string, not the character string itself. PL/I always uses the length when doing assignment statements and will give the expected results. It's best to initialize the string to a length of zero (0).
NULL_THE_STRING =''; will do the trick. Use LENGTH(BIG_STRING) to get the string's current length. Make complex examples as shown below at your own risk.
STRUCTURES OF ARRAYS DCL 1
STRUCTURE,
2 ARRAY(10) 3 FIELD(5)
ARRAYS OF
STRUCTURES DCL 1 ARRAY(10), 2 STRUC, 3
TABLE(10)
Choose from the following keywords to assign a storage class (not required and, in fact, almost never used):
|
Storage Class Keyword |
|
|
STATIC |
Field stays defined. Don't link-edit very large static arrays unless taking a long lunch. Use ARRAY(*) = A_VALUE to initialize a large array at execution time. |
|
AUTOMATIC |
Field is defined when the program/routine is invoked. · In CICS, always leave all fields AUTOMATIC. · In subroutines, default to AUTOMATIC, then the storage is reinitialized on every entry to the subroutine. This is the correct way to use subroutine internal variables. |
|
CONTROLLED |
Do not use this, because its debugging is difficult. |
Labels are nice. Use a lot of them but never in a GOTO statement. They can be about 30 characters long and end with a colon. Think of them as comments on the listing's left side. Avoid label variables if you plan to stay at your job.
Label examples: START:
END_OF_JOB:
JAIL:
GOTO JAIL;
END JAIL;
|
A neat
trick: Start LABEL and subroutine
names with Pnn_<descriptive_name>, as in P01_, P02_, etc. It's easier to go
through the listing knowing the relative location of names. |
Operators are like royalty. There are rules for who gets served first:
|
Operator |
Description |
|
= |
Assignment - Place the value of the variable on the right in the variable on the left. |
|
+ |
Add - Like the abacus. Remember the order rules. ** is first followed by * / + - |
|
- |
Subtract - See Add. |
|
/ |
Divide - See Add. |
|
* |
Multiply - See Add. |
|
** |
Exponentiation - Always is performed first. |
|
|| |
Concatenate - Put two strings together into one big string. |
|
( ) |
Order (precedence) - Control the order in which the math operators take place. The compiler always does what is inside the parentheses first. Use these to override the royalty default rules. Make sure to match the parentheses (you should have the same number of left and right parentheses). |
|
-> |
Pointer - Define where a variable is located in memory. Often used by programmers who remember the 60s. MY_POINTER->B says to use the value of MY_POINTER to address B. |
With these you get two operations for the price of one and the
opportunity
to confuse anyone not familiar with this syntax. Here are some examples.
They
should be enough to discourage use of complex
operators.
X +=1 is the same as X =X +(1)
X *=Y +Z is the same as X =X*(Y
+Z)
X *=Y +Z is NOT
equivalent to X =X*Y +Z
X(function())+=1 is NOT
equivalent to
X(function())=X(function())+1
|
Operator |
Description |
|
+= |
Add then assign |
|
-= |
Subtract then assign |
|
*= |
Multiply then assign |
|
/= |
Divide then assign |
|
**= |
Exponentiation then assign |
|
¬= |
Exclusive-or then assign |
|
||= |
Concatenate then assign |
|
&= |
And then assign |
|
|= |
Or then assign |
You should create the shell of your program first, with empty or incomplete subroutines. Then start testing the program flow prior to completion of all the detailed logic definition. Testing the shell for error handling is a good way to check out the code that never gets executed until production (when it's too late). One MAIN procedure with optional embedded subroutines makes a program.
When developing a program, all the blocks of important logic should be placed in individual subroutines for three reasons:
1. This technique allows you write and maintain the complex stuff in one place.
2. PL/I has a wonderful facility to tell which subroutine, by name, had an execution error.
3. This style of programming lets you drill down as you flesh out the logic.
PL/I subroutines are like paragraphs in COBOL. The PL/I name for a subroutine is a procedure. Procedures can have more than one entry and/or exit point. Procedures can also be bundled into PACKAGES. A PACKAGE controls what information about the procedure and its variables is externalized to the outside world. The execution performance of procedures with imbedded internal procedures can benefit by being placed into a package. This is true when internal procedures do not use any variables from a parent procedure. In a PACKAGE the internal subroutines are removed from the parent to become peers. As shown in the examples bundled subroutines is a programming style I like to use. Therefore some of my code may get a makeover.
Note: There are not many reasons to have routines with secondary entries. It's best to have one entry point and one exit point.
name: PROC;
statements END
[name];
MYROUTINE: PROC [(PARAMETERS)] [RETURNS(DATA
TYPE(length))];
Clever logic goes here
END MYROUTINE;
PARAMETERS pass data to the procedure for
processing
To invoke a procedure:
· CALL MY_PROCEDURE; if no parameters.
· CALL MY_PROCEDURE (PARM1); if there is a parameter (shown here as with the PARM1 variable). Parameters such as PARM1 deliver data to a procedure.
To use the routine as a function:
I = MYROUTINE();
Code subroutines that serve as functions and do not have parameters with ( ) after the name, as shown above. This way they will not be confused with a variable, even though the result is that they act like a variable. More information will be provided on functions in the following material. Think of a function as a subroutine where the returned value is logically treated as a variable or constant would be as in the assignment statement coded above.
BEGIN blocks and functions are from the same family tree as procedures.
· BEGIN;….END; is like a procedure. It's rarely used for other than error-condition processing.
Built-in functions are PL/I-provided subroutines that do neat things. There are many examples of these in the following pages. Built-in functions are called pseudo-variables, or functions, because they return a single value that looks and acts like a variable. In any syntax, a variable may be replaced with a built-in function.
Declare built-in functions as follows (to define the three functions shown in parentheses):
DCL (DATETIME, HIGH, LOW) BUILTIN;
Some built-in functions work at compile time, if the compiler can figure out what they mean. For example:
SHORT = SUBSTR(LONG,1,4); 1 is where to start. 4 is the assigned length.
This example uses the built-in SUBSTR to assign a portion of LONG's contents to SHORT. If the start and length values are constant, the compiler can figure out what to do.
ABS is another built-in function that returns the absolute value of the variable.
I = -1;
J = ABS(I); J will always be positive (1 here). Learn to use these.
RETURN is the command to exit from a procedure. If there is not a RETURN command, control passes to the statement following the CALL after the last statement in the procedure is executed.
Returning data makes the procedure act like a variable. This is sometimes called a pseudo-variable because the results of the execution can be used just like a variable of the type defined in the RETURN clause of the PROC statement.
|
Return Statement |
Description |
|
RETURN; |
Exits a procedure. |
|
RETURN(A_VALUE); |
Exits and returns a value to the invoking code. This is the function-style procedure. |
|
RETURN(MY_FUNCTION(ABS(I-J+K))); |
Uses a built-in function in the return logic. |
Of course silly statements like GOTO and error conditions (ON conditions) break the rules. Try not to break the rules. In fact, the rules for handling error conditions can be in individual procedures.
Subroutine greatest hints:
· Define working variables within subroutines for loop counters and switches.
· Use subroutines like functions: X=BOILING_POINT(WATER);
· Pass parameters to subroutines explicitly.
· Use subroutines to break up long runs of logic. Keep big thoughts to one page.
· Avoid writing the same code over and over. Call a subroutine instead.
· Don't play U-boat commander. Diving too deep into nested subroutines leads to confusion.
Subroutine parameters are the data elements that are passed to the subroutine. They can be modified by the subroutine. When defined inside the routine, as below, the definitions really are for storage outside the routine. This can ruin your day.
I = 2;
CALL
MY_PROC(I);
MYPROC: PROC(J);
DCL J FIXED BIN;
J is really the variable I outside
the PROC
DCL K FIXED BIN; K is
redefined every time routine is entered
J =4;
END MY_PROC;
The variable I will equal 4. Treat subroutine parameters as read-only! But before you get too upset, the OPTIONS option of the PROCEDURE statement controls how parameters are passed. The options include BYVALUE and BYADDR. There is a lengthy discussion of these in the IBM manuals. For procedures that will be propagated to numerous programs or have a very high level of reuse, clever use of OPTIONS will be rewarded.
Names of variables, procedures, etc., defined in the OPTIONS(MAIN) PROC are known to all procedures. Names defined in a subroutine are only known to the subroutine (and to any subroutines that are nested within it).
In other words, below knows what is above but above does not know what is below.
YourName ENTRY defines a procedure entry point. Think of it as a side door to a room full of code. Over the years I have become fonder of this technique. Remember to have a proper RETURN supporting each entry point. Many programmers have not used this technique when building subroutines, so be careful when it comes to maintenance. This is good for "system" routines