Skip to content
streams.lisp 9.35 KiB
Newer Older
;;;; streams.lisp -- a gray streams interface to octet encoding.

(cl:in-package :binascii)


;;; portability definitions

#+cmu
(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :gray-streams))

;;; TRIVIAL-GRAY-STREAMS has it, we might as well, too...
#+allegro
(eval-when (:compile-toplevel :load-toplevel :execute)
  (unless (fboundp 'stream:stream-write-string)
    (require "streamc.fasl")))

(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *binary-input-stream-class*
  (quote
   #+lispworks stream:fundamental-binary-input-stream
   #+sbcl sb-gray:fundamental-binary-input-stream
   #+openmcl gray:fundamental-binary-input-stream
   #+cmu ext:fundamental-binary-input-stream
   #+allegro excl:fundamental-binary-input-stream
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *binary-output-stream-class*
  (quote
   #+lispworks stream:fundamental-binary-output-stream
   #+sbcl sb-gray:fundamental-binary-output-stream
   #+openmcl gray:fundamental-binary-output-stream
   #+cmu ext:fundamental-binary-output-stream
   #+allegro excl:fundamental-binary-output-stream
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

;;; FIXME: how to do CMUCL support for this?
(defvar *stream-element-type-function*
  (quote
   #+lispworks cl:stream-element-type
   #+sbcl sb-gray::stream-element-type
   #+openmcl cl:stream-element-type
   #+cmu cl:stream-element-type
   #+allegro cl:stream-element-type
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *stream-read-byte-function*
  (quote
   #+lispworks stream:stream-read-byte
   #+sbcl sb-gray:stream-read-byte
   #+openmcl gray:stream-read-byte
   #+cmu ext:stream-read-byte
   #+allegro excl:stream-read-byte
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *stream-write-byte-function*
  (quote
   #+lispworks stream:stream-write-byte
   #+sbcl sb-gray:stream-write-byte
   #+openmcl gray:stream-write-byte
   #+cmu ext:stream-write-byte
   #+allegro excl:stream-write-byte
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *stream-read-sequence-function*
  (quote
   #+lispworks stream:stream-read-sequence
   #+sbcl sb-gray:stream-read-sequence
   #+openmcl ccl:stream-read-vector
   #+cmu ext:stream-read-sequence
   #+allegro excl:stream-read-sequence
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *stream-write-sequence-function*
  (quote
   #+lispworks stream:stream-write-sequence
   #+sbcl sb-gray:stream-write-sequence
   #+openmcl ccl:stream-write-vector
   #+cmu ext:stream-write-sequence
   #+allegro excl:stream-write-sequence
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *stream-finish-output-function*
  (quote
   #+lispworks stream:stream-finish-output
   #+sbcl sb-gray:stream-finish-output
   #+openmcl gray:stream-finish-output
   #+cmu ext:stream-finish-output
   #+allegro excl:stream-finish-output
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *stream-force-output-function*
  (quote
   #+lispworks stream:stream-force-output
   #+sbcl sb-gray:stream-force-output
   #+openmcl gray:stream-force-output
   #+cmu ext:stream-force-output
   #+allegro excl:stream-force-output
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))

(defvar *stream-clear-output-function*
  (quote
   #+lispworks stream:stream-clear-output
   #+sbcl sb-gray:stream-clear-output
   #+openmcl gray:stream-clear-output
   #+cmu ext:stream-clear-output
   #+allegro excl:stream-clear-output
   #-(or lispworks sbcl openmcl cmu allegro)
   (error "octet streams not supported in this implementation")))
)

(defmacro define-stream-write-sequence (specializer type &body body)
  #+sbcl
  `(defmethod sb-gray:stream-write-sequence ((stream ,specializer) seq &optional (start 0) end)
        (let ((end (or end (length seq))))
  #+cmu
  `(defmethod ext:stream-write-sequence ((stream ,specializer) seq &optional (start 0) end)
        (let ((end (or end (length seq))))
  #+allegro
  `(defmethod stream:stream-write-sequence ((stream ,specializer) seq &optional (start 0) end)
        (let ((end (or end (length seq))))
  #+openmcl
  `(defmethod ccl:stream-write-vector ((stream ,specializer) seq start end)
  #+lispworks
  `(defmethod stream:stream-write-sequence ((stream ,specializer) seq start end)

;;; encoding streams

(defclass encoding-stream (#.*binary-output-stream-class*)
  ((buffer :reader buffer :initarg :buffer)
   (index :accessor index :initform 0)
   (encode-fun :reader encode-fun :initarg :encode-fun)
   (encode-table :reader encode-table :initarg :encode-table)
   (writer :reader writer :initarg :writer)))

(defun make-encoding-stream (destination format &key (element-type 'base-char))
  "Return a stream that encodes octets written to it according to FORMAT and
writes the results to DESTINATION.

If DESTINATION is a STREAM, then the result is written to DESTINATION using
WRITE-CHAR or WRITE-BYTE as chosen by ELEMENT-TYPE.

If ELEMENT-TYPE is a subtype of CHARACTER, then DESTINATION may also be a
string with a fill pointer.  The result is written to the string as if by use
of VECTOR-PUSH-EXTEND.  Similarly, if ELEMENT-TYPE is (UNSIGNED-BYTE 8) or an
equivalent type, then DESTINATION may be an octet vector with a fill pointer.

The advantage of using this versus ENCODE-OCTETS is that ENCODE-OCTETS
is only useful if you have the entire entity to be encoded available.
With a stream, you can write the results incrementally, thereby saving
memory. Please note that you do have to call CLOSE, FINISH-OUTPUT, or
FORCE-OUTPUT once you are done encoding data so that any buffered input
may be encoded appropriately."
  (multiple-value-bind (encode-fun length-fun table) (encoding-tools format)
    (let ((canonical-element-type (canonicalize-element-type element-type)))
      ;; FIXME: is it worth supporting this case and saving the user a
      ;; smidgen of coding?
      (when (null destination)
        (case canonical-element-type
          ((character base-char)
           (setf destination (make-string-output-stream
                              :element-type canonical-element-type)))
          (octet
           ;; No octet streams in CLHS, so just output to an adjustable
           ;; octet vector.
           (setf destination (make-array 128 :element-type '(unsigned-byte 8)
                                         :fill-pointer 0 :adjustable t)))))
      (multiple-value-bind (writer return-value)
          (determine-encoding-writer destination 0 canonical-element-type)
        (declare (ignore return-value))
        (make-instance 'encoding-stream
                       :encode-fun encode-fun
                       :encode-table table
                       :writer writer)))))

(defmethod #.*stream-write-byte-function* ((stream encoding-stream) byte)
  (let ((v (make-array 1 :element-type '(unsigned-byte 8)
                       :initial-element byte)))
    (write-sequence v stream)))

(define-stream-write-sequence encoding-stream (simple-array (unsigned-byte 8) (*))
  (loop with buffer = (buffer stream)
     with index = (index stream)
     with chunk-size = (length buffer)
     with length = (- end start)
     with encode-fun of-type function = (encode-fun stream)
     with table = (encode-table stream)
     with writer = (writer stream)
     initially (unless (zerop index)
                 (let ((to-copy (min (- chunk-size index) length)))
                   (replace buffer seq :start1 index :start2 start :end2 end)
                   (decf length to-copy)
                   (incf index to-copy)
                   (incf start to-copy)
                   (cond
                     ((= index chunk-size)
                      (funcall encode-fun
                               buffer 0 chunk-size
                               table writer)
                      (setf index 0))
                     (t (return-from #.*stream-write-sequence-function* start)))))
     while (> length chunk-size)
     do (funcall encode-fun seq start (+ start chunk-size) table writer)
       (decf length chunk-size)
       (incf start chunk-size)
     finally
       (unless (plusp length)
         (replace buffer seq :start1 0 :start2 start :end2 end)
         (setf (index stream) length)
         (incf start length))
       (return start)))

(defmethod #.*stream-finish-output-function* ((stream encoding-stream))
  (flush-buffer stream)
  nil)

(defmethod #.*stream-force-output-function* ((stream encoding-stream))
  (flush-buffer stream)
  nil)

(defmethod #.*stream-clear-output-function* ((stream encoding-stream))
  (setf (index stream) 0)
  nil)