Beginner

Operators in PL/I

Learn arithmetic, comparison, logical, and string operators in PL/I with runnable Docker-ready examples using the Iron Spring compiler

Operators are the verbs of an expression - they take values and combine them into new ones. PL/I, true to its “one language for everything” ambition, ships a broad and consistent operator set: arithmetic for scientific work, rich comparison and logical operators for decision-making, and a dedicated concatenation operator for the string handling that COBOL programmers expected. As an imperative, statically-typed language, PL/I evaluates expressions left to right within well-defined precedence levels, and the strong type system means the compiler knows the type of every intermediate result.

One trait sets PL/I apart from most languages you may know: it uses ^ as the logical not symbol (the ASCII stand-in for the classic ¬ character), so “not equal” is written ^= rather than != or <>. PL/I also has no compound assignment operators like +=; assignment is always the single =, and you write X = X + 1 to accumulate a value. Because = serves double duty as both the assignment operator and the equality comparison, context determines which one applies.

This tutorial walks through the four operator families - arithmetic, comparison, logical, and string - with small programs you can compile and run. Each example is self-contained, so you can run them independently and compare the output against the Expected Output section below.

Arithmetic Operators

PL/I provides the five core arithmetic operators: +, -, *, /, and ** (exponentiation). There is no separate integer-division or modulo operator; instead PL/I supplies the DIVIDE and MOD built-in functions, which give you explicit control over the result precision and avoid PL/I’s notoriously surprising fixed-point division rules.

OperatorMeaningExampleResult
+Addition17 + 522
-Subtraction17 - 512
*Multiplication17 * 585
**Exponentiation17 ** 2289
DIVIDE(a,b,p,q)Division with chosen precisionDIVIDE(17,5,10,0)3
MOD(a,b)RemainderMOD(17,5)2

Create a file named operators_arithmetic.pli:

ARITHMETIC: PROCEDURE OPTIONS(MAIN);
   DCL (A, B) FIXED BINARY(31);

   A = 17;
   B = 5;

   PUT EDIT('A = ', A)                (A, F(2));
   PUT SKIP EDIT('B = ', B)           (A, F(1));
   PUT SKIP EDIT('A + B  = ', A + B)  (A, F(2));
   PUT SKIP EDIT('A - B  = ', A - B)  (A, F(2));
   PUT SKIP EDIT('A * B  = ', A * B)  (A, F(2));
   PUT SKIP EDIT('A ** 2 = ', A ** 2) (A, F(3));
   PUT SKIP EDIT('17 / 5  (DIVIDE) = ', DIVIDE(A, B, 10, 0)) (A, F(1));
   PUT SKIP EDIT('17 mod 5 (MOD)   = ', MOD(A, B)) (A, F(1));
END ARITHMETIC;

Here PUT EDIT pairs each value with an explicit format item: A writes a character string exactly as-is, and F(n) writes a fixed-point number right-justified in a field of width n. Using PUT EDIT instead of PUT LIST gives precise control over spacing, which is why the output lines up cleanly.

Comparison Operators

Comparison operators produce a bit value ('1'B for true, '0'B for false), which is exactly what an IF statement tests. PL/I offers the full set, and the inequality operators use the ^ not-symbol.

OperatorMeaning
=Equal to
^=Not equal to
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal

Create a file named operators_comparison.pli:

COMPARISON: PROCEDURE OPTIONS(MAIN);
   DCL (X, Y) FIXED BINARY(31);

   X = 10;
   Y = 20;

   PUT EDIT('X = 10, Y = 20')(A);

   IF X = Y THEN
      PUT SKIP EDIT('X = Y  : TRUE')(A);
   ELSE
      PUT SKIP EDIT('X = Y  : FALSE')(A);

   IF X ^= Y THEN
      PUT SKIP EDIT('X ^= Y : TRUE')(A);
   ELSE
      PUT SKIP EDIT('X ^= Y : FALSE')(A);

   IF X < Y THEN
      PUT SKIP EDIT('X < Y  : TRUE')(A);
   ELSE
      PUT SKIP EDIT('X < Y  : FALSE')(A);

   IF X > Y THEN
      PUT SKIP EDIT('X > Y  : TRUE')(A);
   ELSE
      PUT SKIP EDIT('X > Y  : FALSE')(A);

   IF X <= Y THEN
      PUT SKIP EDIT('X <= Y : TRUE')(A);
   ELSE
      PUT SKIP EDIT('X <= Y : FALSE')(A);

   IF X >= Y THEN
      PUT SKIP EDIT('X >= Y : TRUE')(A);
   ELSE
      PUT SKIP EDIT('X >= Y : FALSE')(A);
END COMPARISON;

Logical Operators

PL/I’s logical operators work on bit values: & (and), | (or), and the prefix ^ (not). They combine the bit results of comparisons to express compound conditions. Comparison operators bind more tightly than &, and & binds more tightly than |, so AGE >= 18 & HAS_ID is read as (AGE >= 18) & HAS_ID without needing parentheses.

Create a file named operators_logical.pli:

