A new Python toolchain for Malbolge, the esoteric programming language designed in 1998 to be as close to unprogrammable as possible, is now available on PyPI. pyMalbolge compiles a subset of Python into Malbolge20 bytecode, ships with interpreters for both Malbolge variants, and includes a full debugger, all in pure Python with zero dependencies.

Why anyone would want this

Malbolge was created by Ben Olmstead specifically to resist human programming. Every instruction is self-modifying, opcodes depend on the instruction's own address, and arithmetic uses a lookup-table "crazy" operation on ternary digits. The first Malbolge program was not written by a human at all. It was found by beam search two years after the language appeared.

pyMalbolge sits at the opposite end of that history. Instead of hand-crafting programs in a language designed to punish its users, you write Python and the toolchain handles the translation. The project compiles a meaningful subset of Python, including functions, recursion, for loops, chained comparisons, and short-circuit boolean logic, down to Malbolge20 bytecode.

Two backends, byte-exact verification

The compiler offers two independent backends. The first goes through a C intermediate layer, matching the pipeline from Nagoya University's original toolchain. The second, called the direct backend, bypasses C entirely and compiles Python AST directly to Malbolge20 pseudo-instructions. The direct backend roughly halves output size on programs with control flow or function calls because it uses per-function temporaries and real recursion-cycle detection instead of the Nagoya stack's frame strategy.

Both backends produce deterministic output, which is a deliberate departure from the Nagoya toolchain's design. The original tools treat obfuscation as a feature, filling unused memory cells with values from srand(time(NULL)) so the same input produces different output on every compile. pyMalbolge replaces that with byte-for-byte reproducible builds, which made it possible to verify the toolchain against the reference implementation using 441 tests.

Every intermediate stage is exposed individually. The pipeline runs Python AST to Nagoya C subset, to pseudo-instructions, to LAL low-level assembly, to Malbolge20 bytecode. You can dump any intermediate form, and the direct backend is cross-checked end-to-end against the C backend: the same source compiled through both paths must produce identical program output.

A debugger for a language that resists debugging

pyMalbolge ships with both a CLI debugger modeled on GDB and an optional TUI debugger built on Textual. The debugger supports breakpoints, watchpoints, step-back execution, memory inspection, disassembly, and register viewing. Step-back records execution history so you can undo instructions, which is particularly useful in a language where every instruction rewrites memory.

The TUI adds keyboard shortcuts for stepping, scrolling memory, and toggling breakpoints. Both interfaces work with both Malbolge variants.

The debugger is roughly 2 to 2.4x slower than the already-slow interpreter because step-back requires recording the full execution trace. For programs where you need to understand what happened, that is the tradeoff.

Performance reality check

The numbers are honest. On a print("Hello, world!") program, the C interpreter runs it in 0.16 seconds. pyMalbolge's Python interpreter takes 3.07 seconds, a 19x gap. The project documentation recommends the C interpreter for running large compiled programs and the Python interpreter for debugging.

Compilation speed is dominated by the final assembly stage, which runs at roughly 0.5 seconds per megabyte of output. The project's README includes a detailed performance analysis showing how memoizing the address search in the assembly stage cut build times from roughly 40 seconds per megabyte to half a second per megabyte, with byte-identical output.

Output sizes are large by conventional standards but inherent to the target. A "Hello, world!" compiles to 3.47 megabytes. A recursive fibonacci function takes 56.8 megabytes through the direct backend. Malbolge20 has no instructions in the usual sense. Addition alone is a twenty-step loop over ternary digits, and every cell rewrites itself after execution.

What you can actually compile

The accepted Python subset includes integer arithmetic with constant-folded modulo, while and for loops, break and continue, chained comparisons, short-circuit boolean operators, conditional expressions, function definitions with mutual recursion, and putchar and getchar I/O. Negative literals and unary minus are rejected because the value ring is unsigned. Floats, strings at runtime, classes, imports, lambdas, comprehensions, and nested functions are all out of scope.

The project includes a normative specification documenting the accepted AST whitelist, seventeen documented divergences from CPython semantics, and the diagnostic contract for error messages. All errors include line numbers and source excerpts.