1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Indent-tabs-mode: NIL -*-
3 ;;; gray-stream-methods.lisp --- Implementation using gray streams.
5 ;;; Copyright (C) 2006-2008, Stelian Ionescu <sionescu@common-lisp.net>
7 ;;; This code is free software; you can redistribute it and/or
8 ;;; modify it under the terms of the version 2.1 of
9 ;;; the GNU Lesser General Public License as published by
10 ;;; the Free Software Foundation, as clarified by the
11 ;;; preamble found here:
12 ;;; http://opensource.franz.com/preamble.html
14 ;;; This program is distributed in the hope that it will be useful,
15 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;;; GNU General Public License for more details.
19 ;;; You should have received a copy of the GNU Lesser General
20 ;;; Public License along with this library; if not, write to the
21 ;;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 ;;; Boston, MA 02110-1301, USA
24 (in-package :io.streams
)
26 ;;;; Instance Initialization
28 (defun free-stream-buffers (ib ob
)
29 (when ib
(free-iobuf ib
))
30 (when ob
(free-iobuf ob
)))
32 ;;; TODO: use the buffer pool
33 ;;; TODO: handle instance reinitialization
34 (defmethod shared-initialize :after
((stream dual-channel-gray-stream
) slot-names
35 &key
(input-buffer-size +bytes-per-iobuf
+)
36 (output-buffer-size +bytes-per-iobuf
+)
37 (external-format :default
))
38 (declare (ignore slot-names
))
39 (unless input-buffer-size
(setf input-buffer-size
+bytes-per-iobuf
+))
40 (unless output-buffer-size
(setf output-buffer-size
+bytes-per-iobuf
+))
41 (check-type input-buffer-size buffer-index
)
42 (check-type output-buffer-size buffer-index
)
43 (with-accessors ((ib input-buffer-of
)
45 (ef external-format-of
))
47 (setf ib
(allocate-iobuf input-buffer-size
)
48 ob
(allocate-iobuf output-buffer-size
)
50 (trivial-garbage:finalize stream
#'(lambda () (free-stream-buffers ib ob
)))))
54 (defmethod stream-element-type ((stream dual-channel-gray-stream
))
57 ;; TODO: use the buffer pool
58 (defmethod close :around
((stream dual-channel-gray-stream
) &key abort
)
59 (with-accessors ((ib input-buffer-of
)
60 (ob output-buffer-of
))
62 (trivial-garbage:cancel-finalization stream
)
63 (unless (or abort
(null ib
)) (finish-output stream
))
64 (free-stream-buffers ib ob
)
69 (defmethod close ((stream dual-channel-gray-stream
) &key abort
)
70 (declare (ignore stream abort
)))
72 (defmethod (setf external-format-of
)
73 (external-format (stream dual-channel-gray-stream
))
74 (setf (slot-value stream
'external-format
)
75 (babel:ensure-external-format external-format
)))
79 (defun %to-octets
(buff start end ef
)
80 (babel:string-to-octets buff
:start start
:end end
81 :encoding
(babel:external-format-encoding ef
)))
83 (defmethod stream-clear-input ((stream dual-channel-gray-stream
))
84 (with-accessors ((ib input-buffer-of
))
89 (defun %fill-ibuf
(read-fn fd buf
&optional timeout
)
91 (let ((readablep (iomux:wait-until-fd-ready fd
:read timeout
)))
93 (return-from %fill-ibuf
:timeout
))))
94 (let ((num (nix:repeat-upon-eintr
95 (funcall read-fn fd
(iobuf-end-pointer buf
)
96 (iobuf-end-space-length buf
)))))
99 (incf (iobuf-end buf
) num
))))
101 (defun %read-into-simple-array-ub8
(stream array start end
)
102 (declare (type dual-channel-gray-stream stream
))
103 (with-accessors ((ib input-buffer-of
)
105 (read-fn read-fn-of
))
107 (let ((octets-needed (- end start
)))
108 (loop :with array-offset
:= start
109 :for octets-in-buffer
:= (iobuf-length ib
)
110 :for nbytes
:= (min octets-needed octets-in-buffer
)
111 :when
(plusp nbytes
) :do
112 (iobuf-copy-into-lisp-array ib
(iobuf-start ib
)
113 array array-offset nbytes
)
114 (incf array-offset nbytes
)
115 (decf octets-needed nbytes
)
116 (incf (iobuf-start ib
) nbytes
)
117 :if
(zerop octets-needed
) :do
(loop-finish)
118 :else
:do
(iobuf-reset ib
)
119 :when
(eq :eof
(%fill-ibuf read-fn fd ib
)) :do
(loop-finish)
120 :finally
(return array-offset
)))))
122 (defun %read-into-string
(stream string start end
)
123 (declare (type dual-channel-gray-stream stream
))
124 (loop :for offset
:from start
:below end
125 :for char
:= (stream-read-char stream
)
126 :if
(eq char
:eof
) :do
(loop-finish)
127 :else
:do
(setf (char string offset
) char
)
128 :finally
(return offset
)))
130 (defun %read-into-vector
(stream vector start end
)
131 (declare (type dual-channel-gray-stream stream
))
132 (loop :for offset
:from start
:below end
133 :for octet
:= (stream-read-byte stream
)
134 :if
(eq octet
:eof
) :do
(loop-finish)
135 :else
:do
(setf (aref vector offset
) octet
)
136 :finally
(return offset
)))
138 (defmacro check-bounds
(sequence start end
)
139 (with-gensyms (length)
140 `(let ((,length
(length ,sequence
)))
143 (unless (<= ,start
,end
,length
)
144 (error "Wrong sequence bounds. start: ~S end: ~S" ,start
,end
)))))
146 (declaim (inline %read-sequence
))
147 (defun %read-sequence
(stream seq start end
)
148 (check-bounds seq start end
)
151 (ub8-sarray (%read-into-simple-array-ub8 stream seq start end
))
152 (string (%read-into-string stream seq start end
))
153 (ub8-vector (%read-into-vector stream seq start end
)))))
155 (declaim (inline read-sequence
*))
156 (defun read-sequence* (stream sequence
&key
(start 0) end
)
157 (%read-sequence stream sequence start end
))
159 (defmethod stream-read-sequence
160 ((stream dual-channel-gray-stream
) sequence start end
&key
)
161 (%read-sequence stream sequence start end
))
163 (defmethod drain-input-buffer
164 ((stream dual-channel-gray-stream
) sequence
&key
(start 0) end
)
165 (check-bounds sequence start end
)
166 (with-accessors ((ib input-buffer-of
))
168 (let ((nbytes (min (- end start
)
170 (iobuf-copy-into-lisp-array ib
(iobuf-start ib
)
173 (incf (iobuf-start ib
) nbytes
)
174 (let ((len (iobuf-length ib
)))
175 (values (+ start nbytes
)
176 (and (plusp len
) len
))))))
180 (defun %write-n-bytes
(write-fn fd buf nbytes
&optional timeout
)
181 (declare (type stream-buffer buf
))
182 (let ((bytes-written 0))
183 (labels ((write-once ()
184 (let ((num (handler-case
185 (nix:repeat-upon-condition-decreasing-timeout
186 ((nix:eintr
) timeout-var timeout
)
188 (funcall write-fn fd
(inc-pointer buf bytes-written
)
190 (when (and timeout-var
(zerop timeout-var
))
191 (return-from %write-n-bytes
192 (values nil
:timeout
)))))
194 (return-from %write-n-bytes
(values nil
:eof
))))))
195 (unless (zerop num
) (incf bytes-written num
))))
199 ;; FIXME signal something better -- maybe analyze the status
200 (return-from %write-n-bytes
(values nil
:fail
)))))
201 (buffer-emptyp () (= bytes-written nbytes
))
202 (errorp () (handler-case (iomux:wait-until-fd-ready fd
:write
)
203 (iomux:poll-error
() t
)
204 (:no-error
(r w
) (declare (ignore r w
)) nil
))))
205 (loop :until
(buffer-emptyp) :do
(write-or-return)
206 :finally
(return (values t bytes-written
))))))
208 (defun %flush-obuf
(write-fn fd buf
&optional timeout
)
209 (declare (type iobuf buf
))
210 (let ((bytes-written 0))
211 (labels ((write-once ()
212 (let ((num (handler-case
213 (nix:repeat-upon-condition-decreasing-timeout
214 ((nix:eintr
) timeout-var timeout
)
216 (funcall write-fn fd
(iobuf-start-pointer buf
)
218 (when (and timeout-var
(zerop timeout-var
))
219 (return-from %flush-obuf
220 (values nil
:timeout
)))))
222 (return-from %flush-obuf
(values nil
:eof
))))))
224 (incf (iobuf-start buf
) num
)
225 (incf bytes-written num
))))
229 ;; FIXME signal something better -- maybe analyze the status
230 (return-from %flush-obuf
(values nil
:fail
)))))
232 (when (iobuf-empty-p buf
)
233 (iobuf-reset buf
) t
))
234 (errorp () (handler-case (iomux:wait-until-fd-ready fd
:write
)
235 (iomux:poll-error
() t
)
236 (:no-error
(r w
) (declare (ignore r w
)) nil
))))
237 (loop :until
(buffer-emptyp) :do
(write-or-return)
238 :finally
(return (values t bytes-written
))))))
240 ;;; TODO: add timeout support
241 (defun %flush-obuf-if-needed
(stream)
242 (declare (type dual-channel-gray-stream stream
))
243 (with-accessors ((fd output-fd-of
)
244 (write-fn write-fn-of
)
245 (ob output-buffer-of
)
248 (when (or dirtyp
(iobuf-full-p ob
))
249 (%flush-obuf write-fn fd ob
)
252 (defmethod stream-clear-output ((stream dual-channel-gray-stream
))
253 (with-accessors ((ob output-buffer-of
)
260 (defmethod stream-finish-output ((stream dual-channel-gray-stream
))
261 (with-accessors ((fd output-fd-of
)
262 (write-fn write-fn-of
)
263 (ob output-buffer-of
)
266 (%flush-obuf write-fn fd ob
)
270 (defmethod stream-force-output ((stream dual-channel-gray-stream
))
271 (setf (dirtyp stream
) t
))
273 (defun %write-simple-array-ub8
(stream array start end
)
274 (declare (type dual-channel-gray-stream stream
))
275 (with-accessors ((fd output-fd-of
)
276 (write-fn write-fn-of
)
277 (ob output-buffer-of
))
279 (let ((octets-needed (- end start
)))
280 (cond ((<= octets-needed
(iobuf-end-space-length ob
))
281 (iobuf-copy-from-lisp-array array start ob
282 (iobuf-end ob
) octets-needed
)
283 (incf (iobuf-end ob
) octets-needed
)
284 (%flush-obuf-if-needed stream
))
286 (with-pointer-to-vector-data (ptr array
)
287 (%flush-obuf write-fn fd ob
)
288 (let ((ret (%write-n-bytes write-fn fd
(inc-pointer ptr start
)
291 (incf (iobuf-end ob
) octets-needed
))))))
294 (defun %write-vector-ub8
(stream vector start end
)
295 (declare (type dual-channel-gray-stream stream
))
296 (%write-simple-array-ub8 stream
(coerce vector
'ub8-sarray
) start end
))
298 (defun %write-vector
(stream vector start end
)
299 (declare (type dual-channel-gray-stream stream
))
300 (loop :for offset
:from start
:below end
301 :for octet
:= (aref vector offset
)
302 :do
(stream-write-byte stream octet
)
303 :finally
(return vector
)))
305 (declaim (inline %write-sequence
))
306 (defun %write-sequence
(stream seq start end
)
307 (check-bounds seq start end
)
310 (ub8-sarray (%write-simple-array-ub8 stream seq start end
))
311 (string (stream-write-string stream seq start end
))
312 (ub8-vector (%write-vector-ub8 stream seq start end
))
313 (vector (%write-vector stream seq start end
)))))
315 (declaim (inline write-sequence
*))
316 (defun write-sequence* (stream sequence
&key
(start 0) end
)
317 (%write-sequence stream sequence start end
))
319 (defmethod stream-write-sequence ((stream dual-channel-gray-stream
)
320 sequence start end
&key
)
321 (%write-sequence stream sequence start end
))
325 (defun maybe-find-line-ending (read-fn fd ib ef
)
326 (let* ((start-off (iobuf-start ib
))
327 (char-code (bref ib start-off
)))
329 (ecase (babel:external-format-eol-style ef
)
330 (:lf
(when (= char-code
(char-code #\Linefeed
))
331 (incf (iobuf-start ib
))
333 (:cr
(when (= char-code
(char-code #\Return
))
334 (incf (iobuf-start ib
))
336 (:crlf
(when (= char-code
(char-code #\Return
))
337 (when (and (= (iobuf-length ib
) 1)
338 (eq :eof
(%fill-ibuf read-fn fd ib
)))
339 (incf (iobuf-start ib
))
341 (when (= (bref ib
(1+ start-off
))
342 (char-code #\Linefeed
))
343 (incf (iobuf-start ib
) 2)
344 (return #\Newline
))))))))
346 (defconstant +max-octets-per-char
+ 6)
348 ;;; FIXME: currently we return :EOF when read(2) returns 0
349 ;;; we should distinguish hard end-of-files (EOF and buffer empty)
350 ;;; from soft end-of-files (EOF and *some* bytes still in the buffer
351 ;;; but not enough to make a full character)
352 (defmethod stream-read-char ((stream dual-channel-gray-stream
))
353 (with-accessors ((fd input-fd-of
)
356 (unread-index ibuf-unread-index-of
)
357 (ef external-format-of
))
359 (setf unread-index
(iobuf-start ib
))
362 (flet ((fill-buf-or-eof ()
363 (setf ret
(%fill-ibuf read-fn fd ib
))
365 (return-from stream-read-char
:eof
))))
366 (cond ((zerop (iobuf-length ib
))
369 ;; Some encodings such as CESU or Java's modified UTF-8 take
370 ;; as much as 6 bytes per character. Make sure we have enough
371 ;; space to collect read-ahead bytes if required.
372 ((< (iobuf-length ib
) +max-octets-per-char
+)
373 (iobuf-copy-data-to-start ib
)
374 (setf unread-index
0)))
376 (when-let ((it (maybe-find-line-ending read-fn fd ib ef
)))
377 (return-from stream-read-char it
))
380 (setf (values str ret
)
381 (foreign-string-to-lisp
383 :offset
(iobuf-start ib
)
384 :count
(iobuf-length ib
)
385 :encoding
(babel:external-format-encoding ef
)
387 (babel:end-of-input-in-character
()
390 (incf (iobuf-start ib
) ret
))
393 (defun maybe-find-line-ending-no-hang (fd ib ef
)
394 (declare (ignore fd
))
395 (let* ((start-off (iobuf-start ib
))
396 (char-code (bref ib start-off
)))
398 (ecase (babel:external-format-eol-style ef
)
399 (:lf
(when (= char-code
(char-code #\Linefeed
))
400 (incf (iobuf-start ib
))
402 (:cr
(when (= char-code
(char-code #\Return
))
403 (incf (iobuf-start ib
))
405 (:crlf
(when (= char-code
(char-code #\Return
))
406 (when (= (iobuf-length ib
) 1)
407 (incf (iobuf-start ib
))
408 (return :starvation
))
409 (when (= (bref ib
(1+ start-off
))
410 (char-code #\Linefeed
))
411 (incf (iobuf-start ib
) 2)
412 (return #\Newline
))))))))
414 (defmethod stream-read-char-no-hang ((stream dual-channel-gray-stream
))
415 (with-accessors ((fd input-fd-of
)
418 (ef external-format-of
))
424 ;; BUG: this comparision is probably buggy, FIXME. A similar
425 ;; bug was fixed in STREAM-READ-CHAR. Must write a test for
427 (when (< 0 (iobuf-end-space-length ib
) 4)
428 (iobuf-copy-data-to-start ib
))
429 (when (and (iomux:fd-ready-p fd
:read
)
430 (eq :eof
(%fill-ibuf read-fn fd ib
)))
432 (when (zerop (iobuf-length ib
))
433 (return (if eof
:eof nil
)))
435 (let ((line-end (maybe-find-line-ending-no-hang fd ib ef
)))
436 (cond ((eq line-end
:starvation
)
437 (return (if eof
#\Return nil
)))
438 ((characterp line-end
)
442 (setf (values str ret
)
443 (foreign-string-to-lisp
445 :offset
(iobuf-start ib
)
446 :count
(iobuf-length ib
)
447 :encoding
(babel:external-format-encoding ef
)
449 (babel:end-of-input-in-character
()
451 (incf (iobuf-start ib
) ret
)
454 (defun %stream-unread-char
(stream)
455 (declare (type dual-channel-gray-stream stream
))
456 (with-accessors ((ib input-buffer-of
)
457 (unread-index ibuf-unread-index-of
))
459 (symbol-macrolet ((start (iobuf-start ib
)))
461 ((> start unread-index
) (setf start unread-index
))
462 (t (error "No uncommitted character to unread")))))
465 (defmethod stream-unread-char ((stream dual-channel-gray-stream
) character
)
466 (declare (ignore character
))
467 (%stream-unread-char stream
))
469 (defmethod stream-peek-char ((stream dual-channel-gray-stream
))
470 (let ((char (stream-read-char stream
)))
471 (cond ((eq char
:eof
) :eof
)
472 (t (%stream-unread-char stream
)
475 ;; (defmethod stream-read-line ((stream dual-channel-gray-stream))
478 (defmethod stream-listen ((stream dual-channel-gray-stream
))
479 (let ((char (stream-read-char-no-hang stream
)))
480 (cond ((characterp char
) (stream-unread-char stream char
) t
)
484 ;;;; Character Output
486 (defmethod stream-write-char ((stream dual-channel-gray-stream
)
487 (character character
))
488 (%flush-obuf-if-needed stream
)
489 (if (char= character
#\Newline
)
490 (%write-line-terminator
491 stream
(babel:external-format-eol-style
(external-format-of stream
)))
492 ;; FIXME: avoid consing a string here. At worst, declare it dynamic-extent
493 (stream-write-string stream
(make-string 1 :initial-element character
))))
495 (defmethod stream-line-column ((stream dual-channel-gray-stream
))
498 (defmethod stream-start-line-p ((stream dual-channel-gray-stream
))
501 (defmethod stream-terpri ((stream dual-channel-gray-stream
))
502 (write-char #\Newline stream
) nil
)
504 (defmethod stream-fresh-line ((stream dual-channel-gray-stream
))
505 (write-char #\Newline stream
) t
)
507 (define-constant +unix-line-terminator
+
508 (make-array 1 :element-type
'ub8
:initial-contents
'(10))
511 (define-constant +dos-line-terminator
+
512 (make-array 2 :element-type
'ub8
:initial-contents
'(13 10))
515 (define-constant +mac-line-terminator
+
516 (make-array 1 :element-type
'ub8
:initial-contents
'(13))
519 (defun %write-line-terminator
(stream line-terminator
)
520 (case line-terminator
521 (:lf
(%write-simple-array-ub8 stream
+unix-line-terminator
+ 0 1))
522 (:cr
(%write-simple-array-ub8 stream
+mac-line-terminator
+ 0 1))
523 (:crlf
(%write-simple-array-ub8 stream
+dos-line-terminator
+ 0 2))))
525 (defmethod stream-write-string ((stream dual-channel-gray-stream
)
526 (string string
) &optional
(start 0) end
)
527 (check-bounds string start end
)
530 (ef (external-format-of stream
))
531 (line-terminator (babel:external-format-eol-style ef
)))
532 (loop :for off1
:= start
:then
(1+ off2
)
533 :for nl-off
:= (position #\Newline string
:start off1
)
534 :for off2
:= (or nl-off end
)
535 :when nl-off
:do
(%write-line-terminator stream line-terminator
)
536 :when
(> off2 off1
) :do
537 ;; FIXME: should probably convert directly to a foreign buffer?
538 (setf octets
(%to-octets string off1 off2 ef
))
539 (%write-simple-array-ub8 stream octets
0 (length octets
))
540 :while
(< off2 end
))))
545 (defmethod stream-read-byte ((stream dual-channel-gray-stream
))
546 (with-accessors ((fd input-fd-of
)
548 (ib input-buffer-of
))
550 (flet ((fill-buf-or-eof ()
552 (when (eq :eof
(%fill-ibuf read-fn fd ib
))
553 (return-from stream-read-byte
:eof
))))
554 (when (zerop (iobuf-length ib
))
556 (iobuf-pop-octet ib
))))
560 (defmethod stream-write-byte ((stream dual-channel-gray-stream
) integer
)
561 (check-type integer ub8
"an unsigned 8-bit value")
562 (with-accessors ((ob output-buffer-of
))
564 (%flush-obuf-if-needed stream
)
565 (iobuf-push-octet ob integer
)))
567 ;;;; Buffer-related stuff
569 (defmethod input-buffer-size ((stream dual-channel-gray-stream
))
570 (iobuf-length (input-buffer-of stream
)))
572 (defmethod input-buffer-empty-p ((stream dual-channel-gray-stream
))
573 (iobuf-empty-p (input-buffer-of stream
)))
575 (defmethod output-buffer-size ((stream dual-channel-gray-stream
))
576 (iobuf-length (output-buffer-of stream
)))
578 (defmethod output-buffer-empty-p ((stream dual-channel-gray-stream
))
579 (iobuf-empty-p (output-buffer-of stream
)))