2.0 KiB
Brainfuck2Scheme
A simple compiler from Brainfuck to R5RS. The compiler will output a Scheme list that is a lambda with three arguments:
data
: The data vector.dptr
: The initial data pointer.debugger
: Debugger function
To turn the list into an executable Scheme function, just give it to
eval
. (execute scheme len)
will run the Scheme code in scheme
with a data vector of length len
.
This dialect of Brainfuck supports #
to call the debugger
procedure
with data
and dptr
as arguments.
Since Brainfuck programs become Scheme procedures, you can modularize Brainfuck code and (ab)use the debugger for things like procedure calls and foreign libraries.
How It Works
Brainfuck is a very simple Harvard architecture computer. The data is
stored as a vector data
and the data pointer is an integer dptr
.
The big idea is that all the code is compiled to a big lambda form, but
there are some wrinkles due to jumps.
Data access brainfuck instructions are translated like
+
->(vector-set! data dptr (+ 1 (vector-ref data dptr)))
-
->(vector-set! data dptr (+ -1 (vector-ref data dptr)))
>
->(set! dptr (+ dptr 1))
<
->(set! dptr (- dptr 1))
.
->(display (vector-ref data dptr))
,
->(vector-set! data dptr (read-char))
#
->(debugger data dptr)
Branches are trickier. Basically, all code that will be executed in a block
is in a lambda. Given [code...]
, the code...
will be compiled to a
lambda in a letrec
, with a conditional at the end that will tail-call the
lambda if the current data pointer is not zero.
The transformation then goes like
[ CODE ... ] REST ...
->
(letrec ((between (lambda ()
(TRANSLATE CODE ...)
(if (not (zero? (vector-ref data dptr)))
(between)))))
(if (not (zero? (vector-ref data dptr)))
(between)))
(TRANSLATE REST ...)
where (TRANSLATE CODE ...)
translates CODE
to Scheme instructions
like above.