Beginner

Hello World in PL/I

Your first PL/I program - the classic Hello World example with Docker setup using Iron Spring PL/I compiler

Every programming journey starts with Hello World. Let’s write our first PL/I program using the Iron Spring PL/I compiler - a modern, free compiler that brings this classic mainframe language to contemporary systems.

The Code

Create a file named hello.pli:

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello, World!');
END HELLO;

Yes, that’s it! PL/I’s Hello World is remarkably concise for a language from the 1960s.

Understanding the Code

Let’s break down each element of this simple program:

  • HELLO: - A label naming the procedure (optional but conventional)
  • PROCEDURE OPTIONS(MAIN); - Declares this as the main entry point procedure
  • PUT LIST('Hello, World!'); - Outputs the text string to standard output
  • END HELLO; - Closes the procedure (label reference is optional but good practice)

The PROCEDURE Block

PL/I programs are structured as procedures:

name: PROCEDURE OPTIONS(MAIN);
   /* declarations and statements */
END name;

The OPTIONS(MAIN) tells the compiler this procedure is the program’s entry point - where execution begins.

PUT LIST Statement

PUT LIST is PL/I’s flexible output statement:

PUT LIST(expression1, expression2, ...);

It automatically:

  • Converts values to strings
  • Adds spacing between items
  • Handles different data types

The LIST option provides list-directed output - simple and human-readable.

Semicolons and Statements

PL/I uses semicolons to terminate statements:

PUT LIST('Hello');        /* Statement ends with ; */
X = 10;                   /* Another statement */

Multiple statements can appear on one line or span multiple lines - whitespace is flexible.

Running with Docker

The easiest way to run PL/I without installing a compiler locally uses our custom Docker image with the Iron Spring PL/I compiler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Create the source file
cat > hello.pli << 'EOF'
HELLO: PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello, World!');
END HELLO;
EOF

# Build the Docker image (first time only)
cd docker/pli
docker build -t codearchaeology/pli:latest .
cd ../..

# Compile and run (using plicc wrapper that handles compile+link)
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest sh -c 'plicc hello.pli && ./hello'

Understanding the Commands

Docker Build (one-time setup):

  • docker build - Creates a container image
  • -t codearchaeology/pli:latest - Tags the image
  • . - Build context (current directory with Dockerfile)

Compile and Run:

  • --security-opt seccomp=unconfined - Required for the 32-bit PL/I compiler on Docker Desktop
  • plicc hello.pli - Wrapper script that compiles and links PL/I programs
  • ./hello - Runs the compiled executable

The plicc wrapper script:

  1. Compiles the source code with plic -C -dELF
  2. Links with the PL/I runtime library using ld
  3. Produces an executable named hello (based on source filename)

Expected Output

Hello, World!

Simple and clean - exactly what we expect!

Alternative Dockerfile Approach

If you prefer to keep everything contained, create a simpler test Dockerfile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y wget ca-certificates

# Download and install Iron Spring PL/I compiler (version 1.4.0a)
RUN wget -q --no-check-certificate http://www.iron-spring.com/pli-1.4.0a.tgz && \
    tar -xzf pli-1.4.0a.tgz && \
    mv pli-1.4.0a/plic /usr/local/bin/plic && \
    chmod +x /usr/local/bin/plic && \
    mkdir -p /usr/local/lib/pli && \
    cp pli-1.4.0a/lib/*.a /usr/local/lib/pli/ 2>/dev/null || true && \
    rm -rf pli-1.4.0a.tgz pli-1.4.0a

ENV LD_LIBRARY_PATH=/usr/local/lib/pli:$LD_LIBRARY_PATH

WORKDIR /app
COPY hello.pli .

RUN plic hello.pli

CMD ["./hello"]

Build and run:

1
2
docker build -t pli-hello .
docker run --rm pli-hello

Running Locally (Linux)

If you have Linux and want to install Iron Spring PL/I directly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Download the compiler (version 1.4.0a)
wget --no-check-certificate http://www.iron-spring.com/pli-1.4.0a.tgz
tar -xzf pli-1.4.0a.tgz

# Install (requires root or sudo)
sudo mv pli-1.4.0a/plic /usr/local/bin/plic
sudo chmod +x /usr/local/bin/plic
sudo mkdir -p /usr/local/lib/pli
sudo cp pli-1.4.0a/lib/*.a /usr/local/lib/pli/

# Set up library path
export LD_LIBRARY_PATH=/usr/local/lib/pli:$LD_LIBRARY_PATH

# Compile and run
plic hello.pli
./hello

Tested Platforms

Iron Spring PL/I is tested on:

  • Ubuntu 24.04 (Noble Numbat) - Officially tested
  • Ubuntu 22.04 (Jammy Jellyfish) - Works
  • Debian 12 - Works
  • Fedora 39+ - Works
  • Windows WSL2 - Works with Ubuntu distribution

Program Structure Variations

PL/I is flexible - here are valid alternatives:

Without Label

PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello, World!');
END;

The label is optional (though good practice for larger programs).

With DCL for Declarations

HELLO: PROCEDURE OPTIONS(MAIN);
   DCL MESSAGE CHAR(20);
   MESSAGE = 'Hello, World!';
   PUT LIST(MESSAGE);
END HELLO;

DCL (or DECLARE) introduces variable declarations.

Using PUT SKIP

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT SKIP LIST('Hello, World!');
END HELLO;

PUT SKIP adds a newline before output (useful for formatting).

Multiple Statements

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello,');
   PUT LIST(' World!');
END HELLO;

Output: Hello, World! (LIST automatically spaces items)

PUT STRING for Precise Control

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT STRING('Hello, World!');
END HELLO;

PUT STRING writes exactly the characters specified without extra spacing.

Understanding PUT Variants

PL/I offers several output methods:

PUT LIST (List-Directed)

PUT LIST('Text', 42, 3.14);

Automatic formatting, spacing between items.

PUT EDIT (Edit-Directed)

PUT EDIT('Hello, World!')(A);

Format control - (A) means character format.

PUT DATA

DCL NAME CHAR(20);
NAME = 'Ada Lovelace';
PUT DATA(NAME);

Outputs: NAME='Ada Lovelace'; - variable name and value.

PUT SKIP

PUT SKIP LIST('First line');
PUT SKIP LIST('Second line');

SKIP adds newlines for formatting.

Character Strings in PL/I

PL/I handles strings flexibly:

String Literals

'Hello, World!'      /* Single quotes */
"Hello, World!"      /* Double quotes also work */

