Part 2 — PL/I Compiler Reference¶
2.1 Compiler Overview¶
The Heirloom PL/I Compiler (pli_compiler2) is a source-to-source transpiler that converts PL/I programs into semantically equivalent Java code. It is built on ANTLR 4 for parsing and JavaParser for Java code generation.
2.1.1 Compilation Pipeline¶
PL/I Source
│
▼
┌──────────────┐
│ Preprocessor │ %INCLUDE, macro expansion, conditional compilation
└──────┬───────┘
▼
┌──────────────┐
│ Lexer │ Token recognition, margin handling (columns 1–72)
└──────┬───────┘
▼
┌──────────────┐
│ Parser │ ANTLR-based recursive descent, 10 ANTLR grammar files
└──────┬───────┘
▼
┌──────────────┐
│ Semantic │ Symbol resolution, type checking, scope management
│ Analysis │
└──────┬───────┘
▼
┌──────────────┐
│ Transpiler │ Java AST generation via JavaParser
└──────┬───────┘
▼
Java Source Files
2.1.2 Invoking the Compiler¶
Command-line usage and batch compilation instructions.
2.1.3 Compiler Return Codes¶
- 0 — Successful compilation
- 4 — Warning(s) issued
- 8 — Error(s) encountered
- 12 — Severe error(s)
2.2 Compiler Options¶
2.2.1 Source Options¶
| Option | Default | Description |
|---|---|---|
--source | Required | Source PL/I file or directory |
--margins | 1,72 | Source margins (start,end columns) |
--include | — | Include file search path |
--comment | false | Generate inline code comments |
--copycomments | false | Copy PL/I source comments into JavaDoc |
2.2.2 Target Options¶
| Option | Default | Description |
|---|---|---|
--target | — | Output directory for generated Java files |
--package | com.pli | Java package for generated classes |
--strategy | static | Code generation strategy (static / instance) |
--types | primitive | Type system (primitive / manifold) |
2.2.3 Processing Options¶
| Option | Default | Description |
|---|---|---|
--sql | false | Enable EXEC SQL statement processing |
--cics | false | Enable EXEC CICS statement processing |
--exci | false | Enable EXCI (External CICS Interface) processing |
--dateformat | EUR | SQL date format (EUR/ISO/USA/JIS/LOCAL) |
--entry | false | Process ENTRY data types |
--byaddr | false | Pass arguments by reference (by address) |
2.2.4 Advanced Options¶
| Option | Default | Description |
|---|---|---|
--helper | — | Helper JSON configuration file path |
--loglevel | INFO | Logging verbosity (ERROR/WARN/INFO/DEBUG) |
--ast | false | Print AST in JSON format (diagnostic) |
--lex | false | Print lexical tokens (diagnostic) |
--ext | false | Generate external reference report |
2.2.5 Code Generation Strategies¶
Static Strategy (--strategy static)¶
- Class-level variables declared as
private static main()method entry point- Suitable for batch programs and standalone execution
Instance Strategy (--strategy instance)¶
- Class-level variables as instance fields
- Constructor initialization
- Suitable for CICS transactions, MQ listeners, and application server deployment
2.3 Preprocessor¶
2.3.1 Preprocessor Overview¶
The PL/I Preprocessor (pli-preprocessor module) is a standalone component that processes PL/I source files before the compiler parses them. It runs as the first phase of the compilation pipeline and handles:
- %INCLUDE directive resolution — Inserts external copybook files
- Macro expansion — Processes macro definitions and invocations
- %REPLACE directives — Text substitution
- Conditional compilation — %IF/%THEN/%ELSE directives
- Source formatting — Column margin handling (1-72 by default)
- Comment preservation — Maintains comments for debugging
Pipeline Position:
PL/I Source (.pli)
│
▼
┌──────────────────┐
│ Preprocessor │ ← First phase
│ │ • %INCLUDE resolution
│ │ • Macro expansion
│ │ • %REPLACE substitution
│ │ • Conditional compilation
└────────┬─────────┘
▼
Expanded PL/I Source
│
▼
Compiler (Parser/Transpiler)
The preprocessor ensures that the compiler receives fully expanded source code with all includes resolved and macros expanded.
2.3.2 %INCLUDE Directive¶
The %INCLUDE directive inserts external files (copybooks) into the source stream.
Syntax¶
Search Path Resolution¶
The preprocessor searches for include files in this order:
- Current directory (same directory as source file)
- Configured include paths (
--includeoption) - Environment variable (
PLI_INCLUDE_PATH)
Example:
java -jar pli_compiler2.jar \
--source MYPROG.pli \
--include /copybooks/common \
--include /copybooks/db
%INCLUDE Example¶
Source file (CUSTOMER.pli):
CUSTOMER: PROCEDURE OPTIONS(MAIN);
%INCLUDE CUSTDEF; /* Include customer structure definition */
DCL CUST CUSTOMER_REC;
/* Use customer structure */
CUST.CUST_ID = 'C001';
CUST.NAME = 'John Smith';
END CUSTOMER;
Include file (CUSTDEF.pli):
/* Customer record definition */
DCL 1 CUSTOMER_REC,
2 CUST_ID CHAR(10),
2 NAME CHAR(30),
2 BALANCE DEC FIXED(15,2);
After preprocessing:
CUSTOMER: PROCEDURE OPTIONS(MAIN);
/* Customer record definition */
DCL 1 CUSTOMER_REC,
2 CUST_ID CHAR(10),
2 NAME CHAR(30),
2 BALANCE DEC FIXED(15,2);
DCL CUST CUSTOMER_REC;
/* Use customer structure */
CUST.CUST_ID = 'C001';
CUST.NAME = 'John Smith';
END CUSTOMER;
Nested %INCLUDE¶
Include files can themselves contain %INCLUDE directives:
Main source:
COMMON.pli:
The preprocessor recursively resolves all includes up to a configurable depth limit (default: 10 levels).
Circular Include Detection¶
The preprocessor detects circular includes and reports an error:
ERROR: Circular include detected: FILEA.pli → FILEB.pli → FILEA.pli
2.3.3 %REPLACE Directive¶
The %REPLACE directive performs text substitution throughout the source.
Syntax¶
%REPLACE Example¶
Source:
%REPLACE MAX_CUSTOMERS BY 1000;
%REPLACE STATUS_ACTIVE BY 'A';
%REPLACE STATUS_INACTIVE BY 'I';
SAMPLE: PROCEDURE OPTIONS(MAIN);
DCL CUSTOMERS(MAX_CUSTOMERS) CHAR(10);
DCL STATUS CHAR(1);
STATUS = STATUS_ACTIVE;
IF STATUS = STATUS_INACTIVE THEN
PUT SKIP LIST('Inactive');
END SAMPLE;
After preprocessing:
SAMPLE: PROCEDURE OPTIONS(MAIN);
DCL CUSTOMERS(1000) CHAR(10);
DCL STATUS CHAR(1);
STATUS = 'A';
IF STATUS = 'I' THEN
PUT SKIP LIST('Inactive');
END SAMPLE;
Scope¶
%REPLACE directives have global scope within the compilation unit. They affect all subsequent source text, including included files processed after the directive.
2.3.4 Macro Processing¶
The preprocessor supports macro definitions with parameters and macro invocations.
Macro Definition Syntax¶
Simple Macro Example¶
Definition:
Invocation:
After expansion:
Macro with Multiple Statements¶
Definition:
%LOG_ERROR: MACRO (MSG, CODE)
PUT SKIP LIST('ERROR:', MSG);
PUT SKIP LIST('CODE:', CODE);
ERROR_COUNT = ERROR_COUNT + 1;
%MEND;
Invocation:
After expansion:
PUT SKIP LIST('ERROR:', 'Invalid customer ID');
PUT SKIP LIST('CODE:', 100);
ERROR_COUNT = ERROR_COUNT + 1;
Built-in Macro Variables¶
The preprocessor provides built-in variables:
| Variable | Description | Example Value |
|---|---|---|
&SYSDATE | Current date (YYYYMMDD) | 20260302 |
&SYSTIME | Current time (HHMMSS) | 143022 |
&SYSPARM | System parameter from command line | User-defined |
Example:
2.3.5 Conditional Compilation¶
The preprocessor supports conditional compilation using %IF, %THEN, %ELSE, and %END.
Syntax¶
Example: Debug Mode¶
Source:
%REPLACE DEBUG_MODE BY 1;
PROGRAM: PROCEDURE OPTIONS(MAIN);
%IF DEBUG_MODE = 1 %THEN
PUT SKIP LIST('DEBUG: Starting program');
%END;
/* Main logic */
DCL RESULT FIXED BIN(31);
RESULT = 42;
%IF DEBUG_MODE = 1 %THEN
PUT SKIP LIST('DEBUG: Result =', RESULT);
%END;
END PROGRAM;
After preprocessing (with DEBUG_MODE = 1):
PROGRAM: PROCEDURE OPTIONS(MAIN);
PUT SKIP LIST('DEBUG: Starting program');
/* Main logic */
DCL RESULT FIXED BIN(31);
RESULT = 42;
PUT SKIP LIST('DEBUG: Result =', RESULT);
END PROGRAM;
After preprocessing (with DEBUG_MODE = 0):
PROGRAM: PROCEDURE OPTIONS(MAIN);
/* Main logic */
DCL RESULT FIXED BIN(31);
RESULT = 42;
END PROGRAM;
Conditional Compilation by Environment¶
Source:
%IF &SYSPARM = 'PROD' %THEN
%REPLACE LOG_LEVEL BY 'ERROR';
%ELSE
%REPLACE LOG_LEVEL BY 'DEBUG';
%END;
Command line:
2.3.6 Source Margins and Column Handling¶
The preprocessor handles fixed-form source format where code must appear within specific columns.
Default Margins¶
| Columns | Purpose |
|---|---|
| 1-2 | Sequence number area (ignored) |
| 2-72 | Statement area (compiled) |
| 73-80 | Identification area (ignored) |
IBM Mainframe Card Image Format (traditional PL/I source):
1 2 3 4 5 6 7 8
|---------|---------|---------|---------|---------|---------|---------|
SS<--- Statement area (columns 2-72) ------------------------->IIIIIIII
|| ||||||||
Sequence Identification
Configuring Margins¶
Use the --margins option to customize:
# Default (columns 2-72)
java -jar pli_compiler2.jar --source PROG.pli
# Custom (columns 1-80)
java -jar pli_compiler2.jar --source PROG.pli --margins 1,80
# Modern (no margin restrictions)
java -jar pli_compiler2.jar --source PROG.pli --margins 1,500
Example with Margins¶
Source file (72-column format):
CUSTOMER: PROCEDURE OPTIONS(MAIN); 00001000
DCL NAME CHAR(30); 00002000
NAME = 'John Smith'; 00003000
END CUSTOMER; 00004000
Columns 1-2 (sequence) and 73-80 (identification) are ignored by the preprocessor.
2.3.7 Comment Handling¶
The preprocessor preserves comments for debugging but can also strip them for production builds.
PL/I Comment Syntax¶
Comment Preservation Options¶
| Option | Effect |
|---|---|
--comment | Generate inline comments in Java code |
--copycomments | Copy PL/I comments to Java as JavaDoc |
| (default) | Strip comments from generated Java |
Example with --copycomments:
PL/I:
Generated Java:
2.3.8 Line Number Tracking¶
The preprocessor maintains line number mapping between original source and expanded source for accurate error reporting.
Error message format:
E:001 CUSTOMER.pli(45,10): Undefined variable 'CUSTOMER_ID'
^ ^
| |
Original file Original line number
Even after %INCLUDE expansion and macro processing, error messages reference the original source location.
2.3.9 Preprocessor Output¶
The preprocessor can optionally output the expanded source for inspection.
Viewing Preprocessed Source¶
Command:
Output: Expanded source written to stdout or file, showing: - All %INCLUDE files inserted - All macros expanded - All %REPLACE substitutions applied - Original line number comments preserved
Example output:
/* Original: CUSTOMER.pli line 1 */
CUSTOMER: PROCEDURE OPTIONS(MAIN);
/* Original: CUSTDEF.pli line 1 (included from CUSTOMER.pli line 3) */
DCL 1 CUSTOMER_REC,
2 CUST_ID CHAR(10),
2 NAME CHAR(30);
/* Original: CUSTOMER.pli line 5 */
DCL CUST CUSTOMER_REC;
END CUSTOMER;
2.3.10 Preprocessor Best Practices¶
DO:¶
✅ Use %INCLUDE for shared definitions (structures, constants) ✅ Use %REPLACE for configuration values (array sizes, limits) ✅ Use conditional compilation for debug/production builds ✅ Keep include files focused and modular ✅ Document macro parameters clearly
DON'T:¶
❌ Create deeply nested includes (limit: 5 levels) ❌ Use overly complex macros (hard to debug) ❌ Duplicate definitions across include files ❌ Use circular includes ❌ Rely on macro side effects
Example: Well-Organized Includes¶
project/
├── src/
│ └── main/
│ └── pli/
│ ├── CUSTPROC.pli (main program)
│ └── includes/
│ ├── COMMON.pli (common constants)
│ ├── CUSTDEF.pli (customer structures)
│ └── SQLDEF.pli (SQL declarations)
CUSTPROC.pli:
CUSTPROC: PROCEDURE OPTIONS(MAIN);
%INCLUDE includes/COMMON;
%INCLUDE includes/CUSTDEF;
%INCLUDE includes/SQLDEF;
/* Program logic */
END CUSTPROC;
2.4 Compiler Diagnostics and Messages¶
2.4.1 Diagnostic Message Format¶
Format: <severity>:<code> <file>(<line>,<col>): <message>
2.4.2 Severity Levels¶
- I — Informational
- W — Warning
- E — Error
- S — Severe error
2.4.3 Common Compiler Messages¶
Catalog of compiler-generated messages organized by category (syntax errors, semantic errors, type mismatches, unsupported features, etc.).
2.5 Helper System (JSON Configuration)¶
2.5.1 Overview¶
The Helper JSON system provides custom code generation overrides to handle edge cases and customer-specific requirements without modifying the compiler.
2.5.2 Helper Categories¶
| Category | Purpose |
|---|---|
classes | Ordinal interface name mapping |
methods | Method name and strategy overrides |
attributes | Variable declaration replacements |
params | Parameter expression overrides |
java | Substitute with custom Java class |
override | Replace generated Java methods |
additionalMethods | Add helper methods to generated classes |
struct | Replace structure field declarations |
init | Array initialization patching |
symbols | Type override for symbols |
sqlProc | SQL parameter direction (IN/OUT/INOUT) |
refactor | Move inner class nesting |
sharedParam | Promote parameter to instance variable |
callByRef | Wrap arguments for pass-by-reference |
statements | Replace specific statements |
2.5.3 Helper Configuration Examples¶
The Helper JSON system allows customization of the Java code generation process without modifying the PL/I source or recompiling the compiler. This section provides real-world examples of each helper type.
Example 1: Class Helper - Ordinal Interface Naming¶
Use Case: Define custom names for ordinal interfaces generated from PL/I DEFINE ORDINAL statements.
PL/I Source:
Helper JSON:
Generated Java (without helper):
Generated Java (with helper):
Example 2: Method Helper - Method Strategy Configuration¶
Use Case: Control how methods are generated (Java class, Java method, instance class, or main method) and provide custom implementations.
Helper JSON:
{
"methods": {
"p9dcfse": {
"camelCase": "p9dcfse",
"strategy": "JavaInstanceClass",
"replacement": ""
},
"ptrNotNullNotSysnull": {
"camelCase": "ptrnotnullnotsysnull",
"strategy": "JavaMethod",
"replacement": ""
}
}
}
Effect: - p9dcfse generates as an instance class (separate .java file) - ptrNotNullNotSysnull generates as a regular Java method
Example 3: Attribute Helper - Variable Declaration Override¶
Use Case: Override how a variable is declared in the generated Java class, useful for custom initialization or type changes.
PL/I Source:
Helper JSON:
{
"attributes": {
"BASE_VAR": {
"camelCase": "baseVar1",
"exclude": false,
"scope": "JavaInstanceClass",
"replacement": "Base_var baseVar2 = new Base_var()"
}
}
}
Generated Java (without helper):
Generated Java (with helper):
Important Notes: - No modifier keywords (public/private/protected) should be included in replacement - No semicolon at the end of the replacement statement - Changing the variable name in replacement does not update references to it
Example 4: Param Helper - Function Parameter Replacement¶
Use Case: Override how function parameters are passed, often used to simplify pointer operations.
PL/I Source:
Helper JSON:
Generated Java (without helper):
Generated Java (with helper):
Example 5: Java Helper - Custom Method Implementation¶
Use Case: Replace a PL/I procedure with custom Java implementation from a helper file.
Helper JSON:
{
"java": {
"IS_DRITTLAND": {
"javaClass": "P1npst.java",
"javaMethod": "not important"
},
"A20_LESEN_EINGANG_QUEUE": {
"javaClass": "P1npst.java",
"javaMethod": "not important"
}
}
}
Effect: - The compiler looks for IS_DRITTLAND method in helper/P1npst.java - The custom Java implementation replaces the transpiled version - Useful for optimizing critical methods or integrating with Java libraries
Example 6: Struct Helper - Structure Field Replacement¶
Use Case: Override structure field declarations and initialization, all fields must be specified.
Helper JSON:
{
"struct": {
"RF_TAB": [
{
"name": "max_rf",
"type": "Integer",
"instance": "P5npnm.rf_tab_elems"
},
{
"name": "tab",
"type": "Array<Tab>",
"instance": "new Array<Tab>(P5npnm.rf_tab_elems, Tab.class, Object.class, new Tab(), \"TAB\")"
}
]
}
}
Effect: - All fields of RF_TAB structure are replaced - Custom initialization expressions can be provided - All fields must be listed, not just those being changed
Example 7: Init Helper - Array Initialization Patch¶
Use Case: Add extra initialization code for uninitialized arrays, often needed for complex array structures.
Helper JSON:
{
"init": {
"PIKSDAT4": {
"code": "for(int i = 1; i < piksdat4.tab.size();i++) {\nPiksdat4.Tab tab = new Piksdat4.Tab();\ntab.fromBytes(\"\".getBytes());\npiksdat4.tab.set(tab,i);\n}"
}
}
}
Effect: - The code string is inserted as initialization logic - Useful for arrays of structures that need element-by-element initialization - Allows complex initialization that the compiler doesn't generate automatically
Example 8: SQL Proc Helper - SQL Parameter Directions¶
Use Case: Define input/output directions for SQL stored procedure parameters.
Helper JSON:
{
"sqlProc": {
"HST.P7HS020": [
{"param": ":HS020_HDL_NR", "io": "IN"},
{"param": ":HS020_VZWEIG", "io": "IN"},
{"param": ":HS020_F_TEXT", "io": "OUT"},
{"param": ":HS020_F_INFO", "io": "OUT"}
]
}
}
Effect: - SQL builder knows which parameters are input, output, or both (INOUT) - Affects how parameters are bound and retrieved from SQL calls - Critical for correct stored procedure invocation
Example 9: Statement Helper - Statement Replacement¶
Use Case: Replace specific PL/I statements with custom Java code.
Helper JSON:
{
"statements": {
"DATUM = SUBSTR(H_DATUM,1,8) ;": {
"replacement": "datum = Builtin.SUBSTR(h_datum, 1, 8);"
},
"AKT_DATUM = TRANSLATE('AB.CD.EFGH',DATUM,'EFGHCDAB');": {
"replacement": "akt_datum = Builtin.TRANSLATE(\"AB.CD.EFGH\", datum, \"EFGHCDAB\");"
},
"X=ANLSTAT((L+R)/2).KEY;": {
"replacement": "x = anlstat[(l + r) / 2].key;"
}
}
}
Important Notes: - The key must be the exact PL/I statement including whitespace - Does not work reliably for statements inside if/else clauses - For complex cases, use the override helper instead
Example 10: CallByRef Helper - Pass Parameters by Reference¶
Use Case: Wrap function parameters in an array to enable pass-by-reference semantics.
PL/I Source:
getConsignmentPflicht: proc(h_deloccat,
h_deloc,
h_delmarket,
consignmentPflicht);
call getConsignmentPflicht(dclvnppfg.ghdlcat,
dclvnppfg.ghdl,
h_delmarket,
consignmentPflicht);
Helper JSON:
Generated Java:
// Wrap params at call site
ArrayList<Object> _paramsByRefList3 = new ArrayList<>(
Arrays.asList(dclvnppfg.deloccat, dclvnppfg.deloc,
h_delmarket, consignmentpflicht)
);
getconsignmentpflicht(_paramsByRefList3);
// Unwrap params in method signature
private void getconsignmentpflicht(ArrayList<Object> _paramsByRefList) {
// Access via _paramsByRefList.get(0), etc.
}
Effect: - Parameters wrapped in ArrayList at call site - Method signature changed to accept ArrayList - Enables modification of parameters inside the method to affect the caller
2.5.4 Helper JSON Best Practices¶
File Location: Place helper JSON files in the helper/ directory relative to the compiled PL/I program.
File Naming: Typically named <ProgramName>.helper.json to match the PL/I program.
Compilation: Helpers are applied during the transpilation phase (PL/I → Java).
Testing: After adding helpers: 1. Clean and rebuild the project 2. Verify the generated Java code in target/generated-sources/ 3. Run unit tests to ensure behavior is correct
Common Pitfalls: - Statement helper keys must match PL/I source exactly (including whitespace) - Attribute replacement cannot change variable names effectively - Struct helper requires all fields to be specified - Statement helper doesn't work inside if/else blocks
When to Use Helpers: - ✅ Optimize critical methods with custom Java implementations - ✅ Fix incorrect code generation without modifying the compiler - ✅ Integrate with Java libraries and frameworks - ✅ Initialize complex data structures - ✅ Work around temporary compiler limitations - ❌ Avoid over-reliance; fix root causes in the compiler when possible