DrBrainf-ck/main.rkt

343 lines
11 KiB
Racket
Raw Normal View History

2018-12-01 21:06:12 +00:00
#lang racket
2018-11-21 14:27:15 +00:00
2018-12-01 21:06:12 +00:00
; 2018-11-21 - Made by Claudio Maggioni - Tommaso Rodolfo Masera
2018-11-21 14:27:15 +00:00
; easybf
2018-12-01 21:06:12 +00:00
(require racket/base)
(require racket/struct)
(require test-engine/racket-tests)
2018-11-27 11:31:22 +00:00
(require 2htdp/batch-io)
(require 2htdp/image)
(require 2htdp/universe)
2018-11-21 14:27:15 +00:00
2018-12-01 21:06:12 +00:00
(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)
2018-11-21 14:27:15 +00:00
; A Byte is an Int between 0 and 255
; Interpretation: a byte in decimal notation.
; A Tape is a NEList<Byte>
; 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.
2018-11-21 14:27:15 +00:00
; A Program is a String of:
2018-11-28 13:37:02 +00:00
; - ">" (tape-right)
; - "<" (tape-left)
; - "+" (add1)
; - "-" (sub1)
; - "." (out)
2018-11-21 14:27:15 +00:00
; - ","
2018-11-30 12:18:59 +00:00
; - "[" (loop-start)
; - "]" (loop-end)
2018-11-21 14:27:15 +00:00
; Interpretation: the brainf*ck program.
; A InstructionPointer (IP) is a NonNegInt
; Interpretation: a pointer to the instruction to execute.
2018-12-01 21:06:12 +00:00
; A World is a (world tape dp output program ip) where:
2018-11-21 14:27:15 +00:00
; - tape: Tape
; - dp: DataPointer
2018-11-28 13:37:02 +00:00
; - tape-len: Nat
2018-11-21 14:27:15 +00:00
; - output: String
; - program: Program
; - ip: InstructionPointer
; Interpretation: the current state of execution of a brainf*ck program.
2018-12-01 21:06:12 +00:00
(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)))))])
2018-11-21 14:27:15 +00:00
; Template function for World
2018-12-01 21:06:12 +00:00
#;(define (fn-for-world w)
#; (... (world-tape w)
#; (world-dp w)
#; (world-tape-len w)
#; (world-output w)
#; (world-program w)
#; (world-ip w)))
2018-11-21 14:27:15 +00:00
2018-11-27 11:31:22 +00:00
; string->program: String -> Program
2018-11-30 12:18:59 +00:00
; Given a string, returns a b(world-dp-len w)f program without
; any invalid character
2018-11-27 11:31:22 +00:00
(define (string->program s)
2018-12-01 21:06:12 +00:00
(local [; valid-char: Char -> Boolean
2018-11-27 11:31:22 +00:00
; Given a valid-char, returns #t if the character is a valid bf
; instruction.
(define (valid-char? s)
2018-12-01 21:06:12 +00:00
(ormap (lambda (x) (char=? s x))
'(#\> #\< #\+ #\- #\, #\. #\[ #\])))]
(list->string (filter valid-char? (string->list s)))))
2018-11-27 11:31:22 +00:00
; 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)
2018-12-01 21:06:12 +00:00
(world (cons 0 '()) 0 1 "" p 0))
2018-11-27 11:31:22 +00:00
; Tests for program->world
2018-12-01 21:06:12 +00:00
(check-expect (program->world "[->+<]") (world (list 0) 0 1 "" "[->+<]" 0))
2018-11-27 11:31:22 +00:00
; 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))]
2018-11-28 13:37:02 +00:00
[else (cons (first tape) (tape-help (rest tape) (sub1 dp) alter))]))
2018-11-27 11:31:22 +00:00
; 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)
2018-12-01 21:06:12 +00:00
(world (tape-help (world-tape w) (world-dp w) add1-byte)
2018-11-27 11:31:22 +00:00
(world-dp w)
2018-11-28 13:37:02 +00:00
(world-tape-len w)
2018-11-27 11:31:22 +00:00
(world-output w)
(world-program w)
(add1 (world-ip w))))
2018-11-30 12:18:59 +00:00
; Tests for exec-add1
2018-12-01 21:06:12 +00:00
(check-expect (exec-add1 (world
2018-11-30 12:18:59 +00:00
(list 1 2 3 4 5 6 7) 3 7 "" "+" 0))
2018-12-01 21:06:12 +00:00
(world (list 1 2 3 5 5 6 7) 3 7 "" "+" 1))
(check-expect (exec-add1 (world
2018-11-30 12:18:59 +00:00
(list 255 1 2 3) 0 4 "" "+" 0))
2018-12-01 21:06:12 +00:00
(world (list 0 1 2 3) 0 4 "" "+" 1))
2018-11-30 12:18:59 +00:00
2018-11-27 11:31:22 +00:00
; exec-sub1: World -> World
2018-11-28 13:37:02 +00:00
; Given a world, returns a new world with the - instruction executed
2018-11-27 11:31:22 +00:00
(define (exec-sub1 w)
2018-12-01 21:06:12 +00:00
(world (tape-help (world-tape w) (world-dp w) sub1-byte)
2018-11-27 11:31:22 +00:00
(world-dp w)
2018-11-28 13:37:02 +00:00
(world-tape-len w)
2018-11-27 11:31:22 +00:00
(world-output w)
(world-program w)
(add1 (world-ip w))))
2018-11-30 12:18:59 +00:00
; Tests for exec-sub1
2018-12-01 21:06:12 +00:00
(check-expect (exec-sub1 (world
2018-11-30 12:18:59 +00:00
(list 1 2 3 4 5 6 7) 3 7 "" "-" 0))
2018-12-01 21:06:12 +00:00
(world (list 1 2 3 3 5 6 7) 3 7 "" "-" 1))
(check-expect (exec-sub1 (world
2018-11-30 12:18:59 +00:00
(list 0 1 2 3) 0 4 "" "-" 0))
2018-12-01 21:06:12 +00:00
(world (list 255 1 2 3) 0 4 "" "-" 1))
2018-11-30 12:18:59 +00:00
2018-11-28 13:37:02 +00:00
; 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")
2018-12-01 21:06:12 +00:00
(world (world-tape w)
2018-11-28 13:37:02 +00:00
(sub1 (world-dp w))
(world-tape-len w)
(world-output w)
(world-program w)
(add1 (world-ip w)))))
2018-11-30 12:18:59 +00:00
; Tests for exec-tape-left
2018-12-01 21:06:12 +00:00
(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))
2018-11-30 12:18:59 +00:00
2018-11-28 13:37:02 +00:00
; 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))))]
2018-12-01 21:06:12 +00:00
(world
2018-11-28 13:37:02 +00:00
(if end-of-tape
2018-11-30 12:18:59 +00:00
(append (world-tape w) (list 0))
2018-11-28 13:37:02 +00:00
(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)))))
2018-11-30 12:18:59 +00:00
; Tests for exec-tape-right
2018-12-01 21:06:12 +00:00
(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))
2018-11-30 12:18:59 +00:00
2018-11-28 13:37:02 +00:00
; exec-out: World -> World
; Given a world, returns a new world with the . instruction executed
(define (exec-out w)
2018-12-01 21:06:12 +00:00
(world
2018-11-28 13:37:02 +00:00
(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))))
2018-11-30 12:18:59 +00:00
; Tests for exec-out
2018-12-01 21:06:12 +00:00
(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))
2018-11-30 12:18:59 +00:00
; 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)
2018-11-28 13:37:02 +00:00
; 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))))]
2018-12-01 21:06:12 +00:00
(world
2018-11-28 13:37:02 +00:00
(world-tape w)
(world-dp w)
(world-tape-len w)
(world-output w)
(world-program w)
(add1 (if jump
2018-11-30 12:18:59 +00:00
(find-first "]" (world-program w) (world-ip w) add1)
2018-11-28 13:37:02 +00:00
(world-ip w))))))
2018-11-30 12:18:59 +00:00
; Tests for exec-loop-start
(check-expect (exec-loop-start
2018-12-01 21:06:12 +00:00
(world '(0) 0 1 "" "[++--]++--+-[]" 0))
(world '(0) 0 1 "" "[++--]++--+-[]" 6))
2018-11-30 12:18:59 +00:00
(check-expect (exec-loop-start
2018-12-01 21:06:12 +00:00
(world '(1) 0 1 "" "[++--]++--+-[]" 0))
(world '(1) 0 1 "" "[++--]++--+-[]" 1))
2018-11-30 12:18:59 +00:00
; 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)))))]
2018-12-01 21:06:12 +00:00
(world
2018-11-30 12:18:59 +00:00
(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
2018-12-01 21:06:12 +00:00
(world '(0) 0 1 "" "[++--]++--+-[]" 5))
(world '(0) 0 1 "" "[++--]++--+-[]" 6))
2018-11-30 12:18:59 +00:00
(check-expect (exec-loop-end
2018-12-01 21:06:12 +00:00
(world '(1) 0 1 "" "[++--]++--+-[]" 5))
(world '(1) 0 1 "" "[++--]++--+-[]" 1))
2018-11-30 12:18:59 +00:00
; 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)]
2018-12-01 21:06:12 +00:00
[(string=? inst ",") #f])))])))
2018-11-30 12:18:59 +00:00
; Tests for execute
2018-12-01 21:06:12 +00:00
(check-expect (execute (world (list 0) 0 3 "" "" 0))
(world (list 0) 0 3 "" "" 0))
2018-11-30 12:18:59 +00:00
; assert that 5+2 to ASCII = "7" (WTF)
2018-12-01 21:06:12 +00:00
(check-expect (execute (world (list 0) 0 1 ""
2018-11-30 12:18:59 +00:00
"++>+++++[<+>-]++++++++[<++++++>-]<." 0))
2018-12-01 21:06:12 +00:00
(world (list 55 0) 0 2 "7"
2018-11-30 12:18:59 +00:00
"++>+++++[<+>-]++++++++[<++++++>-]<." 35))
2018-11-28 13:37:02 +00:00
2018-11-27 11:31:22 +00:00
; main: Filename -> World
; Given a Filename, return the last world state of execution of the
; corresponding bf program in the file
(define (main file)
2018-11-30 12:18:59 +00:00
(execute (program->world (string->program (read-file file)))))
2018-12-01 21:06:12 +00:00
(test)
2018-11-21 14:27:15 +00:00
; TODO:
2018-11-30 12:18:59 +00:00
; fix matching bracket bug
2018-11-21 14:27:15 +00:00
; - big-bang:
2018-12-01 21:06:12 +00:00
; - - - - - - inital state (world (list 0) 0 "" <program> 0)
2018-11-27 11:31:22 +00:00
; - - - - - - on-tick fetch, decode, execute
2018-11-21 14:27:15 +00:00
; - - - - - - on-key for ","
; - - - - - - (on-mouse for stepper)
2018-11-27 11:31:22 +00:00
; - - - - - - (draw-scene for output and tape)