In this beginner's tutorial, we will write a simple cat program in Malbolge.
The result will be the Malbolge cat program from Esolangs.
We start looking at some screenshots of a cat program in the HeLL IDE.
Afterwards, we will start writing the cat program only using the Malbolge pages of Lou Scheffer
and a tool to convert numbers into the ternary number system.
I.e., you need not install any software in order to follow this tutorial.
The HeLL IDE allows to write and debug Malbolge programs using the assembly language
HeLL.
Before we start with plain Malbolge, we will learn some basic about HeLL.
For that purpose, we take a look at a sample cat program in HeLL.
Remark. You are reading the very first version of this Malbolge tutorial.
I want to improve this tutorial.
So, please send me any comments.
Especially, write me if there is anything unclear or hard to understand,
so that I can improve the affected things.
Contact me even for trivia please, e.g. typos or grammatical errors.
Email: matthias@lutter.cc
Thank you very much!
When writing programs in Malbolge,
we partition the memory into a the code section,
in HeLL introduced by .CODE,
and a data section,
in HeLL introduced by .DATA.
Within these sections, we use labels to address code and data.
Malbolge consists of cyclic self-modifying code.
We can specify such a cycle in the code section by separating its instructions with slashes.
E.g., Nop/MovD is a Nop instruction (no operation) that turns into a MovD instruction after execution.
Afterwards, it turns into a Nop instruction again, and so on.
If no cycle is specified (in the code above, this is the case for the Jmp instructions),
then the corresponding instruction can change in any and every possible way.
Note that there are a lot of restrictions concerning the choice of cycles, of which more later.
The actual program flow happens in the data section.
When LMAO translates
the HeLL source to Malbolge, it must write the code and data into concrete memory cells.
LMAO uses code blocks and data blocks for this purpose.
Everything inside a block is guaranteed to stay consecutive in Malbolge's memory cells.
However, the position of a block will be chosen freely by LMAO.
In HeLL, blocks are divided by an empty line.
Thus, if you try the example above by yourself, you have to take care of the empty line in the code above.
Now let us start the debug mode to execute the program step-by-step in order to learn the functional principle of HeLL and Malbolge programs.
The HeLL IDE changed to the debug mode.
The yellow arrow points to the memory position the virtual Malbolge machine's D register is pointing to.
In the code section, the current position of each instruction cycle is marked bold.
At the beginning, the C register points to a Jmp instruction somewhere outside the HeLL code.
Thus the position of the C register is not marked yet.
Starting with the next step, the C register's position will be marked by a green arrow.
At the bottom of the window we can see a terminal for interactive input and output.
At the right side, we can see the state of the Malbolge registers and memory.
While the C register points to some unknown memory address containing a Jmp/Nop/Nop/... instruction cycle,
the D register points to a memory cell containing the value "IN_OUT - 1".
Effectively, we can see in the left part of the window, that the memory cell should contain the value "IN_OUT".
However, the statement on the right side is the correct one:
Whenever a HeLL program is translated into Malbolge, every reference is decremented by one.
The reason is as follows.
Whenever the Malbolge interpreter has executed an instruction, it encrypts the memory cell the C register points to
and increments the C and D register afterwards. Thus, if a Jmp is executed while the D register points to a memory cell containing the value
IN_OUT - 1, the C register is set to IN_OUT - 1 and incremented, resulting in the C register containing the value IN_OUT.
Note that in this case the memory cell at IN_OUT - 1, but not the memory cell containing the Jmp instruction, is encrypted.
However, to save the user from decrementing every jump address, LMAO does this automatically.
The same holds for modifications of the D register by MovD.
The value of the A register at the beginning of a HeLL program is not defined. Thus, we should not read from the A register until we
wrote something into it (using the Rot or In instruction).
In fact, the A register starts with the value ENTRY - 1 when the current version of LMAO is used.
However, this may change in later versions of LMAO, so we should not rely on this.
Additionally to the registers and memory cells described above, it is possible to watch own expressions.
This is not really necessary for a simple cat program.
However, to demonstrate this feature, we watch the content of the memory cells at MOVD and IN_OUT.
They contain the internal Malbolge representation of the Nop/MovD and In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop cycles.
Later we will see, that the value stored in these memory cells will change whenever the current cycle position is shifted.
Let us execute one single step of the program now.
Note that the C register points to a Jmp instruction while the D register points to a memory cell containing the value IN_OUT"-"1, as you can see at the right side of the window.
After the first step, the code register points at the In instruction while the D register has been incremented
and points to a memory cell with undefined content (indicated by "?-").
Before executing the next step, in which the program will read one byte of user input, we write something into the terminal that can be read by the program.
Let us just write "foo".
Now we can execute the next step that will load the first letter "f" of our input into the A register.
As you can see, the A register holds the value 'f' (ternary 10210) now.
Further, the executed instruction In has been modified, so that the cycle's position is now its first Nop instruction.
The C and D register have been incremented, with the result that the next action of the program is to jump to "R_MOVD - 1".
The meaning of R_MOVD is explained below the following screenshot.
The prefix "R_" means "restore".
However, it is just an other way to write down the addition by one.
Thus, "R_MOVD" is equivalent to "MOVD + 1".
So, the memory cell will contain the position MOVD finally, because LMAO
decrements each reference by one as stated above.
Let's see what this does.
As you can see above, once the jump to MOVD has been executed,
the code register points to a Jmp instruction again,
but the jump instruction behind the MOVD label.
This may seem to be useless, because the code register has already pointed
to a jump instruction before.
The idea is to control Malbolge's instruction modification.
Whenever Malbolge has executed an instruction, the instruction the C register points to will be modified
(before the C and D registers are incremented).
For the jump instruction this means that the instruction at the C register is modified after the jump
has been performed, i.e. the instruction at the destination.
Thus, with HeLL's subtraction of one from references, it looks like the instruction right before
the destination has been modified.
Indeed, at the screenshot above we can see that the active instruction of the Nop/MovD instruction cycle has changed to MovD,
while the Jmp instruction below the IN_OUT label is still functioning (it is still bold).
Restoring a 2-cycle instruction by a specific jump is an essential technique when writing HeLL or Malbolge programs.
This is the reason why the prefix is called "restore".
Okay, let us execute the next step.
The code register points at the MovD instruction and the data register points at the value "ENTRY - 1".
Because both registers will be incremented after executing the instruction, the D register will contain the address of the ENTRY label after the next step.
Indeed, the machine is nearly in its start state again.
The code register points at a Jmp instruction and the Nop/MovD cycle
is in the Nop state again.
The data register points to the entry point ENTRY.
Nonetheless, there are two important differences.
The A register still holds the ASCII value of "f"
and the cycle In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop is now
in the configuration Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop/In.
Let us add a breakpoint for faster running through the program.
The breakpoint is visualized by a red circle.
With that breakpoint, we can run the program straight instead of step-wise.
Whenever it reaches the breakpoint (again), execution will be paused.
So, let us run the program.
The program has reached the breakpoint again.
The cycle behind the IN_OUT label points to the Out instruction now.
Hence, the current state seems to become a bit more interesting than the last one,
so let us step through the program again to watch the Out instruction.
After the first step, the code register jumped to the Out instruction.
Now we can see that the content of the A register has been printed to the terminal.
Further, the Out instruction has been modified.
Let us go through the program faster again.
As expected, we reached the breakpoint again.
After some more iterations, the cycle behind the IN_OUT label is in its initial configuration again.
Now everything is equivalent to the begin. Thus the program will read the next character and print it.
After the whole input has been processed, we should abort the program execution.
Otherwise the program would run for ever, because we have not implemented a break condition.
We will manually translate the HeLL cat program into Malbolge now. This makes sense for simple programs, because this way the code will become more succinct than the code generated by LMAO. Additionally, we can learn how to write programs in pure Malbolge.
We continue with the HeLL syntax for now.
Using the @ operator, we can specify an offset for the subsequent code or data block.
For code blocks, only specific offsets are allowed, depending on the instruction cycles.
We will see the details of this later.
The goal is to replace the references by the absolute memory addresses, so that the HeLL code
approximates real Malbolge code a bit more.
We have replaced the references now.
Note that we have to specify the address of the memory cell
preceding the actual jump destination, as stated above.
It is still possible to run the program in the HeLL IDE.
Before we translate the cat program to Malbolge, we should change a detail.
In Malbolge, every "invalid" instruction is interpreted as a Nop.
Thus, must of the Nops of the instruction cycles written down in HeLL are not "real" Nops.
However, only cycles starting with a "real" instruction can be initialized directly in Malbolge.
It is possible to initialize the other cycles during runtime, and this is what LMAO does.
However, it is much easier to write down the instruction directly.
For that reason, we have replaced the Nop/MovD instruction cycle by a MovD/Nop instruction cycle above.
It is left to place the code and data of the new cat program at an absolute address.
This time we choose addresses at the beginning of the memory.
Choosing addresses in the range of ASCII characters has two advantages.
First and obviously, the Malbolge code need not be padded to fill these memory cells.
Second the references in the data section are in the ASCII range. Thus there is a little
chance that the reference matches a valid Malbolge command and can be initialized directly,
which is much easier than initializing it during runtime (as we will see below).
The address of the data section is chosen a bit arbitrary. The data section must not intersect
with the code section.
Note that it must not intersect with a memory cell directly preceding a code block,
because this memory cell will be modifier whenever the program jumps into the code block
(this behavior has already been explained). If the data section intersects with
such a memory cell, it will cause undefined behavior or even crash the Malbolge reference
interpreter.
The choice of the addresses of the two code blocks are more interesting than the address of the data block.
We have to ensure that the given instruction cycles exist at the corresponding addresses.
We can look up this at Lou Scheffer's website Instruction Cycles in Malbolge.
We can see that there exists a Nop/In/Nop/Out/Nop/Nop/Nop/Nop/Nop cycle at address 37.
Note that the dots indicate invalid instructions that are interpreted as Nops while Nop indicates a valid instruction. Thus, only the non-dot instructions can be initialized directly. If we put an In or an Out instruction at address 37, it matches the desired instruction cycle. We want to start the cycle with an In instruction.
A MovD/Nop cycle exists at address 60 and address 64. Let us choose address 60 randomly.
This leads to the memory layout you have already seen above.
Now it's time to write real Malbolge code. Therefore, we use a further page of Lou Scheffer: Valid Instructions
At first, we place an In instruction at address 37 to get the In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop instruction cycle.
To put the In instruction there – a slash in normalized Malbolge – we have to write a P at that address. For the MovD instruction, a j in normalized Malbolge, we write a J at address 60.
Let us place the Jmp instructions (normalized Malbolge: i) right behind these two instructions.
Now our Malbolge program is as following.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
We have constructed the entire code section already. It remains to initialize the data section. We continue to use Lou Scheffer's valid instructions overview, to identify the memory cells we can directly initialize in the Malbolge code.
We are lucky: The 60 at address 39 and the 38 at address 43 can be initialized directly.
The other two values of the data section – the 36 at address 40 and the 59 at address 42 – must not be written into the Malbolge code directly. We will solve this problem in the following.
Before we do so, let us take a look at our current Malbolge program.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
43 | & | v | 38 (loop) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
We have to initialize the remaining memory cells of the data section now.
Since this cannot be done directly, the memory cells must be initialized during runtime.
Thus, we have to write Malbolge code that initializes the remaining memory cells.
For this task, use-once code is sufficient, i.e. we need not care about instruction cycles.
We need the ternary number system and the Opr instruction now, because data manipulation
in Malbolge is only possible in ternary and by the two instruction Opr and Rot.
For the ternary number system, you may want to use an online calculator. You may also want to look up the Opr instruction in Esolang's Malbolge documentation if you don't know it by heart yet.
Before we write Malbolge code for runtime initialization, we should do some very basic initialization. Every Malbolge program begins with its three registers set to zero. We have to separate the code and data pointer before any Rot or Opr instruction is performed. Otherwise it is very likely that the reference interpreter will crash. We start our Malbolge program with a MovD instruction to separate both pointers (alternatively, a Jmp instruction would also work). A MovD instruction at address 0 is coded by an opening bracket, which has the ASCII value 40. Thus, in the next step, the D register will have the value 41. Our Malbolge program looks as follows now:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
43 | & | v | 38 (loop) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
As stated above, the A register is initialized with zero. This value, resp. 0t1111111111, is very useful, so let us save the A register before we do anything that changes its value. We can safe the register by performing an Opr instruction on a memory cell that does not contain any ternary 2. We can directly use the current address of the D register, address 41, which is perfect for this purpose: It is not used by our HeLL code and it can be initialized with 0t0000001111, which does not contain any ternary 2. Now our Malbolge program looks as follows:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
41 | ( | v | 0t0000001111; will be 0t1111111111 after the 2nd step |
43 | & | v | 38 (loop) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
The first memory cell we will initialize is cell 40. We have to generate the value 36 there – 1100 in ternary.
From Lou Scheffer's valid instructions overview, we know that address 40 can be initialized with 58 – ternary 2011. This value is close to 0t0000001100, because it can be transformed into the desired value by a single Opr with A=0t1111112011.
So, let us write code that sets the A register to 0t1111112011. This can be done by reading 0t0000000200 into the A register and doing a Opr in 0t0000002000. Both are easy tasks since 0t0000002000 is a valid ASCII code.
Our Malbolge code to initialize address 40 is as follows:
Rot 0t0000002000 // afterwards A = 0t0000000200 Opr A into 0t0000002000 // afterwards A = 0t1111112011 Opr A into 0t0000002011 at address 40 // afterwards [40] = 0t0000001100
The D register points at address 42 right now. However, we should not use address 42, because we need to initialize it later and we may want to write a value there that helps us for its initialization (like the value 0t0000002011 that we put at address 40.
However, we are lucky and can initialize address 44 and address 45 with 0t0000002000, which is 54 in the decimal number system).
We can use the Nop instruction to set the D register to 44, because it is incremented after every instruction.
After two Nops, the D register points to address 44, so that we can perform the Rot instruction followed by an Opr instruction on address 45.
Our current Malbolge program:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, should become 0t0000001100 later |
41 | ( | v | 0t0000001111; will be 0t1111111111 after the 2nd step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000200 after the 5th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
The A register contains the value 0t1111112011 now and the D register points to address 46. To complete initialization of address 40, the D register must be moved there for an Opr instruction. Therefore, we write the value 35 at address 46 and move the D register to address 36 by MovD. Afterwards, we add four Nop instructions to reach address 40, where we perform the Opr instruction.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t1111111111 after the 2nd step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000200 after the 5th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
It remains to initialize memory cell 42 with the value 59 – 2012 in ternary. It is possible to initialize cell 42 with 0t0000002002, which can be changed to the target value by an Opr with A = 0t1111111101. 0t1111111101 can be generated by performing an Opr instruction with A = 0t0000000020 and [D] = 0t0000000000. We have written the value 0t1111111111 at address 41 before, which can be changed to 0t0000000000 by an Opr with itself.
Rot 0t1111111111 at address 41 // afterwards A = 0t1111111111 Opr A into 0t1111111111 at address 41 // afterwards [41] = 0t0000000000 Rot 0t0000000200 at address 44 // afterwards A = 0t0000000020 Opr A into 0t0000000000 at address 41 // afterwards A = 0t1111111101 Opr A into 0t0000002002 at address 42 // afterwards [42] = 0t0000002012
We start with setting the value at address 41 to 0t0000000000. At the moment, the D register points to address 41, because the last thing we did was initializing address 40. We can use the value 38 stored at address 43 to move the D register back to address 41 again and write the following code.
// D = 41, [D] = A = 0t1111111111 Rot Nop // D = 43, [D] = 38 MovD // D = 39 Nop Nop // D = 41, [D] = 0t1111111111 Opr
Our Malbolge code is now at follows.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
12 | y | * | Rot |
13 | 7 | o | Nop |
14 | x | j | MovD |
15 | 5 | o | Nop |
16 | 4 | o | Nop |
17 | - | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t0000000000 after the 18th step |
42 | 8 | i | 0t0000002002, should become 0t0000002012 later |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000200 after the 5th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
The D register points to address 42 now. We can rotate 0t0000000200 at address 44 to load 0t0000000020 into the A register, Opr it into address 41 and Opr the result into address 42.
// D = 42 Nop Nop // D = 44, [D] = 0t0000000200 Rot // A = 0t0000000020 Nop // D = 46, [D] = 35 MovD // D = 36 Nop Nop Nop Nop Nop // D = 41, [D] = 0t0000000000 Opr // D = 42, A = 0t1111111101, [D] = 0t0000002002 Opr // [42] = 0t0000002010
Our current Malbolge program:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
12 | y | * | Rot |
13 | 7 | o | Nop |
14 | x | j | MovD |
15 | 5 | o | Nop |
16 | 4 | o | Nop |
17 | - | p | Opr |
18 | 2 | o | Nop |
19 | 1 | o | Nop |
20 | q | * | Rot |
21 | / | o | Nop |
22 | p | j | MovD |
23 | - | o | Nop |
24 | , | o | Nop |
25 | + | o | Nop |
26 | * | o | Nop |
27 | ) | o | Nop |
28 | " | p | Opr |
29 | ! | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t1111111101 after the 29th step |
42 | 8 | i | 0t0000002002, will be 0t0000002012 after the 30th step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000020 after the 21th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
It remains to start the completely initialized HeLL program. Therefore we only need to perform a Jmp instruction while the D register points to the entry point (labeled with ENTRY in HeLL), which is at address 40. At the moment, the D register points to 43. After a MovD instruction, it will point to address 39. Thus, we add the following code:
MovD Nop Jmp
The final Malbolge program looks as follows.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
12 | y | * | Rot |
13 | 7 | o | Nop |
14 | x | j | MovD |
15 | 5 | o | Nop |
16 | 4 | o | Nop |
17 | - | p | Opr |
18 | 2 | o | Nop |
19 | 1 | o | Nop |
20 | q | * | Rot |
21 | / | o | Nop |
22 | p | j | MovD |
23 | - | o | Nop |
24 | , | o | Nop |
25 | + | o | Nop |
26 | * | o | Nop |
27 | ) | o | Nop |
28 | " | p | Opr |
29 | ! | p | Opr |
30 | h | j | MovD |
31 | % | o | Nop |
32 | B | i | Jmp |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t1111111101 after the 29th step |
42 | 8 | i | 0t0000002002, will be 0t0000002012 after the 30th step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000020 after the 21th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
We are lucky that the entire initialization code fits into the memory before address 37.
The only thing that is left to obtain our final Malbolge program is to fill the unused memory cells – addresses 33 to 36 and addresses 47 59 – with arbitrary valid instructions. We randomly choose the Hlt instruction for this purpose and obtain the final Malbolge cat program:
(=BA#9"=<;:3y7x54-21q/p-,+*)"!h%B0/. ~P< <:(8& 66#"!~}|{zyxwvu gJ%
Note that this program does not terminate.
You have learned some very basic Malbolge programming techniques. Below you can find some resources that may help you to improve your Malbolge skills.
Please visit my other Malbolge and Malbolge Unshackled pages!
You may also write me an email: matthias@lutter.cc