~ chicken-core (master) /port.scm
Trap1;;; port.scm - Optional non-standard ports
2;
3; Copyright (c) 2008-2022, The CHICKEN Team
4; Copyright (c) 2000-2007, Felix L. Winkelmann
5; All rights reserved.
6;
7; Redistribution and use in source and binary forms, with or without
8; modification, are permitted provided that the following conditions
9; are met:
10;
11; Redistributions of source code must retain the above copyright
12; notice, this list of conditions and the following disclaimer.
13; Redistributions in binary form must reproduce the above copyright
14; notice, this list of conditions and the following disclaimer in
15; the documentation and/or other materials provided with the
16; distribution.
17; Neither the name of the author nor the names of its contributors
18; may be used to endorse or promote products derived from this
19; software without specific prior written permission.
20;
21; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24; FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25; COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
26; INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30; STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
32; OF THE POSSIBILITY OF SUCH DAMAGE.
33
34
35(declare
36 (unit port)
37 (uses extras))
38
39(module chicken.port
40 (call-with-input-string
41 call-with-output-string
42 copy-port
43 make-input-port
44 make-output-port
45 port-encoding
46 port-fold
47 port-for-each
48 port-map
49 port-name
50 port-position
51 make-bidirectional-port
52 make-broadcast-port
53 make-concatenated-port
54 set-buffering-mode!
55 terminal-name
56 terminal-port?
57 terminal-size
58 with-error-output-to-port
59 with-input-from-port
60 with-input-from-string
61 with-output-to-port
62 with-output-to-string
63 with-error-output-to-string)
64
65(import scheme
66 chicken.base
67 chicken.fixnum
68 chicken.foreign
69 chicken.io)
70(import (only (scheme base) open-output-string get-output-string open-input-string))
71
72(include "common-declarations.scm")
73
74#>
75
76#if !defined(_WIN32)
77# include <sys/ioctl.h>
78# include <termios.h>
79#endif
80
81#if !defined(__ANDROID__) && defined(TIOCGWINSZ)
82static int get_tty_size(int fd, int *rows, int *cols)
83{
84 struct winsize tty_size;
85 int r;
86
87 memset(&tty_size, 0, sizeof tty_size);
88
89 r = ioctl(fd, TIOCGWINSZ, &tty_size);
90 if (r == 0) {
91 *rows = tty_size.ws_row;
92 *cols = tty_size.ws_col;
93 }
94 return r;
95}
96#else
97static int get_tty_size(int fd, int *rows, int *cols)
98{
99 *rows = *cols = 0;
100 errno = ENOSYS;
101 return -1;
102}
103#endif
104
105#if defined(_WIN32) && !defined(__CYGWIN__)
106char *ttyname(int fd) {
107 errno = ENOSYS;
108 return NULL;
109}
110#endif
111
112<#
113
114
115(define-foreign-variable _iofbf int "_IOFBF")
116(define-foreign-variable _iolbf int "_IOLBF")
117(define-foreign-variable _ionbf int "_IONBF")
118(define-foreign-variable _bufsiz int "BUFSIZ")
119
120(define port-encoding
121 (getter-with-setter
122 (lambda (port)
123 (##sys#check-port port 'port-encoding)
124 (##sys#slot port 15))
125 (lambda (port enc)
126 (##sys#check-port port 'port-encoding)
127 (##sys#check-symbol enc 'port-encoding)
128 (##sys#setslot port 15 enc))
129 "(chicken.port#port-encoding port)"))
130
131(define port-name
132 (getter-with-setter
133 (lambda (#!optional (port ##sys#standard-input))
134 (##sys#check-port port 'port-name)
135 (##sys#slot port 3))
136 (lambda (port name)
137 (##sys#check-port port 'set-port-name!)
138 (##sys#check-string name 'set-port-name!)
139 (##sys#setslot port 3 name))
140 "(chicken.port#port-name port)"))
141
142(define (port-position #!optional (port ##sys#standard-input))
143 (##sys#check-port port 'port-position)
144 (if (##core#inline "C_input_portp" port)
145 (##sys#values (##sys#slot port 4) (##sys#slot port 5))
146 (##sys#error 'port-position "cannot compute position of port" port)))
147
148(define (set-buffering-mode! port mode . size)
149 (##sys#check-port port 'set-buffering-mode!)
150 (let ((size (if (pair? size) (car size) _bufsiz))
151 (mode (case mode
152 ((#:full) _iofbf)
153 ((#:line) _iolbf)
154 ((#:none) _ionbf)
155 (else (##sys#error 'set-buffering-mode! "invalid buffering-mode" mode port)))))
156 (##sys#check-fixnum size 'set-buffering-mode!)
157 (when (fx< (if (eq? 'stream (##sys#slot port 7))
158 ((foreign-lambda* int ((scheme-object p) (int m) (int s))
159 "C_return(setvbuf(C_port_file(p), NULL, m, s));")
160 port mode size)
161 -1)
162 0)
163 (##sys#error 'set-buffering-mode! "cannot set buffering mode" port mode size))))
164
165;;;; Port-mapping (found in Gauche):
166
167(define (port-for-each fn thunk)
168 (let loop ()
169 (let ((x (thunk)))
170 (unless (eof-object? x)
171 (fn x)
172 (loop) ) ) ) )
173
174(define port-map
175 (lambda (fn thunk)
176 (let loop ((xs '()))
177 (let ((x (thunk)))
178 (if (eof-object? x)
179 (##sys#fast-reverse xs)
180 (loop (cons (fn x) xs)))))))
181
182(define (port-fold fn acc thunk)
183 (let loop ((acc acc))
184 (let ((x (thunk)))
185 (if (eof-object? x)
186 acc
187 (loop (fn x acc))) ) ) )
188
189(define-constant +buf-size+ 1024)
190
191(define copy-port
192 (let ((read-char read-char)
193 (write-char write-char))
194 (define (read-and-write src dest)
195 (##sys#check-port src 'copy-port)
196 (##sys#check-port dest 'copy-port)
197 (let ((buf (##sys#make-bytevector +buf-size+)))
198 (let loop ()
199 (let ((n (chicken.io#read-bytevector!/port +buf-size+
200 buf src 0)))
201 (unless (eq? n 0)
202 (chicken.io#write-bytevector buf dest 0 n)
203 (loop))))))
204 (define (read-and-delegate src dest writer)
205 (##sys#check-port src 'copy-port)
206 (let ((buf (##sys#make-bytevector +buf-size+)))
207 (let loop ((p 0))
208 (let* ((n (chicken.io#read-bytevector!/port
209 (fx- +buf-size+ p)
210 buf src p))
211 (fc (##core#inline "C_utf_fragment_counts" buf 0 n))
212 (full (fxshr fc 4))
213 (part (fxand fc 7))
214 (str (##sys#buffer->string buf 0 (fx- n part))))
215 (unless (eq? n 0)
216 (do ((i 0 (fx+ i 1)))
217 ((fx>= i full))
218 (writer (string-ref str i) dest))
219 ;; overlaps, buf source will be at end of buffer
220 (##core#inline "C_copy_memory_with_offset"
221 buf buf
222 (fx- (fx- (##sys#size (##sys#slot str 0)) 1) part)
223 0 part)
224 (loop part))))))
225 (define (delegate src reader dest writer)
226 (let loop ()
227 (let ((x (reader src)))
228 (unless (eof-object? x)
229 (writer x dest)
230 (loop)))))
231 (define (delegate-and-write src reader dest)
232 (##sys#check-port dest 'copy-port)
233 (let ((buf (##sys#make-bytevector (fx+ 4 +buf-size+))))
234 (let loop ((n 0))
235 (when (fx>= n +buf-size+)
236 (chicken.io#write-bytevector buf dest 0 n)
237 (set! n 0))
238 (let ((c (reader src)))
239 (cond ((eof-object? c)
240 (when (fx>= n 0)
241 (chicken.io#write-bytevector buf dest 0 n)))
242 (else
243 (loop (##core#inline "C_utf_insert" buf n c))))))))
244 (lambda (src dest #!optional (read read-char) (write write-char))
245 ;; does not check port args intentionally
246 (cond ((eq? read read-char)
247 (if (eq? write write-char)
248 (read-and-write src dest)
249 (read-and-delegate src dest write)))
250 ((eq? write write-char)
251 (delegate-and-write src read dest))
252 (else (delegate src read dest write))))))
253
254
255;;;; funky-ports
256
257(define (make-broadcast-port . ports)
258 (make-output-port
259 (lambda (s) (for-each (cut scheme#write-string s <>) ports))
260 void
261 (lambda () (for-each flush-output ports)) ) )
262
263(define (make-concatenated-port p1 . ports)
264 (let ((ports (cons p1 ports)))
265 ;;XXX should also forward other port-methods
266 (make-input-port
267 (lambda ()
268 (let loop ()
269 (if (null? ports)
270 #!eof
271 (let ((c (read-char (car ports))))
272 (cond ((eof-object? c)
273 (set! ports (cdr ports))
274 (loop) )
275 (else c) ) ) ) ) )
276 (lambda ()
277 (and (not (null? ports))
278 (char-ready? (car ports))))
279 void
280 peek-char:
281 (lambda ()
282 (let loop ()
283 (if (null? ports)
284 #!eof
285 (let ((c (peek-char (car ports))))
286 (cond ((eof-object? c)
287 (set! ports (cdr ports))
288 (loop) )
289 (else c))))))
290 read-bytevector:
291 (lambda (p n dest start)
292 (let loop ((n n) (c 0) (p start))
293 (cond ((null? ports) c)
294 ((fx<= n 0) c)
295 (else
296 (let ((m (read-bytevector! dest (car ports) p (+ p n))))
297 (when (fx< m n)
298 (set! ports (cdr ports)) )
299 (loop (fx- n m) (fx+ c m) (fx+ p m))))))))))
300
301
302;;; Redirect standard ports:
303
304(define (with-input-from-port port thunk)
305 (##sys#check-input-port port #t 'with-input-from-port)
306 (fluid-let ((##sys#standard-input port))
307 (thunk) ) )
308
309(define (with-output-to-port port thunk)
310 (##sys#check-output-port port #t 'with-output-to-port)
311 (fluid-let ((##sys#standard-output port))
312 (thunk) ) )
313
314(define (with-error-output-to-port port thunk)
315 (##sys#check-output-port port #t 'with-error-output-to-port)
316 (fluid-let ((##sys#standard-error port))
317 (thunk) ) )
318
319;;; Extended string-port operations:
320
321(define call-with-input-string
322 (lambda (str proc)
323 (let ((in (open-input-string str)))
324 (proc in) ) ) )
325
326(define call-with-output-string
327 (lambda (proc)
328 (let ((out (open-output-string)))
329 (proc out)
330 (get-output-string out) ) ) )
331
332(define with-input-from-string
333 (lambda (str thunk)
334 (fluid-let ([##sys#standard-input (open-input-string str)])
335 (thunk) ) ) )
336
337(define with-output-to-string
338 (lambda (thunk)
339 (fluid-let ((##sys#standard-output (open-output-string)))
340 (thunk)
341 (get-output-string ##sys#standard-output) ) ) )
342
343(define with-error-output-to-string
344 (lambda (thunk)
345 (fluid-let ((##sys#standard-error (open-output-string)))
346 (thunk)
347 (get-output-string ##sys#standard-error) ) ) )
348
349;;; Custom ports:
350;
351; - Port-slots:
352;
353; 10: last/peeked
354
355(define make-input-port
356 (lambda (read ready? close #!rest r
357 #!key peek-char read-bytevector read-line read-buffered)
358 ;XXX this is for ensuring old-style calls fail and can be removed at some stage
359 (when (and (pair? r) (not (##core#inline "C_i_keywordp" (car r))))
360 (error 'make-input-port "invalid invocation - use keyword parameters" r))
361 (let* ((class
362 (vector
363 (lambda (p) ; read-char
364 (let ((last (##sys#slot p 10)))
365 (cond (peek-char (read))
366 (last
367 (##sys#setislot p 10 #f)
368 last)
369 (else (read)) ) ) )
370 (lambda (p) ; peek-char
371 (let ((last (##sys#slot p 10)))
372 (cond (peek-char (peek-char))
373 (last last)
374 (else
375 (let ((last (read)))
376 (##sys#setslot p 10 last)
377 last) ) ) ) )
378 #f ; write-char
379 #f ; write-bytevector
380 (lambda (p d) ; close
381 (close))
382 #f ; flush-output
383 (lambda (p) ; char-ready?
384 (ready?) )
385 (or read-bytevector ; read-bytevector!
386 (lambda (p n dest start)
387 (error "binary I/O not supported for custom text input port without bytevector-read method" p)))
388 read-line ; read-line
389 read-buffered))
390 (data (vector #f))
391 (port (##sys#make-port 1 class "(custom)" 'custom)))
392 (##sys#setslot port 10 #f)
393 (##sys#set-port-data! port data)
394 port) ) )
395
396(define make-output-port
397 (lambda (write close #!rest r #!key force-output)
398 ;XXX this is for ensuring old-style calls fail and can be removed at some stage
399 (when (and (pair? r) (not (##core#inline "C_i_keywordp" (car r))))
400 (error 'make-output-port "invalid invocation - use keyword parameters" r))
401 (let* ((class
402 (vector
403 #f ; read-char
404 #f ; peek-char
405 (lambda (p c) ; write-char
406 (write (string c)) )
407 (lambda (p bv from to) ; write-bytevector
408 (let ((len (fx- to from)))
409 (write (##sys#buffer->string bv from len))))
410 (lambda (p d) ; close
411 (close))
412 (lambda (p) ; flush-output
413 (when force-output (force-output)) )
414 #f ; char-ready?
415 #f ; read-bytevector!
416 #f ; read-line
417 #f)) ; read-buffered
418 (data (vector #f))
419 (port (##sys#make-port 2 class "(custom)" 'custom)))
420 (##sys#set-port-data! port data)
421 port) ) )
422
423(define (make-bidirectional-port i o)
424 (let* ((class (vector
425 (lambda (_) ; read-char
426 (read-char i))
427 (lambda (_) ; peek-char
428 (peek-char i))
429 (lambda (_ c) ; write-char
430 (write-char c o))
431 (lambda (_ bv from to) ; write-bytevector
432 (chicken.io#write-bytevector bv o from to))
433 (lambda (_ d) ; close
434 (case d
435 ((1) (close-input-port i))
436 ((2) (close-output-port o))))
437 (lambda (_) ; flush-output
438 (flush-output o))
439 (lambda (_) ; char-ready?
440 (char-ready? i))
441 (lambda (_ n d s) ; read-bytevector!
442 (chicken.io#read-bytevector! d i s (fx+ s n)))
443 (lambda (_ l) ; read-line
444 (read-line i l))
445 (lambda () ; read-buffered
446 (read-buffered i))))
447 (port (##sys#make-port 3 class "(bidirectional)" 'bidirectional)))
448 (##sys#set-port-data! port (vector #f))
449 port))
450
451;; Duplication from posix-common.scm
452(define posix-error
453 (let ((strerror (foreign-lambda c-string "strerror" int))
454 (string-append string-append))
455 (lambda (type loc msg . args)
456 (let ((rn (##sys#update-errno)))
457 (apply ##sys#signal-hook/errno
458 type rn loc (string-append msg " - " (strerror rn)) args)))))
459
460;; Terminal ports
461(define (terminal-port? port)
462 (##sys#check-open-port port 'terminal-port?)
463 (let ((fp (##sys#peek-unsigned-integer port 0)))
464 (and (not (eq? 0 fp)) (##core#inline "C_tty_portp" port))))
465
466(define (check-terminal! caller port)
467 (##sys#check-open-port port caller)
468 (unless (and (eq? 'stream (##sys#slot port 7))
469 (##core#inline "C_tty_portp" port))
470 (##sys#error caller "port is not connected to a terminal" port)))
471
472(define terminal-name
473 (let ((ttyname (foreign-lambda c-string "ttyname" int)))
474 (lambda (port)
475 (check-terminal! 'terminal-name port)
476 (or (ttyname (##core#inline "C_port_fileno" port))
477 (posix-error #:error 'terminal-name
478 "cannot determine terminal name" port)))))
479
480(define terminal-size
481 (let ((ttysize (foreign-lambda int "get_tty_size" int
482 (nonnull-c-pointer int)
483 (nonnull-c-pointer int))))
484 (lambda (port)
485 (check-terminal! 'terminal-size port)
486 (let-location ((columns int)
487 (rows int))
488 (if (fx= 0 (ttysize (##core#inline "C_port_fileno" port)
489 (location columns)
490 (location rows)))
491 (values columns rows)
492 (posix-error #:error 'terminal-size
493 "cannot determine terminal size" port))))))
494
495)