PL/I Logo





Copyright © 2002 by Robin Vowels. All rights reserved for the authors.
  1. Editorial
  2. Tweaking PLISRTx, by Peter Flass
  3. Matrix Operations in PL/I, by Robin Vowels
  4. New PL/I Book
  5. Reading the Unknown, by Robin Vowels
  6. New PL/I web site; PL/I for HP
  7. Usefulle Webbe Lynx ...
  8. IBM PL/I Offerings in the U.S.
  9. IBM PL/I Offerings in Australia.
  10. Cool Codes
  11. Quute Questions

  1. Editorial


    We welcome your contributions to this newsletter. It will continue only through your efforts. Thank you in advance.
    Please send any comments and contributions for the next newsletter to email:

    The fifth issue may be downloaded from: The PL/I Newsletter, No. 5, August 2002.
    The fourth issue may be downloaded from: The PL/I Newsletter, No. 4, November 2001.
    The third issue may be downloaded from: The PL/I Newsletter, No. 3, June 2001.
    The second issue may be downloaded from: The PL/I Newsletter, No. 2, September 2000.
    The first issue may be downloaded from: The PL/I Newsletter, No. 1, July 2000.

  2. Tweaking PLISRTx

    Copyright © 2002 by Peter Flass

    The IBM PL/I SORT procedures, generically PLISRTx (x=A,B,C,D), provide easy access to powerful sorting capabilities. However I have always felt them to lack flexibility, particularly compared with the COBOL SORT verb. Any change to the layout of the data to be sorted seems to require changes to several points in the program, which is an invitation to mistakes.

    Recently I wrote a program using PLISRTD which caused me to rethink this in the hope of simplifying the process. With a few simple "tweaks" to IBM's sample logic, I believe I have accomplished this. I'm using PL/I for MVS & VM 1.1. The sort interface is documented in the Programming Guide (SC26-3113) beginning on page 348. I believe this logic should be applicable to other IBM PL/I compilers. The sample program as written requires the compiler option 'LANGLVL(SPROG)' to allow pointer arithmetic, but the same results could be achieved using the PTRADD, PTRVALUE and BINVALUE builtin functions. Line number references in [] in the text refer to the sample program below.

    The first step was to define the record to be sorted [lines 3-8]. I defined this as a BASED structure, essentially a template, to facilitate reuse throughout the program. The 'BASED( SYSNULL() )' is not strictly required, but allows me to reference the structure without locator qualification.

    The second step was to define SORT's SORT and RECORD statements. The problem with all of IBM's examples is that they show these statements hard-coded as constant character strings. As constants these statements have to be changed every time the sort record length or sort key information changes. Instead, using the structure defined for the sort record I dynamically built these statements [lines 13-17]. Many changes to the sort record format now don't require changes. I show only one - character - sort key, but the same logic applies to multiple keys in varying formats. Only adding a sort key or changing its format requires a change to the SORT statement logic. The position of each 'sort_key' is computed as "ADDR(sort_key) - ADDR(sort_rec) + 1", and its length is "STG(sort_key)". For this example I print out the values of the SORT and RECORD statements. The results are as follows:

      SORT FIELDS=(11,10,CH,A)

    Once this was done the next step was to tweak the Sort Exit E15 and E35 (sort input and sort output) procedures. PLISRTA uses no exits, PLISRTB and PLISTRC use one each, but the same general discussion applies as in the example, where PLISRTD uses both exits.

    Both procedures access the sort records as a character string. IBM's examples define the value returned by the E15 procedure as 'CHAR(x)' where x is the length of the sort record -- another changeable item. In an ideal world it would be defined as 'CHAR( STG(sort_rec) )', but this isn't allowed. Instead I found that 'CHAR(32767) VARYING' would apparently work [line 24]. I am not especially happy with this solution, and would like to research it farther to see what is actually going on behind the scenes1.

    Exit E15 builds sort records one at a time and passes them to the sort via the RETURN statement. In this case my data is all character and 'RETURN( STRING(card) )' works. If the sort record is not all character it will be necessary here - as well as IBM's examples - to define an overlay and return that. Something like
    'RETURN( SUBSTR(ADDR(record)->overlay, 1, STG(sort_rec) )'
    should work, where overlay is a BASED character string long enough to overlay the entire sort record, although PL/I doesn't check unless STRINGRANGE is enabled.

    Exit E35 is easier. The sorted record passed by the sort can be declared as 'CHAR(*)' [line 35]. The data can be accessed by overlaying the definition of sort_rec on this string [line 39].

    Procedure itod [lines 43-68] is a small formatting routine I've found useful. It formats an unscaled fixed binary number as a VARYING character string to exactly the length required, without leading or trailing blanks.

    I didn't attempt any tests on variable-length sort records. Presumably all or most of these ideas would be applicable there also.

    Most of this code violates all suggested portability guidelines. However use of PLISRTx itself is not particularly portable. In addition, using one declaration of the sort record throughout the program and declaring character strings in terms of 'STG(sort_rec)' should mitigate some of the potential problems.

    1.Presumably PLISRTx allocates storage for the string returned from exit E15 using the length from the RECORD statement and allowing additional storage for a possible VARYING string prefix. The RETURN statement may truncate (or pad, if fixed-length string) to this length. The determination of VARYING vs. NONVARYING is made by examination of the descriptor for the returned value.

    Sample PL/I sort program

     1  /* Sample Sort Program for PL/I Newsletter     */
     2  sortsamp: proc options(main);
     3    dcl 1 sort_rec            based( sysnull() ),
     4          2 sort_stuff1       char(10),
     5          2 sort_key,
     6            3 sort_key1       char(4),
     7            3 sort_key2       char(6),
     8          2 sort_stuff2       char(60);
     9    dcl   rc                  fixed bin(31);
    10    dcl  (sort_stmt,rcrd_stmt)char(100) varying;
    11    dcl   max_mem             fixed bin(31)       init(1000000);
    13    sort_stmt = ' SORT FIELDS=(' ||
    14                itod( addr(sort_key) - addr(sort_rec) + 1 ) ||
    15                ',' || itod( stg(sort_key) ) || ',CH,A) ';
    16    rcrd_stmt = ' RECORD TYPE=F,LENGTH=(' ||
    17                itod( stg(sort_rec) ) || ') ';
    18    put edit( sort_stmt, rcrd_stmt )(skip,a);
    19    call PLISRTD( sort_stmt, rcrd_stmt, max_mem, rc,
    20                  E15, E35 );
    21    call PLIRETC(rc);
    22    return;
    24  E15: proc returns( char(32767) varying );
    25    dcl   card                char(80);
    26    dcl   eof                 bit(1)    static    init( '0'b );
    27    on endfile(sysin) eof='1'b;
    28    get edit( card )(a(80));
    29    if eof then call PLIRETC(8);
    30    else        call PLIRETC(12);
    31    return( string(card) );
    32    end E15;
    34  E35: proc(data);
    35    dcl   data                char(*);
    36    dcl   my_data             like sort_rec  based( addr(data) );
    37    dcl   cnt                 fixed dec static    init(0);
    38    cnt = cnt+1;
    39    put skip edit( cnt, string(my_data) )(p'zzzz9B',a);
    40    call PLIRETC(4);
    41    end E35;
    43  itod: proc(iFB31) returns( char(11) varying );
    44  dcl     iFB31               fixed bin(31);
    45  dcl     w                   fixed bin(31);
    46  dcl     c                   char(11) varying;
    47  dcl     s                   char(1)  varying init('');
    49  w = iFB31;
    50  if w<0 then do;
    51    s='-';
    52    w=-w;
    53    end;
    54  select;
    55    when( w>999999999 ) put string(c) edit(w)(p'(10)9');
    56    when( w>99999999  ) put string(c) edit(w)(p'(9)9');
    57    when( w>9999999   ) put string(c) edit(w)(p'(8)9');
    58    when( w>999999    ) put string(c) edit(w)(p'(7)9');
    59    when( w>99999     ) put string(c) edit(w)(p'(6)9');
    60    when( w>9999      ) put string(c) edit(w)(p'(5)9');
    61    when( w>999       ) put string(c) edit(w)(p'(4)9');
    62    when( w>99        ) put string(c) edit(w)(p'(3)9');
    63    when( w>9         ) put string(c) edit(w)(p'(2)9');
    64    otherwise           put string(c) edit(w)(p'(1)9');
    65    end; /* select */
    67   c=s||c;
    68  return(c);
    70  end itod;
    72   end sortsamp;


    Copyright © 2002 by R. A. Vowels.

    Matrix arithmetic in PL/I ?
    How would you like to be able to write C = A x B; for matrix multiplication?
    or C = A transpose; for the transpose?
    This PL/I program enables matrix operations to be specified simply and concisely.
    Matrix operations may be specified in appropriate terms.
    For example, to form the sum of corresponding elements of two matrices A and B, write:
    C = A + B;
    To form the inverse of matrix A, write: C = A inverse;
    To form the transpose of matrix A, write: C = A transpose;
    To solve a set of linear simultaneous equations, write: C = A solve; (the right-hand sides are included with the coefficients in matrix A).
    To form matrix multiplication of two matrixes A and B, write C = A x B;
    These statements may be intermixed with normal PL/I statements to make a useful program.
    For example, to read in a matrix, print it, and to compute the inverse, and to print it, write:

    GET (A); C = A inverse; PUT (C); Note that the matrix GET statement also reads in the dimensions of the matrix. Likewise, printing matrix C prints the dimensions of C as well as the elements of the matrix.
    It is not necessary to define (declare) the matrices A and C, nor is it necessary to be concerned with the allocation of storage, nor with the specification and checking of array dimensions, and so on. The declarations are formed automatically, and the code to allocate and free storage is generated automatically.
    In fact, checks are performed to ensure that a matrix has been allocated and initialized prior to its being used. The dimensions of each array are checked before an operation is performed, in order to ensure that the matrices involved have consistent dimensions for that operation.
    Thus, it will be seen that the implementation is a form of object-oriented programming.
    To implement the feature, extensive use of CONTROLLED storage is used. Normal PL/I statements may be intermixed with matrix statements. Some of the matrix statements appear to be exactly like normal PL/I assignmens statements (for example, C = A + B;). However, there is more work implied in the matrix form.
    For example, the matrix statement C = A + B; is translated as follows:
    First the bounds are checked, to ensure that each matrix has the same number of rows and columns.
    Next, the matrix C is checked that it has not been allocated. If it has been, storage is freed.
    Next, A and B are checked that they have been allocated (that is, they have been assigned values).
    Finally, a normal PL/I array assignment C = A + B; is executed.
    The invisible (generated) code looks like this: DECLARE C(*,*) FLOAT (16) CONTROLLED; << This declaration is generated once << only in the entire program. IF ALLOCATION (A) = 0 THEN << write an error message>> IF ALLOCATION (B) = 0 THEN << write an error message >> CALL CHECK_BOUNDS (A, B); IF ALLOCATION (C) > 0 THEN FREE C; ALLOCATE C(HBOUND(A,1), HBOUND(A,2)); C = A + B; (Incidentally, when the statement is A = A + B; then the code to free A and to re-allocate A is not generated.)
    It should be noted that this is not an interpretive scheme. The detailed PL/I code is generated by the pre-processor, and is compiled by the PL/I compiler.
    Procedures for performing any task may be included by the programmer, and are called via the mechanism described.

    Commands available include:

    command meaning ------- ------- identity generate an identity matrix of order N solve solve linear simultaneous equations invert invert a matrix transpose transpose a matrix attach attach columns to a matrix. attach_rows attach rows to a matrix diagonal Extract diagonal. sqrt square root sin sine cos cosine tan tangent x matrix multiplication (written as C = A x B;) term-by-term matrix arithmetic: A = B + C sums corresponding elements of arrays B and C. A = B - C subtracts corresponding elements of arrays B and C. A = B * C product of corresponding elements of B and C (NOT matrix multiplication. For matrix multiplication, see above) A = B / C division of corresponding elements of B and C. Examples: A = N identity; Generate a unit matrix of order N, and assign it to A. A = B transpose; Transpose matrix B, and assign it to matrix A. X = B solve; Solve the equations stored in B, storing the solutions in X. A = B x C; Matrix multiplication of B and C. A = B invert; Invert matrix B, and assign the result to matrix A. A = B attach C; Columns of B are followed by columns of C and are assigned to A. A = B attach_rows C; Rows of B are followed by rows of C, and are assigned to A. A = B sin; The sine of B is assigned to A.
  4. New PL/I Book

    The 5th edition of Eberhard Sturm's book (in German)

    Das neue PL/I (für PC, Workstation und Mainframe) was published in September 2002 by Vieweg-Verlag, ISBN 3-528-44792-3.

    There's a new section about the WIDECHAR attribute and a chapter about "Interfaces to the World", dealing with interfacing to C, REXX, and Java. An example shows starting a Java Virtual Machine, creating objects, and calling methods for asking an internet time server for the current time. Other examples demonstrate CGI programming and using the PLISAX builtin function for XML parsing. (A nice idea of the publisher: a SAXophonist is shown on the cover :-) You can find the preface ("Vorwort"), a table of contents ("Inhaltsverzeichnis"), and the examples ("Beispiele") on Eberhard's web page: Eberhard Sturm's web page

  5. Reading the Unknown

    Copyright © 2002 by R. A. Vowels.

    Sometimes data has to be read in and stored in an array, but the number of elements to be read is unknown. This situation arises when data has been prepared by an automatic recording machine.
    There are several ways in which this problem may be tackled in PL/I.

    • One way is to read in the elements, storing them one-by-one in nodes of a linked list. A count of the elements is performed as each is read in. When the end of the data has been reached, an array is allocated, the list is traversed, and each element is then copied into successive positions of the array. Extra storage is required: 3*n elements (consisting of two words for each node, and one word for each element of the array).

    • The second way is to read in the elements twice. The first time, the elements are counted. Then an array of suitable size is allocated, and the elements are read in with a simple statement. If time is not of great importance, this is a concise yet effective method. And it has the advantage that no extra storage is needed.

    • A third way is to read each element into a CONTROLLED scalar. After all the elements have been read in, an array of the required size is allocated, and the elements from the CONTROLLED scalar are assigned to it.
      This method is simplest, and is a legitimate solution where neither time nor storage space is important. Such would be the case where the number of elements is, say, of the order of 1000. A: PROC OPTIONS (MAIN); DCL A FLOAT CONTROLLED; DECLARE X(*) FLOAT CONTROLLED; DECLARE (I, K) FIXED BINARY; ON ENDFILE (SYSIN) GO TO COMPLETED; DO FOREVER; ALLOCATE A; GET (A); PUT (A); END; COMPLETED: K = ALLOCATION (A) - 1; /* Now that we know how many elements there are, allocate a new array X. */ ALLOCATE X (K); FREE A; /* (the last allocation contains nothing.) */ DO I = K TO 1 BY -1; X(I) = A; FREE A; END; PUT SKIP LIST (X); END A;

    • Yet a fourth way is to allocate an array of modest size (say, 100 elements), and then to read in elements. If the array is filled before encountering the end of the data, the array is allocated again, and more elements are read in. If that allocation becomes filled, the array is allocated again, and so on. When the end of the data is eventually encountered, an array of known size can then be allocated, and the elements of the allocated array are copied into it.

      This latter method is illustrated below. We use a CONTROLLED array. A CONTROLLED array has the feature that storage for the array is not obtained until an ALLOCATE statement is executed. If an ALLOCATE statement for the same array is executed, a second generation of the array is created, and is now accessible. The previous allocation is "stacked", and is not accessible until a FREE statement is executed for the current generation, in which case the first generation becomes visible or accessible again. There may be many generations for a given array. To assist in the use of the array, the built-in function ALLOCATION tells us how many generations there are at any particular moment.

      This method is more efficient than the linked list method in terms of time and storage. Extra storage for approximately 2*n elments must be available (where n is the number of elements in the array).

      GENERAL: PROCEDURE OPTIONS (MAIN); DECLARE A(0:9) FIXED CONTROLLED; DECLARE B(*) FIXED CONTROLLED; DECLARE X FIXED; DECLARE No_of_Elements FIXED BINARY (31); DECLARE (J, N) FIXED BINARY (31); ON ENDFILE (SYSIN) GO TO COMPLETED; N = -1; DO FOREVER; GET LIST (X); N = N + 1; IF MOD(N, 10) = 0 THEN DO; ALLOCATE A; A = 0; END; A(MOD(N, 10)) = X; END; COMPLETED: No_of_Elements = N+1; /* Now that we know how many elements there are, allocate a new array B. */ ALLOCATE B(No_of_Elements); DO J = N TO 0 BY -1; B(J+1) = A(MOD(J, 10)); IF MOD (J, 10) = 0 THEN FREE A; END; /* The elements are stored in B(1) through B(No_of_Elements). */ PUT SKIP LIST (B); END GENERAL;

  6. New PL/I site

    Tom Linden has set up a web site for Kednos PL/I for Compaq (late Digital) PL/I compilers for Tru64 unix. There you will find manuals and details of the compiler, and even a hobbyist licence. Kednos Corporation PL/I
    You may contact Tom at:

    He also has installed PL/I on an OpenVMS test drive system that HP operates. They haven't as yet made it onto their home page.
    To use the compiler you need to register by following the link from his site ( ) to HP's.
    Once you are logged in, you can type HELP PLI. User documentation you can get off Kednos' web site.

  7. Usefulle Webbe Lynx ...

    Peter Flass is building a resource at: Peter Flass's PL/I home page.

    The spring 2000 edition of the COBOL and PL/I newsletter has some topics about PL/I. The specific PL/I topics may be viewed separately, or the entire newsletter in PDF format may be downloaded.
    Of particular interest is the use of PL/I as a programming language for the Common Gateway Interface (CGI).

    The PL/I Connection newsletter.

    The PL/I Connection Newsletter No. 11 , December 1997.

    The PL/I Connection Newsletter No. 10 , April 1997.

  8. Current IBM PL/I offerings in the U.S.

    IBM has enhanced VisualAge® PL/I with powerful features to increase development productivity, simplify the maintenance of your legacy code, and provide seamless portability from your host to your workstation. It also provides the tools needed to identify your Year 2000 date-related fields on both OS/2 and Windows NT.

    VisualAge PL/I Enterprise Edition Version 2.1 combines the two separate offerings from the previous release of VisualAge PL/I (Standard and Professional) into a single offering available on both the OS/2 and Windows NT platforms. By doing so, it provides these productivity features:

    • remote edit/compile
    • redevelopment tools
    • cross platform development
    • year 2000 assessment strategies, find and fix, and test capabilities

    Also included as an extra bonus offering is VisualAge CICS® Enterprise Application Development, which enables CICS host application development on the workstation.

    • Product number 04L6564: VisualAge PL/1 Enterprise V2.1
    • 04L6565: VisualAge PL/I Enterprise V2.1 Upgrade from Prior IBM PL/I Workstation or Competitive Products Program Package CD-ROM
    • 04L6567: VisualAge PL/I Enterprise V2.1 Upgrade from Professional V2.0 Program Package CD-ROM
    • 04L6566: VisualAge PL/I Enterprise V2.1 Upgrade from Standard V2.0 Package CD-ROM

    Current IBM PL/I offerings in Australia.

    Personal PL/I for Windows is also available online from IBM Australia for $330. Click on: Ready to Buy Products and Services Application Development PL/I Personal
  9. Cool Codes

    by Robin Vowels

    When it is necessary to check whether a given character is one of many, the VERIFY function may be used.
    For example, to check whether a given character is a letter, write:

    DECLARE C CHARACTER (1); GET EDIT (C) (A); IF VERIFY (C, Alphabet) = 0 THEN /* The character is a letter. */ Be sure to declare Alphabet thus: DECLARE Alphabet CHARACTER (26) VALUE ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'); because the compiler constructs the translation table at compile time, and thus is very fast. (Alternatively, code the alphabet as a constant.)

    This code is considerably more efficient than:

    IF INDEX (Alphabet, C) > 0 THEN /* The character is a letter. */ which requires a search of the string Alphabet (up to 26 comparisons, compared to the VERIFY version, which requires one comparison).

    Subject: Re: Are 64 bit integers supported in PL1 ? From: "Alan Haisley" <> Date: Tue, 03 Dec 2002 04:21:23 GMT "N. Shamsundar" <> wrote in message > IBM's VAPLI comes with an example for building a DB/2 stored procedure using PL/I, called SQSRVOUT. In the include file SQL.CPY called in by this example, there is a line containing > > dcl SQL_MAXBIGINTVAL fixed bin(63) value(9223372036854775807); > > (this is 2^(63)-1, the largest signed integer that can be held in 64 bits). > > The compiler does not accept this line, saying > > sql.CPY(122:1) : IBM1921I S FIXED DECIMAL constant contains too many significant digits > > Are there some compiler flags that can be set to get this to work? > > N. Shamsundar > University of Houston <xmp> ANSWER The VA PL/I compiler allows a compile-time option of LIMITS(FIXEDBIN(31,63)) or LIMITS(FIXEDBIN(63,63)) which will allow what you want.
    The first allows 8 byte integers, but will use 4 byte integers in all expressions except those that contain at least one 8 byte integer. Expressions that contain an 8 byte integer will be carried out using 63 bit precision.
    The second will cause all binary integer arithmetic to be carried out to 63 bits.
    Subject: Re: How to Suspend Program Execution ? From: "James J. Weinkam" <>, Simon Fraser University Date: Mon, 02 Dec 2002 11:43:05 -0800 Chuck Moore wrote: > Hiya Everyone, > > I have a Personal PL/1 program that does miscellaneous calculations. > It executes in a maximized CMD window (Win2K). At the end of the > program, after the results have been printed to the screen, I'd like the > window to remain open for (say) 5 minutes, then close automatically. > > I don't want to loop for 5 minutes because I don't want to impact the > performance of other programs. I'd like therefore, to call some analogue > to the ObjectRexx SysSleep function. > > I've tried using the SYSTEM function (to no avail). In fact, I haven't > gotten the darn function to work at all. > > My questions are: > 1) How do a I call an OS command (perhaps "REXX.EXE > SLEEP.OREXX") ? > 2) Is there a "native" PL/1 command sequence to suspend execution for > an arbitrary amount of time ? ANSWER: The DELAY statement does the job.
    Subject: PUT DATA format question From: "Tim" <tiNmoOthy.challSengerP@aApk.Mat>Date: Wed, 18 Dec 2002 11:29:29 +0100 I have a structure of which I want to print out each element, along with its name. The standard way would be the PUT DATA, but this produces a list of the elements nicely formatted agross the page. Like this: <<< FXPARM.B2.BBVBNR='2' FXPARM.B2.GUELVONT='0001-01-01' FXPARM.B2.GUELBIST='9999-12-31' FXPARM.B2.ZINSTAG= 11 FXPARM.B2.RZINSJ= 3.500 >>> Does anyone know a way of getting the elements printed one each on a line ? Like this: <<<< FXPARM.B2.BBVBNR='2' FXPARM.B2.GUELVONT='0001-01-01' FXPARM.B2.GUELBIST='9999-12-31' FXPARM.B2.ZINSTAG= 11 >>>> ...other than using EDIT and typing the name in by hand? Idealy I'd like to possibly combine the DATA and EDIT to format the output a bit like this: <<<< FXPARM.B2.BBVBNR = '2' FXPARM.B2.GUELVONT = '0001-01-01' FXPARM.B2.GUELBIST = '9999-12-31' FXPARM.B2.ZINSTAG = 11 >>>> Any ideas, tricks welcome. tia, Tim. ANSWER: Set the number of tabs to zero.

Any comments and contributions for the next newsletter to email: (Team PL/I) please.