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.
A SlimeNENC-family tool that moves legacy code into a modern language without changing its meaning. A hand rewrite quietly drifts and causes incidents; SlimeNENC doesn't interpret meaning — it copies only the "skeleton" (structure), so the computed results stay identical to the original. It proves the behaviour first, so migration anxiety disappears. It copies only what it can, honestly, and isolates what it can't.
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.
Source is projected onto a Slot IR (language-independent structural intermediate form) and transcribed structure-preserving into the target. The statically-determined core is made bit-exact; dynamic, state-dependent parts are honestly isolated (isolate, don't confabulate). Backed by differential fuzzing plus formal methods where applicable, with deterministic verification a third party can reproduce locally.
Projection (π) of source as unique structure, not semantics. Primitives are modelled rigorously in bit-vector theory and formally verified over all inputs; composition/loops are covered by Csmith-style differential fuzzing — a two-tier guarantee. Non-reproducible computation (float/parallel/AI) goes to tier-③: meaning-equivalence + convergence + residual. "Where there is form, prove it in bytes; where there is none, in meaning. Lie on neither face."
📋 "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.
Measured on real source (not demos)
RPG (SlimeRPG → Java/runtime)
| Stage | Result | Read |
|---|---|---|
| Lexing | 99.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 |
| Hangs | 0 / 1,711 | 10 emitter infinite-loops found & fixed (DO-UNTIL, MONITOR, inline data-structures) |
CL (SlimeCL → bash / Rust)
| Stage | Result | Read |
|---|---|---|
| Lexing | 100% (46 / 46) | reads all real CL |
| Emit | 100% (46 / 46), 0 hangs | never stalls or crashes |
| bash syntax-valid | 100% (46 / 46) | bash is the natural target for CL’s command-orchestration nature |
| Rust compile | 100% (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.
IBM i output == SlimeCL bash == SlimeCL Rust, byte for byte.
| Program | IBM i | SlimeCL bash | SlimeCL Rust | |
|---|---|---|---|---|
countdown (sum 1..5, *DEC→*CHAR) | 00000000000000000015 | 00000000000000000015 | 00000000000000000015 | PASS |
exprdemo (parens, *AND, *CAT) | OK | OK | OK | PASS |
sstdemo (%SST substring) | HELLO | HELLO | HELLO | PASS |
dtaara (data area + ALCOBJ/DLCOBJ) | HELLO | HELLO | HELLO | PASS |
ebcdic ('A' *LT 'a' — EBCDIC collation) | GE | GE | GE | PASS |
binconv (%BIN('//') — big-endian *INT2) | …24929 | …24929 | …24929 | PASS |
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*CHARas a variable-length string (givingOK!). Fixed — both targets now model fixed width. *DEC→*CHARconversion: a packed-decimal total assigned into a character field is right-justified and zero-filled to the field width (00000000000000000015). SlimeCL had emitted a bare15. Fixed.SNDPGMMSG MSG()requires*CHAR, and theCALLcommand’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.
SIGNOFF 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.
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.
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 ==
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
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.
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.
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,RCVMSGcompile 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.
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
SlimeCL product page Request an early-access PoC ← All resources
