~ chicken-core (chicken-5) /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-fold
46 port-for-each
47 port-map
48 port-name
49 port-position
50 make-bidirectional-port
51 make-broadcast-port
52 make-concatenated-port
53 set-buffering-mode!
54 set-port-name!
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
71(include "common-declarations.scm")
72
73#>
74
75#if !defined(_WIN32)
76# include <sys/ioctl.h>
77# include <termios.h>
78#endif
79
80#if !defined(__ANDROID__) && defined(TIOCGWINSZ)
81static int get_tty_size(int fd, int *rows, int *cols)
82{
83 struct winsize tty_size;
84 int r;
85
86 memset(&tty_size, 0, sizeof tty_size);
87
88 r = ioctl(fd, TIOCGWINSZ, &tty_size);
89 if (r == 0) {
90 *rows = tty_size.ws_row;
91 *cols = tty_size.ws_col;
92 }
93 return r;
94}
95#else
96static int get_tty_size(int fd, int *rows, int *cols)
97{
98 *rows = *cols = 0;
99 errno = ENOSYS;
100 return -1;
101}
102#endif
103
104#if defined(_WIN32) && !defined(__CYGWIN__)
105char *ttyname(int fd) {
106 errno = ENOSYS;
107 return NULL;
108}
109#endif
110
111<#
112
113
114(define-foreign-variable _iofbf int "_IOFBF")
115(define-foreign-variable _iolbf int "_IOLBF")
116(define-foreign-variable _ionbf int "_IONBF")
117(define-foreign-variable _bufsiz int "BUFSIZ")
118
119(define (port-name #!optional (port ##sys#standard-input))
120 (##sys#check-port port 'port-name)
121 (##sys#slot port 3))
122
123(define (set-port-name! port name)
124 (##sys#check-port port 'set-port-name!)
125 (##sys#check-string name 'set-port-name!)
126 (##sys#setslot port 3 name))
127
128(define (port-position #!optional (port ##sys#standard-input))
129 (##sys#check-port port 'port-position)
130 (if (##core#inline "C_input_portp" port)
131 (##sys#values (##sys#slot port 4) (##sys#slot port 5))
132 (##sys#error 'port-position "cannot compute position of port" port)))
133
134(define (set-buffering-mode! port mode . size)
135 (##sys#check-port port 'set-buffering-mode!)
136 (let ((size (if (pair? size) (car size) _bufsiz))
137 (mode (case mode
138 ((#:full) _iofbf)
139 ((#:line) _iolbf)
140 ((#:none) _ionbf)
141 (else (##sys#error 'set-buffering-mode! "invalid buffering-mode" mode port)))))
142 (##sys#check-fixnum size 'set-buffering-mode!)
143 (when (fx< (if (eq? 'stream (##sys#slot port 7))
144 ((foreign-lambda* int ((scheme-object p) (int m) (int s))
145 "C_return(setvbuf(C_port_file(p), NULL, m, s));")
146 port mode size)
147 -1)
148 0)
149 (##sys#error 'set-buffering-mode! "cannot set buffering mode" port mode size))))
150
151;;;; Port-mapping (found in Gauche):
152
153(define (port-for-each fn thunk)
154 (let loop ()
155 (let ((x (thunk)))
156 (unless (eof-object? x)
157 (fn x)
158 (loop) ) ) ) )
159
160(define port-map
161 (lambda (fn thunk)
162 (let loop ((xs '()))
163 (let ((x (thunk)))
164 (if (eof-object? x)
165 (##sys#fast-reverse xs)
166 (loop (cons (fn x) xs)))))))
167
168(define (port-fold fn acc thunk)
169 (let loop ((acc acc))
170 (let ((x (thunk)))
171 (if (eof-object? x)
172 acc
173 (loop (fn x acc))) ) ) )
174
175(define-constant +buf-size+ 1024)
176
177(define copy-port
178 (let ((read-char read-char)
179 (write-char write-char))
180 (define (read-buf port writer)
181 (let ((buf (make-string +buf-size+)))
182 (let loop ()
183 (let ((n (read-string! +buf-size+ buf port)))
184 (unless (eq? n 0)
185 (writer buf n)
186 (loop))))))
187 (define (write-buf buf n port writer)
188 (do ((i 0 (fx+ i 1)))
189 ((fx>= i n))
190 (writer (integer->char (##sys#byte buf i)) port)))
191 (define (read-and-write reader writer)
192 (let loop ()
193 (let ((x (reader)))
194 (unless (eof-object? x)
195 (writer x)
196 (loop)))))
197 (define (read-and-write-buf src dest reader)
198 (let ((buf (make-string +buf-size+)))
199 (let loop ((n 0))
200 (when (fx>= n +buf-size+)
201 (write-string buf +buf-size+ dest)
202 (set! n 0))
203 (let ((c (reader src)))
204 (cond ((eof-object? c)
205 (when (fx>= n 0)
206 (write-string buf n dest)))
207 (else
208 (##sys#setbyte buf n (char->integer c))
209 (loop (fx+ n 1))))))))
210 (lambda (src dest #!optional (read read-char) (write write-char))
211 ;; does not check port args intentionally
212 (cond ((eq? read read-char)
213 (read-buf
214 src
215 (if (eq? write write-char)
216 (lambda (buf n) (write-string buf n dest))
217 (lambda (buf n) (write-buf buf n dest write)))))
218 ((eq? write write-char)
219 (read-and-write-buf src dest read))
220 (else
221 (read-and-write
222 (lambda () (read src))
223 (lambda (x) (write x dest))))))))
224
225
226;;;; funky-ports
227
228(define (make-broadcast-port . ports)
229 (make-output-port
230 (lambda (s) (for-each (cut write-string s #f <>) ports))
231 void
232 (lambda () (for-each flush-output ports)) ) )
233
234(define (make-concatenated-port p1 . ports)
235 (let ((ports (cons p1 ports)))
236 ;;XXX should also forward other port-methods
237 (make-input-port
238 (lambda ()
239 (let loop ()
240 (if (null? ports)
241 #!eof
242 (let ((c (read-char (car ports))))
243 (cond ((eof-object? c)
244 (set! ports (cdr ports))
245 (loop) )
246 (else c) ) ) ) ) )
247 (lambda ()
248 (and (not (null? ports))
249 (char-ready? (car ports))))
250 void
251 (lambda ()
252 (let loop ()
253 (if (null? ports)
254 #!eof
255 (let ((c (peek-char (car ports))))
256 (cond ((eof-object? c)
257 (set! ports (cdr ports))
258 (loop) )
259 (else c))))))
260 (lambda (p n dest start)
261 (let loop ((n n) (c 0))
262 (cond ((null? ports) c)
263 ((fx<= n 0) c)
264 (else
265 (let ((m (read-string! n dest (car ports) (fx+ start c))))
266 (when (fx< m n)
267 (set! ports (cdr ports)) )
268 (loop (fx- n m) (fx+ c m))))))))))
269
270
271;;; Redirect standard ports:
272
273(define (with-input-from-port port thunk)
274 (##sys#check-input-port port #t 'with-input-from-port)
275 (fluid-let ((##sys#standard-input port))
276 (thunk) ) )
277
278(define (with-output-to-port port thunk)
279 (##sys#check-output-port port #t 'with-output-to-port)
280 (fluid-let ((##sys#standard-output port))
281 (thunk) ) )
282
283(define (with-error-output-to-port port thunk)
284 (##sys#check-output-port port #t 'with-error-output-to-port)
285 (fluid-let ((##sys#standard-error port))
286 (thunk) ) )
287
288;;; Extended string-port operations:
289
290(define call-with-input-string
291 (lambda (str proc)
292 (let ((in (open-input-string str)))
293 (proc in) ) ) )
294
295(define call-with-output-string
296 (lambda (proc)
297 (let ((out (open-output-string)))
298 (proc out)
299 (get-output-string out) ) ) )
300
301(define with-input-from-string
302 (lambda (str thunk)
303 (fluid-let ([##sys#standard-input (open-input-string str)])
304 (thunk) ) ) )
305
306(define with-output-to-string
307 (lambda (thunk)
308 (fluid-let ((##sys#standard-output (open-output-string)))
309 (thunk)
310 (get-output-string ##sys#standard-output) ) ) )
311
312(define with-error-output-to-string
313 (lambda (thunk)
314 (fluid-let ((##sys#standard-error (open-output-string)))
315 (thunk)
316 (get-output-string ##sys#standard-error) ) ) )
317
318;;; Custom ports:
319;
320; - Port-slots:
321;
322; 10: last
323
324(define make-input-port
325 (lambda (read ready? close #!optional peek read-string read-line read-buffered)
326 (let* ((class
327 (vector
328 (lambda (p) ; read-char
329 (let ([last (##sys#slot p 10)])
330 (cond [peek (read)]
331 [last
332 (##sys#setislot p 10 #f)
333 last]
334 [else (read)] ) ) )
335 (lambda (p) ; peek-char
336 (let ([last (##sys#slot p 10)])
337 (cond [peek (peek)]
338 [last last]
339 [else
340 (let ([last (read)])
341 (##sys#setslot p 10 last)
342 last) ] ) ) )
343 #f ; write-char
344 #f ; write-string
345 (lambda (p d) ; close
346 (close))
347 #f ; flush-output
348 (lambda (p) ; char-ready?
349 (ready?) )
350 read-string ; read-string!
351 read-line ; read-line
352 read-buffered))
353 (data (vector #f))
354 (port (##sys#make-port 1 class "(custom)" 'custom)))
355 (##sys#set-port-data! port data)
356 port) ) )
357
358(define make-output-port
359 (lambda (write close #!optional flush)
360 (let* ((class
361 (vector
362 #f ; read-char
363 #f ; peek-char
364 (lambda (p c) ; write-char
365 (write (string c)) )
366 (lambda (p s) ; write-string
367 (write s) )
368 (lambda (p d) ; close
369 (close))
370 (lambda (p) ; flush-output
371 (when flush (flush)) )
372 #f ; char-ready?
373 #f ; read-string!
374 #f) ) ; read-line
375 (data (vector #f))
376 (port (##sys#make-port 2 class "(custom)" 'custom)))
377 (##sys#set-port-data! port data)
378 port) ) )
379
380(define (make-bidirectional-port i o)
381 (let* ((class (vector
382 (lambda (_) ; read-char
383 (read-char i))
384 (lambda (_) ; peek-char
385 (peek-char i))
386 (lambda (_ c) ; write-char
387 (write-char c o))
388 (lambda (_ s) ; write-string
389 (write-string s #f o))
390 (lambda (_ d) ; close
391 (case d
392 ((1) (close-input-port i))
393 ((2) (close-output-port o))))
394 (lambda (_) ; flush-output
395 (flush-output o))
396 (lambda (_) ; char-ready?
397 (char-ready? i))
398 (lambda (_ n d s) ; read-string!
399 (read-string! n d i s))
400 (lambda (_ l) ; read-line
401 (read-line i l))
402 (lambda () ; read-buffered
403 (read-buffered i))))
404 (port (##sys#make-port 3 class "(bidirectional)" 'bidirectional)))
405 (##sys#set-port-data! port (vector #f))
406 port))
407
408;; Duplication from posix-common.scm
409(define posix-error
410 (let ((strerror (foreign-lambda c-string "strerror" int))
411 (string-append string-append))
412 (lambda (type loc msg . args)
413 (let ((rn (##sys#update-errno)))
414 (apply ##sys#signal-hook/errno
415 type rn loc (string-append msg " - " (strerror rn)) args)))))
416
417;; Terminal ports
418(define (terminal-port? port)
419 (##sys#check-open-port port 'terminal-port?)
420 (let ((fp (##sys#peek-unsigned-integer port 0)))
421 (and (not (eq? 0 fp)) (##core#inline "C_tty_portp" port))))
422
423(define (check-terminal! caller port)
424 (##sys#check-open-port port caller)
425 (unless (and (eq? 'stream (##sys#slot port 7))
426 (##core#inline "C_tty_portp" port))
427 (##sys#error caller "port is not connected to a terminal" port)))
428
429(define terminal-name
430 (let ((ttyname (foreign-lambda c-string "ttyname" int)))
431 (lambda (port)
432 (check-terminal! 'terminal-name port)
433 (or (ttyname (##core#inline "C_port_fileno" port))
434 (posix-error #:error 'terminal-name
435 "cannot determine terminal name" port)))))
436
437(define terminal-size
438 (let ((ttysize (foreign-lambda int "get_tty_size" int
439 (nonnull-c-pointer int)
440 (nonnull-c-pointer int))))
441 (lambda (port)
442 (check-terminal! 'terminal-size port)
443 (let-location ((columns int)
444 (rows int))
445 (if (fx= 0 (ttysize (##core#inline "C_port_fileno" port)
446 (location columns)
447 (location rows)))
448 (values columns rows)
449 (posix-error #:error 'terminal-size
450 "cannot determine terminal size" port))))))
451
452)