String Variables

DCL MESSAGE CHAR(50);
MESSAGE = 'Hello, World!';

CHAR(50) - Fixed-length character string (50 characters).

Varying Strings

DCL MESSAGE CHAR(50) VARYING;
MESSAGE = 'Hello, World!';  /* Only uses 13 characters */

VARYING - Variable-length string (more memory-efficient).

Comments in PL/I

PL/I uses C-style block comments:

/* This is a comment */

/*
   This is a
   multi-line comment
*/

HELLO: PROCEDURE OPTIONS(MAIN);
   /* Main procedure starts here */
   PUT LIST('Hello, World!');  /* Output greeting */
END HELLO;  /* End of program */

Case Sensitivity

PL/I is case-insensitive (unlike C):

PROCEDURE  /* Same as */
procedure  /* Same as */
Procedure

Convention: Use UPPERCASE for keywords, though mixed case works fine.

File Extensions

PL/I programs use various extensions:

  • .pli - Most common (Iron Spring, modern)
  • .pl1 - Traditional mainframe
  • .pli or .plx - Various compilers

Iron Spring PL/I accepts .pli, .pl1, and .plx.

Common Beginner Mistakes

Missing Semicolons

PUT LIST('Hello')   /* ❌ Missing semicolon */
PUT LIST('Hello');  /* ✅ Correct */

Forgetting OPTIONS(MAIN)

PROCEDURE;               /* ❌ Not the main procedure */
PROCEDURE OPTIONS(MAIN); /* ✅ Correct for entry point */

Mismatched Labels

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello, World!');
END GOODBYE;  /* ❌ Label doesn't match */

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello, World!');
END HELLO;    /* ✅ Correct */

Wrong Quote Types in Some Contexts

While PL/I accepts both ' and ", be consistent:

PUT LIST('Hello, World!');   /* ✅ Consistent */
PUT LIST("Hello, World!");   /* ✅ Also works */
PUT LIST('Hello, World!");   /* ❌ Mismatched quotes */

Compiler Options

Iron Spring PL/I compiler (plic) supports various options:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Basic compilation
plic hello.pli

# Specify output filename
plic hello.pli -o myprogram

# Enable all warnings
plic -Wall hello.pli

# Optimize for speed
plic -O2 hello.pli

# Generate debugging information
plic -g hello.pli

# Verbose output
plic -v hello.pli

# Show help
plic --help

Listing Files

Generate compilation listing:

1
plic -Fc hello.pli

Creates hello.lst with compilation details.

Preprocessor Output

See preprocessed source:

1
plic -E hello.pli

PL/I Preprocessor

PL/I includes a powerful preprocessor:

%DECLARE DEBUG BIT;
%DEBUG = '1'B;

HELLO: PROCEDURE OPTIONS(MAIN);
   %IF DEBUG %THEN
      PUT LIST('Debug: Starting program');
   %ENDIF;

   PUT LIST('Hello, World!');

   %IF DEBUG %THEN
      PUT LIST('Debug: Ending program');
   %ENDIF;
END HELLO;

The % prefix indicates preprocessor statements - executed at compile time.

Advanced Hello World Variations

With Declarations Section

HELLO: PROCEDURE OPTIONS(MAIN);

   DECLARE
      MESSAGE CHARACTER(20) VARYING,
      I FIXED BINARY;

   MESSAGE = 'Hello, World!';
   PUT LIST(MESSAGE);

END HELLO;

Structured declaration section for larger programs.

With BEGIN Block

HELLO: PROCEDURE OPTIONS(MAIN);
   BEGIN;
      DECLARE MESSAGE CHAR(20);
      MESSAGE = 'Hello, World!';
      PUT LIST(MESSAGE);
   END;
END HELLO;

BEGIN/END creates nested scope blocks.

With Error Handling

HELLO: PROCEDURE OPTIONS(MAIN);

   ON ERROR SYSTEM;  /* Use system error handler */

   PUT LIST('Hello, World!');

END HELLO;

PL/I’s exception handling - even in Hello World!

With Multiple Outputs

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello,');
   PUT LIST(' World!');
   PUT SKIP;
   PUT LIST('Welcome to PL/I programming.');
END HELLO;

Demonstrates multiple output statements and SKIP for newlines.

Compilation Process

Understanding what happens when you compile:

hello.pli → hello.o (Object file)
          → hello (Executable)

Separate Compilation Steps

You can compile in stages:

1
2
3
4
5
6
7
8
# Compile to object file
plic -c hello.pli

# Link to executable
plic hello.o -o hello

# Or do everything at once
plic hello.pli -o hello

PL/I’s OUTPUT Philosophy

PL/I separates data from formatting:

  1. PUT LIST - Automatic formatting
  2. PUT EDIT - Controlled formatting
  3. PUT DATA - Debug output with variable names
  4. PUT STRING - Exact output

This flexibility was revolutionary in 1964.

Why PL/I’s Hello World Matters

Even this simple program shows PL/I’s character:

  1. Structured - PROCEDURE blocks enforce organization
  2. Labeled - Optional labels improve readability
  3. Flexible - Multiple ways to achieve the same result
  4. Powerful - Built-in I/O, no #include needed
  5. Self-Documenting - OPTIONS(MAIN) makes intent clear

These design choices influenced later languages (Ada, C++, Java).

Historical Context

In 1964, when PL/I was designed:

  • FORTRAN - Used FORMAT statements (complex)
  • COBOL - Used DISPLAY (verbose but readable)
  • Assembly - Used OS system calls (low-level)

PL/I’s PUT LIST was simpler than FORTRAN, more flexible than COBOL, and higher-level than assembly.

Comparing to C

C’s Hello World (1972):

1
2
3
4
5
6
#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}

PL/I’s version (1964):

HELLO: PROCEDURE OPTIONS(MAIN);
   PUT LIST('Hello, World!');
END HELLO;

PL/I is arguably cleaner - no includes, no explicit return.

The Mainframe Connection

On IBM mainframes, Hello World looks similar:

HELLO: PROC OPTIONS(MAIN);
   PUT SKIP LIST('Hello, World!');
END HELLO;

Iron Spring PL/I maintains high compatibility with IBM mainframe PL/I.

Next Steps

Now that you’ve written your first PL/I program, explore:

  • Variables and Data Types - PL/I’s rich type system
  • Control Structures - IF, DO loops, SELECT
  • Procedures and Functions - Modular programming
  • File I/O - Working with datasets
  • Structures - Complex data organization

Key Takeaways

  1. PL/I programs are procedures marked with OPTIONS(MAIN)
  2. PUT LIST provides simple output without format complexity
  3. Semicolons terminate statements (like C, unlike FORTRAN)
  4. Labels are optional but improve code clarity
  5. PL/I is case-insensitive (unlike C)
  6. Iron Spring PL/I brings PL/I to modern platforms for free
  7. Docker makes PL/I accessible without complex installation

Troubleshooting

“plic: command not found”

You need to install Iron Spring PL/I or use Docker:

1
2
# Use Docker (recommended)
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest plicc hello.pli

“SIGACTION returned -1” error

This occurs when running the 32-bit PL/I compiler on Docker Desktop (macOS/Windows). Add the security option:

1
docker run --rm --security-opt seccomp=unconfined -v $(pwd):/app -w /app codearchaeology/pli:latest plicc hello.pli

“Cannot find library”

Set the library path:

1
export LD_LIBRARY_PATH=/usr/local/lib/pli:$LD_LIBRARY_PATH

Or use Docker which handles this automatically.

Compilation Errors

Check for:

  • Missing semicolons
  • Mismatched quotes
  • Mismatched procedure labels
  • OPTIONS(MAIN) on entry procedure

“Permission denied” when running

Make executable:

1
2
chmod +x hello
./hello

Resources

  • Iron Spring PL/I - www.iron-spring.com (compiler download, docs)
  • IBM PL/I Documentation - IBM Knowledge Center (comprehensive reference)
  • comp.lang.pl1 - Usenet newsgroup (active community)
  • Multics PL/I - multicians.org/pl1.html (historical perspective)

The Big Picture

This simple Hello World connects you to:

  • 1960s IBM mainframe computing
  • The Multics operating system (Unix’s predecessor)
  • Decades of mission-critical systems still running today
  • Language design history that influenced C, C++, and Java

You’re not just printing a string - you’re experiencing computing history.

Welcome to PL/I programming!

Running Today

All examples can be run using Docker:

docker pull codearchaeology/pli:latest
Last updated: