Let's continue our look at the PL/I language.

Storage classes

There are four storage classes in PL/I. AUTOMATIC (the default) and STATIC are much like in C:

CONTROLLED variables are assigned space dynamically with ALLOCATE and released with FREE. Unlike C malloc/free, allocations are stacked so you can reallocate space and the program will remember the last allocation after it is freed. For example, the below will print 20 then 10.

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE A FIXED BINARY(16) CONTROLLED;

  ALLOCATE A;
  A = 10;
  ALLOCATE A;
  A = 20;
  PUT FILE (SCARDS) LIST(A);
  FREE A;
  PUT FILE (SCARDS) LIST(A);
  FREE A;
END TEST;  

Use of controlled variables before they are allocated or after they are freed will lead to a runtime error.

BASED variables are similar to CONTROLLED variables but also allow pointer style access.

Variables (and procedures) can also be declared EXTERNAL if they are defined elsewhere in the program.

Procedures and functions

Procedures can be declared at file level, within other procedures, or even within DO blocks. They are introduced with PROCEDURE and executed using CALL. Parameters are passed by name so the procedure can alter them; you can also pass an expression and the compiler will introduce a temporary variable. The below will print 41.

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE A FIXED BINARY(16);
  A = 41;
  CALL INCR(A);
  CALL INCR(A+1);
  PUT FILE (SCARDS) LIST(A);

  INCR: PROCEDURE(X);
    DECLARE X FIXED BINARY(16);
    X = X + 1;
  END INCR;

END TEST;  

A function is a procedure that returns a value, so the above could also be written as:

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE A FIXED BINARY(16);
  A = 41;
  A = INCR(A);
  PUT FILE (SCARDS) LIST(A);

  INCR: PROCEDURE(X) RETURNS(FIXED BINARY(16));
    DECLARE X FIXED BINARY(16);
    RETURN(X + 1);
  END INCR;

END TEST;  

Note that brackets are needed around the expression being returned.

Preprocessor

The source file can be augmented at compile time by using preprocessor directives, which start with %. On MTS, to use this facility you must set the MACRO parameter in the compiler command (eg $run *pl1f scards=in.pl1 spunch=-load par=macro).

%INCLUDE otherfile copies the contents of otherfile into the file currently being compiled.

%DECLARE can be used for macro replacement. %DEACTIVATE will undefine a macro variable allowing it to be used by the program again. The following will print 42.

%DECLARE A CHARACTER, B FIXED;
%A = 'B + 2';
%B = 40;

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE X FIXED BINARY(16);
  X = A;
  PUT FILE (SCARDS) LIST(A);
END TEST;  

So far so much like the C preprocessor. But you can also do compile time programming using labels, %IF and %GOTO. The below will substitute the middle section with X = X + 1;, X = X + 2; etc and prints 10.

TEST: PROCEDURE OPTIONS(MAIN);  
  DECLARE X FIXED BINARY(16);
  X = 0;

  %DECLARE N FIXED;
  %N = 1;
  %LABEL1:;
  X = X + N;
  %N = N + 1;
  %IF N < 5 %THEN %GO TO LABEL1;

  PUT FILE (SCARDS) LIST(X);
END TEST;  

There is also a %DO loop and the ability to create compile time %PROCEDURES.

Further information

There's a lot more we haven't covered. PL/I has extensive I/O operations in both stream and record formats. There is support for multi-tasking, although much of this is not available on MTS.

There is surprisingly little on the internet about PL/I, outside of IBM. There's an (opinionated) comparison between C and PL/I. The experience Multics had using PL/I is also interesting.