LMAO is an assembler that translates programs written in HeLL (Hellish Low-level Language) into executable Malbolge programs.
Malbolge is a notoriously difficult esoteric programming language designed by Ben Olmstead in 1998. Its difficulty comes from its self-modifying instruction set: every instruction changes itself after execution, so writing Malbolge by hand is extremely hard. LMAO removes that burden by letting you write in HeLL, a readable assembly language that maps cleanly to Malbolge's execution model.
Requirements: gcc, flex, bison, make
make
The resulting binary is placed at ./lmao.
To install system-wide (default prefix /usr):
make install
To uninstall:
make uninstall
To generate Doxygen API documentation (requires doxygen):
make doc
lmao [options] <input.hell>
Options:
-o <file> Write output to <file> (default: replace .hell extension
with .mb, or append .mb if no extension is found).
-f Fast mode: place the HeLL program at the end of the
Malbolge address space. Produces larger output files but
compiles faster. Automatically retries in normal mode if
a memory conflict occurs.
-l <number> Insert a newline after every <number> characters in the
output file (useful for editors with line-length limits).
-d Write a debug information file alongside the output (same
name as the output file with a .dbg extension). The .dbg
file is used by HeLL IDE.
Examples:
lmao example_simple_cat.hell
lmao -f -o cat.mb example_simple_cat.hell
lmao -d -o cat.mb example_simple_cat.hell
HeLL source files consist of two kinds of sections: .CODE and .DATA. Both
kinds of sections may appear multiple times and in any order. Within each
section, declarations are grouped into blocks.
A block is a contiguous group of memory cells. Blocks are separated from one
another by a blank line (a line that contains no text, not even a comment).
Alternatively, a block can be explicitly delimited by curly braces { }.
.DATA
LABEL1:
42 0 0
LABEL2:
1 2 3
{ LABEL3: 7 8 9 } ; braces make this a self-contained block
Every block must begin with at least one label. Labels are identifiers (letters, digits, underscore; must start with a letter or underscore) followed by a colon.
Placing .OFFSET <value> or @<value> at the start of a block forces its
first cell to the given Malbolge address. Only use this when the exact
position matters (e.g., for the CARRY label described below).
.CODE
@C21
CARRY: Nop
; line comment
% line comment
# line comment
// line comment
/* multi-line
comment */
The data section must define a label named ENTRY. When the assembled Malbolge
program starts execution, the D register points at the ENTRY cell. The C
register points at a Jmp instruction. The value of the A register is
unspecified.
Each entry in a .DATA block is a single memory cell. Its value is an
arithmetic expression that may reference labels.
42 decimal integer (0 – 59048)
0t1210 ternary integer (digits 0-2; up to 10 ternary digits)
'A' ASCII character literal; escape sequences: \n \r \t \\ \' \0
C0 0t0000000000 = 0
C1 0t1111111111 = 29524
C2 0t2222222222 = 59048
C20 0t2222222220 = 59046
C21 0t2222222221 = 59047
EOF synonym for C2 (the value stored in A on end-of-file)
Operators are evaluated with standard precedence. Parentheses are supported for grouping. All values are 10-trit (modulo C2+1 = 59049).
a + b addition
a - b subtraction
a * b multiplication
a / b integer division
a >> n tritwise rotate right by n positions (0 ≤ n < 10)
a << n tritwise rotate left by n positions (0 ≤ n < 10)
a ! b Malbolge crazy operator (tritwise: see malbolge.h)
Example: compute C2 minus 1 using operators:
C2 - 1
Writing a label name in a .DATA cell stores the Malbolge address of that
label minus 1. The minus-1 adjustment compensates for the automatic D-register
increment that Malbolge performs after every instruction: a Jmp or MovD
that loads this address will therefore land on the correct cell.
SOME_LABEL address of SOME_LABEL minus 1
R_LABEL is equivalent to LABEL + 1, i.e., the address of the cell
immediately following LABEL. It is used when a code cell needs to restore
itself by transforming it to the next entry in its xlat2 cycle.
R_SOME_LABEL address of SOME_LABEL (no minus-1 adjustment)
U_TARGET ANCHOR stores the address of TARGET (which must reside in the
.CODE section) minus the distance from the current position to ANCHOR
(which must follow later in the current .DATA section). This is used to
create a destination jump address for the C register that, when the D register
has been incremented to ANCHOR, will point exactly at the cell TARGET. Any
cells preceding TARGET are automatically filled with RNop instructions
(see the .CODE section below).
.DATA
EXAMPLE:
U_OPR VALUE ; address of OPR minus distance to VALUE, i.e. address of OPR minus 3
SOME_LABEL
SOME_OTHER_LABEL
VALUE:
C1
.CODE
OPR:
Opr/Nop
Jmp
? Reserve this cell; its initial value is unimportant.
?- Mark this cell as unused. LMAO may reuse its address.
Never write a label before ?- (use ? instead).
"Hello" expands to 'H' 'e' 'l' 'l' 'o'
"AB", 0 expands to 'A' 0 'B' (separator between chars)
Supported escape sequences: \n \r \t \\ \" \0
Each entry in a .CODE block is an xlat2 cycle description. An xlat2 cycle
describes how a memory cell's effective Malbolge command changes each time the
cell is executed.
A single command that is executed once and then mutates:
Jmp
MovD
Opr
Rot
Out
In
Hlt
Nop
These cells change their effective opcode every time they execute. In most HeLL programs each such cell is only executed once. The opcode the cell transforms into after execution is unspecified.
A sequence of commands separated by / describes the complete sequence of
opcodes the cell will produce on successive executions:
Jmp/Nop/Nop ; first execution: Jmp; second: Nop; third: Nop; repeat
The cycle is closed: after the last command, the cell's xlat2-encrypted value will equal the first command again. Note that some xlat2 cycles are impossible in Malbolge; LMAO will report an error if this is the case.
RNop ; abbreviation for Nop/Nop
A loop-resistant NOP is a cell that acts as a Nop on every execution. It is
guaranteed to exist at every memory position.
LMAO translates a HeLL source file in several phases:
-
Parsing: The lexer/parser reads the source and builds internal representations of all
.CODEand.DATAblocks, with labels resolved to block pointers. -
Prefix resolution:
U_references are resolved; virtualRNopcells are synthesized before referenced code blocks as needed. -
Memory layout: Each block is assigned an absolute Malbolge address. Code blocks are placed at positions that satisfy the xlat2 position constraint (the start character must be in ASCII range 33–126 and encode the correct opcode for that address). Blocks with explicit
.OFFSETdirectives are placed first. -
Initialization code generation: LMAO generates a sequence of Malbolge instructions that writes every
.DATAand runtime-initialized.CODEcell to its desired value. This sequence uses a pre-compiled constant-generation data module (seedatamodule.txt) to load arbitrary values into the A register, jump to the target addresses, and write the value there via theOprinstruction. -
Output: The final Malbolge source is written to the output file. The pre-compiled code for data module creation is followed by the generated instruction sequence and the RQ terminator. The latter ensures that cells after the end of the Malbolge program are initialized with the correct values by the Malbolge interpreter.
The following example programs ship with LMAO, ordered by complexity:
example_simple_cat.hell Infinite echo loop (cat)
example_simple_hello_world.hell Print "Hello, World!\n" (no loops)
example_hello_world.hell Print "Hello, World!\n" (using loops)
example_cat_halt_on_eof.hell Cat that halts cleanly on EOF
example_digital_root.hell Read a decimal number, print its digital root
example_adder.hell Read two 3-digit numbers, print their sum
To compile and run an example (requires a Malbolge interpreter):
lmao example_simple_cat.hell
malbolge example_simple_cat.mb
- Beginner's tutorial — walks through
example_simple_cat.hellstep by step - HeLL IDE — graphical debugger for HeLL programs
- HeLL IDE online
- Original Malbolge specification
LMAO is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Copyright (C) 2013-2026 Matthias Lutter matthias@lutter.cc