diff --git a/add.bf b/add.bf new file mode 100644 index 0000000..1187314 --- /dev/null +++ b/add.bf @@ -0,0 +1,20 @@ +++ Cell c0 = 2 +> +++++ Cell c1 = 5 + +[ Start your loops with your cell pointer on the loop counter (c1 in our case) +< + Add 1 to c0 +> - Subtract 1 from c1 +] End your loops with the cell pointer on the loop counter + +At this point our program has added 5 to 2 leaving 7 in c0 and 0 in c1 +but we cannot output this value to the terminal since it is not ASCII encoded! + +To display the ASCII character "7" we must add 48 to the value 7 +48 = 6 * 8 so let's use another loop to help us! + +++++ ++++ c1 = 8 and this will be our loop counter again +[ +< +++ +++ Add 6 to c0 +> - Subtract 1 from c1 +] +< . Print out c0 which has the value 55 which translates to "7"! diff --git a/hello.bf b/hello.bf index 38d8f17..4720bed 100644 --- a/hello.bf +++ b/hello.bf @@ -1,44 +1,21 @@ -[ 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 an "initial 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. ++++++ +++++ initialize counter (cell #0) to 10 +[ use loop to set 70/100/30/10 + > +++++ ++ add 7 to cell #1 + > +++++ +++++ add 10 to cell #2 + > +++ add 3 to cell #3 + > + add 1 to cell #4 +<<<< - decrement counter (cell #0) ] -++++++++ 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 - +> ++ . print 'H' +> + . print 'e' ++++++ ++ . print 'l' +. print 'l' ++++ . print 'o' +> ++ . print ' ' +<< +++++ +++++ +++++ . print 'W' +> . print 'o' ++++ . print 'r' +----- - . print 'l' +----- --- . print 'd' +> + . print '!' +> . print '\n' diff --git a/main.rkt b/main.rkt index e0f7a8a..ff98b7f 100644 --- a/main.rkt +++ b/main.rkt @@ -24,8 +24,8 @@ ; - "-" (sub1) ; - "." (out) ; - "," -; - "[" -; - "]" +; - "[" (loop-start) +; - "]" (loop-end) ; Interpretation: the brainf*ck program. ; A InstructionPointer (IP) is a NonNegInt @@ -51,7 +51,8 @@ (world-ip w))) ; string->program: String -> Program -; Given a string, returns a b(world-dp-len w)f program without any invalid character +; Given a string, returns a b(world-dp-len w)f program without +; any invalid character (define (string->program s) (local [; valid-char: 1String -> Boolean ; Given a valid-char, returns #t if the character is a valid bf @@ -113,6 +114,14 @@ (world-program w) (add1 (world-ip w)))) +; Tests for exec-add1 +(check-expect (exec-add1 (make-world + (list 1 2 3 4 5 6 7) 3 7 "" "+" 0)) + (make-world (list 1 2 3 5 5 6 7) 3 7 "" "+" 1)) +(check-expect (exec-add1 (make-world + (list 255 1 2 3) 0 4 "" "+" 0)) + (make-world (list 0 1 2 3) 0 4 "" "+" 1)) + ; exec-sub1: World -> World ; Given a world, returns a new world with the - instruction executed (define (exec-sub1 w) @@ -123,6 +132,14 @@ (world-program w) (add1 (world-ip w)))) +; Tests for exec-sub1 +(check-expect (exec-sub1 (make-world + (list 1 2 3 4 5 6 7) 3 7 "" "-" 0)) + (make-world (list 1 2 3 3 5 6 7) 3 7 "" "-" 1)) +(check-expect (exec-sub1 (make-world + (list 0 1 2 3) 0 4 "" "-" 0)) + (make-world (list 255 1 2 3) 0 4 "" "-" 1)) + ; exec-tape-left: World -> World ; Given a world, returns a new world with the < instruction executed (define (exec-tape-left w) @@ -135,13 +152,18 @@ (world-program w) (add1 (world-ip w))))) +; Tests for exec-tape-left +(check-error (exec-tape-left (make-world (list 1 2 3) 0 3 "" "<" 0))) +(check-expect (exec-tape-left (make-world (list 1 2 3) 2 3 "" "<" 0)) + (make-world (list 1 2 3) 1 3 "" "<" 1)) + ; exec-tape-right: World -> World ; Given a world, returns a new world with the > instruction executed (define (exec-tape-right w) (local [(define end-of-tape (= (world-dp w) (sub1 (world-tape-len w))))] (make-world (if end-of-tape - (cons (world-tape w) 0) + (append (world-tape w) (list 0)) (world-tape w)) (add1 (world-dp w)) (if end-of-tape @@ -151,6 +173,12 @@ (world-program w) (add1 (world-ip w))))) +; Tests for exec-tape-right +(check-expect (exec-tape-right (make-world (list 1 2 3) 0 3 "" ">" 0)) + (make-world (list 1 2 3) 1 3 "" ">" 1)) +(check-expect (exec-tape-right (make-world (list 0 1 2) 2 3 "" ">" 0)) + (make-world (list 0 1 2 0) 3 4 "" ">" 1)) + ; exec-out: World -> World ; Given a world, returns a new world with the . instruction executed (define (exec-out w) @@ -165,10 +193,39 @@ (world-program w) (add1 (world-ip w)))) -; find-first: 1String String Nat -> Nat -(define (find-first char string start) - (cond [(string=? (substring string start (add1 start)) char) start] - [else (find-first char string (add1 start))])) +; Tests for exec-out +(check-expect (exec-out (make-world (list 50) 0 1 "" ".[->+<]" 0)) + (make-world (list 50) 0 1 "2" ".[->+<]" 1)) +(check-expect (exec-out (make-world (list 65) 0 1 "" ".[->+<]" 0)) + (make-world (list 65) 0 1 "A" ".[->+<]" 1)) + +; char-at: String Nat -> 1String +; Given a string and an index, returns the 1String at the position pointed by +; index +(define (char-at s i) + (substring s i (add1 i))) + +; Tests for char-at +(check-expect (char-at "malusa" 2) "l") +(check-error (char-at "another string" 300)) + +; find-first: 1String String Nat (Nat -> Nat) -> Nat +; Given a 1String `char`, a String `string` and a start index, returns +; the position of the first occurrence of `char` in `string` starting from the +; index walking the string changing the index using the `walk` function. +(define (find-first char string start walk) + (cond [(string=? (substring string start (add1 start)) + char) start] + [else (find-first char string (walk start) walk)])) + +; Tests for find-first +(check-expect (find-first "]" "[++++++---->><-]++++]+--" 0 add1) + 15) +(check-expect (find-first "]" "[+++++][+---->><-]++++]+--" 7 add1) + 17) +(check-expect (find-first "[" "[+++++][+---->><-]++++]+--" 17 sub1) + 7) + ; exec-loop-start: World -> World ; Given a world, returns a new world with the [ instruction executed @@ -181,22 +238,75 @@ (world-output w) (world-program w) (add1 (if jump - (find-first "]" (world-program w) (world-ip w)) + (find-first "]" (world-program w) (world-ip w) add1) (world-ip w)))))) -; Tests for exec-out -(check-expect (exec-out (make-world (list 50) 0 1 "" ".[->+<]" 0)) - (make-world (list 50) 0 1 "2" ".[->+<]" 1)) -(check-expect (exec-out (make-world (list 65) 0 1 "" ".[->+<]" 0)) - (make-world (list 65) 0 1 "A" ".[->+<]" 1)) +; Tests for exec-loop-start +(check-expect (exec-loop-start + (make-world '(0) 0 1 "" "[++--]++--+-[]" 0)) + (make-world '(0) 0 1 "" "[++--]++--+-[]" 6)) +(check-expect (exec-loop-start + (make-world '(1) 0 1 "" "[++--]++--+-[]" 0)) + (make-world '(1) 0 1 "" "[++--]++--+-[]" 1)) + +; exec-loop-end: World -> World +; Given a world, returns a new world with the ] instruction executed +(define (exec-loop-end w) + (local [(define jump (not (zero? (list-ref (world-tape w) (world-dp w)))))] + (make-world + (world-tape w) + (world-dp w) + (world-tape-len w) + (world-output w) + (world-program w) + (add1 (if jump + (find-first "[" (world-program w) (world-ip w) sub1) + (world-ip w)))))) + +; Tests for exec-loop-end +(check-expect (exec-loop-end + (make-world '(0) 0 1 "" "[++--]++--+-[]" 5)) + (make-world '(0) 0 1 "" "[++--]++--+-[]" 6)) +(check-expect (exec-loop-end + (make-world '(1) 0 1 "" "[++--]++--+-[]" 5)) + (make-world '(1) 0 1 "" "[++--]++--+-[]" 1)) + +; execute: World -> World +; Given an initial World state, returns the final World state executing the +; program. +(define (execute w) + (local [(define program-len (string-length (world-program w)))] + (cond [(>= (world-ip w) program-len) w] + [else + (local [(define inst + (char-at (world-program w) (world-ip w)))] + (execute (cond [(string=? inst "+") (exec-add1 w)] + [(string=? inst "-") (exec-sub1 w)] + [(string=? inst "<") (exec-tape-left w)] + [(string=? inst ">") (exec-tape-right w)] + [(string=? inst "[") (exec-loop-start w)] + [(string=? inst "]") (exec-loop-end w)] + [(string=? inst ".") (exec-out w)] + [(string=? inst ",") ...])))]))) + +; Tests for execute +(check-expect (execute (make-world (list 0) 0 3 "" "" 0)) + (make-world (list 0) 0 3 "" "" 0)) +; assert that 5+2 to ASCII = "7" (WTF) +(check-expect (execute (make-world (list 0) 0 1 "" + "++>+++++[<+>-]++++++++[<++++++>-]<." 0)) + (make-world (list 55 0) 0 2 "7" + "++>+++++[<+>-]++++++++[<++++++>-]<." 35)) + ; main: Filename -> World ; Given a Filename, return the last world state of execution of the ; corresponding bf program in the file (define (main file) - (program->world (string->program (read-file file)))) + (execute (program->world (string->program (read-file file))))) ; TODO: +; fix matching bracket bug ; - big-bang: ; - - - - - - inital state (make-world (list 0) 0 "" 0) ; - - - - - - on-tick fetch, decode, execute