~ 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-buf port writer)
195 (let ((buf (make-string +buf-size+)))
196 (let loop ()
197 (let ((n (read-string! +buf-size+ buf port)))
198 (unless (eq? n 0)
199 (writer buf n)
200 (loop))))))
201 (define (write-buf buf n port writer)
202 (do ((i 0 (fx+ i 1)))
203 ((fx>= i n))
204 (writer (string-ref buf i) port)))
205 (define (read-and-write reader writer)
206 (let loop ()
207 (let ((x (reader)))
208 (unless (eof-object? x)
209 (writer x)
210 (loop)))))
211 (define (read-and-write-buf src dest reader)
212 (let ((buf (make-string +buf-size+)))
213 (let loop ((n 0))
214 (when (fx>= n +buf-size+)
215 (scheme#write-string buf dest 0 +buf-size+)
216 (set! n 0))
217 (let ((c (reader src)))
218 (cond ((eof-object? c)
219 (when (fx>= n 0)
220 (scheme#write-string buf dest 0 n)))
221 (else
222 (string-set! buf n c)
223 (loop (fx+ n 1))))))))
224 (lambda (src dest #!optional (read read-char) (write write-char))
225 ;; does not check port args intentionally
226 (cond ((eq? read read-char)
227 (read-buf
228 src
229 (if (eq? write write-char)
230 (lambda (buf n) (scheme#write-string buf dest 0 n))
231 (lambda (buf n) (write-buf buf n dest write)))))
232 ((eq? write write-char)
233 (read-and-write-buf src dest read))
234 (else
235 (read-and-write
236 (lambda () (read src))
237 (lambda (x) (write x dest))))))))
238
239
240;;;; funky-ports
241
242(define (make-broadcast-port . ports)
243 (make-output-port
244 (lambda (s) (for-each (cut scheme#write-string s <>) ports))
245 void
246 (lambda () (for-each flush-output ports)) ) )
247
248(define (make-concatenated-port p1 . ports)
249 (let ((ports (cons p1 ports)))
250 ;;XXX should also forward other port-methods
251 (make-input-port
252 (lambda ()
253 (let loop ()
254 (if (null? ports)
255 #!eof
256 (let ((c (read-char (car ports))))
257 (cond ((eof-object? c)
258 (set! ports (cdr ports))
259 (loop) )
260 (else c) ) ) ) ) )
261 (lambda ()
262 (and (not (null? ports))
263 (char-ready? (car ports))))
264 void
265 peek-char:
266 (lambda ()
267 (let loop ()
268 (if (null? ports)
269 #!eof
270 (let ((c (peek-char (car ports))))
271 (cond ((eof-object? c)
272 (set! ports (cdr ports))
273 (loop) )
274 (else c))))))
275 read-bytevector:
276 (lambda (p n dest start)
277 (let loop ((n n) (c 0) (p start))
278 (cond ((null? ports) c)
279 ((fx<= n 0) c)
280 (else
281 (let ((m (read-bytevector! dest (car ports) p (+ p n))))
282 (when (fx< m n)
283 (set! ports (cdr ports)) )
284 (loop (fx- n m) (fx+ c m) (fx+ p m))))))))))
285
286
287;;; Redirect standard ports:
288
289(define (with-input-from-port port thunk)
290 (##sys#check-input-port port #t 'with-input-from-port)
291 (fluid-let ((##sys#standard-input port))
292 (thunk) ) )
293
294(define (with-output-to-port port thunk)
295 (##sys#check-output-port port #t 'with-output-to-port)
296 (fluid-let ((##sys#standard-output port))
297 (thunk) ) )
298
299(define (with-error-output-to-port port thunk)
300 (##sys#check-output-port port #t 'with-error-output-to-port)
301 (fluid-let ((##sys#standard-error port))
302 (thunk) ) )
303
304;;; Extended string-port operations:
305
306(define call-with-input-string
307 (lambda (str proc)
308 (let ((in (open-input-string str)))
309 (proc in) ) ) )
310
311(define call-with-output-string
312 (lambda (proc)
313 (let ((out (open-output-string)))
314 (proc out)
315 (get-output-string out) ) ) )
316
317(define with-input-from-string
318 (lambda (str thunk)
319 (fluid-let ([##sys#standard-input (open-input-string str)])
320 (thunk) ) ) )
321
322(define with-output-to-string
323 (lambda (thunk)
324 (fluid-let ((##sys#standard-output (open-output-string)))
325 (thunk)
326 (get-output-string ##sys#standard-output) ) ) )
327
328(define with-error-output-to-string
329 (lambda (thunk)
330 (fluid-let ((##sys#standard-error (open-output-string)))
331 (thunk)
332 (get-output-string ##sys#standard-error) ) ) )
333
334;;; Custom ports:
335;
336; - Port-slots:
337;
338; 10: last/peeked
339
340(define make-input-port
341 (lambda (read ready? close #!rest r
342 #!key peek-char read-bytevector read-line read-buffered)
343 ;XXX this is for ensuring old-style calls fail and can be removed at some stage
344 (when (and (pair? r) (not (##core#inline "C_i_keywordp" (car r))))
345 (error 'make-input-port "invalid invocation - use keyword parameters" r))
346 (let* ((class
347 (vector
348 (lambda (p) ; read-char
349 (let ((last (##sys#slot p 10)))
350 (cond (peek-char (read))
351 (last
352 (##sys#setislot p 10 #f)
353 last)
354 (else (read)) ) ) )
355 (lambda (p) ; peek-char
356 (let ((last (##sys#slot p 10)))
357 (cond (peek-char (peek-char))
358 (last last)
359 (else
360 (let ((last (read)))
361 (##sys#setslot p 10 last)
362 last) ) ) ) )
363 #f ; write-char
364 #f ; write-bytevector
365 (lambda (p d) ; close
366 (close))
367 #f ; flush-output
368 (lambda (p) ; char-ready?
369 (ready?) )
370 (or read-bytevector ; read-bytevector!
371 (lambda (p n dest start)
372 (error "binary I/O not supported for custom text input port without bytevector-read method" p)))
373 read-line ; read-line
374 read-buffered))
375 (data (vector #f))
376 (port (##sys#make-port 1 class "(custom)" 'custom)))
377 (##sys#setslot port 10 #f)
378 (##sys#set-port-data! port data)
379 port) ) )
380
381(define make-output-port
382 (lambda (write close #!rest r #!key force-output)
383 ;XXX this is for ensuring old-style calls fail and can be removed at some stage
384 (when (and (pair? r) (not (##core#inline "C_i_keywordp" (car r))))
385 (error 'make-output-port "invalid invocation - use keyword parameters" r))
386 (let* ((class
387 (vector
388 #f ; read-char
389 #f ; peek-char
390 (lambda (p c) ; write-char
391 (write (string c)) )
392 (lambda (p bv from to) ; write-bytevector
393 (let ((len (fx- to from)))
394 (write (##sys#buffer->string bv from len))))
395 (lambda (p d) ; close
396 (close))
397 (lambda (p) ; flush-output
398 (when force-output (force-output)) )
399 #f ; char-ready?
400 #f ; read-bytevector!
401 #f ; read-line
402 #f)) ; read-buffered
403 (data (vector #f))
404 (port (##sys#make-port 2 class "(custom)" 'custom)))
405 (##sys#set-port-data! port data)
406 port) ) )
407
408(define (make-bidirectional-port i o)
409 (let* ((class (vector
410 (lambda (_) ; read-char
411 (read-char i))
412 (lambda (_) ; peek-char
413 (peek-char i))
414 (lambda (_ c) ; write-char
415 (write-char c o))
416 (lambda (_ bv from to) ; write-bytevector
417 (chicken.io#write-bytevector bv o from to))
418 (lambda (_ d) ; close
419 (case d
420 ((1) (close-input-port i))
421 ((2) (close-output-port o))))
422 (lambda (_) ; flush-output
423 (flush-output o))
424 (lambda (_) ; char-ready?
425 (char-ready? i))
426 (lambda (_ n d s) ; read-bytevector!
427 (chicken.io#read-bytevector! d i s (fx+ s n)))
428 (lambda (_ l) ; read-line
429 (read-line i l))
430 (lambda () ; read-buffered
431 (read-buffered i))))
432 (port (##sys#make-port 3 class "(bidirectional)" 'bidirectional)))
433 (##sys#set-port-data! port (vector #f))
434 port))
435
436;; Duplication from posix-common.scm
437(define posix-error
438 (let ((strerror (foreign-lambda c-string "strerror" int))
439 (string-append string-append))
440 (lambda (type loc msg . args)
441 (let ((rn (##sys#update-errno)))
442 (apply ##sys#signal-hook/errno
443 type rn loc (string-append msg " - " (strerror rn)) args)))))
444
445;; Terminal ports
446(define (terminal-port? port)
447 (##sys#check-open-port port 'terminal-port?)
448 (let ((fp (##sys#peek-unsigned-integer port 0)))
449 (and (not (eq? 0 fp)) (##core#inline "C_tty_portp" port))))
450
451(define (check-terminal! caller port)
452 (##sys#check-open-port port caller)
453 (unless (and (eq? 'stream (##sys#slot port 7))
454 (##core#inline "C_tty_portp" port))
455 (##sys#error caller "port is not connected to a terminal" port)))
456
457(define terminal-name
458 (let ((ttyname (foreign-lambda c-string "ttyname" int)))
459 (lambda (port)
460 (check-terminal! 'terminal-name port)
461 (or (ttyname (##core#inline "C_port_fileno" port))
462 (posix-error #:error 'terminal-name
463 "cannot determine terminal name" port)))))
464
465(define terminal-size
466 (let ((ttysize (foreign-lambda int "get_tty_size" int
467 (nonnull-c-pointer int)
468 (nonnull-c-pointer int))))
469 (lambda (port)
470 (check-terminal! 'terminal-size port)
471 (let-location ((columns int)
472 (rows int))
473 (if (fx= 0 (ttysize (##core#inline "C_port_fileno" port)
474 (location columns)
475 (location rows)))
476 (values columns rows)
477 (posix-error #:error 'terminal-size
478 "cannot determine terminal size" port))))))
479
480)