Two generations of RPG, and the DDS that gives them their fields.
RPG IV and RPG III, fixed format and free, plus the DDS that declares the files they read — parsed into one AST with one set of node types. 718 automated tests, a regression corpus of 53 open-source repositories, and 30 million lines of production RPG behind it.
An IBM i application is never only RPG. It is RPG plus DDS plus embedded DB2 SQL plus the CL that calls it. This engine reads the first three, and its sibling reads the fourth — into the same model.
RPG3SourceAdapter · column mappings against the IBM RPG/400 Reference
Most shops that still write RPG IV also still run RPG III. The usual answer is two parsers, two models and two sets of tools. Ours is one: an RPG III member is adapted line by line into the equivalent RPG IV fixed-format layout and then run through the same pipeline, so an RPG III program and an RPG IV program produce the same AST node types.
The mechanism matters more than the badge. “We support RPG III” is a claim you have to take on trust. “RPG III specifications are rewritten into their RPG IV equivalents, and the column mappings are documented against the IBM RPG/400 Reference” is a claim you can interrogate — and it has a consequence you can use: the analyzer, the transpiler and the editor you build on top do not need to know which generation a member came from.
Every RPG III operation spelling is mapped, including the ones RPG IV renamed: UPDAT, DELET, LOKUP, REDPE, SETOF and the WHxx family, along with comma array indexing (ARR,3) and *NAMVAR. E specifications become RPG IV array definitions with DIM, PERRCD, FROMFILE, TOFILE, CTDATA and ASCEND/DESCEND. F specification continuation lines — KSFILE, KINFDS, KRENAME, KCOMIT and the rest — are carried across.
RPG II is not supported, and we would rather say that here than in week three. If your estate has System/36 members, that is a separate conversation and we will have it honestly.
Within RPG IV there are not three languages but three layouts, and real members mix them: a fixed-format program with a /FREE island in one subprocedure, or a member that starts **FREE in column 1 of line 1 and never uses a column rule again. The engine switches lexing mode mid-member and still emits one coherent tree with positions on the original source.
RPG III coverage, specification by specification
Status of each RPG III specification letter under the adapter. The limits in the last three rows are documented in the engine, not discovered by you.
Specification
What it carries
Status
C — calculation
control levels, resulting indicators, half adjust, RPG III operation spellings, ARR,3, *NAMVAR
Full
F — file description
including the K continuation lines
Full
E — extension
arrays and tables, mapped to RPG IV array definitions
Full
I — input
record identification, field description, data structures, PSDS, data areas, named constants
Full
** compile-time data
bound to compile-time arrays in declaration order
Full
O — output
records, AND/OR, field lines, edit codes, end positions, constants
Partial — mapped; best effort on rarer entries
H — control
positional in RPG III, keyword-based in RPG IV
Consumed — reported as informational, not represented
L — line counter
no RPG IV counterpart exists
Consumed — not represented
RPG II
System/3, /32, /34, /36 members
Not supported, and not on this page as a promise
Input — fixed-format RPG IV
CALCFIBF.rpgle
* Calculates Fibonacci iterativelyD NBR S80D RESULT S80 INZ(0)
FLOGFILE Up E DiskC FIB BEGSRCSELECTCWHEN NBR = 0CEVAL RESULT = 0COTHERCFOR COUNT = 2 TO NBR
CEVAL RESULT = A + B
CENDFORCENDSLCENDSRC *LOVAL SETLL LOGFILE
CREAD LOGFILE
CDOW NOT %EOF(LOGFILE)CEVAL NBR = %DEC(SNBR : 8 : 0)
CEXSR FIB
CUPDATE RCALCFIB
CENDDO
The first marked line declares an externally described file: nothing in this member says what SNBR or RCALCFIB are. They come from DDS, which is why an RPG parser that does not also read DDS produces a program full of undefined names.
Output — the line model, by class
one classification per physical line
PhysicalLineline 1, commentPhysicalLineline 2, D spec
└── DataDescriptionLineNBR, S, 8 0PhysicalLineline 4, F spec
└── FileDescriptionLineLOGFILE, E, DiskPhysicalLineline 5, C spec
└── LogicalProcedureLineFIB BEGSR
⋯
PhysicalLineline 17, C spec
└── LogicalProcedureLineDOW, %EOF
└── conditionalIndicator, resulting-indicator roles
RPG is read line-first, then token-first. These are the real classes in the shipped engine — PhysicalLine, PhysicalLinesManager, LogicalDefinitionLine, LogicalProcedureLine and the per-specification line classes — because in fixed format columns 26–35 are a field name on a D specification, an operation code on a C specification and a record-format name on an F specification. There is no whitespace-delimited token stream to lex. The AST is derived from this model.
Four things that decide whether an RPG parser is usable
None of them is the grammar. Each is a place where a parser that handles the tutorial examples stops handling the estate.
Indicators are structure, not text
*IN01–*IN99, *INLR, the resulting-indicator columns 71–76 and the conditioning columns 9–17 are global variables and control flow at the same time. Programs branch on an indicator set five statements earlier. They are modeled as typed roles on the statement, not stored as strings — get this wrong and every downstream analysis is wrong in a way you will not see until you transpile.
C KEY CHAIN CUSTMAST 50C50EXSR NOTFNDC N50EXSR FOUND
Externally described everything
Dcl-F CUSTMAST EXTDESC declares no fields at all. The fields, their types and the record format come from DDS — and a logical file gets them from a physical file through PFILE, and a field may get its definition from another field through REFFLD. The engine parses DDS itself and follows both links.
A* physical file A R RCALCFIB
A SNBR 8S 0
A SFIB 8S 0 A* logical file over it A R RCALCFIB PFILE(LOGFILE)
Directives that survive into the tree
All thirteen RPG IV compiler directives are AST nodes, not text that was spliced away before anyone could look at it: /COPY, /INCLUDE, /DEFINE, /UNDEFINE, /IF, /ELSEIF, /ELSE, /ENDIF, /EOF, /FREE, /END-FREE, /TITLE, /EJECT. A copy directive keeps its member, file and library separately, and a conditional carries a real expression tree. That is what lets a tool say “this field came from copybook X”.
An SQLRPGLE member carries EXEC SQL blocks whose host variables are RPG declarations written :VAR. The engine isolates the SQL block as an ExecSqlStatement holding the text with exact positions — including across fixed-format C+ continuation lines — and a dedicated DB2 language module parses it, so DDL columns become resolvable RPG symbols.
Exec SQL Select count(*) Into :wI
From GENLOGPF
Where POBJLIB = :pRefLib;
Two more belong on the same list. Compile-time data — **CTDATA, **FTRANS, **ALTSEQ after the code — binds positionally to array declarations in declaration order, so a missing section silently shifts every array after it. And 88 RPG built-in functions are modeled individually, because %DEC(SNBR : 8 : 0) and %EOF(LOGFILE) are not function calls you can treat generically.
Dialects and formats covered
What the engine reads today. Extensions are detected, and a per-file type override is available for corpora exported without member types.
Source
Extensions
Status
RPG IV (ILE RPG), fixed format
rpgle, rpglem, rpgleinc
Supported
RPG IV, /FREE islands and fully-free **FREE
rpgle
Supported
RPG IV with embedded SQL
sqlrpgle
Supported — block isolated and positioned
RPG III / RPG 400
rpg, rpg3, rpg38, rpgsrc, sqlrpg
Supported — via the RPG IV adapter
DDS — physical, logical, display and printer files
dds, pf, lf, dspf, prtf
Supported
The SQL inside EXEC SQL
—
Parsed by the DB2 language module, not by the RPG grammar
RPG II and the System/36 environment
rpg36
Not supported
IBM i object model — program objects, service programs, job descriptions
—
Not read; the engine reads exported source
One member, three languages
This is what a modern IBM i member actually looks like, and it is the reason a free-form-only RPG grammar does not survive contact with a real library.
Input — fully-free RPG IV with embedded DB2 SQL
GENDDLLOG.sqlrpgle
**FREECtl-opt Option(*SRCSTMT : *NODEBUGIO) DFTACTGRP(*NO);
Dcl-pi *N;
pLogCode char(10) const;
pRefObj char(10) const;
pRefLib char(10) const;
End-pi;
Dcl-s wCurrUser Char(10) Inz(*User);
Dcl-s wSqlStmt Char(400);
Dcl-s wI int(10);
Dcl-c cSQLOK '00000';Exsr srMain;
Begsr srMain;
Exec SQL Set Option Commit = *NONE, DatFmt = *ISO;Exec SQL Select count(*) Into :wI
From GENLOGPF
Where POBJLIB = :pRefLib And POBJNAM = :pRefObj;Endsr;
Six marked constructs, three languages. **FREE in column 1 of line 1 turns off the column rules for the whole member. Dcl-pi, Dcl-s and Dcl-c are free-form declarations that a fixed-format-only grammar cannot see. And :wI, :pRefLib and :pRefObj are host variables crossing from DB2 SQL back into RPG declarations — the join that needs both grammars and the symbol table between them. GENLOGPF is a DDS-described file; without its physical-file source, POBJLIB and POBJNAM are just words.
WHAT THIS ENGINE IS FOR
A library of members is not a codebase until something resolves it.
A parse tree per file answers nothing. “If I change this DDS field, which of my four thousand programs break?” is a question about the whole export at once — and that is what the engine is built to answer.
Symbols are collected across the entire exported source set and then resolved: declarations, /COPY members, DDS record formats and fields, DB2 columns, subroutines, labels and prototypes, all linked across files, with a usages index and prototype-to-procedure links. Resolving one field name can legitimately require four files — the RPG member, its copy member, a logical file and the physical file underneath it.
On top of that sit the analysis products: a dependency-graph builder, a go-to-definition indexer, and a PlantUML sequence-diagram generator that turns a program nobody understands any more into a picture somebody can read. The open counterpart of that last one is published as Strumenta/rpg-puml-sequence.
There is one thing the engine cannot do, and it is worth naming here rather than in a footnote: it cannot divine your library list. IBM i resolves names at run time through *LIBL, set outside the program. Two members that both say CHAIN CUSTMAST may be reading two different files. The engine resolves against a search order — the one you supply.
The corpus behind the engine
named, public, checkable
# open-source RPG repositories in the# regression run, among 53 in total
Flight400
RemainTest/GITST
RemainTest/GITDEMO
IAF/IBMi_Legacy
IBM/tobi-example
# one RPG III corpus is English plus# Shift-JIS (DBCS) comments
--charset SHIFT_JIS
A corpus you can look up is better evidence than a number you cannot. These are real repositories in the regression run, and the DBCS one is there because East Asian shops export in mixed CCSIDs and the engine has to read what comes out.
718
AUTOMATED TESTS
53
OPEN-SOURCE RPG REPOSITORIES IN THE REGRESSION CORPUS
~540,000
LINES IN THAT CORPUS, ACROSS ~3,000 FILES
30M
LINES OF PRODUCTION RPG THE ENGINE HAS RUN OVER
What you receive
A jar, the documents that explain the tree it produces, and a person to write to. No connection to the IBM i is required: you analyze exported source members offline, on laptops or on CI servers.
01The engine
A JVM library and a standalone command-line tool
Kotlin on the JVM, on Kolasu — the JVM implementation of Starlasu. Use it as a library from Java or Kotlin, or run the fat jar over a directory with Windows and Linux launcher scripts and collect serialized trees.
library · CLI · JVM
02The model
One AST for RPG IV, RPG III and DDS
A typed AST with a position on every node, plus a positioned issues list rather than an exception when the input is damaged. Serialize to JSON or XML, export to EMF/Ecore, and interchange through LionWeb so the model travels to tooling we did not write.
JSON · XML · EMF/Ecore · LionWeb
03The documents
HTML documentation and worked example projects
The package is the fat jar, launcher scripts, HTML documentation of the AST structure, and example projects in Java and in Kotlin that you can build on day one instead of guessing at the shape of the tree.
AST documentation · Java and Kotlin examples
04The license
Standard, Distribution or Service — support included
Standard for use inside your own organization, Distribution if the engine ships inside a product you sell, Service if it runs behind a service you operate. A license file is registered once per process, or passed on the command line, and is refreshed from our license service — the same arrangement as every other engine in the catalog.
three tiers · support included
Onboarding
The first week is running the jar over your own exported library — not waiting for an environment.
There is nothing to install on the IBM i and nothing to provision. Before any of that, you can paste a member into the Strumenta Playground — pick RPG — and watch a real AST come back from the same engine. If the integration needs help, that is what the support in the license is for; if it needs more than help, we do that work too, as training, architectural design or coaching alongside your team. Write to products@strumenta.com.
THE REST OF THE BOX
RPG is one quarter of an IBM i application.
The other three quarters are the DDS that defines the files, the DB2 SQL embedded in the members, and the CL that submits the jobs, overrides the files and sets the library list.
Migrating RPG without migrating the CL that calls it, overrides its files and sets its library list produces Java that cannot run. That is not a hypothetical; it is the most common way an IBM i modernization goes wrong.
Every Strumenta engine is built on Starlasu, so an RPG tree, a CL tree, a DDS tree and a DB2 SQL tree have the same shape, the same traversal model and the same API. One tool walks all four. That is exactly what you cannot assemble by gluing four unrelated open-source parsers together: four models, four idioms and four sets of edge cases to reconcile before you have written a line of analysis.
Every engine also supports LionWeb, so the models interchange with LionWeb-compliant tooling instead of being locked inside one process — and every engine is available through bindings from Java, Kotlin, Python, TypeScript and C#. The host application picks the language; the parser does not.
Engines on the same box
one model, one traversal
CL engine → The job flow, the overrides and the library list.
SQL engine → Deeper analysis of what the embedded statements do.
Why license this rather than start from an open-source RPG project
There is good open-source RPG work and we are not going to pretend there is not. The most substantial is JaRIKo, Sme.UP’s Apache 2.0 RPG interpreter for the JVM. It is a real project by people who know the platform. It is also, by its own README, not trying to do what this engine does.
JaRIKo is an interpreter: its job is to run RPG, not to hand you a model of it. The rows below are the difference between those two goals, not a criticism of a project we respect.
What a migration needs
An open-source RPG interpreter
This engine
DDS, so externally described fields have types
Explicitly out of scope — “we are also not implementing DDS”
A second grammar in the same product, with PFILE and REFFLD resolution
The CL that drives the jobs
Explicitly out of scope
A sibling engine, same AST shape
RPG III as well as RPG IV
Not addressed
Adapted into the RPG IV pipeline; same node types
A model to program against
An interpreter’s internal representation, shaped for execution
A designed AST, serialized, with a metamodel
Behavior on damaged or truncated members
Whatever an interpreter does when it cannot run
Partial AST plus a positioned issue list; always returns a tree
Someone to call
The issue tracker
Support included in the license
The same engine is the foundation of our own migration work — see RPG to Java, RPG to Python — we are the only company converting RPG to Python — and, for the large share of IBM i RPG that was generated rather than written, Synon / CA 2E migration, where the point is to lift generated RPG back to the model it came from rather than migrating the generated code. The method behind the estimates has a name: the Chisel Method.
What to discuss before you license
Six things we would rather tell you now than have you find in week three.
RPG II is not supported. RPG III is, RPG IV is, DDS is. If your estate still has System/36 members, that is a scoping conversation and not a checkbox.
RPG III has documented edges. O specifications are best effort on rarer entries; H and L specifications are consumed but not represented; only the first of the three conditioning indicators on a calculation specification is carried; alternating arrays are not represented. And because RPG III is adapted before it is parsed, node line numbers are exact but column numbers refer to the adapted RPG IV layout.
Without DDS, externally described files stay undefined. This is the single biggest reason an RPG project stalls. If you have the RPG but not the physical and logical file sources, we need to know before anyone quotes anything.
The library list is an input, not an inference.*LIBL is set outside the program, in CL and in job descriptions. The engine resolves against the search order you supply; nothing in the source can tell it which CUSTMAST a given job opened.
The embedded SQL is isolated by this engine and parsed by another. You get the block, its text and its exact position from the RPG engine; deeper analysis of the statement needs the DB2 language module. That is a licensing question, not a technical obstacle.
It runs on a JVM, in your environment. If your toolchain is Python, TypeScript or C#, you use the bindings or the serialized tree rather than the jar in-process — which works, and is worth designing for deliberately rather than discovering late.
What we will ask you
Which generations — RPGLE only, or RPG III and RPG/400 members too? Any RPG II?
Roughly how many members, and how are they split between fixed format, /FREE and **FREE?
Do you have the DDS sources for every file the programs use, or only the RPG?
How much is SQLRPGLE, and do you need the embedded SQL analyzed or only isolated?
Was any of it generated — Synon / CA 2E, LANSA, Adelia? Which generator, which version?
How will you export the source, and in which CCSID? Any DBCS content?
Can you supply the library list the applications run under?
What is the target — analysis and documentation, an editor, or a transpilation to Java, Python or C#?
Straight answers
Do you really support RPG III, or is it on a roadmap?
It is implemented. RPG III members are adapted line by line into the equivalent RPG IV fixed-format layout, with the column mappings documented against the IBM RPG/400 Reference, and then run through the same pipeline as RPG IV — so both produce the same AST node types and one set of tools works on both. C, F, E and I specifications and compile-time data are fully mapped; O specifications are best effort on rarer entries; H and L specifications are consumed without representation. RPG II is not supported.
Can it read our DDS as well as our RPG?
Yes, and it has to. Physical, logical, display and printer files all parse, and the symbol provider builds record formats, follows a logical file back to its physical file through PFILE, and follows REFFLD field references. Without that, Dcl-F CUSTMAST EXTDESC declares no fields and the AST is a program full of undefined names.
Do we need a connection to the IBM i?
No. You analyze exported source members offline, on laptops or CI servers, inside your own network, with no connection back to us and none to the machine the code came from. What we do need from you is the search order the applications run under, because the library list is not recoverable from source.
What happens on a truncated member or one that no longer compiles?
You get a tree. Every unsupported construct produces a positioned issue rather than a failure: the parser always returns an AST plus a list of issues with severity and position. This is not a nicety — exported corpora routinely contain truncated members, mixed CCSIDs and dead code, and a batch over ten thousand files has to finish and tell you where it struggled.
Can we use it from Python, TypeScript or C#?
Yes. The engine is built on Starlasu and is available through bindings from Java, Kotlin, Python, TypeScript and C#, and it supports LionWeb, so the model interchanges with LionWeb-compliant tooling. If you would rather not embed anything, the command-line tool serializes the tree to JSON or XML and any language can read that. An EMF/Ecore export is also available.
Two pages, no form-filling firstthe technical overview, as a PDF
Take the specification away and read it properly.
The RPG & DDS Language Engine brochure is the complete technical overview in two pages: coverage, whole-codebase symbol resolution, delivery, serialization formats and licensing. Or send us one exported library — RPG, DDS and all — and we will report what parses, what resolves and what does not.