ISSUE NO. 3 – JUNE 2001

Copyright © 2001 by Robin Vowels. All rights reserved for the authors.
  1. Editorial
  2. COMPLEX ARCSINE and ARCCOSINE, by Robin Vowels
  3. Using GDDM in PL/I, by Peter Flass
  4. The new Built-in Functions of IBM VisualAge PL/I - Part A by Robin Vowels
  5. Language Suggestions
  6. Usefulle Webbe Lynx ...
  7. Cool Codes
  8. Brain Teasers
  9. Puzzle Corner - Try your hand at Eberhard's latest


  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: robin_v@bigpond.com
    The first issue may be downloaded from: The PL/I Newsletter, No. 1, July 2000.
    The second issue may be downloaded from: The PL/I Newsletter, No. 2, September 2000.

    by R. A. Vowels
    The algorithms of T. E. Hull, T. F. Fairgrieve, and P. T. P. Tang for computing complex arccsine and arccosine are ideally suited to PL/I on account of the error-handling features of PL/I.
    These new algorithms use a fast method for computing the arcsine and arccosine, but in certain regions of the argument, values may venture into the realm of too large or too small.
    When they do, error handlers for these conditions cause arcsine and arccosine to be computed using different formulas that do not overflow or underflow.
    The author has translated the algorithms (expressed in a pseudo-Algol/Fortran) into PL/I. The algorithm for arccosine is presented here.
    Voila: /* A procedure for COMPLEX ARCCOSINE. */ /* Based on the algorithm of T. E. Hull, T. F. Fairgrieve, and P. T. P. Tang, */ /* "Implementing the Complex Arcsin and Arccosine Functions using Exception */ /* Handling", in ACM Transactions on Mathematical Software, Vol. 23 No. 3, */ /* September 1997, pp. 299-335. */ /* This translation by R. A. Vowels, 27 May 2001. */ %DECLARE nd CHARACTER; /* nd = the number of floating-point digits. */ %nd = 15; ACOS: PROCEDURE (Z) OPTIONS (REORDER) RETURNS (FLOAT (nd) COMPLEX); DECLARE Z FLOAT (nd) COMPLEX; DECLARE (X, Y, R, S, A, Am1, B) FLOAT (nd); DECLARE E FLOAT (nd) STATIC INITIAL ( (EPSILON(X)) ), Foursqrtu FLOAT (nd) STATIC INITIAL ( (4*SQRT(TINY(X))) ), Log2 FLOAT (nd) STATIC INITIAL ( (LOG(2.00000000000000000E0)) ), Sqrt2 FLOAT (nd) STATIC INITIAL ( (SQRT(2.00000000000000000E0)) ), Piby2 FLOAT (nd) VALUE (1.57079632679489662E0), PI FLOAT (nd) VALUE (3.14159265358979323E0), SqrtAm1 FLOAT (nd), Acrossover FLOAT STATIC INITIAL (1.5), Bcrossover FLOAT (nd) STATIC INITIAL (0.6417); DECLARE Answer FLOAT (nd) COMPLEX, (Answer_real, Answer_imag) FLOAT(nd); DECLARE ACOS BUILTIN; X = ABS(REAL(Z)); Y = ABS(IMAG(Z)); ON ERROR SNAP GO TO Difficult; ON UNDERFLOW SNAP GO TO Difficult; R = SQRT((X+1)**2 + Y**2); S = SQRT((X-1)**2 + Y**2); A = (R+S)/2; B = X/A; IF B <= Bcrossover THEN Answer_real = ACOS(B); ELSE /* Use arctan and an accurate approximation to alpha-x. */ DO; IF X <= 1 THEN Answer_real = ATAN(SQRT((A+X)/2*(Y**2/(R+(X+1)) + (S+(1-X))))/X); ELSE Answer_real = ATAN((Y*SQRT(0.5*((A+X)/(R+(X+1)) + (A+X)/(S+(X-1)))))/X); END; IF A <= Acrossover THEN /* Use log1p and an accurate approximation to alpha-1. */ DO; IF X < 1 THEN Am1 = ( Y**2/(R+(X+1)) + Y**2/(S+(1-X)) )/2; ELSE Am1 = ( Y**2/(R+(X+1)) + (S+(X-1)))/2; Answer_imag = Log1p(Am1 + SQRT(Am1*(A+1))); END; ELSE Answer_imag = LOG(A + SQRT(A**2 - 1)); IF REAL(Z) < 0 THEN Answer_real = Pi - Answer_real; Answer_imag = -SIGN(IMAG(Z)) * Answer_imag; Answer = COMPLEX(Answer_real, Answer_imag); RETURN (Answer); /* Error processing when the argument Z is outside the safe range. */ Difficult: REVERT ERROR, UNDERFLOW; IF Y <= E*ABS(X-1) THEN /* Cases 1 and 1. */ IF X < 1 THEN DO; Answer_real = ACOS(X); Answer_imag = Y/SQRT((1+X)*(1-X)); END; ELSE DO; Answer_real = 0; ON OVERFLOW GO TO RECOVER1; Answer_imag = Log1p((X-1) + SQRT((X-1)*(X+1))); GO TO LAST_STEP; RECOVER1: REVERT OVERFLOW; /* (x-1)*(x+1) overflowed. */ Answer_imag = Log2 + LOG(X); END; ELSE IF Y < Foursqrtu THEN /* Case 3. */ DO; IF E*E >= 2*Foursqrtu THEN /* Section for machines having a large exponent */ /* range. */ Answer_real, Answer_imag = SQRT(Y); ELSE /* Section for machines having a small exponent */ /* range. */ IF X = 1 THEN Answer_real, Answer_imag = SQRT(Y); ELSE DO; S = ABS(X-1)*SQRT(1+(Y/(X-1))**2); IF X < 1 THEN DO; Answer_real = ATAN (SQRT(0.5*(1+X)*(S+(1-X)))/X); SqrtAm1 = 0.5*Y*SQRT(1/(1+X)+2/(S+(1-X))); Answer_imag = Log1p(SqrtAm1*(SqrtAm1+Sqrt2)); END; ELSE DO; Answer_real = ATAN(Y*SQRT(X/(2*(X+1))+X/(S+(X-1)))/X); Am1 = (S+(X-1))/2; Answer_imag = Log1p(Am1+ SQRT(Am1*(1+X))); END; END; END; ELSE IF E*Y-1 >= X THEN /* Case 4. */ DO; Answer_real = Piby2; Answer_imag = Log2 + LOG(Y); END; ELSE IF X > 1 THEN /* Case 5: x+1 and y are both very large. */ DO; Answer_real = ATAN(Y/X); Answer_imag = Log2 + LOG(Y) + Log1p((X/Y)**2)/2; END; ELSE /* Case 6: x alone is very small, and */ DO; /* E <~ y <~ E**(-1). */ Answer_real = Piby2; A = SQRT(1 + Y**2); Answer_imag = Log1p(2*Y*(Y+A))/2; END; /* End of error processing. */ LAST_STEP: IF REAL(Z) < 0 THEN Answer_real = Pi - Answer_real; Answer_imag = -SIGN(IMAG(Z)) * Answer_imag; Answer = COMPLEX(Answer_real, Answer_imag); RETURN (Answer); END ACOS; For further information, including a detailed discussion of the formulas and special conditions for machines having a small exponent range, see: T. E. Hull, T. F. Fairgrieve, and P. T. P. Tang, "Implementing the Complex Arcsin and Arccosine Functions using Exception Handling", in ACM Transactions on Mathematical Software, Vol. 23 No. 3, September 1997, pp. 299-335.
  3. Using GDDM in PL/I

    by Peter Flass, June 2001

    GDDM is device-independent graphics program for IBM OS/390, VM and VSE systems. GDDM is a powerful system supporting a variety of terminals, printers, and plotters, and both raster and vector graphics. The API for GDDM is huge, consisting of nearly 500 functions! Despite this it is possible to generate reasonable graphics with a small number of calls. Best of all, IBM has provided a PL/I interface to GDDM.

    This article is not a systematic description of the GDDM-PL/I interface. It is a walk-through of a specific PL/I application that uses GDDM to generate graphics output.

    UPTIME is a PL/I program which generates a line chart using the GDDM Presentation Graphics Facility (PGF). The data, obtained from SMF, is the daily uptime of several CICS subsystems for a one-month period. Only 31 lines of code are required to generate the chart. Here is the complete chart procedure; code has been rearranged to show the declarations with the code that uses them.

    In the code that follows, MC  is the number of subsystems being graphed (the number of different lines). CHART_CNT  is the number of days in the current month (the number of data points for each line). Here is the procedure:

    1. Include the interface definition:

      GDDM comes with 22 of these include files for different functions and options. On my system they live in 'GDDM.SADMSAM'. An easy way to remember these: all PL/I members begin with ADMUPI. The next character is N for the non-reentrant interface or R for the reentrant version. The last character corresponds to the first letter in the name of an entry point being called. I'm using the non-reentrant interface and making calls to functions whose names begin with A,C,D,F, and G.

    2. Initialize GDDM:
                 CALL FSINIT;

    3. Open the selected output device:
                 DCL     P_LIST           (1)FIXED BIN(31);
                 DCL     N_LIST           (1)CHAR(8)        INIT('CHART');
                 CALL DSOPEN(0,4,'P38PPW3',0,P_LIST,1,N_LIST);
      P38PPW3 is a GDDM device token, a code describing the device type, media, pel density, etc. GDDM suppplies a large number of tokens for various output options and devices. If you don't like the supplied tokens, create your own: in this case, P38PPW3 is an IBM 3800 page-printer, 240ppi, page size 11×8, landscape. The other parameters say that this is the default output device [0], specify the device family  [printer, plotter, terminal, etc] [4], provide miscellaneous options [0,P_LIST], and specify a device name [1,N_LIST].

      In this example P_LIST  is a dummy argument since the count [0] indicates this parameter is not used. N_LIST  is used to specify the DDNAME of the output files: one DD named CHART.

    4. Supply the heading and title information for the chart:
                 DCL     CHART_MARKS  (8)FIXED BIN(31) INIT(8,7,6,4,3,2,1,5);
                 DCL     HEADING         CHAR(120)      VARYING;
                 HEADING = 'SUBSYSTEM UPTIME '||CHART_LBLS(1)||
                            ' TO '||CHART_LBLS(CHART_CNT);
                 HEADING = 'UPTIME (HR)';
                 HEADING = 'DAY';
      CHART_MARKS  override the default identifying characters for the line representing each subsystem because I felt the defaults were hard to read. The other statements provide the title of the chart, and the X- and Y-axis descriptions.

    5. Specify the data ranges to be plotted and the corresponding labels. (The dimension of the controlled variables is the number of data points).
                 DCL     CHART_CNT            FIXED BIN(31) INIT(0);
                 DCL     CHART_POINTS    (*,*)FLOAT         CONTROLLED;
                 DCL     CHART_LBLS        (*)CHAR(10)      CONTROLLED;
                 DCL     CHART_SCALE       (*)FLOAT         CONTROLLED;
      First the X-range:
                 CALL CHXRNG(1,CHART_CNT);
      The parameters are converted to FLOAT and represent the lowest and highest values of the X-axis data: one to the number of days in the month.

      The X-axis labels:

      CHART_SCALE  is an array of values representing the X-axis value for each tick-mark. CHART_LBLS  is an array of labels for the tick-marks (in this case the dates). GDDM will select some of these to be printed when the chart is generated; I have to provide all of them [CHART_CNT]. Labels in the array must be nonvarying character strings with a length indicated by the argument LENGTH(CHART_LBLS(1)).

      The "datum-line" (optional):

                    CALL CHYDTM(FLOAT(PRIME_INT) / INT_PER_HOUR );
      CHYDTM  specifies the Y-axis value the "datum" line is to pass through, in this case I specify a point equal to the number of hours in the prime shift (normally 7.5).

    6. Set the legend "key labels".
                 DCL     MC                      FIXED BIN(15) INIT(0);
                 DCL     MONITOR_TASK   (MAX_MON)CHAR(8);
      MONITOR_TASK  is an array of nonvarying character strings to be used to label the lines in the chart "key". The procedure is called with the number of labels [MC], the length of each label [LENGTH(MONITOR_TASK(1))], and the address of the array of labels.

    7. Draw the chart.
      That's all there is to it! CHART_POINTSX  is an array of FLOAT values in row-major order containing values for each of MC  subsystems for CHART_CNT  days.

    8. Finish up.
                 CALL CHTERM;
                 CALL FSFRCE;
                 CALL FSTERM;
    The file uptime.pli contains the complete original program, minus some includes. You can see that the chart procedure is by far the smallest segment.

    The file uptimel.pdf is a larger example of the sample chart shown above [PDF format].

  4. The (new) Built-in Functions of IBM VisualAge PL/I

    by Robin Vowels
    The (new) built-in functions of IBM VisualAge PL/I are a force majeure in the repertoire of the PL/I programmer. In this article we briefly look at some of those functions that have been added to the VisualAge compiler (compared to the mainframe version). Consider this to be a first instalment. A subsequent issue of the PL/I Newsletter will cover more.
    • COSF, SINF, ACOSF, ASINF, ATANF, SQRTF etc. These functions use specific harware instructions that exist in some processors (e.g, the Intel x86, which has a hardware SQRT instruction). Such instructions execute faster than their software equivalents. However, note that such instructions give their own interrupts (INVALIDOP), and instead of raising the OVERFLOW or UNDERFLOW condition, may raise the INVALIDOP condition. Thus, you may need to supply an error-handler for the INVALIDOP condition (in addition to that for OVERFLOW etc).
    • String-handling utilities:
      • CENTERLEFT centers a string, padding with blanks (or any other specified character) at each end if required. Should the resultant string have an uneven number of blanks (or other character) at each end, the extra one is at the right. Should the given string be longer than the resultant string, the given string is truncated at each end, with a bias to the left.
        Example: CENTERLEFT(S, 25) where S = 'The quick brown fox' yields
        'bbbThe quick brown foxbbb' [where b = blank].
        CENTERLEFT(S, 26) yields 'bbbThe quick brown foxbbbb'
        CENTERLEFT(S, 25, '*') yields '***The quick brown fox***'
      • CENTERRIGHT is the same as CENTERLEFT, except that any bias is to the right.
      • LEFT(S, L) returns a string of length L in which S is at the left-hand end, and is padded on the right with blanks. LEFT(S, L, C) does the same, but the padding character used is C.
      • RIGHT is the same as LEFT, except that S is right-adjusted and the padding is at the left.
    • CHARGRAPHIC converts the argument from GRAPHIC to a mixed string. A length for the result may be specified.
    • COLLATE returns a CHARACTER string of length 256 containing each of the characters in collating sequence order - 00h to FFh from left to right.
    • COMPARE(S, T, n) compares n bytes of strings S and T, and returns -1 if S < T, 0 if S = T, and +1 if S > T.
    • COPY(S, n) returns n copies of string S. The function differs from REPEAT and in a way that is more convenient. If n is zero, a null string is returned.
    • Date-handling functions: These functions considerably simplify the task of recognizing dates in a variety of formats, and of converting dates from one format to another.
      They also trivialize the task of computing a date that is a given number of days and/or seconds forward or backward from a given date.
      It is simple to determine the week day (Monday, or Tuesday, etc) from any given date, and to select the next start of the week, or any other day.
      And it's simple to compute the elapsed time taken for any computation.
      The date-handling functions are:
      • DAYS (no arguments) converts today's date or the date D (as in DAYS(D) ) in standard format to the number of (Lilian) days. DAYS(D, pattern) converts the date in the given pattern to the number of Julian days.
        pattern can be any of the 35 patterns having four-digit and two-digit years, such as
        12Dec2002 (pattern DDMmmYYYY) and
        12DEC02 (pattern DDMMMYY)
        DAYS(D, pattern, window) does the same as DAYS(D, pattern) but using a 100-year window beginning from the specified year.
      • DAYSTODATE(D) converts the number of Lilian days D to a date in the standard date & time pattern of YYYYMMDDHHMMiSS999.
        DAYSTODATE(D, pattern) converts the number of Lilian days D to the date in the specified pattern (one of 35 patterns -- see DAYS).
      • DAYSTOSECS(D) converts the number of (Lilian) days D to seconds.
      • SECS, SECS(D), and SECS(D, pattern) return the number of seconds corresponding to a Julian date D. If D is omitted, the current date and time are assumed. If pattern is omitted, the default DATETIME pattern is assumed. pattern is one of the 35 date patterns.
        This function may be used to compute the elapsed time taken for any computation, by subtracting the values obtained from consecutive calls to SECS().
      • SECSTODATE(S) and SECSTODATE(S, pattern) converts the number of seconds S to a date specified by the (date) pattern. If pattern is missing, the default DATETIME pattern is assumed. pattern is one of the 35 date patterns.
      • SECSTODAYS(S) returns the number of Lilian days corresponding to the number of seconds S. Incomplete days are ignored.
      • WEEKDAY returns the day of the week corresponding to today. WEEKDAY(D) returns the day of the week corresponding to the date D. (The day of the week is returned as an integer: 1 = Sunday, 2=Monday, etc.)
        Using this function you can, among other things, adjust a date so as to avoid a weekend, or to select the next Monday, or whatever.
    • The Millennium Language Extension built-in functions were added to facilitate use of PL/I with 2-digit years, and to assist in the conversion of pre-2000 PL/I programs to handle the year 2000 and beyond. In some cases, only minimal changes to the PL/I program are required to make an old program Y2k compliant.
      As well, a new language feature (the DATE attribute) enables PL/I to work with a 100-year date "window", allowing 2-digit years to be treated automatically as years in the 20th and/or 21st centuries, depending on the beginning of the window.
      These functions are:
      • REPATTERN(D, to_pattern, from_pattern) converts a date D in the from_pattern to the to_pattern. Both patterns may be in any of the 35 date patterns. The function thus may convert from 2-digit years to 4-digit years and vice versa, as well as convert to any of the other patterns.
      • Y4DATE(D) accepts a date D in DATE format (YYMMDD) and converts it to the 4-digit year form YYYYMMDD. (The century is determined from the "window" compiler option.)
        Y4DATE(D, window) does the same as Y4DATE(D), but uses the window value provided as the second argument.
      • Y4YEAR (Y) converts a 2-digit year to a four digit year, using the window specified by the "window" compiler option.
        Y4YEAR(Y, window) converts a 2-digit year to a 4-digit year, using the value of the window provided as the second argument.
      • Y4JULIAN (J) converts a date in the format YYDDD to the format YYYYDDD, using the window specified by the "window" compiler option.
        Y4JULIAN (J, window) does the same as Y4JULIAN (Y), except that the compiler uses the window given as the second argument.
      • Certain of the date functions given above also accept the window argument; for example, DAYS (D, pattern, window) treats a 2-digit year D as a year in the current century or in the previous century, according to the window argument.
    • Certain built-in functions perform Boolean and shifting operations on binary integers. The operations are usually fast, being carried out in registers.
      • IAND and IOR peform, respectively, the logical and and the logical or of two or more binary integer (FIXED BINARY) arguments.
      • IEOR performs the exclusive or of two FIXED BINARY (integer) arguments.
      • INOT performs the logical not of its FIXED BINARY (integer) argument.
      • ISLL(F, n) shifts its FIXED BINARY integer argument F to the left by n places.
        Overflow cannot occur, and the sign is not necessarily preserved.
      • ISRL(F, n) shifts its FIXED BINARY integer argument F to the right by n places.
        The sign is not necessarily preserved.
      • RAISE2(F, n) performs an arithmetic shift of its FIXED BINARY integer argument F of n places to the left (equivalent to multiplying by 2**n).
      • LOWER2(F, n) performs an arithmetic shift of its FIXED BINARY integer argument F of n places to the right. An arithmetic shift is the same as a logical right shift, except that the sign bit is copied. When F is positive, the function is equivalent to dividing by 2**n. When F is negative, however, the operation is not equivalent to normal division. For example, LOWER(F, 1) produces results according to the following table: F result -5 -3 -4 -2 -3 -2 -2 -1 -1 -1 0 0 1 0 2 1 3 1 4 2 5 2 6 3

  5. Language Suggestions

    by Robin Vowels
    Have you ever needed to change the line size and positions of the tabs? I find that the defaults (132 columns) for the screen of the PC to be inconvenient, and need to alter them for most runs.
    Once upon a time, back in 1965, the PL/I language definition contained the magic word TAB that could be used to specify the positions of the tabs in a print file. (IBM PL/I: Language Specifications, C28-6571-0, New York, 1965.) The tabs could be changed dynamically.
    I am not sure whether this feature actually became a statement or part of the OPEN statement in the first release of the (OS) PL/I-F compiler. Regardless of whether it did or not, it would be of considerable advantage to be able to write an OPEN statement like OPEN FILE (SYSPRINT) TAB (20, 40, 60); to set the tabs dynamically, and in a simple way.

    Regardless of whether TAB would or not become part of the language, I have written some macro procedures that will allow a statement like the one above to be written. The macro generates the verbose DECLARE statement that sets the tabs. Of course, the INITIAL values of the DECLARE statement are set at link time, so the TAB option can be "executed" only once in a program.
    The accompanying TAB file implements such a facility using the macro facility of PL/I for OS/2.

  6. 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.

  7. Cool Codes

    From: "May,Jeffrey" Date sent: Wed, 30 May 2001 09:14:39 -0400
    Here is a quick little ditty. It's a preprocessor macro that creates a COBOL-like FILLER for use in defining structures for record overlays or preformatted print lines. Handy for the COBOL transplants or if you just need a quick record overlay to get a few selected fields. /****************************************************************/ /* PL/I Preprocessor Macro - define FILLER */ /****************************************************************/ %noprint; %dcl counter builtin; %filler: procedure returns( character ); return( 'F'||COUNTER ); %end; %activate filler; %note( 'Macro FILLER defined and activated.', 0 ); %print; Jeff __ Jeffrey W. May Programmer, Information Systems Department OCLC Online Computer Library Center, Inc. 6565 Frantz Road, Dublin, OH 43017-3395
  8. Brain Teasers

    From: "GARDINER, Roy, MANU ITI" Date sent: Tue, 29 May 2001 10:51:04 +0100
    Here's a very old PL/I snippet which might be one line of amusement; what's the output from this code? DO I = 1, I = 2; PUT DATA(I); END; [you will get a diagnostic message for this -- Ed]
    or (equally old)
    Where and why might one code this? /*';/**/ This was proposed once as a logo to be printed on a PL/I specialist group T shirt, but was thought to be too obscure.
    Regards, Roy
  9. Puzzle Corner

    Eberhard's PL/I Problem
    In the previous issue of The PL/I Newsletter I presented the following PL/I problem:
    Given an ordinal variable T which can assume two values T1 and T2, I wanted a function also named T which returns the ordinal value when the binary value is passed to it: Problem2: proc options (main); define ordinal T (T1 value (-1), T2 value (3)) prec (7) signed; put (ordinalname(T(-1)), ordinalname(T(3))); T: proc (N) returns (type T); dcl N fixed bin (31); ??? end T; end Problem2; The attributes "value", "precision", and "signed" are only examples of what may happen to occur. Function T should be independent of these attributes. You can regard T as the inverse function of the builtin function BINARYVALUE in respect to ordinals.
    "Unfortunately" Mark Yudkin pointed out that the CAST type function will do this job. This was wrong but only for a few months, now it's implemented in the IBM compiler. So it's useless to present a solution here that doesn't use CAST. (If you are interested I can send it to you.) The missing statement is: return (cast(:T, N:)); If you want to know what CAST does in general, a PL/I programmer is lost: The language reference manual describes it as the equivalent of the C language cast construct. And that I didn't understand either.
    This Month's Problem
    Let's turn to a new problem. Consider the following program: Problem3: proc options (main); dcl X ???; dcl Y dim (100_000, 20_000) char (1) ???; Y(100_000, 20_000) = '+'; put (Y(100_000, 20_000)); end Problem3; The question is: What do you have to write in the declare statements instead of the question marks to have a successfully running program? If you merely delete the question marks you will receive the execution time message IBM3000I Not enough application stack to complete processing. It should be very difficult indeed to define a stack size of 2 GByte. This program makes use of an old PL/I feature just introduced into the current IBM compiler (it's only documented in older PL/I manuals).
    Whereas the recent PL/I problem may have been new to old PL/Iers the new one may be new to new PL/Iers. This is a nonsense program, of course, only designed to show the presence of the old and now new PL/I feature.
    In order to prevent spoilers it should be better to post a solution not to the PL/I newsgroup but to me sturm@uni-muenster.de in the first week after publication of this newsletter. :-)

Please send any comments and contributions for the next newsletter to email: robin_v@bigpond.com