Let's look at the PL/I language in more details. Before we start, PL/I is a big language, and we are going to need two posts to even scratch the surface.

Format

PL/I is free format, but the IBM implementation requires use of upper case letters for keywords and identifiers. By default, characters after the 72nd column are ignored by the IBM compiler but this can be changed by a command line switch.

Comments are introduced with /* and ended with */ like in C.

Keywords are not reserved words, so you can do things like the below, where IF is an identifier; this will print 4. Also note that = is used for assignment or comparison, depending on context.

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE IF FIXED BINARY(16);
  IF = 3;
  IF IF = 3 THEN IF = 4; ELSE IF = 5;
  PUT FILE (SCARDS) LIST(IF);
END TEST;  

Variables and constants

Arithmetic data

Arithmetic data can be either fixed point or floating, both using in decimal or binary as its base. An example of each of these combinations:

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE XB FIXED BINARY(16);
  DECLARE XD FIXED DECIMAL(8,2);
  DECLARE FB FLOAT BINARY(8);
  DECLARE FD FLOAT DECIMAL(5);
  XB = 42;
  XD = 123.45;
  FB = 10110.01B;
  FD = 123.45678;
  PUT FILE (SCARDS) LIST(XB, XD, FB, FD);
END TEST;  

which will print

42      123.45  2.22E+01  1.2345E+02  

Note that for the binary items, the number in brackets specifies the number of bits to be used for storage; for decimals, it is the precision, so FD has been truncated.

In the above, you can see that B can be used as a suffix for binary constants. There is also a L suffix for pre-decimalisation British currency where the elements designate pounds, shillings and pence. This is converted into pence for storage.

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE AMT FIXED BINARY(16);
  AMT = 1.5.2L;
  PUT FILE (SCARDS) LIST(AMT);
END TEST;  

I think no other language has this, and it may reflect that PL/I was originally designed at IBM's labs in England.

You can also declare complex variables and use the I suffix in constants, and there are PICTURE definitions for string representations of numeric data similar to COBOL.

Strings

Strings can be defined with either a fixed length or maximum length (with the VARYING keyword). A repeating constant can be introduced with a prefix number, as the below:

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE A CHARACTER(20);
  DECLARE B CHARACTER(20) VARYING;
  A = 'HELLO';
  B = (3)'PL1 ';
  PUT FILE (SCARDS) LIST(A,B);
END TEST;  

which prints:

'HELLO               ' 'PL1 PL1 PL1 '  

There are also bit strings, eg DECLARE C BIT(8); C = '1010'B.

Arrays and structures

Arrays can be defined by adding dimensions after the variable name, eg DECLARE TABLE(10,20) FIXED DECIMAL(16);

Structures are collections of variables using a hierarchical format, for example

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE 1 MEAL,
            2 NAME CHARACTER(20) VARYING,
            2 CUISINE CHARACTER(20) VARYING,
            2 RESTAURANT,
              3 NAME CHARACTER(20) VARYING,
              3 ADDRESS CHARACTER(80) VARYING,
            2 COST FIXED DECIMAL(5,2);
  MEAL.NAME = 'Dinner';
  MEAL.CUISINE = 'Italian';
  MEAL.RESTAURANT.NAME = 'Alfredo';
  MEAL.ADDRESS = '123 Foo Road';
  MEAL.COST = 123.45;
  PUT FILE (SCARDS) LIST(MEAL);
END TEST;  

Note that when qualifying a structure member we can omit levels if it is not ambiguous, eg MEAL.ADDRESS.

Once a structure has been set up, other variables can be instantiated with the same type using LIKE, eg DECLARE PARTY LIKE MEAL;.

Other types and attributes

As well as statement labels, which can be used for GO TO targets, there are special types for multitasking called events and tasks, but these are not supported by MTS.

An alias to another variable can be set up with DEFINED, eg DECLARE A FIXED BINARY(16); DECLARE B DEFINED A; means B and A point at the same variable.

Other attributes on types include ALIGNED and UNALIGNED and an initial value set at declaration with INITIAL;

Not covered here are variable lifetimes and pointers, which I will come back to in the next part.

Expressions

Expressions work mostly as expected, with conversions allowed implicitly between types - which leads to some complex rules given the number of built in types. || can be used as the string concatenation operator. Whole arrays or structures can be used as parameters to an expression.

Statements

Multiple assignments can be made on the left hand side of an expression, eg A,B = 2*21.

Control flow statements include IF ... THEN ... ELSE and looping via DO, which allows variable and condition testing as in the below:

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE I FIXED BINARY(16);
  DECLARE S FIXED BINARY(16);

  DO I = 1 TO 5, 10 TO 15;
    PUT FILE (SCARDS) LIST(I);
  END;
  PUT FILE (SCARDS) SKIP;

  S = 0;
  DO WHILE (S < 10);
    S = S + 1;
    PUT FILE (SCARDS) LIST(S);
  END;
  PUT FILE (SCARDS) SKIP;

  S = 0;
  DO I = 2 TO 10 BY 2 WHILE (S < 10);
    S = S + I;
    PUT FILE (SCARDS) LIST(S);
  END;
  PUT FILE (SCARDS) SKIP;
END TEST;  

while will print

          1         2         3         4         5        10        11  
         13        14        15 

          1         2         3         4         5         6         7  
          9        10 

          2         6        12 

The STOP and EXIT statements can be used to terminate program execution immediately. There is also exception support for a set of predefined cases, such as end of file, using ON, eg ON ENDFILE(SCARDS) GO TO DONE;

In the next post we will look at pointers, block structure and the pre-processor along with other topics.

Further information

The original IBM documentation for the PL/I language and PL/I F compiler can be found at bitsavers.