1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; indent-tabs-mode: nil -*-
3 ;;; --- Implementation using gray streams.
6 (in-package :io.streams
)
8 ;;;; Instance Initialization
10 (defun free-stream-buffers (ib ob
)
11 (when ib
(free-iobuf ib
))
12 (when ob
(free-iobuf ob
)))
14 ;;; TODO: use the buffer pool
15 ;;; TODO: handle instance reinitialization
16 (defmethod shared-initialize :after
((stream dual-channel-gray-stream
) slot-names
17 &key
(input-buffer-size +bytes-per-iobuf
+)
18 (output-buffer-size +bytes-per-iobuf
+)
19 (external-format :default
))
20 (declare (ignore slot-names
))
21 (unless input-buffer-size
(setf input-buffer-size
+bytes-per-iobuf
+))
22 (unless output-buffer-size
(setf output-buffer-size
+bytes-per-iobuf
+))
23 (check-type input-buffer-size buffer-index
)
24 (check-type output-buffer-size buffer-index
)
25 (with-accessors ((ib input-buffer-of
)
27 (ef external-format-of
))
29 (setf ib
(allocate-iobuf input-buffer-size
)
30 ob
(allocate-iobuf output-buffer-size
)
32 (trivial-garbage:finalize stream
(lambda () (free-stream-buffers ib ob
)))))
36 (defmethod stream-element-type ((stream dual-channel-gray-stream
))
39 ;; TODO: use the buffer pool
40 (defmethod close :around
((stream dual-channel-gray-stream
) &key abort
)
41 (with-accessors ((ib input-buffer-of
)
42 (ob output-buffer-of
))
44 (trivial-garbage:cancel-finalization stream
)
45 (unless (or abort
(null ib
)) (finish-output stream
))
46 (free-stream-buffers ib ob
)
51 (defmethod close ((stream dual-channel-gray-stream
) &key abort
)
52 (declare (ignore stream abort
)))
54 (defmethod (setf external-format-of
)
55 (external-format (stream dual-channel-gray-stream
))
56 (setf (slot-value stream
'external-format
)
57 (babel:ensure-external-format external-format
)))
61 (defun %to-octets
(buff start end ef
)
62 (babel:string-to-octets buff
:start start
:end end
63 :encoding
(babel:external-format-encoding ef
)))
65 (defmethod stream-clear-input ((stream dual-channel-gray-stream
))
66 (with-accessors ((ib input-buffer-of
))
71 (defun %fill-ibuf
(read-fn fd buf
&optional timeout
)
73 (let ((readablep (iomux:wait-until-fd-ready fd
:input timeout
)))
76 (let ((num (nix:repeat-upon-eintr
77 (funcall read-fn fd
(iobuf-end-pointer buf
)
78 (iobuf-end-space-length buf
)))))
81 (incf (iobuf-end buf
) num
))))
83 (defun %read-into-simple-array-ub8
(stream array start end
)
84 (declare (type dual-channel-gray-stream stream
))
85 (with-accessors ((ib input-buffer-of
)
89 (let ((octets-needed (- end start
)))
90 (loop :with array-offset
:= start
91 :for octets-in-buffer
:= (iobuf-length ib
)
92 :for nbytes
:= (min octets-needed octets-in-buffer
)
93 :when
(plusp nbytes
) :do
94 (iobuf-copy-into-lisp-array ib
(iobuf-start ib
)
95 array array-offset nbytes
)
96 (incf array-offset nbytes
)
97 (decf octets-needed nbytes
)
98 (incf (iobuf-start ib
) nbytes
)
99 :if
(zerop octets-needed
) :do
(loop-finish)
100 :else
:do
(iobuf-reset ib
)
101 :when
(eq :eof
(%fill-ibuf read-fn fd ib
)) :do
(loop-finish)
102 :finally
(return array-offset
)))))
104 (defun %read-into-string
(stream string start end
)
105 (declare (type dual-channel-gray-stream stream
))
106 (loop :for offset
:from start
:below end
107 :for char
:= (stream-read-char stream
)
108 :if
(eq char
:eof
) :do
(loop-finish)
109 :else
:do
(setf (char string offset
) char
)
110 :finally
(return offset
)))
112 (defun %read-into-vector
(stream vector start end
)
113 (declare (type dual-channel-gray-stream stream
))
114 (loop :for offset
:from start
:below end
115 :for octet
:= (stream-read-byte stream
)
116 :if
(eq octet
:eof
) :do
(loop-finish)
117 :else
:do
(setf (aref vector offset
) octet
)
118 :finally
(return offset
)))
120 (defmacro check-bounds
(sequence start end
)
121 (with-gensyms (length)
122 `(let ((,length
(length ,sequence
)))
125 (unless (<= ,start
,end
,length
)
126 (error "Wrong sequence bounds. start: ~S end: ~S" ,start
,end
)))))
128 (declaim (inline %read-sequence
))
129 (defun %read-sequence
(stream seq start end
)
130 (check-bounds seq start end
)
133 (ub8-sarray (%read-into-simple-array-ub8 stream seq start end
))
134 (string (%read-into-string stream seq start end
))
135 (ub8-vector (%read-into-vector stream seq start end
)))))
137 (declaim (inline read-sequence
*))
138 (defun read-sequence* (stream sequence
&key
(start 0) end
)
139 (%read-sequence stream sequence start end
))
141 (defmethod stream-read-sequence
142 ((stream dual-channel-gray-stream
) sequence start end
&key
)
143 (%read-sequence stream sequence start end
))
145 (defmethod drain-input-buffer
146 ((stream dual-channel-gray-stream
) sequence
&key
(start 0) end
)
147 (check-bounds sequence start end
)
148 (with-accessors ((ib input-buffer-of
))
150 (let ((nbytes (min (- end start
)
153 (iobuf-copy-into-lisp-array ib
(iobuf-start ib
)
156 (incf (iobuf-start ib
) nbytes
)
157 (let ((len (iobuf-length ib
)))
158 (values (+ start nbytes
)
159 (and (plusp len
) len
)))))))
163 (defun %write-n-bytes
(write-fn fd buf nbytes
&optional timeout
)
164 (declare (type stream-buffer buf
))
165 (let ((bytes-written 0))
166 (labels ((write-once ()
167 (let ((num (handler-case
168 (nix:repeat-upon-condition-decreasing-timeout
169 ((nix:eintr
) timeout-var timeout
)
171 (funcall write-fn fd
(inc-pointer buf bytes-written
)
173 (when (and timeout-var
(zerop timeout-var
))
174 (return* (values nil
:timeout
)))))
176 (return* (values nil
:eof
))))))
177 (unless (zerop num
) (incf bytes-written num
))))
181 ;; FIXME signal something better -- maybe analyze the status
182 (return* (values nil
:fail
)))))
183 (buffer-emptyp () (= bytes-written nbytes
))
184 (errorp () (handler-case (iomux:wait-until-fd-ready fd
:output
)
185 (iomux:poll-error
() t
)
186 (:no-error
(r w
) (declare (ignore r w
)) nil
))))
187 (loop :until
(buffer-emptyp) :do
(write-or-return)
188 :finally
(return (values t bytes-written
))))))
190 (defun %flush-obuf
(write-fn fd buf
&optional timeout
)
191 (declare (type iobuf buf
))
192 (let ((bytes-written 0))
193 (labels ((write-once ()
194 (let ((num (handler-case
195 (nix:repeat-upon-condition-decreasing-timeout
196 ((nix:eintr
) timeout-var timeout
)
198 (funcall write-fn fd
(iobuf-start-pointer buf
)
200 (when (and timeout-var
(zerop timeout-var
))
201 (return* (values nil
:timeout
)))))
203 (return* (values nil
:eof
))))))
205 (incf (iobuf-start buf
) num
)
206 (incf bytes-written num
))))
210 ;; FIXME signal something better -- maybe analyze the status
211 (return* (values nil
:fail
)))))
213 (when (iobuf-empty-p buf
)
214 (iobuf-reset buf
) t
))
215 (errorp () (handler-case (iomux:wait-until-fd-ready fd
:output
)
216 (iomux:poll-error
() t
)
217 (:no-error
(r w
) (declare (ignore r w
)) nil
))))
218 (loop :until
(buffer-emptyp) :do
(write-or-return)
219 :finally
(return (values t bytes-written
))))))
221 ;;; TODO: add timeout support
222 (defun %flush-obuf-if-needed
(stream)
223 (declare (type dual-channel-gray-stream stream
))
224 (with-accessors ((fd output-fd-of
)
225 (write-fn write-fn-of
)
226 (ob output-buffer-of
)
229 (when (or dirtyp
(iobuf-full-p ob
))
230 (%flush-obuf write-fn fd ob
)
233 (defmethod stream-clear-output ((stream dual-channel-gray-stream
))
234 (with-accessors ((ob output-buffer-of
)
241 (defmethod stream-finish-output ((stream dual-channel-gray-stream
))
242 (with-accessors ((fd output-fd-of
)
243 (write-fn write-fn-of
)
244 (ob output-buffer-of
)
247 (%flush-obuf write-fn fd ob
)
251 (defmethod stream-force-output ((stream dual-channel-gray-stream
))
252 (setf (dirtyp stream
) t
))
254 (defun %write-simple-array-ub8
(stream array start end
)
255 (declare (type dual-channel-gray-stream stream
))
256 (with-accessors ((fd output-fd-of
)
257 (write-fn write-fn-of
)
258 (ob output-buffer-of
))
260 (let ((octets-needed (- end start
)))
261 (cond ((<= octets-needed
(iobuf-end-space-length ob
))
262 (iobuf-copy-from-lisp-array array start ob
263 (iobuf-end ob
) octets-needed
)
264 (incf (iobuf-end ob
) octets-needed
)
265 (%flush-obuf-if-needed stream
))
267 (with-pointer-to-vector-data (ptr array
)
268 (%flush-obuf write-fn fd ob
)
269 (%write-n-bytes write-fn fd
(inc-pointer ptr start
) octets-needed
))))
272 (defun %write-vector-ub8
(stream vector start end
)
273 (declare (type dual-channel-gray-stream stream
))
274 (%write-simple-array-ub8 stream
(coerce vector
'ub8-sarray
) start end
))
276 (defun %write-vector
(stream vector start end
)
277 (declare (type dual-channel-gray-stream stream
))
278 (loop :for offset
:from start
:below end
279 :for octet
:= (aref vector offset
)
280 :do
(stream-write-byte stream octet
)
281 :finally
(return vector
)))
283 (declaim (inline %write-sequence
))
284 (defun %write-sequence
(stream seq start end
)
285 (check-bounds seq start end
)
288 (ub8-sarray (%write-simple-array-ub8 stream seq start end
))
289 (string (stream-write-string stream seq start end
))
290 (ub8-vector (%write-vector-ub8 stream seq start end
))
291 (vector (%write-vector stream seq start end
)))))
293 (declaim (inline write-sequence
*))
294 (defun write-sequence* (stream sequence
&key
(start 0) end
)
295 (%write-sequence stream sequence start end
))
297 (defmethod stream-write-sequence ((stream dual-channel-gray-stream
)
298 sequence start end
&key
)
299 (%write-sequence stream sequence start end
))
303 (defun maybe-find-line-ending (read-fn fd ib ef
)
304 (let* ((start-off (iobuf-start ib
))
305 (char-code (bref ib start-off
)))
307 (ecase (babel:external-format-eol-style ef
)
308 (:lf
(when (= char-code
(char-code #\Linefeed
))
309 (incf (iobuf-start ib
))
311 (:cr
(when (= char-code
(char-code #\Return
))
312 (incf (iobuf-start ib
))
314 (:crlf
(when (= char-code
(char-code #\Return
))
315 (when (and (= (iobuf-length ib
) 1)
316 (eq :eof
(%fill-ibuf read-fn fd ib
)))
317 (incf (iobuf-start ib
))
319 (when (= (bref ib
(1+ start-off
))
320 (char-code #\Linefeed
))
321 (incf (iobuf-start ib
) 2)
322 (return #\Newline
))))))))
324 (defconstant +max-octets-per-char
+ 6)
326 ;;; FIXME: currently we return :EOF when read(2) returns 0
327 ;;; we should distinguish hard end-of-files (EOF and buffer empty)
328 ;;; from soft end-of-files (EOF and *some* bytes still in the buffer
329 ;;; but not enough to make a full character)
330 (defmethod stream-read-char ((stream dual-channel-gray-stream
))
331 (with-accessors ((fd input-fd-of
)
334 (unread-index ibuf-unread-index-of
)
335 (ef external-format-of
))
337 (setf unread-index
(iobuf-start ib
))
340 (flet ((fill-buf-or-eof ()
341 (setf ret
(%fill-ibuf read-fn fd ib
))
344 (cond ((zerop (iobuf-length ib
))
347 ;; Some encodings such as CESU or Java's modified UTF-8 take
348 ;; as much as 6 bytes per character. Make sure we have enough
349 ;; space to collect read-ahead bytes if required.
350 ((< (iobuf-length ib
) +max-octets-per-char
+)
351 (iobuf-copy-data-to-start ib
)
352 (setf unread-index
0)))
354 (when-let (it (maybe-find-line-ending read-fn fd ib ef
))
358 (setf (values str ret
)
359 (foreign-string-to-lisp
361 :offset
(iobuf-start ib
)
362 :count
(iobuf-length ib
)
363 :encoding
(babel:external-format-encoding ef
)
365 (babel:end-of-input-in-character
()
368 (incf (iobuf-start ib
) ret
))
371 (defun maybe-find-line-ending-no-hang (fd ib ef
)
372 (declare (ignore fd
))
373 (let* ((start-off (iobuf-start ib
))
374 (char-code (bref ib start-off
)))
376 (ecase (babel:external-format-eol-style ef
)
377 (:lf
(when (= char-code
(char-code #\Linefeed
))
378 (incf (iobuf-start ib
))
380 (:cr
(when (= char-code
(char-code #\Return
))
381 (incf (iobuf-start ib
))
383 (:crlf
(when (= char-code
(char-code #\Return
))
384 (when (= (iobuf-length ib
) 1)
385 (incf (iobuf-start ib
))
386 (return :starvation
))
387 (when (= (bref ib
(1+ start-off
))
388 (char-code #\Linefeed
))
389 (incf (iobuf-start ib
) 2)
390 (return #\Newline
))))))))
392 (defmethod stream-read-char-no-hang ((stream dual-channel-gray-stream
))
393 (with-accessors ((fd input-fd-of
)
396 (ef external-format-of
))
402 ;; BUG: this comparision is probably buggy, FIXME. A similar
403 ;; bug was fixed in STREAM-READ-CHAR. Must write a test for
405 (when (< 0 (iobuf-end-space-length ib
) 4)
406 (iobuf-copy-data-to-start ib
))
407 (when (and (iomux:fd-ready-p fd
:input
)
408 (eq :eof
(%fill-ibuf read-fn fd ib
)))
410 (when (zerop (iobuf-length ib
))
411 (return (if eof
:eof nil
)))
413 (let ((line-end (maybe-find-line-ending-no-hang fd ib ef
)))
414 (cond ((eq line-end
:starvation
)
415 (return (if eof
#\Return nil
)))
416 ((characterp line-end
)
420 (setf (values str ret
)
421 (foreign-string-to-lisp
423 :offset
(iobuf-start ib
)
424 :count
(iobuf-length ib
)
425 :encoding
(babel:external-format-encoding ef
)
427 (babel:end-of-input-in-character
()
429 (incf (iobuf-start ib
) ret
)
432 (defun %stream-unread-char
(stream)
433 (declare (type dual-channel-gray-stream stream
))
434 (with-accessors ((ib input-buffer-of
)
435 (unread-index ibuf-unread-index-of
))
437 (symbol-macrolet ((start (iobuf-start ib
)))
439 ((> start unread-index
) (setf start unread-index
))
440 (t (error "No uncommitted character to unread")))))
443 (defmethod stream-unread-char ((stream dual-channel-gray-stream
) character
)
444 (declare (ignore character
))
445 (%stream-unread-char stream
))
447 (defmethod stream-peek-char ((stream dual-channel-gray-stream
))
448 (let ((char (stream-read-char stream
)))
449 (cond ((eq char
:eof
) :eof
)
450 (t (%stream-unread-char stream
)
453 ;; (defmethod stream-read-line ((stream dual-channel-gray-stream))
456 (defmethod stream-listen ((stream dual-channel-gray-stream
))
457 (let ((char (stream-read-char-no-hang stream
)))
458 (cond ((characterp char
) (stream-unread-char stream char
) t
)
462 ;;;; Character Output
464 (defmethod stream-write-char ((stream dual-channel-gray-stream
)
465 (character character
))
466 (%flush-obuf-if-needed stream
)
467 (if (char= character
#\Newline
)
468 (%write-line-terminator
469 stream
(babel:external-format-eol-style
(external-format-of stream
)))
470 ;; FIXME: avoid consing a string here. At worst, declare it dynamic-extent
471 (stream-write-string stream
(make-string 1 :initial-element character
))))
473 (defmethod stream-line-column ((stream dual-channel-gray-stream
))
476 (defmethod stream-start-line-p ((stream dual-channel-gray-stream
))
479 (defmethod stream-terpri ((stream dual-channel-gray-stream
))
480 (write-char #\Newline stream
) nil
)
482 (defmethod stream-fresh-line ((stream dual-channel-gray-stream
))
483 (write-char #\Newline stream
) t
)
485 (define-constant +unix-line-terminator
+
486 (make-array 1 :element-type
'ub8
:initial-contents
'(10))
489 (define-constant +dos-line-terminator
+
490 (make-array 2 :element-type
'ub8
:initial-contents
'(13 10))
493 (define-constant +mac-line-terminator
+
494 (make-array 1 :element-type
'ub8
:initial-contents
'(13))
497 (defun %write-line-terminator
(stream line-terminator
)
498 (case line-terminator
499 (:lf
(%write-simple-array-ub8 stream
+unix-line-terminator
+ 0 1))
500 (:cr
(%write-simple-array-ub8 stream
+mac-line-terminator
+ 0 1))
501 (:crlf
(%write-simple-array-ub8 stream
+dos-line-terminator
+ 0 2))))
503 (defmethod stream-write-string ((stream dual-channel-gray-stream
)
504 (string string
) &optional
(start 0) end
)
505 (check-bounds string start end
)
508 (ef (external-format-of stream
))
509 (line-terminator (babel:external-format-eol-style ef
)))
510 (loop :for off1
:= start
:then
(1+ off2
)
511 :for nl-off
:= (position #\Newline string
:start off1
)
512 :for off2
:= (or nl-off end
)
513 :when nl-off
:do
(%write-line-terminator stream line-terminator
)
514 :when
(> off2 off1
) :do
515 ;; FIXME: should probably convert directly to a foreign buffer?
516 (setf octets
(%to-octets string off1 off2 ef
))
517 (%write-simple-array-ub8 stream octets
0 (length octets
))
518 :while
(< off2 end
))))
523 (defmethod stream-read-byte ((stream dual-channel-gray-stream
))
524 (with-accessors ((fd input-fd-of
)
526 (ib input-buffer-of
))
528 (flet ((fill-buf-or-eof ()
530 (when (eq :eof
(%fill-ibuf read-fn fd ib
))
532 (when (zerop (iobuf-length ib
))
534 (iobuf-pop-octet ib
))))
538 (defmethod stream-write-byte ((stream dual-channel-gray-stream
) integer
)
539 (check-type integer ub8
"an unsigned 8-bit value")
540 (with-accessors ((ob output-buffer-of
))
542 (%flush-obuf-if-needed stream
)
543 (iobuf-push-octet ob integer
)))
545 ;;;; Buffer-related stuff
547 (defmethod input-buffer-size ((stream dual-channel-gray-stream
))
548 (iobuf-length (input-buffer-of stream
)))
550 (defmethod input-buffer-empty-p ((stream dual-channel-gray-stream
))
551 (iobuf-empty-p (input-buffer-of stream
)))
553 (defmethod output-buffer-size ((stream dual-channel-gray-stream
))
554 (iobuf-length (output-buffer-of stream
)))
556 (defmethod output-buffer-empty-p ((stream dual-channel-gray-stream
))
557 (iobuf-empty-p (output-buffer-of stream
)))