(require 'brainfuck)
(defun brainfuck-test (program output &optional input)
"Run PROGRAM through the BRAINFUCK interpreter and compiler and compare their outputs to OUTPUT.
\nOptional INPUT parameter will be used for the input stream."
(cl-labels ((test-output (output)
(set-buffer "*brainfuck-output*")
(let ((buffer-str (buffer-substring-no-properties (point-min) (point-max))))
(when (> (length buffer-str) (length output))
(setq output (concat output "\n")))
(assert (string= buffer-str output))
(delete-window))))
(save-excursion
(with-temp-buffer
(insert program)
(brainfuck-interpret-run input)
(test-output output))
(with-temp-buffer
(insert program)
(brainfuck-run input)
(test-output output)))))
(assert (equal '(:greater-than :less-than :plus :minus :period :comma :left-bracket :right-bracket)
(brainfuck-parse-statement "><+-.,[]")))
(assert (condition-case err
(equal '(:na)
(brainfuck-parse-statement "><+-.,[[]"))
('error t)))
(assert (condition-case err
(equal '(:na)
(brainfuck-parse-statement "><+-.,[]]"))
('error t)))
(assert (equal "A" (brainfuck-interpret (brainfuck-parse-statement "+++++[>+++++++++++++[>+<-]<-]>>."))))
(assert (equal "ABC" (brainfuck-interpret (brainfuck-parse-statement "+++++[>+++++++++++++[>+<-]<-]>>.+.+."))))
(assert (equal "XYZ" (brainfuck-interpret (brainfuck-parse-statement ",.+.+.") "X")))
(assert (equal "BCD" (brainfuck-interpret (brainfuck-parse-statement ",+.>,+.>,+.") "ABC")))
(assert (equal "Hello World!\n" (brainfuck-interpret (brainfuck-parse-statement "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."))))
(assert (equal "A" (funcall (brainfuck-compile (brainfuck-parse-statement "+++++[>+++++++++++++[>+<-]<-]>>.")))))
(assert (equal "ABC" (funcall (brainfuck-compile (brainfuck-parse-statement "+++++[>+++++++++++++[>+<-]<-]>>.+.+.")))))
(assert (equal "XYZ" (funcall (brainfuck-compile (brainfuck-parse-statement ",.+.+.")) "X")))
(assert (equal "BCD" (funcall (brainfuck-compile (brainfuck-parse-statement ",+.>,+.>,+.")) "ABC")))
(assert (equal "Hello World!\n" (funcall (brainfuck-compile (brainfuck-parse-statement "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.")))))
(brainfuck-test
"
[
This program prints 'Hello World!' and a newline to the screen, its length
is 106 active command characters. [It is not the shortest.]
This loop is a 'comment loop', a simple way of adding a comment to a BF
program such that you don't have to worry about any command characters.
Any '.', ',', '+', '-', '<' and '>' characters are simply ignored, the '['
and ']' characters just have to be balanced. This loop and the commands
it contains are ignored because the current cell defaults to a value of 0;
the 0 value causes this loop to be skipped.
]
+++++ +++ Set Cell #0 to 8
[
>++++ Add 4 to Cell #1; this will always set Cell #1 to 4
[ as the cell will be cleared by the loop
>++ Add 2 to Cell #2
>+++ Add 3 to Cell #3
>+++ Add 3 to Cell #4
>+ Add 1 to Cell #5
<<<<- Decrement the loop counter in Cell #1
] Loop till Cell #1 is zero; number of iterations is 4
>+ Add 1 to Cell #2
>+ Add 1 to Cell #3
>- Subtract 1 from Cell #4
>>+ Add 1 to Cell #6
[<] Move back to the first zero cell you find; this will
be Cell #1 which was cleared by the previous loop
<- Decrement the loop Counter in Cell #0
] Loop till Cell #0 is zero; number of iterations is 8
The result of this is:
Cell No : 0 1 2 3 4 5 6
Contents: 0 0 72 104 88 32 8
Pointer : ^
>>. Cell #2 has value 72 which is 'H'
>---. Subtract 3 from Cell #3 to get 101 which is 'e'
+++++++..+++. Likewise for 'llo' from Cell #3
>>. Cell #5 is 32 for the space
<-. Subtract 1 from Cell #4 for 87 to give a 'W'
<. Cell #3 was set to 'o' from the end of 'Hello'
+++.------.--------. Cell #3 for 'rl' and 'd'
>>+. Add 1 to Cell #5 gives us an exclamation point
>++. And finally a newline from Cell #6
"
"Hello World!
")
(brainfuck-test
"
[
This program enciphers its input with the ROT13 cipher. To do this, it
must map characters A-M (ASCII 65-77) to N-Z (78-90), and vice versa.
Also it must map a-m (97-109) to n-z (110-122) and vice versa. It must
map all other characters to themselves ; it reads characters one at a time
and outputs their enciphered equivalents until it reads an EOF (here
assumed to be represented as either -1 or 'no change'), at which point the
program terminates.
The basic approach used is as follows. Calling the input character x,
divide x-1 by 32, keeping quotient and remainder. Unless the quotient is
2 or 3, just output x, having kept a copy of it during the division. If
the quotient is 2 or 3, divide the remainder ((x-1) modulo 32) by 13; if
the quotient here is 0, output x+13; if 1, output x-13; if 2, output x.
Regarding the division algorithm, when dividing y by z to get a quotient q
and remainder r, there is an outer loop which sets q and r first to the
quotient and remainder of 1/z, then to those of 2/z, and so on; after it
has executed y times, this outer loop terminates, leaving q and r set to
the quotient and remainder of y/z. (The dividend y is used as a
diminishing counter that controls how many times this loop is executed.)
Within the loop, there is code to increment r and decrement y, which is
usually sufficient; however, every zth time through the outer loop, it is
necessary to zero r and increment q. This is done with a diminishing
counter set to the divisor z; each time through the outer loop, this
counter is decremented, and when it reaches zero, it is refilled by moving
the value from r back into it.
]
-,+[ Read first character and start outer character reading loop
-[ Skip forward if character is 0
>>++++[>++++++++<-] Set up divisor (32) for division loop
(MEMORY LAYOUT: dividend copy remainder divisor quotient zero zero)
<+<-[ Set up dividend (x minus 1) and enter division loop
>+>+>-[>>>] Increase copy and remainder / reduce divisor / Normal case: skip forward
<[[>+<-]>>+>] Special case: move remainder back to divisor and increase quotient
<<<<<- Decrement dividend
] End division loop
]>>>[-]+ End skip loop; zero former divisor and reuse space for a flag
>--[-[<->+++[-]]]<[ Zero that flag unless quotient was 2 or 3; zero quotient; check flag
++++++++++++<[ If flag then set up divisor (13) for second division loop
(MEMORY LAYOUT: zero copy dividend divisor remainder quotient zero zero)
>-[>+>>] Reduce divisor; Normal case: increase remainder
>[+[<+>-]>+>>] Special case: increase remainder / move it back to divisor / increase quotient
<<<<<- Decrease dividend
] End division loop
>>[<+>-] Add remainder back to divisor to get a useful 13
>[ Skip forward if quotient was 0
-[ Decrement quotient and skip forward if quotient was 1
-<<[-]>> Zero quotient and divisor if quotient was 2
]<<[<<->>-]>> Zero divisor and subtract 13 from copy if quotient was 1
]<<[<<+>>-] Zero divisor and add 13 to copy if quotient was 0
] End outer skip loop (jump to here if ((character minus 1)/32) was not 2 or 3)
<[-] Clear remainder from first division if second division was skipped
<.[-] Output ROT13ed character from copy and clear it
<-,+ Read next character
] End character reading loop
"
"NOPQRSTUVWXYZABCDEFGHIJKLM"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ")