Added READ-BUFFER-SIZE, READ-BUFFER-EMPTY-P, WRITE-BUFFER-SIZE, WRITE-BUFFER-EMPTY...
[iolib.git] / io.streams / gray / gray-stream-methods.lisp
blobded7d6af315fba89146d835644df421b37c5c9ea
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Indent-tabs-mode: NIL -*-
2 ;;;
3 ;;; gray-stream-methods.lisp --- Implementation using gray streams.
4 ;;;
5 ;;; Copyright (C) 2006-2008, Stelian Ionescu <sionescu@common-lisp.net>
6 ;;;
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
13 ;;;
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.
18 ;;;
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)
44 (ob output-buffer-of)
45 (ef external-format-of))
46 stream
47 (setf ib (allocate-iobuf input-buffer-size)
48 ob (allocate-iobuf output-buffer-size)
49 ef external-format)
50 (trivial-garbage:finalize stream #'(lambda () (free-stream-buffers ib ob)))))
52 ;;;; Common Methods
54 (defmethod stream-element-type ((stream dual-channel-gray-stream))
55 '(unsigned-byte 8))
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))
61 stream
62 (trivial-garbage:cancel-finalization stream)
63 (unless (or abort (null ib)) (finish-output stream))
64 (free-stream-buffers ib ob)
65 (setf ib nil ob nil))
66 (call-next-method)
67 (values stream))
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)))
77 ;;;; Input Methods
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))
85 stream
86 (iobuf-reset ib)
87 nil))
89 (defun %fill-ibuf (read-fn fd buf &optional timeout)
90 (when timeout
91 (let ((readablep (iomux:wait-until-fd-ready fd :read timeout)))
92 (unless readablep
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)))))
97 (if (zerop num)
98 :eof
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)
104 (fd input-fd-of)
105 (read-fn read-fn-of))
106 stream
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)))
141 (unless ,end
142 (setq ,end ,length))
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)
149 (when (< start end)
150 (etypecase seq
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))
167 stream
168 (let ((nbytes (min (- end start)
169 (iobuf-length ib))))
170 (iobuf-copy-into-lisp-array ib (iobuf-start ib)
171 sequence start
172 nbytes)
173 (incf (iobuf-start ib) nbytes)
174 (let ((len (iobuf-length ib)))
175 (values (+ start nbytes)
176 (and (plusp len) len))))))
178 ;;;; Output Methods
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)
187 (prog1
188 (funcall write-fn fd (inc-pointer buf bytes-written)
189 nbytes)
190 (when (and timeout-var (zerop timeout-var))
191 (return-from %write-n-bytes
192 (values nil :timeout)))))
193 (nix:epipe ()
194 (return-from %write-n-bytes (values nil :eof))))))
195 (unless (zerop num) (incf bytes-written num))))
196 (write-or-return ()
197 (unless (write-once)
198 (when (errorp)
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)
215 (prog1
216 (funcall write-fn fd (iobuf-start-pointer buf)
217 (iobuf-length buf))
218 (when (and timeout-var (zerop timeout-var))
219 (return-from %flush-obuf
220 (values nil :timeout)))))
221 (nix:epipe ()
222 (return-from %flush-obuf (values nil :eof))))))
223 (unless (zerop num)
224 (incf (iobuf-start buf) num)
225 (incf bytes-written num))))
226 (write-or-return ()
227 (unless (write-once)
228 (when (errorp)
229 ;; FIXME signal something better -- maybe analyze the status
230 (return-from %flush-obuf (values nil :fail)))))
231 (buffer-emptyp ()
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)
246 (dirtyp dirtyp))
247 stream
248 (when (or dirtyp (iobuf-full-p ob))
249 (%flush-obuf write-fn fd ob)
250 (setf dirtyp nil))))
252 (defmethod stream-clear-output ((stream dual-channel-gray-stream))
253 (with-accessors ((ob output-buffer-of)
254 (dirtyp dirtyp))
255 stream
256 (iobuf-reset ob)
257 (setf dirtyp nil)
258 nil))
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)
264 (dirtyp dirtyp))
265 stream
266 (%flush-obuf write-fn fd ob)
267 (setf dirtyp nil)
268 nil))
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))
278 stream
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)
289 octets-needed)))
290 (when (numberp ret)
291 (incf (iobuf-end ob) octets-needed))))))
292 (values array))))
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)
308 (when (< start end)
309 (etypecase seq
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))
323 ;;;; Character Input
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)))
328 (block nil
329 (ecase (babel:external-format-eol-style ef)
330 (:lf (when (= char-code (char-code #\Linefeed))
331 (incf (iobuf-start ib))
332 (return #\Newline)))
333 (:cr (when (= char-code (char-code #\Return))
334 (incf (iobuf-start ib))
335 (return #\Newline)))
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))
340 (return #\Return))
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)
354 (ib input-buffer-of)
355 (read-fn read-fn-of)
356 (unread-index ibuf-unread-index-of)
357 (ef external-format-of))
358 stream
359 (setf unread-index (iobuf-start ib))
360 (let ((str nil)
361 (ret nil))
362 (flet ((fill-buf-or-eof ()
363 (setf ret (%fill-ibuf read-fn fd ib))
364 (when (eq ret :eof)
365 (return-from stream-read-char :eof))))
366 (cond ((zerop (iobuf-length ib))
367 (iobuf-reset ib)
368 (fill-buf-or-eof))
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)))
375 ;; line-end handling
376 (when-let ((it (maybe-find-line-ending read-fn fd ib ef)))
377 (return-from stream-read-char it))
378 (tagbody :start
379 (handler-case
380 (setf (values str ret)
381 (foreign-string-to-lisp
382 (iobuf-data ib)
383 :offset (iobuf-start ib)
384 :count (iobuf-length ib)
385 :encoding (babel:external-format-encoding ef)
386 :max-chars 1))
387 (babel:end-of-input-in-character ()
388 (fill-buf-or-eof)
389 (go :start)))
390 (incf (iobuf-start ib) ret))
391 (char str 0)))))
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)))
397 (block nil
398 (ecase (babel:external-format-eol-style ef)
399 (:lf (when (= char-code (char-code #\Linefeed))
400 (incf (iobuf-start ib))
401 (return #\Newline)))
402 (:cr (when (= char-code (char-code #\Return))
403 (incf (iobuf-start ib))
404 (return #\Newline)))
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)
416 (read-fn read-fn-of)
417 (ib input-buffer-of)
418 (ef external-format-of))
419 stream
420 (let ((str nil)
421 (ret nil)
422 (eof nil))
423 (block nil
424 ;; BUG: this comparision is probably buggy, FIXME. A similar
425 ;; bug was fixed in STREAM-READ-CHAR. Must write a test for
426 ;; this one first.
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)))
431 (setf eof t))
432 (when (zerop (iobuf-length ib))
433 (return (if eof :eof nil)))
434 ;; line-end handling
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)
439 (return line-end))))
440 ;; octet decoding
441 (handler-case
442 (setf (values str ret)
443 (foreign-string-to-lisp
444 (iobuf-data ib)
445 :offset (iobuf-start ib)
446 :count (iobuf-length ib)
447 :encoding (babel:external-format-encoding ef)
448 :max-chars 1))
449 (babel:end-of-input-in-character ()
450 (return nil)))
451 (incf (iobuf-start ib) ret)
452 (char str 0)))))
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))
458 stream
459 (symbol-macrolet ((start (iobuf-start ib)))
460 (cond
461 ((> start unread-index) (setf start unread-index))
462 (t (error "No uncommitted character to unread")))))
463 nil)
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)
473 (values char)))))
475 ;; (defmethod stream-read-line ((stream dual-channel-gray-stream))
476 ;; )
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)
481 ((eq char :eof) nil)
482 (t 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))
499 (values nil))
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))
509 :test 'equalp)
511 (define-constant +dos-line-terminator+
512 (make-array 2 :element-type 'ub8 :initial-contents '(13 10))
513 :test 'equalp)
515 (define-constant +mac-line-terminator+
516 (make-array 1 :element-type 'ub8 :initial-contents '(13))
517 :test 'equalp)
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)
528 (when (< start end)
529 (let* ((octets nil)
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))))
541 (values string))
543 ;;;; Binary Input
545 (defmethod stream-read-byte ((stream dual-channel-gray-stream))
546 (with-accessors ((fd input-fd-of)
547 (read-fn read-fn-of)
548 (ib input-buffer-of))
549 stream
550 (flet ((fill-buf-or-eof ()
551 (iobuf-reset ib)
552 (when (eq :eof (%fill-ibuf read-fn fd ib))
553 (return-from stream-read-byte :eof))))
554 (when (zerop (iobuf-length ib))
555 (fill-buf-or-eof))
556 (iobuf-pop-octet ib))))
558 ;;;; Binary Output
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))
563 stream
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)))