LOGICAL: PROCEDURE OPTIONS(MAIN);
   DCL AGE    FIXED BINARY(31);
   DCL HAS_ID BIT(1);

   AGE = 25;
   HAS_ID = '1'B;

   PUT EDIT('AGE = 25, HAS_ID = TRUE')(A);

   IF AGE >= 18 & HAS_ID THEN
      PUT SKIP EDIT('AGE >= 18 AND HAS_ID : ENTRY ALLOWED')(A);
   ELSE
      PUT SKIP EDIT('AGE >= 18 AND HAS_ID : ENTRY DENIED')(A);

   IF AGE < 13 | AGE > 65 THEN
      PUT SKIP EDIT('AGE < 13 OR AGE > 65 : DISCOUNT')(A);
   ELSE
      PUT SKIP EDIT('AGE < 13 OR AGE > 65 : NO DISCOUNT')(A);

   IF ^ HAS_ID THEN
      PUT SKIP EDIT('NOT HAS_ID : MISSING ID')(A);
   ELSE
      PUT SKIP EDIT('NOT HAS_ID : ID PRESENT')(A);
END LOGICAL;

A BIT(1) value like HAS_ID can be tested directly as a condition - no comparison needed - because it already holds a truth value.

String Concatenation and Precedence

The || operator concatenates character strings, joining them end to end. It pairs naturally with VARYING strings, whose length grows to fit the result. This example also demonstrates operator precedence: * binds tighter than +, so 2 + 3 * 4 evaluates the multiplication first, while parentheses force a different order.

Create a file named operators_strings.pli:

STRINGS: PROCEDURE OPTIONS(MAIN);
   DCL FIRST_NAME CHAR(20) VARYING;
   DCL LAST_NAME  CHAR(20) VARYING;
   DCL FULL_NAME  CHAR(40) VARYING;

   FIRST_NAME = 'Grace';
   LAST_NAME  = 'Hopper';
   FULL_NAME  = FIRST_NAME || ' ' || LAST_NAME;

   PUT EDIT('Full name: ', FULL_NAME)(A, A);
   PUT SKIP EDIT('Greeting:  ', 'Hello, ' || FIRST_NAME || '!')(A, A);

   PUT SKIP EDIT('2 + 3 * 4   = ', 2 + 3 * 4)   (A, F(2));
   PUT SKIP EDIT('(2 + 3) * 4 = ', (2 + 3) * 4) (A, F(2));
END STRINGS;

PL/I precedence, from highest to lowest, runs: prefix + - ^ and **; then * /; then infix + -; then ||; then the comparison operators; then &; and finally |. When in doubt, parentheses make intent explicit and cost nothing.

Running with Docker

Each program compiles and links with the plicc wrapper in the Iron Spring PL/I image, producing an executable named after the source file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the PL/I compiler image
docker pull codearchaeology/pli:latest

# Run the arithmetic example
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest sh -c 'plicc operators_arithmetic.pli && ./operators_arithmetic'

# Run the comparison example
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest sh -c 'plicc operators_comparison.pli && ./operators_comparison'

# Run the logical example
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest sh -c 'plicc operators_logical.pli && ./operators_logical'

# Run the string example
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest sh -c 'plicc operators_strings.pli && ./operators_strings'

The --security-opt seccomp=unconfined flag is required for the 32-bit Iron Spring compiler on Docker Desktop (macOS/Windows).

Expected Output

Running operators_arithmetic:

A = 17
B = 5
A + B  = 22
A - B  = 12
A * B  = 85
A ** 2 = 289
17 / 5  (DIVIDE) = 3
17 mod 5 (MOD)   = 2

Running operators_comparison:

X = 10, Y = 20
X = Y  : FALSE
X ^= Y : TRUE
X < Y  : TRUE
X > Y  : FALSE
X <= Y : TRUE
X >= Y : FALSE

Running operators_logical:

AGE = 25, HAS_ID = TRUE
AGE >= 18 AND HAS_ID : ENTRY ALLOWED
AGE < 13 OR AGE > 65 : NO DISCOUNT
NOT HAS_ID : ID PRESENT

Running operators_strings:

Full name: Grace Hopper
Greeting:  Hello, Grace!
2 + 3 * 4   = 14
(2 + 3) * 4 = 20

Key Concepts

  • Arithmetic uses + - * / **, but integer-style division and remainder come from the DIVIDE and MOD built-in functions, which sidestep PL/I’s tricky fixed-point division precision rules.
  • The ^ symbol is PL/I’s logical not - inequality is ^=, and ^X negates a bit value. This is the ASCII rendering of the historical ¬ operator.
  • Comparison operators yield bit values ('1'B/'0'B), making them natural to drop straight into an IF condition.
  • Logical operators & | ^ combine conditions; comparisons bind tighter than &, which binds tighter than |.
  • || concatenates strings, and VARYING strings grow to hold the joined result without manual length tracking.
  • There are no compound assignment operators like +=; PL/I assignment is always =, so accumulate with X = X + 1.
  • Precedence runs *** /+ -|| → comparisons → &|; reach for parentheses whenever the intended order isn’t obvious.
  • PUT EDIT with A and F(n) format items gives precise control over how strings and numbers are spaced in output, unlike the automatic spacing of PUT LIST.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/pli:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining