Fixed %WRITE-N-BYTES and %FLUSH-OBUF.
[iolib.git] / io.streams / gray-stream-methods.lisp
blobb02ac2c099b2bc4872e43d685f71e316022df407
1 ;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp -*-
3 ;; Copyright (C) 2007 Stelian Ionescu
4 ;;
5 ;; This code is free software; you can redistribute it and/or
6 ;; modify it under the terms of the version 2.1 of
7 ;; the GNU Lesser General Public License as published by
8 ;; the Free Software Foundation, as clarified by the
9 ;; preamble found here:
10 ;; http://opensource.franz.com/preamble.html
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU Lesser General
18 ;; Public License along with this library; if not, write to the
19 ;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 ;; Boston, MA 02110-1301, USA
22 (in-package :io.streams)
24 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25 ;; ;;
26 ;; Instance Initialization ;;
27 ;; ;;
28 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
30 ;; TODO: use the buffer pool
31 ;; TODO: handle instance reinitialization
32 (defmethod shared-initialize :after ((s dual-channel-gray-stream) slot-names
33 &key (input-buffer-size +bytes-per-iobuf+)
34 (output-buffer-size +bytes-per-iobuf+)
35 (external-format :default))
36 (declare (ignore slot-names))
37 (check-type input-buffer-size buffer-index)
38 (check-type output-buffer-size buffer-index)
39 (when (open-stream-p s) (close s))
40 (with-accessors ((ib input-buffer-of) (ob output-buffer-of)
41 (ef external-format-of)) s
42 (setf ib (allocate-iobuf input-buffer-size)
43 ob (allocate-iobuf output-buffer-size)
44 ef external-format)))
46 ;;;;;;;;;;;;;;;;;;;;
47 ;; ;;
48 ;; Common Methods ;;
49 ;; ;;
50 ;;;;;;;;;;;;;;;;;;;;
52 (defmethod stream-element-type ((stream dual-channel-gray-stream))
53 '(unsigned-byte 8))
55 ;; TODO: use the buffer pool
56 (defmethod close :around ((stream dual-channel-gray-stream) &key abort)
57 (with-accessors ((ib input-buffer-of)
58 (ob output-buffer-of)) stream
59 (unless (or abort (null ib)) (finish-output stream))
60 (when ib (free-iobuf ib))
61 (when ob (free-iobuf ob))
62 (setf ib nil ob nil))
63 (call-next-method)
64 (values stream))
66 (defmethod close ((stream dual-channel-gray-stream) &key abort)
67 (declare (ignore stream abort)))
69 (defmethod (setf external-format-of) (external-format (stream dual-channel-gray-stream))
70 (setf (slot-value stream 'external-format)
71 (ensure-external-format external-format)))
73 ;;;;;;;;;;;;;;;;;;;
74 ;; ;;
75 ;; Input Methods ;;
76 ;; ;;
77 ;;;;;;;;;;;;;;;;;;;
79 (defmethod stream-clear-input ((stream dual-channel-gray-stream))
80 (with-accessors ((ib input-buffer-of)) stream
81 (iobuf-reset ib)
82 nil))
84 (defun %fill-ibuf (buf fd &optional timeout)
85 (when timeout
86 (let ((status
87 (iomux:wait-until-fd-ready fd :read timeout)))
88 ;; FIXME signal something better
89 (cond ((member :timeout status)
90 (return-from %fill-ibuf :timeout))
91 ((member :error status)
92 (error "WAIT-UNTIL-FD-READY returned :ERROR on FD ~S" fd)))))
93 (let ((num (et:repeat-upon-eintr
94 (et:read fd (iobuf-end-pointer buf)
95 (iobuf-end-space-length buf)))))
96 (if (zerop num)
97 :eof
98 (incf (iobuf-end buf) num))))
100 (defun %read-into-simple-array-ub8 (stream array start end)
101 (declare (type dual-channel-gray-stream stream))
102 (with-accessors ((ib input-buffer-of)
103 (fd input-fd-of)) stream
104 (let ((octets-needed (- end start)))
105 (loop :with array-offset := start
106 :for octets-in-buffer := (iobuf-length ib)
107 :for nbytes := (min octets-needed octets-in-buffer)
108 :when (plusp nbytes) :do
109 (iobuf-copy-into-lisp-array ib (iobuf-start ib)
110 array array-offset nbytes)
111 (incf array-offset nbytes)
112 (decf octets-needed nbytes)
113 (incf (iobuf-start ib) nbytes)
114 :if (zerop octets-needed) :do (loop-finish)
115 :else :do (iobuf-reset ib)
116 :when (eql :eof (%fill-ibuf ib fd)) :do (loop-finish)
117 :finally (return array-offset)))))
119 (defun %read-into-string (stream string start end)
120 (declare (type dual-channel-gray-stream stream))
121 (loop :for offset :from start :below end
122 :for char := (stream-read-char stream)
123 :if (eql char :eof) :do (loop-finish)
124 :else :do (setf (char string offset) char)
125 :finally (return offset)))
127 (defun %read-into-vector (stream vector start end)
128 (declare (type dual-channel-gray-stream stream))
129 (loop :for offset :from start :below end
130 :for octet := (stream-read-byte stream)
131 :if (eql octet :eof) :do (loop-finish)
132 :else :do (setf (aref vector offset) octet)
133 :finally (return offset)))
135 #-clisp
136 (defmethod #-openmcl stream-read-sequence
137 #+openmcl stream-read-vector
138 #-lispworks
139 ((stream dual-channel-gray-stream) seq
140 &optional (start 0) end)
141 #+lispworks
142 ((stream dual-channel-gray-stream) seq start end)
143 (setf (values start end) (%check-bounds seq start end))
144 (when (< start end)
145 (etypecase seq
146 (ub8-sarray
147 (%read-into-simple-array-ub8 stream seq start end))
148 (string
149 (%read-into-string stream seq start end))
150 (vector
151 (%read-into-vector stream seq start end)))))
153 #+clisp
154 (defmethod stream-read-byte-sequence ((stream dual-channel-gray-stream) seq
155 &optional (start 0) end
156 no-hang interactive)
157 (declare (ignore no-hang interactive))
158 (setf (values start end) (%check-bounds seq start end))
159 (when (< start end)
160 (etypecase seq
161 (ub8-sarray
162 (%read-into-simple-array-ub8 stream seq start end))
163 (vector
164 (%read-into-vector stream seq start end)))))
166 #+clisp
167 (defmethod stream-read-char-sequence ((stream dual-channel-gray-stream) seq
168 &optional (start 0) end)
169 (setf (values start end) (%check-bounds seq start end))
170 (when (< start end)
171 (etypecase seq
172 (string
173 (%read-into-string stream seq start end)))))
175 ;;;;;;;;;;;;;;;;;;;;
176 ;; ;;
177 ;; Output Methods ;;
178 ;; ;;
179 ;;;;;;;;;;;;;;;;;;;;
181 (defun %write-n-bytes (buf fd nbytes &optional timeout)
182 (declare (type stream-buffer buf))
183 (let ((bytes-written 0))
184 (labels ((write-once ()
185 (let ((num (handler-case
186 (et:repeat-upon-condition-decreasing-timeout
187 ((et:eintr) timeout-var timeout)
188 (prog1
189 (et:write fd (inc-pointer buf bytes-written) nbytes)
190 (when (and timeout-var (zerop timeout-var))
191 (return-from %write-n-bytes (values nil :timeout)))))
192 (et:epipe ()
193 (return-from %write-n-bytes (values nil :eof))))))
194 (unless (zerop num) (incf bytes-written num))))
195 (write-or-return ()
196 (unless (write-once)
197 (when (errorp)
198 ;; FIXME signal something better -- maybe analyze the status
199 (return-from %write-n-bytes (values nil :fail)))))
200 (buffer-emptyp () (= bytes-written nbytes))
201 (errorp () (member :error (iomux:wait-until-fd-ready fd :write))))
202 (loop :until (buffer-emptyp) :do (write-or-return)
203 :finally (return (values t bytes-written))))))
205 (defun %flush-obuf (buf fd &optional timeout)
206 (declare (type iobuf buf))
207 (let ((bytes-written 0))
208 (labels ((write-once ()
209 (let ((num (handler-case
210 (et:repeat-upon-condition-decreasing-timeout
211 ((et:eintr) timeout-var timeout)
212 (prog1
213 (et:write fd (iobuf-start-pointer buf)
214 (iobuf-length buf))
215 (when (and timeout-var (zerop timeout-var))
216 (return-from %flush-obuf (values nil :timeout)))))
217 (et:epipe ()
218 (return-from %flush-obuf (values nil :eof))))))
219 (unless (zerop num)
220 (incf (iobuf-start buf) num)
221 (incf bytes-written num))))
222 (write-or-return ()
223 (unless (write-once)
224 (when (errorp)
225 ;; FIXME signal something better -- maybe analyze the status
226 (return-from %flush-obuf (values nil :fail)))))
227 (buffer-emptyp ()
228 (when (iobuf-empty-p buf)
229 (iobuf-reset buf) t))
230 (errorp () (member :error (iomux:wait-until-fd-ready fd :write))))
231 (loop :until (buffer-emptyp) :do (write-or-return)
232 :finally (return (values t bytes-written))))))
234 ;; TODO: add timeout support
235 (defun %flush-obuf-if-needed (stream)
236 (declare (type dual-channel-gray-stream stream))
237 (with-accessors ((fd output-fd-of) (ob output-buffer-of)
238 (must-flush-output-p must-flush-output-p)) stream
239 (when (or must-flush-output-p (iobuf-full-p ob))
240 (%flush-obuf ob fd)
241 (setf must-flush-output-p nil))))
243 (defmethod stream-clear-output ((stream dual-channel-gray-stream))
244 (with-accessors ((ob output-buffer-of)
245 (must-flush-output-p must-flush-output-p)
246 (fd output-fd-of)) stream
247 (iobuf-reset ob)
248 (setf must-flush-output-p nil)
249 nil))
251 (defmethod stream-finish-output ((stream dual-channel-gray-stream))
252 (with-accessors ((ob output-buffer-of)
253 (must-flush-output-p must-flush-output-p)
254 (fd output-fd-of)) stream
255 (%flush-obuf ob fd)
256 (setf must-flush-output-p nil)
257 nil))
259 (defmethod stream-force-output ((stream dual-channel-gray-stream))
260 (setf (must-flush-output-p stream) t))
262 (defun %write-simple-array-ub8 (stream array start end)
263 (declare (type dual-channel-gray-stream stream))
264 (with-accessors ((ob output-buffer-of)
265 (fd output-fd-of)) stream
266 (let ((octets-needed (- end start)))
267 (if (<= octets-needed (iobuf-end-space-length ob))
268 (progn
269 (iobuf-copy-from-lisp-array array start ob
270 (iobuf-end ob) octets-needed)
271 (incf (iobuf-end ob) octets-needed)
272 (%flush-obuf-if-needed stream))
273 (with-pointer-to-vector-data (ptr array)
274 (%flush-obuf ob fd)
275 (let ((ret (%write-n-bytes (inc-pointer ptr start)
276 fd octets-needed)))
277 (when (numberp ret)
278 (incf (iobuf-end ob) octets-needed)))))
279 (values array))))
281 (defun %write-vector-ub8 (stream vector start end)
282 (declare (type dual-channel-gray-stream stream))
283 (%write-simple-array-ub8 stream (coerce vector 'ub8-sarray) start end))
285 (defun %write-vector (stream vector start end)
286 (declare (type dual-channel-gray-stream stream))
287 (loop :for offset :from start :below end
288 :for octet := (aref vector offset)
289 :do (stream-write-byte stream octet)
290 :finally (return vector)))
292 #-clisp
293 (defmethod #-openmcl stream-write-sequence
294 #+openmcl stream-write-vector
295 #-lispworks
296 ((stream dual-channel-gray-stream) seq
297 &optional (start 0) end)
298 #+lispworks
299 ((stream dual-channel-gray-stream) seq start end)
300 (setf (values start end) (%check-bounds seq start end))
301 (when (< start end)
302 (etypecase seq
303 (ub8-sarray
304 (%write-simple-array-ub8 stream seq start end))
305 (string
306 (stream-write-string stream seq start end))
307 (ub8-vector
308 (%write-vector-ub8 stream seq start end))
309 (vector
310 (%write-vector stream seq start end)))))
312 #+clisp
313 (defmethod stream-write-byte-sequence ((stream dual-channel-gray-stream) seq
314 &optional (start 0) end
315 no-hang interactive)
316 (declare (ignore no-hang interactive))
317 (setf (values start end) (%check-bounds seq start end))
318 (when (< start end)
319 (etypecase seq
320 (ub8-sarray
321 (%write-simple-array-ub8 stream seq start end))
322 (ub8-vector
323 (%write-vector-ub8 stream seq start end))
324 (vector
325 (%write-vector stream seq start end)))))
327 #+clisp
328 (defmethod stream-write-char-sequence ((stream dual-channel-gray-stream) seq
329 &optional (start 0) end)
330 (setf (values start end) (%check-bounds seq start end))
331 (when (< start end)
332 (etypecase seq
333 (string
334 (stream-write-string stream seq start end)))))
336 ;;;;;;;;;;;;;;;;;;;;;
337 ;; ;;
338 ;; Character Input ;;
339 ;; ;;
340 ;;;;;;;;;;;;;;;;;;;;;
342 (defun maybe-find-line-ending (fd ib ef)
343 (let* ((start-off (iobuf-start ib))
344 (char-code (bref ib start-off)))
345 (block nil
346 (ecase (ioenc:ef-line-terminator ef)
347 (:unix (when (= char-code (char-code #\Linefeed))
348 (incf (iobuf-start ib))
349 (return #\Newline)))
350 (:mac (when (= char-code (char-code #\Return))
351 (incf (iobuf-start ib))
352 (return #\Newline)))
353 (:dos (when (= char-code (char-code #\Return))
354 (when (and (= (iobuf-length ib) 1)
355 (eql (%fill-ibuf ib fd) :eof))
356 (incf (iobuf-start ib))
357 (return #\Return))
358 (when (= (bref ib (1+ start-off))
359 (char-code #\Linefeed))
360 (incf (iobuf-start ib) 2)
361 (return #\Newline))))))))
363 (define-constant +max-octets-per-char+ 6)
365 ;; FIXME: currently we return :EOF when read(2) returns 0
366 ;; we should distinguish hard end-of-files(EOF and buffer empty)
367 ;; from soft end-of-files(EOF and *some* bytes still in the buffer
368 ;; but not enough to make a full character)
369 (defmethod stream-read-char ((stream dual-channel-gray-stream))
370 (with-accessors ((fd input-fd-of) (ib input-buffer-of)
371 (unread-index ibuf-unread-index-of)
372 (ef external-format-of)) stream
373 (flet ((decode-one-char (str ib ef)
374 (ioenc::%octets-to-string (iobuf-data ib) str (iobuf-start ib)
375 (iobuf-end ib) ef 1)))
376 (setf unread-index (iobuf-start ib))
377 (let ((str (make-string 1))
378 (ret nil))
379 (flet ((fill-buf-or-eof ()
380 (setf ret (%fill-ibuf ib fd))
381 (when (eql ret :eof)
382 (return-from stream-read-char :eof))))
383 (cond ((zerop (iobuf-length ib))
384 (iobuf-reset ib)
385 (fill-buf-or-eof))
386 ;; Some encodings such as CESU or Java's modified UTF-8 take
387 ;; as much as 6 bytes per character. Make sure we have enough
388 ;; space to collect read-ahead bytes if required.
389 ((< 0 (iobuf-end-space-length ib) +max-octets-per-char+)
390 (iobuf-copy-data-to-start ib)
391 (setf unread-index 0)))
392 ;; line-end handling
393 (return-if stream-read-char (maybe-find-line-ending fd ib ef))
394 (tagbody :start
395 (handler-case
396 (setf ret (nth-value 1 (decode-one-char str ib ef)))
397 (end-of-input-in-character ()
398 (fill-buf-or-eof)
399 (go :start)))
400 (incf (iobuf-start ib) ret))
401 (char str 0))))))
403 (defun maybe-find-line-ending-no-hang (fd ib ef)
404 (declare (ignore fd))
405 (let* ((start-off (iobuf-start ib))
406 (char-code (bref ib start-off)))
407 (block nil
408 (ecase (ioenc:ef-line-terminator ef)
409 (:unix (when (= char-code (char-code #\Linefeed))
410 (incf (iobuf-start ib))
411 (return #\Newline)))
412 (:mac (when (= char-code (char-code #\Return))
413 (incf (iobuf-start ib))
414 (return #\Newline)))
415 (:dos (when (= char-code (char-code #\Return))
416 (when (= (iobuf-length ib) 1)
417 (incf (iobuf-start ib))
418 (return :starvation))
419 (when (= (bref ib (1+ start-off))
420 (char-code #\Linefeed))
421 (incf (iobuf-start ib) 2)
422 (return #\Newline))))))))
424 (defmethod stream-read-char-no-hang ((stream dual-channel-gray-stream))
425 (with-accessors ((fd input-fd-of) (ib input-buffer-of)
426 (ef external-format-of)) stream
427 (let ((str (make-string 1))
428 (ret nil)
429 (eof nil))
430 (block nil
431 (when (< 0 (iobuf-end-space-length ib) 4)
432 (iobuf-copy-data-to-start ib))
433 (when (and (iomux:fd-ready-p fd :read)
434 (eql :eof (%fill-ibuf ib fd)))
435 (setf eof t))
436 (when (zerop (iobuf-length ib))
437 (return (if eof :eof nil)))
438 ;; line-end handling
439 (let ((line-end
440 (maybe-find-line-ending-no-hang fd ib ef)))
441 (cond ((eql line-end :starvation)
442 (return (if eof #\Return nil)))
443 ((characterp line-end)
444 (return line-end))))
445 ;; octet decoding
446 (handler-case
447 (setf ret (nth-value 1 (ioenc::%octets-to-string
448 (iobuf-data ib) str
449 (iobuf-start ib)
450 (iobuf-end ib) ef 1)))
451 (end-of-input-in-character ()
452 (return nil)))
453 (incf (iobuf-start ib) ret)
454 (char str 0)))))
456 (defun %stream-unread-char (stream)
457 (declare (type dual-channel-gray-stream stream))
458 (with-accessors ((ib input-buffer-of)
459 (unread-index ibuf-unread-index-of)) stream
460 (symbol-macrolet ((start (iobuf-start ib)))
461 (cond
462 ((> start unread-index)
463 (setf start unread-index))
465 (error "No uncommitted character to unread")))))
466 nil)
468 (defmethod stream-unread-char ((stream dual-channel-gray-stream) character)
469 ;; unreading anything but the latest character is wrong,
470 ;; but checking is not mandated by the standard
471 #+iolib-debug
472 (progn
473 (%stream-unread-char stream)
474 (unless (ignore-errors (eql (stream-read-char stream) character))
475 (error "Trying to unread wrong character ~S" character)))
476 #-iolib-debug
477 (declare (ignore character))
478 #-iolib-debug
479 (%stream-unread-char stream))
481 (defmethod stream-peek-char ((stream dual-channel-gray-stream))
482 (let ((char (stream-read-char stream)))
483 (cond ((eql char :eof) :eof)
484 (t (%stream-unread-char stream)
485 (values char)))))
487 ;; (defmethod stream-read-line ((stream dual-channel-gray-stream))
488 ;; )
490 (defmethod stream-listen ((stream dual-channel-gray-stream))
491 (let ((char (stream-read-char-no-hang stream)))
492 (cond ((characterp char)
493 (stream-unread-char stream char)
495 ((eql char :eof)
496 nil)
497 (t t))))
499 ;;;;;;;;;;;;;;;;;;;;;;
500 ;; ;;
501 ;; Character Output ;;
502 ;; ;;
503 ;;;;;;;;;;;;;;;;;;;;;;
505 (defmethod stream-write-char ((stream dual-channel-gray-stream)
506 (character character))
507 (%flush-obuf-if-needed stream)
508 (if (char= character #\Newline)
509 (%write-line-terminator stream (ioenc:ef-line-terminator (external-format-of stream)))
510 ;; FIXME: avoid consing a string here. At worst, declare it dynamic-extent
511 (stream-write-string stream (make-string 1 :initial-element character))))
513 (defmethod stream-line-column ((stream dual-channel-gray-stream))
516 (defmethod stream-start-line-p ((stream dual-channel-gray-stream))
517 (values nil))
519 (defmethod stream-terpri ((stream dual-channel-gray-stream))
520 (write-char #\Newline stream) nil)
522 (defmethod stream-fresh-line ((stream dual-channel-gray-stream))
523 (write-char #\Newline stream) t)
525 (define-constant +unix-line-terminator+
526 (make-array 1 :element-type 'ub8 :initial-contents '(10)))
527 (define-constant +dos-line-terminator+
528 (make-array 2 :element-type 'ub8 :initial-contents '(13 10)))
529 (define-constant +mac-line-terminator+
530 (make-array 1 :element-type 'ub8 :initial-contents '(13)))
532 (defun %write-line-terminator (stream line-terminator)
533 (case line-terminator
534 (:unix (%write-simple-array-ub8 stream +unix-line-terminator+ 0 1))
535 (:dos (%write-simple-array-ub8 stream +dos-line-terminator+ 0 2))
536 (:mac (%write-simple-array-ub8 stream +mac-line-terminator+ 0 1))))
538 (defmethod stream-write-string ((stream dual-channel-gray-stream)
539 (string string)
540 &optional (start 0) end)
541 (setf (values start end) (%check-bounds string start end))
542 (when (< start end)
543 (let* ((octets nil)
544 (ef (external-format-of stream))
545 (line-terminator (ioenc:ef-line-terminator ef)))
546 (loop :for off1 := start :then (1+ off2)
547 :for nl-off := (position #\Newline string :start off1)
548 :for off2 := (or nl-off end)
549 :when nl-off :do (%write-line-terminator stream line-terminator)
550 :when (> off2 off1) :do
551 (setf octets (ioenc:string-to-octets
552 string :start off1 :end off2
553 :external-format ef))
554 (%write-simple-array-ub8 stream octets 0 (length octets))
555 :while (< off2 end))))
556 (values string))
558 ;;;;;;;;;;;;;;;;;;
559 ;; ;;
560 ;; Binary Input ;;
561 ;; ;;
562 ;;;;;;;;;;;;;;;;;;
564 (defmethod stream-read-byte ((stream dual-channel-gray-stream))
565 (with-accessors ((fd input-fd-of)
566 (ib input-buffer-of)) stream
567 (flet ((fill-buf-or-eof ()
568 (iobuf-reset ib)
569 (when (eql :eof (%fill-ibuf ib fd))
570 (return-from stream-read-byte :eof))))
571 (when (zerop (iobuf-length ib))
572 (fill-buf-or-eof))
573 (iobuf-pop-octet ib))))
575 ;;;;;;;;;;;;;;;;;;;
576 ;; ;;
577 ;; Binary Output ;;
578 ;; ;;
579 ;;;;;;;;;;;;;;;;;;;
581 (defmethod stream-write-byte ((stream dual-channel-gray-stream) integer)
582 (check-type integer ub8 "an unsigned 8-bit value")
583 (with-accessors ((ob output-buffer-of)) stream
584 (%flush-obuf-if-needed stream)
585 (iobuf-push-octet ob integer)))