#lang racket ; 2018-11-21 - Made by Claudio Maggioni - Tommaso Rodolfo Masera ; easybf (require racket/base) (require racket/struct) (require test-engine/racket-tests) (require 2htdp/batch-io) (require 2htdp/image) (require 2htdp/universe) (provide main) (provide world) (provide world?) (provide world-tape) (provide world-dp) (provide world-output) (provide world-program) (provide world-ip) (provide execute) (provide string->program) (provide program->world) ; A Byte is an Int between 0 and 255 ; Interpretation: a byte in decimal notation. ; A Tape is a NEList ; Interpretation: a tape in brainf*ck's Turing machine. ; A DataPointer (DP) is a NonNegInt ; Interpretation: a data pointer in the brainf*ck language in a tape. ; A Program is a String of: ; - ">" (tape-right) ; - "<" (tape-left) ; - "+" (add1) ; - "-" (sub1) ; - "." (out) ; - "," ; - "[" (loop-start) ; - "]" (loop-end) ; Interpretation: the brainf*ck program. ; A InstructionPointer (IP) is a NonNegInt ; Interpretation: a pointer to the instruction to execute. ; A World is a (world tape dp output program ip) where: ; - tape: Tape ; - dp: DataPointer ; - tape-len: Nat ; - output: String ; - program: Program ; - ip: InstructionPointer ; Interpretation: the current state of execution of a brainf*ck program. (struct world (tape dp tape-len output program ip) #:transparent #:methods gen:custom-write [(define write-proc (make-constructor-style-printer (lambda (w) 'world) (lambda (w) (list (world-tape w) (world-dp w) (world-tape-len w) (world-output w) (world-program w) (world-ip w)))))]) ; Template function for World #;(define (fn-for-world w) #; (... (world-tape w) #; (world-dp w) #; (world-tape-len w) #; (world-output w) #; (world-program w) #; (world-ip w))) ; string->program: String -> Program ; Given a string, returns a b(world-dp-len w)f program without ; any invalid character (define (string->program s) (local [; valid-char: Char -> Boolean ; Given a valid-char, returns #t if the character is a valid bf ; instruction. (define (valid-char? s) (ormap (lambda (x) (char=? s x)) '(#\> #\< #\+ #\- #\, #\. #\[ #\])))] (list->string (filter valid-char? (string->list s))))) ; Tests for string->program (check-expect (string->program "hello") "") (check-expect (string->program "+world50-[]") "+-[]") (check-expect (string->program "") "") ; add1-byte: Byte -> Byte ; Given a byte, returns the byte+1 simulating overflows (define (add1-byte b) (modulo (add1 b) 256)) ; Tests for add1-byte (check-expect (add1-byte 255) 0) (check-expect (add1-byte 254) 255) ; sub1-byte: Byte -> Byte ; Given a byte, returns the byte-1 simulating underflows (define (sub1-byte b) (cond [(zero? b) 255] [else (sub1 b)])) ; Tests for sub1-byte (check-expect (sub1-byte 0) 255) (check-expect (sub1-byte 1) 0) ; program->world: Program -> World ; Given a program, returns the corresponding initial world state. (define (program->world p) (world (cons 0 '()) 0 1 "" p 0)) ; Tests for program->world (check-expect (program->world "[->+<]") (world (list 0) 0 1 "" "[->+<]" 0)) ; tape-help: Tape DP (Byte -> Byte) -> Tape ; Given a tape and a data pointer, returns the same tape with the data in the ; location of the data pointer altered by the function `alter`. (define (tape-help tape dp alter) (cond [(zero? dp) (cons (alter (first tape)) (rest tape))] [else (cons (first tape) (tape-help (rest tape) (sub1 dp) alter))])) ; Tests for tape-help (check-expect (tape-help (list 0) 0 add1-byte) (list 1)) (check-expect (tape-help (list 0 1 2 3) 2 sub1-byte) (list 0 1 1 3)) ; exec-add1: World -> World ; Given a world, returns a new world with the + instruction executed (define (exec-add1 w) (world (tape-help (world-tape w) (world-dp w) add1-byte) (world-dp w) (world-tape-len w) (world-output w) (world-program w) (add1 (world-ip w)))) ; Tests for exec-add1 (check-expect (exec-add1 (world (list 1 2 3 4 5 6 7) 3 7 "" "+" 0)) (world (list 1 2 3 5 5 6 7) 3 7 "" "+" 1)) (check-expect (exec-add1 (world (list 255 1 2 3) 0 4 "" "+" 0)) (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) (world (tape-help (world-tape w) (world-dp w) sub1-byte) (world-dp w) (world-tape-len w) (world-output w) (world-program w) (add1 (world-ip w)))) ; Tests for exec-sub1 (check-expect (exec-sub1 (world (list 1 2 3 4 5 6 7) 3 7 "" "-" 0)) (world (list 1 2 3 3 5 6 7) 3 7 "" "-" 1)) (check-expect (exec-sub1 (world (list 0 1 2 3) 0 4 "" "-" 0)) (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) (if (zero? (world-dp w)) (error "Can't access negative tape positions") (world (world-tape w) (sub1 (world-dp w)) (world-tape-len w) (world-output w) (world-program w) (add1 (world-ip w))))) ; Tests for exec-tape-left (check-error (exec-tape-left (world (list 1 2 3) 0 3 "" "<" 0))) (check-expect (exec-tape-left (world (list 1 2 3) 2 3 "" "<" 0)) (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))))] (world (if end-of-tape (append (world-tape w) (list 0)) (world-tape w)) (add1 (world-dp w)) (if end-of-tape (add1 (world-tape-len w)) (world-tape-len w)) (world-output w) (world-program w) (add1 (world-ip w))))) ; Tests for exec-tape-right (check-expect (exec-tape-right (world (list 1 2 3) 0 3 "" ">" 0)) (world (list 1 2 3) 1 3 "" ">" 1)) (check-expect (exec-tape-right (world (list 0 1 2) 2 3 "" ">" 0)) (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) (world (world-tape w) (world-dp w) (world-tape-len w) (string-append (world-output w) (list->string (list (integer->char (list-ref (world-tape w) (world-dp w)))))) (world-program w) (add1 (world-ip w)))) ; Tests for exec-out (check-expect (exec-out (world (list 50) 0 1 "" ".[->+<]" 0)) (world (list 50) 0 1 "2" ".[->+<]" 1)) (check-expect (exec-out (world (list 65) 0 1 "" ".[->+<]" 0)) (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 (define (exec-loop-start w) (local [(define jump (zero? (list-ref (world-tape w) (world-dp w))))] (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) add1) (world-ip w)))))) ; Tests for exec-loop-start (check-expect (exec-loop-start (world '(0) 0 1 "" "[++--]++--+-[]" 0)) (world '(0) 0 1 "" "[++--]++--+-[]" 6)) (check-expect (exec-loop-start (world '(1) 0 1 "" "[++--]++--+-[]" 0)) (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)))))] (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 (world '(0) 0 1 "" "[++--]++--+-[]" 5)) (world '(0) 0 1 "" "[++--]++--+-[]" 6)) (check-expect (exec-loop-end (world '(1) 0 1 "" "[++--]++--+-[]" 5)) (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 ",") #f])))]))) ; Tests for execute (check-expect (execute (world (list 0) 0 3 "" "" 0)) (world (list 0) 0 3 "" "" 0)) ; assert that 5+2 to ASCII = "7" (WTF) (check-expect (execute (world (list 0) 0 1 "" "++>+++++[<+>-]++++++++[<++++++>-]<." 0)) (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) (execute (program->world (string->program (read-file file))))) (test) ; TODO: ; fix matching bracket bug ; - big-bang: ; - - - - - - inital state (world (list 0) 0 "" 0) ; - - - - - - on-tick fetch, decode, execute ; - - - - - - on-key for "," ; - - - - - - (on-mouse for stepper) ; - - - - - - (draw-scene for output and tape)