Follow

How the Heirloom PL/I Compiler Translates Your Code

Table of Contents

  1. Overview
  2. Compiled, not emulated
  3. The translation pipeline
  4. What the compiler decides for you
  5. What is preserved
  6. Summary
  7. 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 --include option 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 representationFIXED DECIMAL stays packed decimal, PICTURE editing 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 flowDO, SELECT, IF/THEN/ELSE, GOTO, LEAVE, ITERATE behave as written.
  • ConditionsON ENDFILE, ON ERROR, ON ZERODIVIDE, and the rest are registered and raised as they were on the host.
  • Your CICS / SQL / MQ surfaceEXEC 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.

Was this article helpful?
0 out of 0 found this helpful
Have more questions? Submit a request

0 Comments

Article is closed for comments.
Powered by Zendesk