47 lines
1.6 KiB
Markdown
47 lines
1.6 KiB
Markdown
|
# Brainfuck2Scheme
|
||
|
|
||
|
A simple compiler from Brainfuck to R5RS. The compiler will output a Scheme
|
||
|
list that is a lambda with two arguments:
|
||
|
|
||
|
* `data`: The data vector.
|
||
|
* `dptr`: The initial data pointer.
|
||
|
|
||
|
To turn the list into an executable Scheme function, just give it to
|
||
|
`eval`.
|
||
|
|
||
|
## 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))`
|
||
|
|
||
|
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.
|