#lang racket/base
(require racket/list
rackunit)
(define-struct tstream (elts) #:mutable)
(define (get-tokens in)
(make-tstream
(let loop ()
(let ([next-char (read-char in)])
(cond
[(eof-object? next-char)
empty]
[(member next-char '(#\> #\< #\+ #\- #\. #\, #\[ #\]))
(cons next-char (loop))]
[else
(loop)])))))
(define (next a-tstream)
(cond
[(empty? (tstream-elts a-tstream))
eof]
[else
(let ([next-token (first (tstream-elts a-tstream))])
(set-tstream-elts! a-tstream (rest (tstream-elts a-tstream)))
next-token)]))
(define (peek a-tstream)
(cond
[(empty? (tstream-elts a-tstream))
eof]
[else
(first (tstream-elts a-tstream))]))
(let ([a-tstream (get-tokens (open-input-string "<>"))])
(check-equal? (next a-tstream) #\<)
(check-equal? (next a-tstream) #\>)
(check-equal? (next a-tstream) eof)
(check-equal? (next a-tstream) eof))
(let ([a-tstream (get-tokens (open-input-string "<>"))])
(check-equal? (peek a-tstream) #\<)
(check-equal? (peek a-tstream) #\<)
(check-equal? (next a-tstream) #\<)
(check-equal? (next a-tstream) #\>)
(check-equal? (next a-tstream) eof)
(check-equal? (next a-tstream) eof))
(let ([a-tstream (get-tokens (open-input-string ""))])
(check-equal? (peek a-tstream) eof)
(check-equal? (next a-tstream) eof))
(let ([a-tstream (get-tokens (open-input-string " [ ] + - . ,"))])
(check-equal? (next a-tstream) #\[)
(check-equal? (next a-tstream) #\])
(check-equal? (next a-tstream) #\+)
(check-equal? (next a-tstream) #\-)
(check-equal? (next a-tstream) #\.)
(check-equal? (next a-tstream) #\,)
(check-equal? (next a-tstream) eof))
(define (parse-expr a-tstream)
(let ([next-token (next a-tstream)])
(cond
[(eof-object? next-token)
(error 'parse-expr "unexpected eof")]
[else
(case next-token
[(#\>)
(datum->syntax #f '(increment-data-pointer))]
[(#\<)
(datum->syntax #f '(decrement-data-pointer))]
[(#\+)
(datum->syntax #f '(increment-byte))]
[(#\-)
(datum->syntax #f '(decrement-byte))]
[(#\.)
(datum->syntax #f '(output-byte))]
[(#\,)
(datum->syntax #f '(accept-byte))]
[(#\[)
(let ([inner-exprs (parse-exprs a-tstream)])
(unless (char=? (next a-tstream) #\])
(error 'parse-expr "Expected ']"))
(datum->syntax #f (cons 'loop inner-exprs)))]
[else
(error 'parse-expr)])])))
(define (parse-exprs a-tstream)
(let ([peeked-token (peek a-tstream)])
(cond
[(eof-object? peeked-token)
empty]
[(char=? peeked-token #\])
empty]
[else
(let ([next-expr (parse-expr a-tstream)])
(cons next-expr
(parse-exprs a-tstream)))])))
(define (parse-toplevel a-tstream)
(datum->syntax #f (cons 'toplevel
(parse-exprs a-tstream))))
(let ([tstream (get-tokens (open-input-string "<>+-.,"))])
(check-equal? (syntax->datum (parse-expr tstream))
'(decrement-data-pointer))
(check-equal? (syntax->datum (parse-expr tstream))
'(increment-data-pointer))
(check-equal? (syntax->datum (parse-expr tstream))
'(increment-byte))
(check-equal? (syntax->datum (parse-expr tstream))
'(decrement-byte))
(check-equal? (syntax->datum (parse-expr tstream))
'(output-byte))
(check-equal? (syntax->datum (parse-expr tstream))
'(accept-byte))
(check-exn exn:fail? (lambda () (parse-expr tstream))))
(let ([tstream (get-tokens (open-input-string "[+-]"))])
(check-equal? (syntax->datum (parse-expr tstream))
'(loop (increment-byte) (decrement-byte)))
(check-exn exn:fail? (lambda () (parse-expr tstream))))
(let ([tstream (get-tokens (open-input-string "[>[+-]<]"))])
(check-equal? (syntax->datum (parse-expr tstream))
'(loop (increment-data-pointer)
(loop (increment-byte)
(decrement-byte))
(decrement-data-pointer)))
(check-exn exn:fail? (lambda () (parse-expr tstream))))
(check-exn exn:fail? (lambda () (parse-expr (get-tokens (open-input-string "[")))))
(check-exn exn:fail? (lambda () (parse-expr (get-tokens (open-input-string "]")))))
(check-exn exn:fail? (lambda () (parse-expr (get-tokens (open-input-string "[[]")))))
(check-exn exn:fail? (lambda () (parse-expr (get-tokens (open-input-string "[[[][+[[[[]")))))
(provide parse-toplevel get-tokens peek)