#lang racket
(require rackunit) (provide (all-defined-out))
(define-struct state (data ptr)
#:mutable)
(define (new-state)
(make-state (make-vector 30000 0)
0))
(define (state-ptr-out-of-range? a-state)
(or (>= (state-ptr a-state)
(vector-length (state-data a-state)))
(< (state-ptr a-state) 0)))
(define (detect-range-errors! a-state caller-name stx)
(when (state-ptr-out-of-range? a-state)
(if stx
(raise-syntax-error #f "pointer went out of range of data" stx)
(error caller-name "pointer went out of range of data"))))
(define (increment-ptr a-state [stx #f])
(set-state-ptr! a-state (add1 (state-ptr a-state)))
(detect-range-errors! a-state 'increment-ptr stx))
(define (decrement-ptr a-state [stx #f])
(set-state-ptr! a-state (sub1 (state-ptr a-state)))
(detect-range-errors! a-state 'decrement-ptr stx))
(define (increment-byte a-state [stx #f])
(let ([v (state-data a-state)]
[i (state-ptr a-state)])
(vector-set! v i (add1 (vector-ref v i)))))
(define (decrement-byte a-state [stx #f])
(let ([v (state-data a-state)]
[i (state-ptr a-state)])
(vector-set! v i (sub1 (vector-ref v i)))))
(define (write-byte-to-stdout a-state [stx #f])
(let ([v (state-data a-state)]
[i (state-ptr a-state)])
(write-byte (vector-ref v i) (current-output-port))))
(define (read-byte-from-stdin a-state [stx #f])
(let ([v (state-data a-state)]
[i (state-ptr a-state)])
(vector-set! v i (let ([v (read-byte (current-input-port))])
(if (eof-object? v)
0
v)))))
(define-syntax-rule (loop a-state body ...)
(let loop ()
(unless (= (vector-ref (state-data a-state)
(state-ptr a-state))
0)
body ...
(loop))))
(let ([s (new-state)])
(increment-byte s)
(check-equal? 1 (vector-ref (state-data s) 0))
(increment-byte s)
(check-equal? 2 (vector-ref (state-data s) 0))
(decrement-byte s)
(check-equal? 1 (vector-ref (state-data s) 0)))
(let ([s (new-state)])
(increment-ptr s)
(increment-byte s)
(check-equal? 0 (vector-ref (state-data s) 0))
(check-equal? 1 (vector-ref (state-data s) 1))
(decrement-ptr s)
(increment-byte s)
(check-equal? 1 (vector-ref (state-data s) 0))
(check-equal? 1 (vector-ref (state-data s) 1)))
(let ([s (new-state)])
(parameterize ([current-input-port
(open-input-bytes (bytes 3 1 4))])
(read-byte-from-stdin s)
(increment-ptr s)
(read-byte-from-stdin s)
(increment-ptr s)
(read-byte-from-stdin s))
(check-equal? 3 (vector-ref (state-data s) 0))
(check-equal? 1 (vector-ref (state-data s) 1))
(check-equal? 4 (vector-ref (state-data s) 2)))
(let ([s (new-state)])
(set-state-data! s (vector 80 76 84))
(let ([simulated-stdout (open-output-string)])
(parameterize ([current-output-port simulated-stdout])
(write-byte-to-stdout s)
(increment-ptr s)
(write-byte-to-stdout s)
(increment-ptr s)
(write-byte-to-stdout s))
(check-equal? "PLT" (get-output-string simulated-stdout))))
(let ([s (new-state)])
(set-state-data! s (vector 0 104 101 108 112 109 101 105
109 109 101 108 116 105 110 103 ))
(set-state-ptr! s 15)
(loop s
(loop s (decrement-byte s))
(decrement-ptr s))
(check-equal? 0 (state-ptr s))
(check-equal? (make-vector 16 0) (state-data s)))