An esoteric programming language with two mutually modifying Brainfuck-like programs. I've been intrigued by the idea of a programming language where two programs interact with each other (probably inspired by ROCB).
The design goals were to make the language Turing-complete while each of its parts individually are not Turing-complete. Furthermore, even both of them together should not be Turing-complete without making use of source code manipulation. I think I've succeeded with that, but I haven't proven any of those things formally yet.
Brian and Chuck are two Brainfuck-like programs. Only one of them is being executed at any given time, starting with Brian. The catch is that Brian's memory tape is also Chuck's source code. And Chuck's memory tape is also Brian's source code. Furthermore, Brian's tape head is also Chuck's instruction pointer and vice versa. The tapes are semi-infinite (i.e. infinite to the right) and can hold signed arbitrary-precision integers, initialised to zero (unless specified otherwise by the source code).
Since the source code is also a memory tape, commands are technically defined by integer values, but they correspond to reasonable characters. The following commands exist:
,
(44
): Read a character from STDIN into the current memory cell. Only Brian can do this. This command is a no-op for Chuck..
(46
): Write the current memory cell, modulo 256, as a character to STDOUT. Only Chuck can do this. This command is a no-op for Brian.+
(43
): Increment the current memory cell.-
(45
): Decrement the current memory cell.?
(63
): If the current memory cell is zero, this is a no-op. Otherwise, hand control over to the other program. The tape head on the program which uses?
will remain on the?
. The other program's tape head will move one cell to the right before executing the first command (so the cell which is used as the test is not executed itself).<
(60
): Move the tape head one cell to the left. This is a no-op if the tape head is already at the left end of the tape.>
(62
): Move the tape head one cell to the right.{
(123
): Repeatedly move the tape head to the left until either the current cell is zero or the left end of the tape is reached.}
(125
): Repeatedly move the tape head to the right until the current cell is zero.
The program terminates when the active program's instruction pointer reaches a point where there are no more instructions to its right.
The source file is processed as follows:
- If the file contains the string
```
, the file will be split into two parts around the first occurrence of that string. All leading and trailing whitespace is stripped and the first part is used as the source code for Brian and the second part for Chuck. - If the file does not contain this string, the first line of the file will be used as the source for Brian and the second part for Chuck (apart from the delimiting newline, no whitespace will be removed).
- All occurrences of
_
in both programs are replaced with NULL bytes. This allows you to insert zero cells more easily into the source. - The two memory tapes are initialised with the character codes corresponding to the resulting string.
As an example, the following source file
abc
```
0_1
23
Would yield the following initial tapes:
Brian: [97 98 99 0 0 0 0 ...]
Chuck: [48 0 49 10 50 51 0 0 0 0 ...]
The reference interpreter is written in Ruby. It takes two command-line flags (which are not part of the actual language specification):
-d
: With this flag, Brian and Chuck understand two more commands.!
will print a string representation of both memory tapes, the active program being listed first (a^
will mark the current tape heads).@
will also do this, but then immediately terminate the program. Because I'm lazy, neither of those work if they are the last command in the program, so if you want to use them there, repeat them or write a no-op after them.-D
: This is the verbose debug mode. It will print the same debug information as!
after every single tick.@
also works in this mode.