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: Using the Pre-Built Docker Image

If you don’t want to build the image yourself, you can use the pre-built image from Docker Hub:

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

This uses the same codearchaeology/pli:latest image with the plicc compile-and-link wrapper.

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
17
# Download the compiler from iron-spring.com (check site for latest version)
wget --no-check-certificate http://www.iron-spring.com/pli-latest.tgz
tar -xzf pli-latest.tgz

# Install (requires root or sudo) - adjust directory names per version
sudo cp plic /usr/local/bin/plic
sudo chmod +x /usr/local/bin/plic
sudo mkdir -p /usr/local/lib/pli
sudo cp lib/*.a /usr/local/lib/pli/

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

# Compile and link (note: plic requires explicit compile and link steps)
plic -C -dELF hello.pli -o hello.o
ld -z muldefs -Bstatic --oformat=elf32-i386 -melf_i386 -e main hello.o /usr/local/lib/pli/libprf.a -o hello
./hello

Tested Platforms

Iron Spring PL/I is reported to work on:

  • Ubuntu 24.04 (Noble Numbat) - Officially tested
  • Ubuntu 22.04 (Jammy Jellyfish) - Reportedly works
  • Debian 12 - Reportedly works
  • Windows WSL2 - Supported since version 1.2.0 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 EDIT for Precise Control

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

PUT EDIT with format item (A) 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 STRING (String Variable Output)

DCL RESULT CHAR(50);
PUT STRING(RESULT) LIST('Hello');

PUT STRING writes to a string variable, not to stdout.

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 (standard) */

Note: Single quotes are the standard PL/I string delimiter. Some compilers also accept double quotes, but single quotes are the most portable choice.

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 /* */ block comments (a style that C later adopted):

/* 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 */

Mismatched Quotes

Always use matching quote characters:

PUT LIST('Hello, World!');   /* ✅ Correct */
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
# Compile to object file (ELF format for Linux)
plic -C -dELF hello.pli -o hello.o

# Compile to assembler output
plic -S hello.pli

# Compile and link to executable
plic -L hello.pli -o hello

# Set source margins (columns start,end)
plic -m1,72 hello.pli

# Specify include file directory
plic -i /usr/local/include/pli hello.pli

Note: The plicc wrapper script included in our Docker image handles the compile-and-link process automatically, which is the simplest way to build PL/I programs.

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 ELF object file
plic -C -dELF hello.pli -o hello.o

# Link to 32-bit executable using ld
ld -z muldefs -Bstatic --oformat=elf32-i386 -melf_i386 -e main hello.o /usr/local/lib/pli/libprf.a -o hello

# Or use the plicc wrapper to do everything at once
plicc hello.pli

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 - Output to a string variable

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 Ada, 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:

Comments

Loading comments...

Leave a Comment

2000 characters remaining