SNOBOL - Language features

Hello again. Let’s take a closer look at the SNOBOL language: to run these on MTS see the instructions in the previous article.

Statements

There is only one statement format in SNOBOL, but each part of the statement is optional:

label subject pattern = replacement :goto

An example will make this clearer. This will read lines from input and print to output until QUIT is found anywhere in the input line.

BEGIN  LINE = INPUT
       LINE "QUIT"     :S(END)
       OUTPUT = LINE   :(BEGIN)
END

The first line BEGIN LINE = INPUT has a label (BEGIN) and assigns the subject variable LINE to the replacement value INPUT, which is a special keyword meaning take a line from the input device.

The second line has the subject LINE and matches that against the pattern "QUIT". If the match was a success, the goto statement :S(END) will jump to the final line with the end label.

The third line prints the value of the replacement, LINE, by assigning it to the subject, which is the special keyword OUTPUT. The goto here is unconditionally back to BEGIN.

The fourth line contains just the label END.

Dynamic variables

SNOBOL is the first language we’ve seen on MTS that has dynamic typing. Variables do not need to be pre-declared and their values can change types easily. In the below, J starts off containing a string but then is changed so it contains an integer; 42 is printed.

J = "ABC"
A = 20
B = "22"
J = A + B
OUTPUT = J

You cam also refer to and create variables indirectly via the $ operator. The below will print “123”.

A = 'B"
B = "123
OUT = $"A"

Arrays, tables and data types

Arrays can be defined and initialised with ARRAY, eg for a tic-tac-toe board you could do:

BOARD = ARRAY("3,3", " ")
BOARD<2,2> = "O"
BOARD<1,1> = "X"

Associative arrays can be defined with TABLE

DIRECTORY = TABLE()
DIRECTORY<"John"> = 123

User defined data types can be created, eg for a 2D point type, the below will print 4:

DATA("POINT(X,Y)")
P = POINT(3, 4)
OUTPUT = Y(P)

Control flow

The only control flow available in SNOBOL is goto. Each statement evaluates to either success or failure, and a jump to another statement can be made based on this result or unconditionally. In the below, a different jump for success and failure is defined on the third line.

START X = INPUT
      X "YES" :S(START)F(END)
      OUTPUT = "This is not reached"
END

Pattern matching

The above example is a simple pattern matching test: if the variable X contains YES then the statement is successful. SNOBOL has many more pattern matching constructs: some are showed below along with a string that would be a successful match.

  • Simple concatenation of strings matches the sequence, either | or ANY can be used for alternation.
"HAMLET"      "ML" "ET"
"THE TEMPEST" "TOMP" | "TEMP"
"MACBETH"     "MA" ANY("CDY") "BETH"
  • Patterns can be grouped together with brackets:
"A MIDSUMMER'S NIGH DREAM"   "MID" ("SUMMER" | "WINTER")
  • ARB matches and arbitrary number of chars
"OTHELLO"     "H" ARB "LO"
  • LEN matches a fixed length run of characters
"HENRY IV PART I"     "HENRY " LEN(2) " PART " LEN(1)
  • SPAN matches a run of anything from a set of characters, BREAK the opposite
"PERICLES"   "PER" SPAN("CIXZ") BREAK("ABCDE") "ES"
  • BAL matches an string which has balanced parentheses (including no parentheses), so the pattern
"TO BE" BAL "."

would match “TO BE (OR NOT TO BE).” and “TO BE OR NOT TO BE.” but not “TO BE ((OR NOT TO BE).”

By default, SNOBOL will match at any position on the line; it can be forced to match from a certain column by setting the variable &ANCHOR.

Patterns can be defined and referred to later; a pattern can be referred to recursively in the same pattern with *.

A more complex example is below, which will match simple arithmetic expressions, eg Z=21 or X+Y*Z=42.

BEGIN LINE = INPUT
      &ANCHOR = 1
      NUM     = SPAN("0123456789")
      TERM    = ANY("XYZ")
      OP      = ANY("+-*/")
      EXPR    = TERM | *EXPR OP TERM
      LINE    EXPR "=" NUM            :S(END)
      OUTPUT = LINE                   :(BEGIN)
END

Replacement and assignment

If any of the above patterns matches, simple replacement can be done by using pattern = replacement, so the below will replace the first occurrence of A with Z.

 LINE = INPUT
 LINE "A" = "Z"
 OUTPUT = LINE
END

Assignment of a substring to a variable can be done with the binary operator . (which will match if the whole pattern matches) or $ (which will match even if the whole pattern fails. So for the line below, AQQQZ will cause FIRST to be A and LAST to be Z, but AQQQ will cause neither to be set.

LINE ANY("ABC") . FIRST BREAK("XYZ") ANY("XYZ") . LAST

Instead, if you do

LINE ANY("ABC") $ FIRST BREAK("XYZ") ANY("XYZ") $ LAST

then AQQQ will cause FIRST to be set to A. Note that QQQZ will not match to LAST in either case.

Built in functions

SNOBOL has a number of built in functions. Function parameters are passed by value and the function can return a value.

  • LT, GT, EQ etc for numeric equality
  • LGT compares two values lexically and returns true if the first is after the second
  • INTEGER to test if a value is an integer
  • IDENT and DIFFER to compare two values and return true (or false for DIFFER) if they have the same type and value
  • SIZE for string size, DUPL(n, x) to create a string of size n by repeating the value of X
  • TRIM to remove trailing blanks
  • EVAL(x) will evaluate the expression in the string x at run time; APPLY(f, a, b…) will take the string f and run its value as a function, passing in variables a, b etc.

User defined functions

It is possible to define functions, but the syntax is clumsy. First you need to define a function with DEFINE("name(args)locals", "entrypoint"). args and locals are a list of arguments and local variables used by the function. The label entrypoint sets the start of the function, if omitted it will use name as the entrypoint.

Next, define the function at the label given by name. The return value can be set by assigning to the function name, and the function is exited by goto-ing RETURN.

A simple example:

       DEFINE("CENTRE(S)L,P")

BEGIN  LINE = TRIM(INPUT)
       CENTRED = CENTRE(LINE)
       OUTPUT = CENTRED        :(END)

CENTRE L = (80 - SIZE(S)) / 2
       P = DUPL(".", L)
       CENTRE = P S P          :(RETURN)
END

This will input a string and then pad it with leading and trailing dots to make it display on the centre of a line.

Functions can be recursive in SNOBOL.

Further information

The language reference manual “The SNOBOL4 Programming Language”, linked at snobol4.org, has complete information on the language and was used to assemble this. Take a look at the example programs in the appendix for a taste of what can be done in SNOBOL.


SNOBOL - Introduction

In this series we’ll look at SNOBOL, a unique pattern matching language, and its implementation on MTS.

SNOBOL overview

SNOBOL (StriNg Oriented and symBOlic Language) was developed at Bell Labs in the 1960s to help with a symbolic manipulation project. It had powerful pattern matching and string manipulation features but had a simple syntax: it has no control flow instructions apart from goto and variables are dynamically typed and don’t need declarations. It started to spread to other sites and was taught at some universities in the 197-s. The original implementation was for the IBM 7090 but versions were ported to the IBM S/360 and DEC PDP/10. Its use started to die out in the 1980s but its creators went on to work on the ICON language and it influenced later text manipulation languages such as AWK and Perl.

SNOBOL on MTS

The main implementation that we will run here is the *SNOBOL4 interpreter. Also available on the D6.0 tapes is *SNOBOL4B which has an extension to the core language for printing blocks, two- and three-dimensional visualisations of data.

MTS originally had a number of other implementations of SNOBOL that are not available on the D6.0 tapes due to copyright reasons:

  • *SPITBOL - a fast SNOBOL 4 compiler from the Illinois Institute of Technology.
  • *SNOSTORM - a SNOBOL preprocessor written at UM to add structured programming features

(Note that SPITBOL/360 has now been GPL’d and should be eligible for inclusion in future distributions of MTS - see here.)

Prerequisites

No special installation instructions to get SNOBOL running - just do the standard D6.0 setup as described in this guide and then sign on as a regular user such as ST01.

Using *SNOBOL

*SNOBOL4 will read the source code for the program and then any input from unit 5 (by default *source* ie standard input). If you want to take the source code from a file prog.sn and then enter input from the keyboard you could do something like:

# $run *snobol4 5=prog.sn+*source*

Other parameters to *SNOBOL4 are listed in MTS Volume 9.

Hello world

Here’s a transcript of a session where we run a Hello world program. This assumes the source code is contained in the file hello.sn. Note that the code is not free format: only goto labels and comments (starting with *) are allowed in the first column.

# $list hello.sn

      1     * SNOBOL program to print Hello World
      2           I = 1
      3     LOOP  OUTPUT = "Hello, world!"
      4           I = I + 1
      5           LE(I, 5) : S(LOOP)
      6     END

# $run *snobol4 5=hello.sn

 SNOBOL4 (VERSION 3.10, APRIL 1, 1973)
 (MTS IMPLEMENTATION MAY 1, 1975)

         * SNOBOL program to print Hello World
 *1            I = 1
 *2      LOOP  OUTPUT = "Hello, world!"
 *3            I = I + 1
 *4            LE(I, 5) : S(LOOP)
 *5      END

    0 SYNTACTIC ERROR(S) IN SOURCE PROGRAM

 Hello, world!
 Hello, world!
 Hello, world!
 Hello, world!
 Hello, world!

 NORMAL TERMINATION AT LEVEL  0
 LAST STATEMENT EXECUTED WAS    4

SNOBOL4 STATISTICS SUMMARY

               5 MS. COMPILATION TIME
               2 MS. EXECUTION TIME
              16 STATEMENTS EXECUTED,       1 FAILED
               5 ARITHMETIC OPERATIONS PERFORMED
               0 PATTERN MATCHES PERFORMED
               0 REGENERATIONS OF DYNAMIC STORAGE
               0 READS PERFORMED
               5 WRITES PERFORMED
            0.13 MS. AVERAGE PER STATEMENT EXECUTED

Further information

MTS Volume 9 describes the SNOBOL compilers available on MTS and includes a basic tutorial on the language.

snobol4.org has lots of information about SNOBOL’s history, implementations and links to books including the main reference manual for the language, “The SNOBOL4 Programming Language”.


PL/I - Radix sort

For the final part of this series on the PL/I programming language as implemented on MTS, let’s implement radix sort from Rosetta Stone.

The problem

Radix sort is intended as a fast way to sort an array of integers by grouping them by digits that share the same value. Digits can be defined depending on the numeric base and the sort can take place from either the most significant digit or the least. Here we’ll sort by most significant digit and use base 2. so we are comparing by bits.

The algorithm we’ll use sorts in place. Starting at the largest bit it will group the numbers into one range where the bit is 1 and another where it is 0 by swapping values in the array. It will then recursively sort each range by the next smallest bit.

The solution

Here’s how the implementation looks; this was based on the C version at Rosetta Code.

RADIX_SORT: PROCEDURE(FROM, TO, BIT, ARR) RECURSIVE;
  DECLARE (FROM, TO) FIXED DECIMAL;
  DECLARE BIT FIXED BINARY(16);
  DECLARE ARR(*) FIXED BINARY(16);

  DECLARE (LEFT, RIGHT) FIXED DECIMAL;
  DECLARE TEMP FIXED BINARY(16);

  /* Exit early if bit down to zero or range is zero */
  IF (BIT = 0) | (TO <= FROM) THEN RETURN;

  LEFT = FROM;
  RIGHT = TO;

  LOOP:
  /* Find the leftmost with the bit set */
  DO WHILE ((LEFT < RIGHT) & ((ARR(LEFT) & BIT) = 0));
    LEFT = LEFT + 1;
  END;

  /* Find the rightmost with the bit not set */
  DO WHILE ((LEFT < RIGHT) & ((ARR(RIGHT) & BIT) ¬= 0));
    RIGHT = RIGHT - 1;
  END;

  /* If found one of each, swap and loop again to look for more */
  IF LEFT < RIGHT THEN BEGIN;
    TEMP = ARR(LEFT);
    ARR(LEFT) = ARR(RIGHT);
    ARR(RIGHT) = TEMP;
    GO TO LOOP;
  END;

  /* If nothing found, make the first sub search do the full range */
  IF ((BIT & ARR(LEFT)) = 0) & (LEFT <= TO) THEN
    LEFT = LEFT + 1;

  /* The two ranges bounded by LEFT are now sorted by BIT.
   * Sort each range by the next least significant bit */
  BIT = BIT / 2;
  CALL RADIX_SORT(FROM, LEFT-1, BIT, ARR);
  CALL RADIX_SORT(LEFT, TO, BIT, ARR);
END RADIX_SORT;

The definition of the function is marked as RECURSIVE, similar to other languages like ALGOL, as otherwise calling the function from itself will not work.

We pass in an array of fixed point numbers of up to 16 bits in length with the dimensions of the array bounded by FROM and TO. The initial call will set BIT to be 2^15. To handle an array of any size we can use * in the declaration DECLARE ARR(*) FIXED BINARY(16);.

Although PL/I has logical operators - and is &, or is |, not is ¬ - it does not seem to have the Boolean not operator like ! in C, so we have to write (ARR(LEFT) & BIT) = 0 rather than something like ! (ARR(LEFT) & BIT).

We’ll also need a procedure to print the array which will be called before and after the sort.

SHOW_ARR: PROCEDURE(LABEL, ARR);
  DECLARE LABEL CHARACTER(80) VARYING;
  DECLARE ARR(*) FIXED BINARY(16);
  PUT FILE (SCARDS) LIST(LABEL);
  PUT FILE (SCARDS) SKIP;
  PUT FILE (SCARDS) LIST(ARR);
  PUT FILE (SCARDS) SKIP;
END SHOW_ARR;

Finally, we set up the main function

TEST: PROCEDURE OPTIONS(MAIN);
  DECLARE RADIX_SORT ENTRY (FIXED DECIMAL, FIXED DECIMAL,
                            FIXED BINARY(16),);

  DECLARE NUMS(10) FIXED BINARY(16);
  NUMS(1) = 3;       NUMS(6) = 1630;
  NUMS(2) = 42;      NUMS(7) = 4;
  NUMS(3) = 910;     NUMS(8) = 2721;
  NUMS(4) = 3241;    NUMS(9) = 684;
  NUMS(5) = 99;      NUMS(10) = 11;

  CALL SHOW_ARR('Before', NUMS);
  CALL RADIX_SORT(1, 10, 1000000000000000B, NUMS);
  CALL SHOW_ARR('After', NUMS);

  /* Definition of RADIX_SORT and SHOW_ARR follow inside TEST here. */
END TEST;

Note that we need to declare the signature of RADIX_SORT before we use it.

We can now compile and run the program.

# $run *pl1f scards=radix.pl1 spunch=-load
# Execution begins   20:03:01 

  PROCEDURE TEST: SYNTAX CHECK COMPLETED. COMPILATION CONTINUES.


   NO ERRORS OR WARNINGS DETECTED.
# Execution terminated   20:03:02  T=0.128 

# $run -load+*pl1flib
# Execution begins   20:03:03 
  'Before' 
          3        42       910      3241        99      1630         4      2721
        684        11 
  'After' 
          3         4        11        42        99       684       910      1630
       2721      3241 
   
# Execution terminated   20:03:03  T=0.003 

Final thoughts about PL/I

To a C programmer, PL/I appears the most familiar of the languages we’ve seen so far, from the comments style and semicolon as line terminators to the storage classes and system level access, but with a more ALGOLy syntax. Writing the above I certainly found it expressive and easy to use, but it lacks even the minimal type checking found in C and I wasn’t quite sure what conversions were going on behind the scenes.

In its historical context, PL/I never really took off - it was tough to implement a compiler and too much identified with IBM to be adopted widely. It was important in the development of MULTICS and it nearly had a renaissance when it was made available for OS/2 in the 1990s, but ultimately it did not gain enough adoption from programmers to be truly successful.

For MTS at UM, according to the Introduction to programming and debugging in MTS, it was the third most popular language (8% by frequency of use) in 1984, but way behind FORTRAN and PASCAL. It would be interesting to know what types of users it had - teaching, business or scientific research? It does not seem to have been used by MTS system programmers much, going by the lack of components in PL/I on the D6.0 tapes.

Further information

Full source code for this program is on github.

Go back to the first article in the series on PL/I or see the other languages being looked at.


PL/I - Language features 2

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.


PL/I - Language features 1

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.


← Previous  Next →