STRUMENTA LANGUAGE ENGINES · IBM RPG & DDS

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.

The engine at a glance
DIALECTSRPG IV (ILE) · RPG III · DDS
LAYOUTSfixed · /FREE · **FREE
TESTS718 automated
CORPUS53 repositories · ~3,000 files
OUTPUTAST · JSON · XML · EMF · LionWeb
RUNTIMEJVM library · CLI

RPG III does not get its own AST

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.

How RPG III and RPG IV members reach the same AST node types An RPG III member passes through a source adapter that rewrites each specification line into the equivalent RPG IV fixed-format layout. An RPG IV member enters directly. Both then go through the same lexing, parsing and AST-building pipeline, and both produce the same AST node types. RPG III member RPG IV member RPG3SourceAdapter one pipeline one AST .rpg .rpg3 .rpg38 .rpgle .sqlrpgle line by line, to RPG IV fixed layout lex · parse · map same node types POSITIONS: LINE NUMBERS ARE EXACT FOR BOTH. FOR RPG III, COLUMNS REFER TO THE ADAPTED LAYOUT.

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 iteratively
     D NBR             S              8  0
     D RESULT          S              8  0 INZ(0)
     FLOGFILE   Up   E             Disk
     C     FIB           BEGSR
     C                   SELECT
     C                   WHEN      NBR = 0
     C                   EVAL      RESULT = 0
     C                   OTHER
     C                   FOR       COUNT = 2 TO NBR
     C                   EVAL      RESULT = A + B
     C                   ENDFOR
     C                   ENDSL
     C                   ENDSR
     C     *LOVAL        SETLL     LOGFILE
     C                   READ      LOGFILE
     C                   DOW       NOT %EOF(LOGFILE)
     C                   EVAL      NBR = %DEC(SNBR : 8 : 0)
     C                   EXSR      FIB
     C                   UPDATE    RCALCFIB
     C                   ENDDO
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

PhysicalLine line 1, comment
PhysicalLine line 2, D spec
  └── DataDescriptionLine NBR, S, 8 0
PhysicalLine line 4, F spec
  └── FileDescriptionLine LOGFILE, E, Disk
PhysicalLine line 5, C spec
  └── LogicalProcedureLine FIB BEGSR
⋯
PhysicalLine line 17, C spec
  └── LogicalProcedureLine DOW, %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                   50
     C   50              EXSR      NOTFND
     C  N50              EXSR      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”.

      /COPY QRPGLESRC,CUSTPR
      /IF DEFINED(PRODUCTION)
      /DEFINE LOGLEVEL
      /ENDIF

SQL inside RPG, and RPG inside SQL

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

**FREE
Ctl-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.

01 The 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
02 The 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
03 The 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
04 The 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.

COBOL engine →
For the estates that run both platforms.

The whole catalog →
15+ engines, one AST framework underneath.

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

  1. Which generations — RPGLE only, or RPG III and RPG/400 members too? Any RPG II?
  2. Roughly how many members, and how are they split between fixed format, /FREE and **FREE?
  3. Do you have the DDS sources for every file the programs use, or only the RPG?
  4. How much is SQLRPGLE, and do you need the embedded SQL analyzed or only isolated?
  5. Was any of it generated — Synon / CA 2E, LANSA, Adelia? Which generator, which version?
  6. How will you export the source, and in which CCSID? Any DBCS content?
  7. Can you supply the library list the applications run under?
  8. 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 first the 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.

Scroll to Top