;;; brainfuck-test.el --- Test Suite for brainfuck.el ;; ;;; Copyright (C) 2016 Kyle W T Sherman ;; ;; Author: Kyle W T Sherman <kylewsherman at gmail dot com> ;; Created: 2016-03-19 ;; Version: 1.0 ;; Keywords: brainfuck compiler interpreter mode test ;; ;; This file is not part of GNU Emacs. ;; ;; This is free software; you can redistribute it and/or modify it under the ;; terms of the GNU General Public License as published by the Free Software ;; Foundation; either version 2, or (at your option) any later version. ;; ;; This is distributed in the hope that it will be useful, but WITHOUT ANY ;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ;; details. ;; ;; You should have received a copy of the GNU General Public License along ;; with GNU Emacs; see the file COPYING. If not, write to the Free Software ;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;; ;;; Commentary: ;; ;; Tests `brainfuck.el' functionality. ;; ;;; Installation: ;; ;; Load `brainfuck-test.el': ;; ;; (load "brainfuck-test.el") ;; ;;; Usage: ;; ;; Run tests: ;; ;; (eval-buffer) ;;; Code: ;; brainfuck (require 'brainfuck) ;; assert (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 ;; interpreter test (with-temp-buffer (insert program) (brainfuck-interpret-run input) (test-output output)) ;; compiler test (with-temp-buffer (insert program) (brainfuck-run input) (test-output output))))) ;;; Unit Tests: Parser ;; parse valid statement (assert (equal '(:greater-than :less-than :plus :minus :period :comma :left-bracket :right-bracket) (brainfuck-parse-statement "><+-.,[]"))) ;; parse invalid statement with extra jump forward (assert (condition-case err (equal '(:na) (brainfuck-parse-statement "><+-.,[[]")) ('error t))) ;; parse invalid statement with extra jump back (assert (condition-case err (equal '(:na) (brainfuck-parse-statement "><+-.,[]]")) ('error t))) ;;; Unit Tests: Interpret (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 "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.")))) ;;; Unit Tests: Compile (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 "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."))))) ;;; Functional Tests ;; hello world ;; source: https://en.wikipedia.org/wiki/Brainfuck#Hello_World.21 (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! ") ;; rot-13 ;; source: https://en.wikipedia.org/wiki/Brainfuck#ROT13 (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") ;;; brainfuck-test.el ends here