OPEN · technical deep-dive

AS/400 (IBM i) modernization — what SlimeCL / SlimeRPG can & can’t do

An honest, real-corpus account. The numbers below come from actual public IBM i source — not hand-picked demos. We publish the gaps too, because transparency is the moat: competitors rarely publish multi-target pass rates at all.

🎛 AI GATE This page, at your resolution.

Hand rewrites drift on subtle numeric/exception differences (boundary conditions), and verifying that (old-vs-new testing) costs enormous labour. SlimeNENC faithfully mirrors language-specific traps, proves "zero divergence" via differential testing, backed by an independent reference implementation. The deliverable is a machine-checkable "certificate of behavioural invariance," not human UAT. No overstating; what it can't do isn't hidden.

📋 "Ask your AI at this level" copies this page's explanation with an instruction matched to the level you picked. Paste it into your own AI (Claude · GPT · Gemini · Grok) to dig deeper at that resolution.

The thesis. CL is the glue of an IBM i shop; RPG / COBOL are the business logic. We translate them to portable bash and a single-binary Rust — continuity, not rewrite. This page states exactly how far that works today, measured, with hangs and compile-gaps shown.

Measured on real source (not demos)

1,711
real RPG programs swept (jariko / OSSILE / noxDB / IBM samples)
46
real CL programs — public “IBM i RPG Free CLP” corpus (.clp / .clle)
0
hangs / crashes across all of the above (after fixing the 10 we first found)

RPG (SlimeRPG → Java/runtime)

StageResultRead
Lexing99.4% (1,701 / 1,711)reads almost all real RPG, including fixed-format RPG/400
Emit (produces output)99.8%no crashes; structured translation
javac compile (sample)~82%honest gap — surrounding RPG (BIFs, cursor fields) not yet complete
Hangs0 / 1,71110 emitter infinite-loops found & fixed (DO-UNTIL, MONITOR, inline data-structures)

CL (SlimeCL → bash / Rust)

StageResultRead
Lexing100% (46 / 46)reads all real CL
Emit100% (46 / 46), 0 hangsnever stalls or crashes
bash syntax-valid100% (46 / 46)bash is the natural target for CL’s command-orchestration nature
Rust compile100% (46 / 46)every program in this public corpus now compiles to both targets

This iteration (2026-05-26), on this same corpus, Rust compile rose 69.6% → 100% and bash 76.1% → 100% — not by adding shims (unhandled commands already emit as compiling comments), but by hardening the emitter for the dialects of older CL: positional parameter syntax (CHGVAR &X '1', DCL (&X) (*CHAR), positional IF/THEN), reserved-word variable names (&Type), substring assignment (CHGVAR %SST(&v s l)), symbolic comparison operators (= > <), implicit DCLF *CHAR fields, %BIN, special values (*YES), *CHAR*DEC conversion both ways, Rust ownership (borrow vs. move), and a DDS front-end that reads DCLF record-field types straight from the file’s .DSPF/.PF definition (the same positional-parser idea as our BMS / XMAP map parsers). Each fix was verified for no regression (29 unit tests + 4/4 bit-exact). Reproducible with bitexact/measure_compile.sh.

Verified bit-exact on a live IBM i ✓ new

We compiled and ran the original CL on a real IBM i (7.5, on PUB400.com) and compared its output — byte for byte — against the SlimeCL-emitted bash and Rust. This is the milestone we previously listed as “not yet published”. It is now.

6 / 6 bit-exact, 3-way. For every program below, IBM i output == SlimeCL bash == SlimeCL Rust, byte for byte.
ProgramIBM iSlimeCL bashSlimeCL Rust
countdown (sum 1..5, *DEC*CHAR)000000000000000000150000000000000000001500000000000000000015PASS
exprdemo (parens, *AND, *CAT)OKOKOKPASS
sstdemo (%SST substring)HELLOHELLOHELLOPASS
dtaara (data area + ALCOBJ/DLCOBJ)HELLOHELLOHELLOPASS
ebcdic ('A' *LT 'a' — EBCDIC collation)GEGEGEPASS
binconv (%BIN('//') — big-endian *INT2)…24929…24929…24929PASS

The last two close runtime gaps that compile clean but would silently diverge on real hardware: a *CHAR comparison must use EBCDIC collation (where lowercase < uppercase < digits — the reverse of ASCII), so 'A' *LT 'a' is false on IBM i (→ GE), not true as a naïve ASCII string compare would give; and %BIN reads the field’s bytes as a big-endian signed integer (*INT2/*INT4), not the host’s little-endian. Both are bound to the IBM i semantics and verified byte-for-byte above.

The valuable part is what real hardware caught — fidelity gaps no synthetic test would expose, which we then fixed:

  • Fixed-length *CHAR: IBM i character fields are blank-padded to their declared length, so &MSG *CAT '!' on a 20-byte field pushes the ! off the end → OK. SlimeCL had treated *CHAR as a variable-length string (giving OK!). Fixed — both targets now model fixed width.
  • *DEC*CHAR conversion: a packed-decimal total assigned into a character field is right-justified and zero-filled to the field width (00000000000000000015). SlimeCL had emitted a bare 15. Fixed.
  • SNDPGMMSG MSG() requires *CHAR, and the CALL command’s char-vs-packed parameter typing can silently corrupt a numeric value. Both surfaced on first contact and are documented.

Honest scope. This is three representative programs, not the whole corpus. The point is the method: a reusable harness that compiles each program on a real IBM i, runs it, and diffs the output against both emitted targets. Extending the program set is ongoing. We know of no competitor that publishes live-hardware, byte-for-byte transpiler validation at all.

5250 screen harness — a full live session, reconstructed ✓ new

After batch-output bit-exact, we can now reproduce the interactive screen (5250) on real hardware too. No off-the-shelf scriptable 5250 client exists, so we built a headless one, s5250, in std-only, zero-external-library Rust (consistent with JAVATEL’s no-external-lib stack). It drives a complete authenticated session against a real IBM i (PUB400.com), reconstructing each panel as a 24×80 grid.

The full flow, verified on hardware: TELNET negotiation → reconstruct the sign-on panel → sign on (input fields located from SF/FFW) → answer the device Query (RFC 1205 Query Reply) → reconstruct the IBM i Main MenuSIGNOFF from the command line for a clean disconnect (no session left hanging).
--- IBM i Main Menu, reconstructed from live PUB400 ---
 MAIN  IBM i Main Menu
          System:  PUB400
 Select one of the following:
      1.User tasks   ...   9.Display a menu
     90.Sign off
 Selection or command
 ===>
 F3=Exit  F4=Prompt  F9=Retrieve  F12=Cancel ...
 (C) COPYRIGHT IBM CORP. 1980, 2021.

Protocol findings (the rigor behind it): the inbound stream also needs the 10-byte GDS header — without it the host reads the first data byte as a record length and drops the connection. A successful sign-on triggers a device Query before the host sends the menu, so without a Query Reply it simply waits (looks like a hang). The sign-on panel’s “Server name / Subsystem” lines are SBA-written literals, not input fields (SF) — parsing SF/FFW is what separates fields from labels. QMAXSIGN respected: only correct credentials sent; the inbound format was proven with a credential-free probe.

P1 — the first interactive bit-exact (L2). A tiny DCLF program (SNDRCVF displaying an all-constant panel) is CALLed from the menu via s5250 and the displayed panel is scraped. The same DDS, rendered by SlimeGENBA-LUI, is diffed against it as a normalized 24×80 text grid → PASS (the real screen == the render, line for line). This is the screen-level counterpart of the 6/6 batch-output bit-exact.
== P1 L2 screen diff: live DCLF panel (real IBM i) vs DDS render ==
  => PASS  (L2 bit-exact: live panel == DDS render)
 SlimeCL P1 -- DCLF panel
 Rendered from DDS constants.
 Live via s5250 (5250/TELNET).
 Same DDS via SlimeGENBA-LUI.
 Diff = L2 screen bit-exact.
 Press Enter to exit.
P2 — keystroke scripting + multi-panel. A DCLF program with three record formats (MAIN / PAGE2 / PAGE3) is walked by s5250 with scripted Enter keys, scraping each panel; each is L2-diffed against its per-format DDS render → 3/3 panels match. And F3 ≠ Enter is proven — F3 fires the CL’s IF &IN03 RETURN, returning to the menu instead of PAGE2. The keys carry real, distinct meaning (not blind advance).
== P2 keystroke + multi-panel L2 diff (real IBM i) vs DDS render ==
  panel 0 (MAIN):  => PASS  (L2 bit-exact)
  panel 1 (PAGE2): => PASS  (L2 bit-exact)
  panel 2 (PAGE3): => PASS  (L2 bit-exact)
  == result: 3/3 panels L2 bit-exact ==
P3 — fixture + computed field values. Beyond constant panels, both sides read the same fixture. Live: P3DRV reads the data area P3FIX, computes, and displays it. Candidate: SlimeCL transpiles the same CL; the emitted program reads the same fixture (injected via an env var), computes, dumps its field values, and SlimeGENBA-LUI renders the DSPF with them. L2 diff → match, holding across two fixture values (42 → …042/…126; 100 → …100/…300). So translated logic + DDS render == the real screen, including computed values — the piece P1/P2 deferred.
== P3 fixture + computed-field L2 diff (fixture P3FIX=42) ==
  => PASS  (L2 bit-exact: live computed panel == DDS render with candidate values)
 SlimeCL P3 -- record count
 Active (RECA):    0000000042   <- fixture 42
 Total  (RECT):    0000000126   <- 42 * 3, computed by the transpiled logic
P4 — operator field input. Past output-only panels, s5250 now types into a live input field (type:VALUE); the program reads it and computes (char→dec, ×3, dec→char), then displays the result. The candidate reads the same typed value as an input fixture (injected via an env var); L2 matches (7→…021, 15→…045). We found that a 5250 input field re-sends with internal structure on redisplay, shifting its column, so we split the input format from an output-only result format (echo of the entry + the result) and diff the aligned output panel — head-on, no fudging.
P4F — file fixture. A physical file (loaded with N records) is the fixture: the live program RTVMBRDs the real file’s record count and displays it; the candidate counts the same N-record fixture file. L2 matches (verified at 5 and 12 records). Honest scope: this reads file metadata (the record count) — the realistic way a CL program reads a file under the one-DCLF limit. Record-level field reads (RCVF over a DB file) via the SlimeTree-VSAM backend are the deeper next step.
== P4 field-input ==                  == P4F file-fixture ==
  => PASS (typed 7 -> Result …021)      => PASS (5 records -> RTVMBRD …005)

Why this matters. The panel the operator sees is exactly what a product that lets you keep your Legacy UI must reproduce. This harness is the screen-side half (reconstruction from real hardware); paired with the other half — SlimeGENBA-LUI (DDS → 2-layer UI render) — it diffs “the real screen == the converted screen” panel by panel, which is exactly P1 through P4F above. Honest scope: screen reconstruction (P0/P0.5), L2 bit-exact (P1), keystroke + multi-panel (P2), computed field values (P3), field input (P4), and file-fixture record counts (P4F) are done. Record-level DB-file reads come next via SlimeTree-VSAM; DDS numeric edit codes and L3 attributes/color are also next. DBCS panels need a Japanese-CCSID IBM i (the same blocker as EBCDIC collation).

Transport generalization — reaching RS-232C office computers ✓ new

Screen reconstruction is transport-agnostic — it just “parses a terminal datastream and rebuilds a 24×80 grid” — so the harness extends beyond TCP. Many Japanese office computers (Fujitsu ASP, NEC A-VX, Hitachi …) still run with no SSH and no TCP/IP — just an RS-232C line to a block-mode terminal. Cloud-migration tools physically cannot reach a machine living on a single serial cable in the corner of a factory.

sserial — the transport swapped to RS-232C. The same reconstruction core as the 5250-over-TELNET client (s5250, above) is put on a raw serial line. termios (raw 8N1 / baud / idle timeout) is driven through self-declared extern "C" bindings — no libc, no serialport crate (the same no-external-lib stack as the in-house SHA-256 / EBCDIC tables). A non-tty path is replay mode, so a captured datastream reconstructs with no hardware.

Transport-agnosticism proven on real data. A PUB400 sign-on datastream captured over TELNET, replayed through sserial’s serial/file path, reconstructs the identical grid s5250 produced over TCP — same datastream, different transport, same screen. “The transport is swappable; the meaning of the screen is one.”

Honest scope. No real office computer has been connected yet. The dialect today is 5250, and the proof above is replay of a TELNET capture. Each vendor’s block-mode dialect (including the KEIS/JEF/JIPS code pages) has no RFC — the first real serial-line capture defines the work. Live serial validation, like DBCS, needs a physical machine (or a line capture) from a cooperating site. The skeleton — transport + reconstruction core — is built so adding one dialect parser extends it.

What it can do today

  • Control flow: IF/ELSE, DOWHILE/DOUNTIL/DOFOR, GOTO+labels (modeled with an exact program-counter state machine, since neither bash nor safe Rust has goto).
  • Intra-program subroutines (SUBR/CALLSUBR) → real functions. CL variables are program-global, so we model them faithfully (module statics + functions in Rust; shell functions over globals in bash).
  • Expressions with proper precedence & parentheses: (&A *GT 5) *AND (&B *LT 3), string concat *CAT, and BIFs %SST / %SCAN / %LEN / %TRIM / %CHAR / %UPPER / %LOWER.
  • Typed variables: *DEC → i64/f64, *CHAR → String, *LGL → bool.
  • Older-CL dialects: positional parameter syntax (CHGVAR &X '1', DCL (&X) (*CHAR)), symbolic comparison operators (= > < ¬=), substring assignment (CHGVAR %SST(&v s l)), and reserved-word variable names.
  • OS-service runtime shims: data areas (RTV/CHG/CRT/DLT DTAARA) run via a process-local store; object locks (ALCOBJ/DLCOBJ) and file overrides are recognized.
  • Single static binary output (Rust target) — runs on commodity x86/Linux, no IBM i runtime, no external libraries.

What it can’t do yet (honest)

  • Behavioral validation is the next frontier, not compilation. All 46 compile; bit-exact is verified on 6 representative programs (incl. EBCDIC collation and big-endian %BIN). OS-service execution (SBMJOB, RCVMSG compile but aren’t fully executable; data areas & object locks run via runtime shims) is the honest remaining gap.
  • DBCS (Japanese double-byte) collation: single-byte EBCDIC ordering is implemented and verified, but the double-byte (IBM-Kanji, CCSID 5026/5035) order needs a Japanese-CCSID IBM i to verify bit-exact (our test machine is CCSID 273) — so we don’t ship it unverified.
  • ~18% of real RPG doesn’t compile — missing BIF coverage and cursor host-variable typing (SQLRPGLE).
  • Full-corpus behavioral validation: bit-exact validation on a live IBM i is now done for a representative set (6/6, see above) — extending it across the full corpus is the ongoing work.
  • DDS (display files / 5250): L2 bit-exact (P1), multi-panel (P2), computed field values (P3), field input (P4), and file-fixture record counts (P4F) are done (above). What remains is record-level DB-file reads (via SlimeTree-VSAM), DDS numeric edit codes, and L3 attributes/color. DB2 for i embedded-SQL semantics and QUERY/400 are captured but not yet fully executed.

The pipeline

CL1 lexer   →  CL2 Slot IR + expression parser  →  CL6 emitter { bash | Rust }
(same staged shape across the JAVATEL transpiler family: COBOL, RPG, PL/I, MUMPS, …)

Example — a CL DOFOR loop calling a subroutine becomes idiomatic Rust:

i = 1;
while i <= 5 {
    subr_addit();   /* CALLSUBR SUBR(ADDIT) */
    i += 1;
}
fn subr_addit() { unsafe { sum = sum + i; } }   // program-global vars

Why we publish the gaps

Micro Focus, Blu Age, AWS MMA and others rarely publish per-target compile pass rates at all. We publish lex / emit / compile / hang counts on real corpus, name the gaps, and show the improvement track record (10 hangs → 0 in one session). That transparency is the differentiator — for banking, public-sector and SI evaluators who must trust the result, “honest and improving fast” beats “trust us, it’s 100%.”

Japanese offcon modernization — seeking a partner site

Many live Japanese midrange systems (Fujitsu ASP, NEC A‑VX, Hitachi) are RS‑232C only — cloud migration tools (AWS MMA, Blu Age) assume SSH/TCP and physically cannot reach them. JAVATEL works exactly on those floors, handling screen, data and logic byte‑for‑byte.

Already running: on a real public IBM i (PUB400) we verified, byte‑exact against the machine, panel reconstruction, full file CRUD, every data format (EBCDIC / zoned / packed COMP‑3 / binary / IEEE float / DECFLOAT decimal64 & 128), plus COMMIT/ROLLBACK, OVRDBF, MONMSG, an RS‑232C transport, and DBCS (SO/SI). Rust + WASM, zero external libraries — we can show you the whole implementation.

The last missing piece is one real machine: confirming the vendor‑specific datastream and the Japanese code points (KEIS83 / JEF). We offer this modernization PoC at no cost (0 JPY), on‑site if you prefer, with no data export required — modernization and continuity, never forcing you to abandon what works.

Discuss a Japanese‑offcon collaboration

Acknowledgement

Every live-hardware result on this page — the 6/6 bit-exact and the P0–P3 5250 screen harness — was produced on PUB400.com, a free, public IBM i operated by RZKH GmbH. We’re grateful for a place where anyone can learn on a real IBM i at no cost. We respect its terms: short, clean sessions (always signing off) and minimal sign-on attempts.

SlimeCL product page Request an early-access PoC ← All resources