Follow

Reading Your Generated Java

Table of Contents

  1. Overview
  2. Programs, procedures, and CALL
  3. Assignment
  4. Structures and arrays
  5. Control flow
  6. Built-in functions
  7. Pointers and BASED storage
  8. ON-conditions
  9. Reading tips
  10. Summary
  11. 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:

  1. PL/I data is not raw Java data. A CHARACTER field is not a Java String, and a FIXED DECIMAL is not a Java int. 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().
  2. PL/I built-ins are static calls. SUBSTR, VERIFY, HBOUND, ADDR, ROUND, and the rest become Builtin.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.UCHKmsxx.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; CALLinvoke(...).
  • Assignments are method calls (.assign / .set) on runtime-typed fields, not Java =.
  • DOfor/while, SELECTswitch, IFif with an explicit boolean coercion chosen by operand type.
  • Built-ins → Builtin.NAME(...); pointers/BASEDPointer + .based(...); ON → registered condition handlers.
  • The translation is literal and traceable, so the Java maps cleanly back to your PL/I.

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