Table of Contents
- Overview
- Programs, procedures, and CALL
- Assignment
- Structures and arrays
- Control flow
- Built-in functions
- Pointers and BASED storage
- ON-conditions
- Reading tips
- Summary
- Related articles
Audience: developers who need to read, review, or debug the Java that the Heirloom PL/I compiler produces. This is a field guide to the idioms — how familiar PL/I constructs look once translated.
Overview
After migration, each PL/I program is a Java class. The translation is deliberately literal and traceable: the generated Java follows your source statement by statement so you can map output back to input. This article shows the common patterns so the code reads like something you wrote, not something foreign.
Two things explain almost everything you will see:
-
PL/I data is not raw Java data. A
CHARACTERfield is not a JavaString, and aFIXED DECIMALis not a Javaint. They are runtime types that carry PL/I semantics (fixed length, packed decimal, precision). You assign to them with methods like.assign(...)or.set(...), and read them with methods like.intValue()or.bigDecimalValue(). -
PL/I built-ins are static calls.
SUBSTR,VERIFY,HBOUND,ADDR,ROUND, and the rest becomeBuiltin.SUBSTR(...),Builtin.VERIFY(...), etc.
Every generated class imports the runtime: import com.heirloomcomputing.epli.runtime.*;.
Note: The snippets below use illustrative names. Your variable, structure, and program names are preserved from your source (lower-cased per Java convention).
Programs, procedures, and CALL
A PL/I program becomes a Java class; its procedures become methods. A CALL becomes a method invocation — typically invoke(...) on the target program.
CALL PROJ072(CBCWPRO);
CALL VCXA;
cwpro.proj072.invoke(cbcwpro);
cwpro.vcxa.invoke();
Arguments that PL/I passes by reference are passed as the address of the variable, so you will often see Builtin.ADDR(...) wrapping a call argument:
P9npfco.invoke(Builtin.ADDR(mq_con_parms), ...);
Assignment
A PL/I assignment is not a Java =. It is a method call on the target, because the target is a runtime-typed field that knows how to store a value with the right length, padding, or precision:
TCPIPS = '';
NEW_PTR = ORDER2;
tcpips.assign("");
new_ptr.assign(order2);
Numeric fields commonly use .set(...), and decimal arithmetic flows through helpers that preserve precision (Extras.asBigDecimal, .bigDecimalValue()):
seg1d.set("0000");
le_x = Extras.asBigDecimal(life_expect.le_int.bigDecimalValue()
+ life_expect.le_dec.bigDecimalValue() / Extras.asBigDecimal(10));
Structures and arrays
Structure members are reached with dotted access, mirroring the PL/I qualification. Array subscripts become Java [...] index expressions, with the subscript read as an integer:
IF MSXX.UCHK ^= ADTOP.DUMENT THEN ...
... ADSPO.P8CB09X(SEXP) ...
if (msxx.uchk != adtop.dument) { ... }
adspo.p8cb09x[sexp.intValue()]
Control flow
DO loops → for
A counted DO becomes a Java for. With a plain integer counter it reads naturally:
DO EL = 1 TO NEC;
...
END;
for (el = 1; el <= nec; el++) {
...
}
When the loop counter is a runtime-typed variable, the same shape uses its accessor methods instead of ++:
for (c1.set(1); c1.intValue() <= 12; c1.set(c1.intValue() + 1)) {
...
}
DO WHILE / DO UNTIL become while loops; LEAVE and ITERATE become break and continue.
SELECT → switch
A SELECT group becomes a Java switch, with each WHEN a case and OTHERWISE the default. Cases work for both numeric and string selectors:
switch (...) {
case "A1": ...; break;
case "A2": ...; break;
case "": ...; break; // OTHERWISE / empty
}
IF and boolean conditions
IF/THEN/ELSE becomes a Java if/else. The interesting part is how PL/I's loose notion of "truth" is made explicit, because Java requires a real boolean. The compiler inserts the correct coercion based on the operand's type:
| PL/I expression in a test | Generated Java condition |
|---|---|
Integer-returning built-in (VERIFY, INDEX, …) |
Builtin.VERIFY(h_kez, "X ") != 0 |
| Character string |
(...).isEmpty() (empty = false) |
| Bit value | (...).booleanValue() |
| Entry / procedure reference | (...) != null |
IF VERIFY(H_KEZ, 'X ') ^= 0 THEN ...
if (Builtin.VERIFY(h_kez, "X ") != 0) { ... }
GOTO
GOTO is preserved. Labels in your source become reachable jump targets in the generated Java so that label-driven flow continues to work as written.
Built-in functions
PL/I built-ins map to static methods on Builtin (and a few helpers on Extras). The names are preserved, so they are easy to recognize:
N = HBOUND(NPVMSDT2.SDATA_2.SOWUTAB, 1);
CALL_STRING = STRING(SAXX.SAXT(1), SAVE.SAVE_SAXT1);
ERGEND = ROUND(ERGZWI / 3600, 2);
Builtin.HBOUND(npvmsdt2.sdata_2.sowutab, 1)
Builtin.STRING(saxx.saxt[1], save.save_saxt1)
ergend = Builtin.ROUND(ergzwi / Extras.asBigDecimal(3600), 2)
See Supported PL/I Built-in Functions for the full catalog.
Pointers and BASED storage
POINTER variables become runtime Pointer objects, and ADDR becomes Builtin.ADDR(...). A BASED variable — one that overlays storage addressed by a pointer — is rendered as a runtime overlay you cast to the based type:
ALLOCATE GRPBASED1 SET(P); /* later: */ GRPBASED1.SEG1.SEG1D = '0000';
((Grpbased1) grpbased1.based(Builtin.ADDR(buf0))).seg1.seg1a.seg1d.set("0000");
This is how PL/I's storage overlays survive on the JVM without raw memory — the runtime manages the backing storage. (See How the PL/I Runtime Manages Memory.)
ON-conditions
An ON block registers a condition handler with the runtime; when the condition is raised, the handler runs. You will see registration calls rather than inline try/catch, because PL/I conditions are dynamic and scoped:
ON ENDFILE(INFILE) ...;
setConditionHandler(condition, MyProgram.class, "handler", snap);
The exact shape (and whether an instance reference is passed) depends on the compile strategy — see Static vs Instance Strategy.
Reading tips
-
Lower-cased names.
MSXX.UCHK→msxx.uchk. Your names are preserved, just re-cased. -
A method where you expected an operator.
.assign(),.set(),.intValue(),.bigDecimalValue()— these are the runtime types doing PL/I work that a bare Java operator could not. -
Builtin./Extras.prefixes mark PL/I built-ins and conversions. - Source line mapping lets debuggers and stack traces point back to your original PL/I lines — turn it on so the Java you are reading is anchored to the PL/I it came from.
Summary
- One PL/I program → one Java class; procedures → methods;
CALL→invoke(...). - Assignments are method calls (
.assign/.set) on runtime-typed fields, not Java=. -
DO→for/while,SELECT→switch,IF→ifwith an explicit boolean coercion chosen by operand type. - Built-ins →
Builtin.NAME(...); pointers/BASED→Pointer+.based(...);ON→ registered condition handlers. - The translation is literal and traceable, so the Java maps cleanly back to your PL/I.
Related articles
- How the Heirloom PL/I Compiler Translates Your Code — the pipeline behind these outputs.
- Static vs Instance Strategy — how the compile mode changes class shape and condition handling.
- Supported PL/I Statements and Supported PL/I Built-in Functions.
-
How the Heirloom PL/I Runtime Manages Memory — pointers,
BASED, and storage on the JVM. - Supported PL/I Data Types — the runtime types behind the field accessors.
0 Comments