| ;;; Bencode decoder. Converts bencoded strings/streams to CL data | |
| ;;; structures. | |
| ;;; Usage: | |
| ;;; (bencode:decode stream-or-string) | |
| ;;; Bencode dictionaries will be converted to alists and lists will be | |
| ;;; converted to CL lists. | |
| ;;; TODO: error handling. | |
| (in-package :cl-user) | |
| (defpackage bencode | |
| (:use :cl) | |
| (:export :decode)) | |
| (in-package :bencode) | |
| (defparameter *parse-dictionaries-as* 'alist) | |
| (defun read-until (stream char &optional unread-p) | |
| "Read chars from stream and write them to a string until target char. | |
| Unread last char if unread-p is t." | |
| (with-output-to-string (output-stream) | |
| (loop for ch = (read-char stream) | |
| while (not (eql ch char)) | |
| do (write-char ch output-stream)) | |
| (when unread-p | |
| (unread-char char stream)) | |
| output-stream)) | |
| (defun read-string-length (stream) | |
| (parse-integer | |
| (read-until stream #\:))) | |
| (defun read-string (stream length) | |
| (with-output-to-string (output-stream) | |
| (dotimes (i length) | |
| (write-char (read-char stream) output-stream)) | |
| output-stream)) | |
| (defun read-integer (stream) | |
| (parse-integer | |
| (read-until stream #\e))) | |
| (defun read-list (stream) | |
| (loop for ch = (read-char stream nil) | |
| while (and ch (not (eql ch #\e))) | |
| do (unread-char ch stream) | |
| collect (read-value stream))) | |
| (defun read-dict (stream) | |
| (loop for ch = (read-char stream nil) | |
| while (and ch (not (eql ch #\e))) | |
| do (unread-char ch stream) | |
| collect `(,(read-string stream (read-string-length stream)) | |
| ,(read-value stream)))) | |
| (defun read-value (stream) | |
| (let ((ch (read-char stream))) | |
| (case ch | |
| (#\i | |
| (read-integer stream)) | |
| ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) | |
| ;#.(loop for i from 0 to 9 collect (digit-char i)) | |
| (unread-char ch stream) | |
| (read-string stream (read-string-length stream))) | |
| (#\l | |
| (read-list stream)) | |
| (#\d | |
| (read-dict stream))))) | |
| (defgeneric decode (stream-or-string) | |
| (:documentation "Convert bencoded string or stream to CL data structures. | |
| Bencode dictionaries will be converted to alists and lists will be converted to CL lists.")) | |
| (defmethod decode ((string string)) | |
| (decode (make-string-input-stream string))) | |
| (defmethod decode ((stream stream)) | |
| (read-value stream)) |