(module bencode mzscheme
(define-syntax %bencode:testeez
(syntax-rules () ((_ x ...)
(error "Tests disabled.")
)))
(define-syntax %bencode:peek-byte
(syntax-rules () ((_ PORT) (peek-byte PORT))))
(define-syntax %bencode:read-byte
(syntax-rules () ((_ PORT) (read-byte PORT))))
(define-syntax %bencode:write-byte
(syntax-rules () ((_ BYTE PORT) (write-byte BYTE PORT))))
(define-syntax %bencode:eat-byte
(syntax-rules ()
((_ PORT) (%bencode:read-byte PORT))))
(define-syntax %bencode:open-output-bytes
(syntax-rules ()
((_) (open-output-bytes))))
(define-syntax %bencode:get-output-bytes
(syntax-rules ()
((_ PORT) (get-output-bytes PORT))))
(define-syntax %bencode:premature-eof-error
(syntax-rules ()
((_) (error "bencoding premature eof"))))
(define-syntax %bencode:invalid-char-error
(syntax-rules ()
((_ CHAR) (error "bencode invalid char:" CHAR))))
(define unbencode-single
(letrec ((do-digits
(lambda (port term num)
(let ((c (%bencode:read-byte port)))
(cond ((eof-object? c) (%bencode:premature-eof-error))
((eqv? term c) num)
(else (case c
((48) (do-digits port term (* 10 num) ))
((49) (do-digits port term (+ (* 10 num) 1)))
((50) (do-digits port term (+ (* 10 num) 2)))
((51) (do-digits port term (+ (* 10 num) 3)))
((52) (do-digits port term (+ (* 10 num) 4)))
((53) (do-digits port term (+ (* 10 num) 5)))
((54) (do-digits port term (+ (* 10 num) 6)))
((55) (do-digits port term (+ (* 10 num) 7)))
((56) (do-digits port term (+ (* 10 num) 8)))
((57) (do-digits port term (+ (* 10 num) 9)))
(else (%bencode:invalid-char-error c))))))))
(do-string
(lambda (port num)
(let ((os (%bencode:open-output-bytes)))
(let loop ((len (do-digits port 58 num)))
(if (zero? len)
(let ((bytes (%bencode:get-output-bytes os)))
(close-output-port os)
bytes)
(let ((b (%bencode:read-byte port)))
(if (eof-object? b)
(%bencode:premature-eof-error)
(begin (%bencode:write-byte b os)
(loop (- len 1)))))))))))
(lambda (port)
(let ((c (%bencode:read-byte port)))
(if (eof-object? c)
#f
(case c
((105) (let ((c (%bencode:peek-byte port)))
(if (eqv? 45 c) (begin (%bencode:eat-byte port)
(- (do-digits port 101 0)))
(do-digits port 101 0))))
((108) (let loop ()
(let ((c (%bencode:peek-byte port)))
(cond ((eof-object? c) (%bencode:premature-eof-error))
((eqv? 101 c) (%bencode:eat-byte port) '())
(else (cons (or (unbencode-single port)
(%bencode:premature-eof-error))
(loop)))))))
((100) (cons
'dictionary
(let loop ()
(let ((c (%bencode:peek-byte port)))
(cond ((eof-object? c)
(%bencode:premature-eof-error))
((eqv? 101 c) (%bencode:eat-byte port) '())
(else
(cons (cons (or (unbencode-single port)
(%bencode:premature-eof-error))
(or (unbencode-single port)
(%bencode:premature-eof-error)))
(loop))))))))
((48) (do-string port 0))
((49) (do-string port 1))
((50) (do-string port 2))
((51) (do-string port 3))
((52) (do-string port 4))
((53) (do-string port 5))
((54) (do-string port 6))
((55) (do-string port 7))
((56) (do-string port 8))
((57) (do-string port 9))
(else (%bencode:invalid-char-error c))))))))
(define (unbencode port)
(let ((obj (unbencode-single port)))
(if obj
(cons obj (unbencode port))
'())))
(define (%bencode:unbencode-string str)
(let* ((port (open-input-string str))
(result (unbencode port)))
(close-input-port port)
result))
(define (%bencode:test)
(%bencode:testeez
"bencode.scm"
(test/equal "" (%bencode:unbencode-string "4:spam") '(#"spam"))
(test/equal "" (%bencode:unbencode-string "i3e") '(3))
(test/equal "" (%bencode:unbencode-string "i-3e") '(-3))
(test/equal "" (%bencode:unbencode-string "i0e") '(0))
(test/equal "" (%bencode:unbencode-string "i123e") '(123))
(test/equal "" (%bencode:unbencode-string "i-123e") '(-123))
(test/equal ""
(%bencode:unbencode-string "l4:spam4:eggse")
'((#"spam" #"eggs")))
(test/equal ""
(%bencode:unbencode-string "d3:cow3:moo4:spam4:eggse")
'((dictionary (#"cow" . #"moo") (#"spam" . #"eggs"))))
(test/equal ""
(%bencode:unbencode-string "d4:spaml1:a1:bee")
'((dictionary (#"spam" . (#"a" #"b")))))
(test/equal ""
(%bencode:unbencode-string
(string-append "4:spami3ei-3ei0ei123ei-123el4:spam4:eggsed3:co"
"w3:moo4:spam4:eggsed4:spaml1:a1:bee"))
'(#"spam" 3 -3 0 123 -123 (#"spam" #"eggs")
(dictionary (#"cow". #"moo") (#"spam" . #"eggs"))
(dictionary (#"spam". (#"a" #"b")))))
))
(provide (all-defined)))