Table of Contents
- Overview
- Compiled, not emulated
- The translation pipeline
- What the compiler decides for you
- What is preserved
- Summary
- Related articles
Audience: developers and technical decision-makers who want to understand what the Heirloom PL/I compiler does to their source — how a PL/I program becomes Java, and why the result is compiled, not emulated. A good companion to "Understanding Your Migrated PL/I Application."
Overview
The Heirloom PL/I compiler is a source-to-source translator: it reads your PL/I source and writes standard Java source that preserves your program's behavior. That Java is then compiled to ordinary Java bytecode and runs on a normal Java Virtual Machine (JVM) — there is no PL/I interpreter and no mainframe emulator at run time. Your logic becomes Java.
This article walks through what the compiler does, stage by stage, so the output makes sense when you read it (see Reading Your Generated Java) and so you know which knobs affect the result (see Static vs Instance Strategy).
Note: Program, file, and variable names in the examples are illustrative. Your application keeps your names and your logic — the compiler translates structure and behavior, it does not rewrite your business rules.
Compiled, not emulated
The distinction matters because it shapes everything downstream:
- Emulation would run your PL/I unchanged inside a host process that imitates the mainframe. You would carry that emulator everywhere, and you would debug, profile, and operate the emulator — not your application.
- Compilation turns each PL/I program into a Java class. The result is ordinary Java: you read it, debug it with Java tools, profile it with Java profilers, and deploy it like any other Java application.
What keeps behavior identical is not an emulator but the Heirloom PL/I runtime library. PL/I semantics that have no direct Java equivalent — packed decimal arithmetic, PICTURE editing, fixed-length space-padded strings, pointer and BASED storage, ON-conditions — are provided by runtime types and helper classes (Builtin, PLIString, decimal types, Pointer, condition handling) that the generated Java calls into. The compiler's job is to emit the right calls to that library.
The translation pipeline
A PL/I source file moves through these stages:
PL/I source (.pli / .pl1)
│
▼
[Preprocessor] %INCLUDE expansion, macro processing → expanded PL/I
│
▼
[Lexer] characters → tokens (margins applied)
│
▼
[Parser] tokens → parse tree (grammar rules)
│
▼
[AST builder] parse tree → PL/I AST (statements, expressions, declarations)
│
▼
[Transpiler] PL/I AST → Java AST (one handler per statement type)
│
▼
[Code generator] Java AST → Java source (.java)
│
▼
Java source → (javac + Heirloom runtime) → bytecode → runs on the JVM
Each stage has a single, well-defined job.
1. Preprocessor — resolve includes and macros
Before translation, %INCLUDE directives are resolved and PL/I macros are expanded, producing a single, fully expanded source. In current builds this is a standalone preprocessor step (preprocessor-core) that you run before the compiler, so the compiler sees source with all copybooks already inlined.
Note: The compiler's old
--includeoption is deprecated and ignored. Resolve includes with the standalone preprocessor first.
2. Lexer — characters to tokens
The lexer turns the source text into a stream of tokens (keywords, identifiers, literals, operators). PL/I's fixed-format margins are applied here: by default the compiler reads columns 1–72 (--margins 1,72), so sequence numbers or other content past column 72 are not treated as code.
3. Parser — tokens to a parse tree
The parser matches the token stream against PL/I's grammar to build a parse tree. This is where genuine syntax problems surface. The most common parser error by far is a PL/I keyword used as a statement label — see PL/I Compiler Helpers for how those are diagnosed and resolved.
4. AST builder — a structured model of your program
The parse tree is converted into an abstract syntax tree (AST): a clean, typed model of your program as declarations, statements, and expressions. This is the representation the rest of the compiler reasons about — scopes, symbol resolution, LIKE expansion, and data types all live here.
5. Transpiler — PL/I AST to Java AST
This is the heart of the compiler. Each kind of PL/I statement has a dedicated handler that knows how to express that statement in Java. A DO loop becomes a Java for; a SELECT becomes a switch; a CALL becomes a method invocation; an ON block becomes a registered condition handler. The result is a Java AST — Java represented as a tree, not yet text. (See Reading Your Generated Java for the construct-by-construct mapping.)
6. Code generator — Java AST to Java source
Finally the Java AST is rendered to formatted .java files — typically one Java class per PL/I program. From here it is ordinary Java: it compiles with a standard Java compiler, links against the Heirloom runtime library, and packages and deploys like any other Java application.
What the compiler decides for you
A few translation choices are governed by compiler options rather than your source. The most important:
| Option | What it controls |
|---|---|
--strategy static\instance |
Whether a program becomes a static main-style class (batch) or an instantiable class managed by the application server (CICS/MQ online). See Static vs Instance Strategy. |
--cics |
Translate EXEC CICS commands (online transaction programs). |
--sql |
Translate embedded EXEC SQL to run over standard JDBC. |
--types |
Type-system mapping (primitive vs. manifold). |
--pkg |
Java package for the generated classes. |
--byaddr |
Pass arguments by reference (matching PL/I BYADDR). |
--comment / --copycomments
|
Carry source comments into the generated Java. |
These are normally set once for a project by the build, not per file. The point to remember: the same PL/I source can be compiled for batch or for an online container by changing the strategy, without touching your logic.
What is preserved
Because the runtime library backs the generated Java, the translation preserves:
-
Data representation —
FIXED DECIMALstays packed decimal,PICTUREediting is honored,CHARACTER(n)stays fixed-length and space-padded. Record layouts are byte-for-byte the same. - Arithmetic and precision — decimal arithmetic keeps PL/I precision and rounding rules.
-
Control flow —
DO,SELECT,IF/THEN/ELSE,GOTO,LEAVE,ITERATEbehave as written. -
Conditions —
ON ENDFILE,ON ERROR,ON ZERODIVIDE, and the rest are registered and raised as they were on the host. -
Your CICS / SQL / MQ surface —
EXEC CICS,EXEC SQL, and MQI calls are preserved and bridged to real resources by the runtime.
What changes is operational, not logical — see Understanding Your Migrated PL/I Application.
Summary
- The Heirloom PL/I compiler translates PL/I to standard Java source, which is then compiled to bytecode — your program is compiled, not emulated.
- The translation runs as a pipeline: preprocessor → lexer → parser → AST → transpiler → Java code generator, producing roughly one Java class per PL/I program.
- PL/I semantics with no direct Java equivalent are supplied by the Heirloom runtime library; the compiler emits calls into it.
- A few compiler options (notably
--strategy,--cics,--sql) shape the output, but your business logic and data layouts are preserved.
Related articles
- Reading Your Generated Java — how each PL/I construct appears in the output.
- Static vs Instance Strategy — choosing the compile mode for batch vs. online.
- Supported PL/I Statements — what the transpiler handles.
- Supported PL/I Built-in Functions — the built-ins it recognizes.
- Understanding Your Migrated PL/I Application — the runtime/operations view.
- Supported PL/I Data Types — the types that are preserved byte-for-byte.
0 Comments