1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Indent-tabs-mode: NIL -*-
3 ;;; gray-stream-methods.lisp --- Implementation using gray streams.
5 ;;; Copyright (C) 2006-2007, 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 ;;; TODO: use the buffer pool
29 ;;; TODO: handle instance reinitialization
30 (defmethod shared-initialize :after
((s dual-channel-gray-stream
) slot-names
31 &key
(input-buffer-size +bytes-per-iobuf
+)
32 (output-buffer-size +bytes-per-iobuf
+)
33 (external-format :default
))
34 (declare (ignore slot-names
))
35 (check-type input-buffer-size buffer-index
)
36 (check-type output-buffer-size buffer-index
)
37 (when (open-stream-p s
) (close s
))
38 (with-accessors ((ib input-buffer-of
) (ob output-buffer-of
)
39 (ef external-format-of
)) s
40 (setf ib
(allocate-iobuf input-buffer-size
)
41 ob
(allocate-iobuf output-buffer-size
)
46 (defmethod stream-element-type ((stream dual-channel-gray-stream
))
49 ;; TODO: use the buffer pool
50 (defmethod close :around
((stream dual-channel-gray-stream
) &key abort
)
51 (with-accessors ((ib input-buffer-of
)
52 (ob output-buffer-of
)) stream
53 (unless (or abort
(null ib
)) (finish-output stream
))
54 (when ib
(free-iobuf ib
))
55 (when ob
(free-iobuf ob
))
60 (defmethod close ((stream dual-channel-gray-stream
) &key abort
)
61 (declare (ignore stream abort
)))
63 (defmethod (setf external-format-of
)
64 (external-format (stream dual-channel-gray-stream
))
65 (setf (slot-value stream
'external-format
)
66 (babel:ensure-external-format external-format
)))
70 (defmethod stream-clear-input ((stream dual-channel-gray-stream
))
71 (with-accessors ((ib input-buffer-of
)) stream
75 (defun %fill-ibuf
(buf fd
&optional timeout
)
77 (let ((readablep (iomux:wait-until-fd-ready fd
:read timeout
)))
79 (return-from %fill-ibuf
:timeout
))))
80 (let ((num (nix:repeat-upon-eintr
81 (nix:read fd
(iobuf-end-pointer buf
)
82 (iobuf-end-space-length buf
)))))
85 (incf (iobuf-end buf
) num
))))
87 (defun %read-into-simple-array-ub8
(stream array start end
)
88 (declare (type dual-channel-gray-stream stream
))
89 (with-accessors ((ib input-buffer-of
) (fd input-fd-of
)) stream
90 (let ((octets-needed (- end start
)))
91 (loop :with array-offset
:= start
92 :for octets-in-buffer
:= (iobuf-length ib
)
93 :for nbytes
:= (min octets-needed octets-in-buffer
)
94 :when
(plusp nbytes
) :do
95 (iobuf-copy-into-lisp-array ib
(iobuf-start ib
)
96 array array-offset nbytes
)
97 (incf array-offset nbytes
)
98 (decf octets-needed nbytes
)
99 (incf (iobuf-start ib
) nbytes
)
100 :if
(zerop octets-needed
) :do
(loop-finish)
101 :else
:do
(iobuf-reset ib
)
102 :when
(eql :eof
(%fill-ibuf ib fd
)) :do
(loop-finish)
103 :finally
(return array-offset
)))))
105 (defun %read-into-string
(stream string start end
)
106 (declare (type dual-channel-gray-stream stream
))
107 (loop :for offset
:from start
:below end
108 :for char
:= (stream-read-char stream
)
109 :if
(eql char
:eof
) :do
(loop-finish)
110 :else
:do
(setf (char string offset
) char
)
111 :finally
(return offset
)))
113 (defun %read-into-vector
(stream vector start end
)
114 (declare (type dual-channel-gray-stream stream
))
115 (loop :for offset
:from start
:below end
116 :for octet
:= (stream-read-byte stream
)
117 :if
(eql octet
:eof
) :do
(loop-finish)
118 :else
:do
(setf (aref vector offset
) octet
)
119 :finally
(return offset
)))
121 (defmacro check-bounds
(sequence start end
)
122 (alexandria:once-only
(start end
)
123 (alexandria:with-unique-names
(length)
124 `(let ((,length
(length ,sequence
)))
127 (unless (<= ,start
,end
,length
)
128 (error "Wrong sequence bounds. start: ~S end: ~S" ,start
,end
))))))
130 (defmethod stream-read-sequence
131 ((stream dual-channel-gray-stream
) seq start end
&key
)
132 (check-bounds seq start end
)
135 (ub8-sarray (%read-into-simple-array-ub8 stream seq start end
))
136 (string (%read-into-string stream seq start end
))
137 (ub8-vector (%read-into-vector stream seq start end
)))))
141 (defun %write-n-bytes
(buf fd nbytes
&optional timeout
)
142 (declare (type stream-buffer buf
))
143 (let ((bytes-written 0))
144 (labels ((write-once ()
145 (let ((num (handler-case
146 (nix:repeat-upon-condition-decreasing-timeout
147 ((nix:eintr
) timeout-var timeout
)
150 fd
(inc-pointer buf bytes-written
) nbytes
)
151 (when (and timeout-var
(zerop timeout-var
))
152 (return-from %write-n-bytes
153 (values nil
:timeout
)))))
155 (return-from %write-n-bytes
(values nil
:eof
))))))
156 (unless (zerop num
) (incf bytes-written num
))))
160 ;; FIXME signal something better -- maybe analyze the status
161 (return-from %write-n-bytes
(values nil
:fail
)))))
162 (buffer-emptyp () (= bytes-written nbytes
))
163 (errorp () (handler-case (iomux:wait-until-fd-ready fd
:write
)
164 (iomux:poll-error
()))))
165 (loop :until
(buffer-emptyp) :do
(write-or-return)
166 :finally
(return (values t bytes-written
))))))
168 (defun %flush-obuf
(buf fd
&optional timeout
)
169 (declare (type iobuf buf
))
170 (let ((bytes-written 0))
171 (labels ((write-once ()
172 (let ((num (handler-case
173 (nix:repeat-upon-condition-decreasing-timeout
174 ((nix:eintr
) timeout-var timeout
)
176 (nix:write fd
(iobuf-start-pointer buf
)
178 (when (and timeout-var
(zerop timeout-var
))
179 (return-from %flush-obuf
180 (values nil
:timeout
)))))
182 (return-from %flush-obuf
(values nil
:eof
))))))
184 (incf (iobuf-start buf
) num
)
185 (incf bytes-written num
))))
189 ;; FIXME signal something better -- maybe analyze the status
190 (return-from %flush-obuf
(values nil
:fail
)))))
192 (when (iobuf-empty-p buf
)
193 (iobuf-reset buf
) t
))
194 (errorp () (handler-case (iomux:wait-until-fd-ready fd
:write
)
195 (iomux:poll-error
()))))
196 (loop :until
(buffer-emptyp) :do
(write-or-return)
197 :finally
(return (values t bytes-written
))))))
199 ;;; TODO: add timeout support
200 (defun %flush-obuf-if-needed
(stream)
201 (declare (type dual-channel-gray-stream stream
))
202 (with-accessors ((fd output-fd-of
) (ob output-buffer-of
)
203 (must-flush-output-p must-flush-output-p
)) stream
204 (when (or must-flush-output-p
(iobuf-full-p ob
))
206 (setf must-flush-output-p nil
))))
208 (defmethod stream-clear-output ((stream dual-channel-gray-stream
))
209 (with-accessors ((ob output-buffer-of
)
210 (must-flush-output-p must-flush-output-p
)
211 (fd output-fd-of
)) stream
213 (setf must-flush-output-p nil
)
216 (defmethod stream-finish-output ((stream dual-channel-gray-stream
))
217 (with-accessors ((ob output-buffer-of
)
218 (must-flush-output-p must-flush-output-p
)
219 (fd output-fd-of
)) stream
221 (setf must-flush-output-p nil
)
224 (defmethod stream-force-output ((stream dual-channel-gray-stream
))
225 (setf (must-flush-output-p stream
) t
))
227 (defun %write-simple-array-ub8
(stream array start end
)
228 (declare (type dual-channel-gray-stream stream
))
229 (with-accessors ((ob output-buffer-of
)
230 (fd output-fd-of
)) stream
231 (let ((octets-needed (- end start
)))
232 (if (<= octets-needed
(iobuf-end-space-length ob
))
234 (iobuf-copy-from-lisp-array array start ob
235 (iobuf-end ob
) octets-needed
)
236 (incf (iobuf-end ob
) octets-needed
)
237 (%flush-obuf-if-needed stream
))
238 (with-pointer-to-vector-data (ptr array
)
240 (let ((ret (%write-n-bytes
(inc-pointer ptr start
)
243 (incf (iobuf-end ob
) octets-needed
)))))
246 (defun %write-vector-ub8
(stream vector start end
)
247 (declare (type dual-channel-gray-stream stream
))
248 (%write-simple-array-ub8 stream
(coerce vector
'ub8-sarray
) start end
))
250 (defun %write-vector
(stream vector start end
)
251 (declare (type dual-channel-gray-stream stream
))
252 (loop :for offset
:from start
:below end
253 :for octet
:= (aref vector offset
)
254 :do
(stream-write-byte stream octet
)
255 :finally
(return vector
)))
257 (defmethod stream-write-sequence ((stream dual-channel-gray-stream
)
259 (check-bounds seq start end
)
262 (ub8-sarray (%write-simple-array-ub8 stream seq start end
))
263 (string (stream-write-string stream seq start end
))
264 (ub8-vector (%write-vector-ub8 stream seq start end
))
265 (vector (%write-vector stream seq start end
)))))
269 (defun maybe-find-line-ending (fd ib ef
)
270 (let* ((start-off (iobuf-start ib
))
271 (char-code (bref ib start-off
)))
273 (ecase (babel:external-format-eol-style ef
)
274 (:lf
(when (= char-code
(char-code #\Linefeed
))
275 (incf (iobuf-start ib
))
277 (:cr
(when (= char-code
(char-code #\Return
))
278 (incf (iobuf-start ib
))
280 (:crlf
(when (= char-code
(char-code #\Return
))
281 (when (and (= (iobuf-length ib
) 1)
282 (eql (%fill-ibuf ib fd
) :eof
))
283 (incf (iobuf-start ib
))
285 (when (= (bref ib
(1+ start-off
))
286 (char-code #\Linefeed
))
287 (incf (iobuf-start ib
) 2)
288 (return #\Newline
))))))))
290 (defconstant +max-octets-per-char
+ 6)
292 ;; FIXME: currently we return :EOF when read(2) returns 0
293 ;; we should distinguish hard end-of-files (EOF and buffer empty)
294 ;; from soft end-of-files (EOF and *some* bytes still in the buffer
295 ;; but not enough to make a full character)
296 (defmethod stream-read-char ((stream dual-channel-gray-stream
))
297 (with-accessors ((fd input-fd-of
) (ib input-buffer-of
)
298 (unread-index ibuf-unread-index-of
)
299 (ef external-format-of
)) stream
300 (setf unread-index
(iobuf-start ib
))
303 (flet ((fill-buf-or-eof ()
304 (setf ret
(%fill-ibuf ib fd
))
306 (return-from stream-read-char
:eof
))))
307 (cond ((zerop (iobuf-length ib
))
310 ;; Some encodings such as CESU or Java's modified UTF-8 take
311 ;; as much as 6 bytes per character. Make sure we have enough
312 ;; space to collect read-ahead bytes if required.
313 ((< 0 (iobuf-end-space-length ib
) +max-octets-per-char
+)
314 (iobuf-copy-data-to-start ib
)
315 (setf unread-index
0)))
317 (alexandria:when-let
((it (maybe-find-line-ending fd ib ef
)))
318 (return-from stream-read-char it
))
321 (setf (values str ret
)
322 (foreign-string-to-lisp
324 :offset
(iobuf-start ib
)
325 :encoding
(babel:external-format-encoding ef
)
327 (babel:end-of-input-in-character
()
330 (incf (iobuf-start ib
) ret
))
333 (defun maybe-find-line-ending-no-hang (fd ib ef
)
334 (declare (ignore fd
))
335 (let* ((start-off (iobuf-start ib
))
336 (char-code (bref ib start-off
)))
338 (ecase (babel:external-format-eol-style ef
)
339 (:lf
(when (= char-code
(char-code #\Linefeed
))
340 (incf (iobuf-start ib
))
342 (:cr
(when (= char-code
(char-code #\Return
))
343 (incf (iobuf-start ib
))
345 (:crlf
(when (= char-code
(char-code #\Return
))
346 (when (= (iobuf-length ib
) 1)
347 (incf (iobuf-start ib
))
348 (return :starvation
))
349 (when (= (bref ib
(1+ start-off
))
350 (char-code #\Linefeed
))
351 (incf (iobuf-start ib
) 2)
352 (return #\Newline
))))))))
354 (defmethod stream-read-char-no-hang ((stream dual-channel-gray-stream
))
355 (with-accessors ((fd input-fd-of
) (ib input-buffer-of
)
356 (ef external-format-of
)) stream
361 (when (< 0 (iobuf-end-space-length ib
) 4)
362 (iobuf-copy-data-to-start ib
))
363 (when (and (iomux:fd-ready-p fd
:read
)
364 (eql :eof
(%fill-ibuf ib fd
)))
366 (when (zerop (iobuf-length ib
))
367 (return (if eof
:eof nil
)))
369 (let ((line-end (maybe-find-line-ending-no-hang fd ib ef
)))
370 (cond ((eql line-end
:starvation
)
371 (return (if eof
#\Return nil
)))
372 ((characterp line-end
)
376 (setf (values str ret
)
377 (foreign-string-to-lisp
379 :offset
(iobuf-start ib
)
380 :encoding
(babel:external-format-encoding ef
)
382 (babel:end-of-input-in-character
()
384 (incf (iobuf-start ib
) ret
)
387 (defun %stream-unread-char
(stream)
388 (declare (type dual-channel-gray-stream stream
))
389 (with-accessors ((ib input-buffer-of
)
390 (unread-index ibuf-unread-index-of
)) stream
391 (symbol-macrolet ((start (iobuf-start ib
)))
393 ((> start unread-index
) (setf start unread-index
))
394 (t (error "No uncommitted character to unread")))))
397 (defmethod stream-unread-char ((stream dual-channel-gray-stream
) character
)
398 ;; unreading anything but the latest character is wrong,
399 ;; but checking is not mandated by the standard
402 (%stream-unread-char stream
)
403 (unless (ignore-errors (eql (stream-read-char stream
) character
))
404 (error "Trying to unread wrong character ~S" character
)))
406 (declare (ignore character
))
408 (%stream-unread-char stream
))
410 (defmethod stream-peek-char ((stream dual-channel-gray-stream
))
411 (let ((char (stream-read-char stream
)))
412 (cond ((eql char
:eof
) :eof
)
413 (t (%stream-unread-char stream
)
416 ;; (defmethod stream-read-line ((stream dual-channel-gray-stream))
419 (defmethod stream-listen ((stream dual-channel-gray-stream
))
420 (let ((char (stream-read-char-no-hang stream
)))
421 (cond ((characterp char
) (stream-unread-char stream char
) t
)
422 ((eql char
:eof
) nil
)
425 ;;;; Character Output
427 (defmethod stream-write-char ((stream dual-channel-gray-stream
)
428 (character character
))
429 (%flush-obuf-if-needed stream
)
430 (if (char= character
#\Newline
)
431 (%write-line-terminator
432 stream
(babel:external-format-eol-style
(external-format-of stream
)))
433 ;; FIXME: avoid consing a string here. At worst, declare it dynamic-extent
434 (stream-write-string stream
(make-string 1 :initial-element character
))))
436 (defmethod stream-line-column ((stream dual-channel-gray-stream
))
439 (defmethod stream-start-line-p ((stream dual-channel-gray-stream
))
442 (defmethod stream-terpri ((stream dual-channel-gray-stream
))
443 (write-char #\Newline stream
) nil
)
445 (defmethod stream-fresh-line ((stream dual-channel-gray-stream
))
446 (write-char #\Newline stream
) t
)
448 (define-constant +unix-line-terminator
+
449 (make-array 1 :element-type
'ub8
:initial-contents
'(10))
452 (define-constant +dos-line-terminator
+
453 (make-array 2 :element-type
'ub8
:initial-contents
'(13 10))
456 (define-constant +mac-line-terminator
+
457 (make-array 1 :element-type
'ub8
:initial-contents
'(13))
460 (defun %write-line-terminator
(stream line-terminator
)
461 (case line-terminator
462 (:lf
(%write-simple-array-ub8 stream
+unix-line-terminator
+ 0 1))
463 (:cr
(%write-simple-array-ub8 stream
+dos-line-terminator
+ 0 2))
464 (:crlf
(%write-simple-array-ub8 stream
+mac-line-terminator
+ 0 1))))
466 (defmethod stream-write-string ((stream dual-channel-gray-stream
)
468 &optional
(start 0) end
)
469 (check-bounds string start end
)
472 (ef (external-format-of stream
))
473 (line-terminator (babel:external-format-eol-style ef
)))
474 (loop for off1
= start then
(1+ off2
)
475 for nl-off
= (position #\Newline string
:start off1
)
476 for off2
= (or nl-off end
)
477 when nl-off do
(%write-line-terminator stream line-terminator
)
478 when
(> off2 off1
) do
479 ;; FIXME: should probably convert directly to a foreign buffer?
480 (setf octets
(babel:string-to-octets
481 string
:start off1
:end off2
482 :encoding
(babel:external-format-encoding ef
)))
483 (%write-simple-array-ub8 stream octets
0 (length octets
))
484 while
(< off2 end
))))
489 (defmethod stream-read-byte ((stream dual-channel-gray-stream
))
490 (with-accessors ((fd input-fd-of
)
491 (ib input-buffer-of
)) stream
492 (flet ((fill-buf-or-eof ()
494 (when (eql :eof
(%fill-ibuf ib fd
))
495 (return-from stream-read-byte
:eof
))))
496 (when (zerop (iobuf-length ib
))
498 (iobuf-pop-octet ib
))))
502 (defmethod stream-write-byte ((stream dual-channel-gray-stream
) integer
)
503 (check-type integer ub8
"an unsigned 8-bit value")
504 (with-accessors ((ob output-buffer-of
)) stream
505 (%flush-obuf-if-needed stream
)
506 (iobuf-push-octet ob integer
)))