streams.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
HTML git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR README
DIR LICENSE
---
streams.lisp (18470B)
---
1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
2 ;;;
3 ;;; streams.lisp --- Conversions between strings and UB8 vectors.
4 ;;;
5 ;;; Copyright (c) 2005-2007, Dr. Edmund Weitz. All rights reserved.
6 ;;; Copyright (c) 2008, Attila Lendvai. All rights reserved.
7 ;;;
8 ;;; Redistribution and use in source and binary forms, with or without
9 ;;; modification, are permitted provided that the following conditions
10 ;;; are met:
11 ;;;
12 ;;; * Redistributions of source code must retain the above copyright
13 ;;; notice, this list of conditions and the following disclaimer.
14 ;;;
15 ;;; * Redistributions in binary form must reproduce the above
16 ;;; copyright notice, this list of conditions and the following
17 ;;; disclaimer in the documentation and/or other materials
18 ;;; provided with the distribution.
19 ;;;
20 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
21 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
24 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
26 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 ;;; STATUS
33 ;;;
34 ;;; - in-memory output streams support binary/bivalent/character
35 ;;; element-types and file-position
36
37 ;;; TODO
38 ;;;
39 ;;; - filter-stream types/mixins that can wrap a binary stream and
40 ;;; turn it into a bivalent/character stream
41 ;;; - in-memory input streams with file-position similar to in-memory
42 ;;; output streams
43 ;;; - in-memory input/output streams?
44
45 (in-package #:babel)
46
47 (defpackage #:babel-streams
48 (:use #:common-lisp #:babel #:trivial-gray-streams #:alexandria)
49 (:export
50 #:in-memory-stream
51 #:vector-output-stream
52 #:vector-input-stream
53 #:make-in-memory-output-stream
54 #:make-in-memory-input-stream
55 #:get-output-stream-sequence
56 #:with-output-to-sequence
57 #:with-input-from-sequence))
58
59 (in-package :babel-streams)
60
61 (declaim (inline check-if-open check-if-accepts-octets
62 check-if-accepts-characters stream-accepts-characters?
63 stream-accepts-octets? vector-extend
64 extend-vector-output-stream-buffer))
65
66 (defgeneric get-output-stream-sequence (stream &key &allow-other-keys))
67
68 ;;;; Some utilities (on top due to inlining)
69
70 (defun vector-extend (extension vector &key (start 0) (end (length extension)))
71 ;; copied over from cl-quasi-quote
72 (declare (optimize speed)
73 (type vector extension vector)
74 (type array-index start end))
75 (let* ((original-length (length vector))
76 (extension-length (- end start))
77 (new-length (+ original-length extension-length))
78 (original-dimension (array-dimension vector 0)))
79 (when (< original-dimension new-length)
80 (setf vector
81 (adjust-array vector (max (* 2 original-dimension) new-length))))
82 (setf (fill-pointer vector) new-length)
83 (replace vector extension :start1 original-length :start2 start :end2 end)
84 vector))
85
86 (defclass in-memory-stream (trivial-gray-stream-mixin)
87 ((element-type ; :default means bivalent
88 :initform :default :initarg :element-type :accessor element-type-of)
89 (%external-format
90 :initform (ensure-external-format *default-character-encoding*)
91 :initarg :%external-format :accessor external-format-of)
92 #+cmu
93 (open-p
94 :initform t :accessor in-memory-stream-open-p
95 :documentation "For CMUCL we have to keep track of this manually."))
96 (:documentation "An IN-MEMORY-STREAM is a binary stream that reads octets
97 from or writes octets to a sequence in RAM."))
98
99 (defmethod stream-element-type ((self in-memory-stream))
100 ;; stream-element-type is a CL symbol, we may not install an accessor on it.
101 ;; so, go through this extra step.
102 (element-type-of self))
103
104 (defun stream-accepts-octets? (stream)
105 (let ((element-type (element-type-of stream)))
106 (or (eq element-type :default)
107 (equal element-type '(unsigned-byte 8))
108 (subtypep element-type '(unsigned-byte 8)))))
109
110 (defun stream-accepts-characters? (stream)
111 (let ((element-type (element-type-of stream)))
112 (member element-type '(:default character base-char))))
113
114 (defclass in-memory-input-stream (in-memory-stream fundamental-binary-input-stream)
115 ()
116 (:documentation "An IN-MEMORY-INPUT-STREAM is a binary stream that reads
117 octets from a sequence in RAM."))
118
119 #+cmu
120 (defmethod output-stream-p ((stream in-memory-input-stream))
121 "Explicitly states whether this is an output stream."
122 (declare (optimize speed))
123 nil)
124
125 (defclass in-memory-output-stream (in-memory-stream
126 fundamental-binary-output-stream)
127 ()
128 (:documentation "An IN-MEMORY-OUTPUT-STREAM is a binary stream that
129 writes octets to a sequence in RAM."))
130
131 #+cmu
132 (defmethod input-stream-p ((stream in-memory-output-stream))
133 "Explicitly states whether this is an input stream."
134 (declare (optimize speed))
135 nil)
136
137 (defun make-in-memory-output-stream (&key (element-type :default)
138 external-format
139 initial-buffer-size)
140 "Returns a binary output stream which accepts objects of type
141 ELEMENT-TYPE \(a subtype of OCTET) and makes available a sequence that
142 contains the octes that were actually output."
143 (declare (optimize speed))
144 (unless external-format
145 (setf external-format *default-character-encoding*))
146 (when (eq element-type :bivalent)
147 (setf element-type :default))
148 (make-instance 'vector-output-stream
149 :vector (make-vector-stream-buffer
150 :element-type
151 (cond
152 ((or (eq element-type :default)
153 (equal element-type '(unsigned-byte 8)))
154 '(unsigned-byte 8))
155 ((eq element-type 'character)
156 'character)
157 ((subtypep element-type '(unsigned-byte 8))
158 '(unsigned-byte 8))
159 (t (error "Illegal element-type ~S" element-type)))
160 :initial-size initial-buffer-size)
161 :element-type element-type
162 :%external-format (ensure-external-format external-format)))
163
164 (defun make-in-memory-input-stream (data &key (element-type :default)
165 external-format)
166 "Returns a binary input stream which provides the elements of DATA when read."
167 (declare (optimize speed))
168 (unless external-format
169 (setf external-format *default-character-encoding*))
170 (when (eq element-type :bivalent)
171 (setf element-type :default))
172 (make-instance 'vector-input-stream
173 :vector data
174 :element-type element-type
175 :end (length data)
176 :%external-format (ensure-external-format external-format)))
177
178 (defclass vector-stream ()
179 ((vector
180 :initarg :vector :accessor vector-stream-vector
181 :documentation "The underlying vector of the stream which \(for output)
182 must always be adjustable and have a fill pointer.")
183 (index
184 :initform 0 :initarg :index :accessor vector-stream-index
185 :type (integer 0 #.array-dimension-limit)
186 :documentation "An index into the underlying vector denoting the
187 current position."))
188 (:documentation
189 "A VECTOR-STREAM is a mixin for IN-MEMORY streams where the underlying
190 sequence is a vector."))
191
192 (defclass vector-input-stream (vector-stream in-memory-input-stream)
193 ((end
194 :initarg :end :accessor vector-stream-end
195 :type (integer 0 #.array-dimension-limit)
196 :documentation "An index into the underlying vector denoting the end
197 of the available data."))
198 (:documentation "A binary input stream that gets its data from an
199 associated vector of octets."))
200
201 (defclass vector-output-stream (vector-stream in-memory-output-stream)
202 ()
203 (:documentation
204 "A binary output stream that writes its data to an associated vector."))
205
206 (define-condition in-memory-stream-error (stream-error)
207 ()
208 (:documentation "Superclass for all errors related to IN-MEMORY streams."))
209
210 (define-condition in-memory-stream-closed-error (in-memory-stream-error)
211 ()
212 (:report (lambda (condition stream)
213 (format stream "~S is closed."
214 (stream-error-stream condition))))
215 (:documentation "An error that is signalled when someone is trying to read
216 from or write to a closed IN-MEMORY stream."))
217
218 (define-condition wrong-element-type-stream-error (stream-error)
219 ((expected-type :accessor expected-type-of :initarg :expected-type))
220 (:report (lambda (condition output)
221 (let ((stream (stream-error-stream condition)))
222 (format output "The element-type of ~S is ~S while expecting ~
223 a stream that accepts ~S."
224 stream (element-type-of stream)
225 (expected-type-of condition))))))
226
227 (defun wrong-element-type-stream-error (stream expected-type)
228 (error 'wrong-element-type-stream-error
229 :stream stream :expected-type expected-type))
230
231 #+cmu
232 (defmethod open-stream-p ((stream in-memory-stream))
233 "Returns a true value if STREAM is open. See ANSI standard."
234 (declare (optimize speed))
235 (in-memory-stream-open-p stream))
236
237 #+cmu
238 (defmethod close ((stream in-memory-stream) &key abort)
239 "Closes the stream STREAM. See ANSI standard."
240 (declare (ignore abort) (optimize speed))
241 (prog1
242 (in-memory-stream-open-p stream)
243 (setf (in-memory-stream-open-p stream) nil)))
244
245 (defun check-if-open (stream)
246 "Checks if STREAM is open and signals an error otherwise."
247 (declare (optimize speed))
248 (unless (open-stream-p stream)
249 (error 'in-memory-stream-closed-error :stream stream)))
250
251 (defun check-if-accepts-octets (stream)
252 (declare (optimize speed))
253 (unless (stream-accepts-octets? stream)
254 (wrong-element-type-stream-error stream '(unsigned-byte 8))))
255
256 (defun check-if-accepts-characters (stream)
257 (declare (optimize speed))
258 (unless (stream-accepts-characters? stream)
259 (wrong-element-type-stream-error stream 'character)))
260
261 (defmethod stream-read-byte ((stream vector-input-stream))
262 "Reads one byte and increments INDEX pointer unless we're beyond END pointer."
263 (declare (optimize speed))
264 (check-if-open stream)
265 (let ((index (vector-stream-index stream)))
266 (cond ((< index (vector-stream-end stream))
267 (incf (vector-stream-index stream))
268 (aref (vector-stream-vector stream) index))
269 (t :eof))))
270
271 #+#:ignore
272 (defmethod stream-read-char ((stream vector-input-stream))
273 ;; TODO
274 )
275
276 (defmethod stream-listen ((stream vector-input-stream))
277 "Checking whether INDEX is beyond END."
278 (declare (optimize speed))
279 (check-if-open stream)
280 (< (vector-stream-index stream) (vector-stream-end stream)))
281
282 (defmethod stream-read-sequence ((stream vector-input-stream)
283 sequence start end &key)
284 (declare (optimize speed) (type array-index start end))
285 ;; TODO check the sequence type, assert for the element-type and use
286 ;; the external-format.
287 (loop with vector-end of-type array-index = (vector-stream-end stream)
288 with vector = (vector-stream-vector stream)
289 for index from start below end
290 for vector-index of-type array-index = (vector-stream-index stream)
291 while (< vector-index vector-end)
292 do (setf (elt sequence index)
293 (aref vector vector-index))
294 (incf (vector-stream-index stream))
295 finally (return index)))
296
297 (defmethod stream-write-byte ((stream vector-output-stream) byte)
298 "Writes a byte \(octet) by extending the underlying vector."
299 (declare (optimize speed))
300 (check-if-open stream)
301 (check-if-accepts-octets stream)
302 (vector-push-extend byte (vector-stream-vector stream))
303 (incf (vector-stream-index stream))
304 byte)
305
306 (defun extend-vector-output-stream-buffer (extension stream &key (start 0)
307 (end (length extension)))
308 (declare (optimize speed)
309 (type array-index start end)
310 (type vector extension))
311 (vector-extend extension (vector-stream-vector stream) :start start :end end)
312 (incf (vector-stream-index stream) (- end start))
313 (values))
314
315 (defmethod stream-write-char ((stream vector-output-stream) char)
316 (declare (optimize speed))
317 (check-if-open stream)
318 (if (eq (element-type-of stream) 'character)
319 (vector-push-extend char (vector-stream-vector stream))
320 (let ((octets (string-to-octets (string char)
321 :encoding (external-format-of stream))))
322 (extend-vector-output-stream-buffer octets stream)))
323 char)
324
325 (defmethod stream-write-sequence ((stream vector-output-stream)
326 sequence start end &key)
327 "Just calls VECTOR-PUSH-EXTEND repeatedly."
328 (declare (optimize speed)
329 (type array-index start end))
330 (etypecase sequence
331 (string
332 (if (stream-accepts-octets? stream)
333 ;; TODO this is naiive here, there's room for optimization
334 (let ((octets (string-to-octets sequence
335 :encoding (external-format-of stream)
336 :start start
337 :end end)))
338 (extend-vector-output-stream-buffer octets stream))
339 (progn
340 (assert (stream-accepts-characters? stream))
341 (extend-vector-output-stream-buffer sequence stream
342 :start start :end end))))
343 ((vector (unsigned-byte 8))
344 ;; specialized branch to help inlining
345 (check-if-accepts-octets stream)
346 (extend-vector-output-stream-buffer sequence stream :start start :end end))
347 (vector
348 (check-if-accepts-octets stream)
349 (extend-vector-output-stream-buffer sequence stream :start start :end end)))
350 sequence)
351
352 (defmethod stream-write-string ((stream vector-output-stream)
353 string &optional (start 0) (end (length string)))
354 (stream-write-sequence stream string start (or end (length string))))
355
356 (defmethod stream-line-column ((stream vector-output-stream))
357 "Dummy line-column method that always returns NIL. Needed for
358 character output streams."
359 nil)
360
361 (defmethod stream-file-position ((stream vector-stream))
362 "Simply returns the index into the underlying vector."
363 (declare (optimize speed))
364 (vector-stream-index stream))
365
366 (defun make-vector-stream-buffer (&key (element-type '(unsigned-byte 8))
367 initial-size)
368 "Creates and returns an array which can be used as the underlying vector
369 for a VECTOR-OUTPUT-STREAM."
370 (declare (optimize speed)
371 (type (or null array-index) initial-size))
372 (make-array (the array-index (or initial-size 32))
373 :adjustable t
374 :fill-pointer 0
375 :element-type element-type))
376
377 (defmethod get-output-stream-sequence ((stream in-memory-output-stream) &key (return-as 'vector))
378 "Returns a vector containing, in order, all the octets that have
379 been output to the IN-MEMORY stream STREAM. This operation clears any
380 octets on STREAM, so the vector contains only those octets which have
381 been output since the last call to GET-OUTPUT-STREAM-SEQUENCE or since
382 the creation of the stream, whichever occurred most recently. If
383 AS-LIST is true the return value is coerced to a list."
384 (declare (optimize speed))
385 (let ((vector (vector-stream-vector stream)))
386 (prog1
387 (ecase return-as
388 (vector vector)
389 (string (octets-to-string vector :encoding (external-format-of stream)))
390 (list (coerce vector 'list)))
391 (setf (vector-stream-vector stream)
392 (make-vector-stream-buffer :element-type (element-type-of stream))))))
393
394 (defmacro with-output-to-sequence
395 ((var &key (return-as ''vector) (element-type '':default)
396 (external-format '*default-character-encoding*) initial-buffer-size)
397 &body body)
398 "Creates an IN-MEMORY output stream, binds VAR to this stream and
399 then executes the code in BODY. The stream stores data of type
400 ELEMENT-TYPE \(a subtype of OCTET). The stream is automatically closed
401 on exit from WITH-OUTPUT-TO-SEQUENCE, no matter whether the exit is
402 normal or abnormal. The return value of this macro is a vector \(or a
403 list if AS-LIST is true) containing the octets that were sent to the
404 stream within BODY."
405 (multiple-value-bind (body declarations) (parse-body body)
406 ;; this is here to stop SBCL complaining about binding them to NIL
407 `(let ((,var (make-in-memory-output-stream
408 :element-type ,element-type
409 :external-format ,external-format
410 :initial-buffer-size ,initial-buffer-size)))
411 ,@declarations
412 (unwind-protect
413 (progn
414 ,@body
415 (get-output-stream-sequence ,var :return-as ,return-as))
416 (close ,var)))))
417
418 (defmacro with-input-from-sequence
419 ((var data &key (element-type '':default)
420 (external-format '*default-character-encoding*))
421 &body body)
422 "Creates an IN-MEMORY input stream that will return the values
423 available in DATA, binds VAR to this stream and then executes the code
424 in BODY. The stream stores data of type ELEMENT-TYPE \(a subtype of
425 OCTET). The stream is automatically closed on exit from
426 WITH-INPUT-FROM-SEQUENCE, no matter whether the exit is normal or
427 abnormal. The return value of this macro is the return value of BODY."
428 (multiple-value-bind (body declarations) (parse-body body)
429 ;; this is here to stop SBCL complaining about binding them to NIL
430 `(let ((,var (make-in-memory-input-stream
431 ,data :element-type ,element-type
432 :external-format ,external-format)))
433 ,@declarations
434 (unwind-protect
435 (progn
436 ,@body)
437 (close ,var)))))