ikarus/scheme/ikarus.io.output-files.ss

228 lines
8.2 KiB
Scheme

;;; Ikarus Scheme -- A compiler for R6RS Scheme.
;;; Copyright (C) 2006,2007 Abdulaziz Ghuloum
;;;
;;; This program is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License version 3 as
;;; published by the Free Software Foundation.
;;;
;;; This program is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;; General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(library (ikarus io output-files)
(export standard-output-port standard-error-port
console-output-port current-output-port
open-output-file with-output-to-file call-with-output-file)
(import
(ikarus system $ports)
(ikarus system $io)
(ikarus system $strings)
(ikarus system $chars)
(ikarus system $bytevectors)
(ikarus system $fx)
(rnrs bytevectors)
(except (ikarus)
standard-output-port standard-error-port
console-output-port current-output-port
*standard-output-port* *standard-error-port*
*current-output-port*
open-output-file with-output-to-file
call-with-output-file))
(define-syntax message-case
(syntax-rules (else)
[(_ msg args
[(msg-name msg-arg* ...) b b* ...] ...
[else else1 else2 ...])
(let ([tmsg msg] [targs args])
(define-syntax match-and-bind
(syntax-rules ()
[(__ y () body)
(if (null? y)
body
(error 'message-case "unmatched" (cons tmsg targs)))]
[(__ y (a a* (... ...)) body)
(if (pair? y)
(let ([a (car y)] [d (cdr y)])
(match-and-bind d (a* (... ...)) body))
(error 'message-case "unmatched" (cons tmsg targs)))]))
(case tmsg
[(msg-name)
(match-and-bind targs (msg-arg* ...) (begin b b* ...))] ...
[else else1 else2 ...]))]))
(define guardian (make-guardian))
(define close-ports
(lambda ()
(cond
[(guardian) =>
(lambda (p)
(close-output-port p)
(close-ports))])))
(define do-write-buffer
(lambda (fd port-name buff idx caller)
(let ([bytes (foreign-call "ikrt_write_file" fd buff idx)])
(if (fixnum? bytes)
bytes
(error caller "cannot write to file" port-name bytes)))))
(define make-output-file-handler
(lambda (fd port-name)
(define open? #t)
(define output-file-handler
(lambda (msg . args)
(message-case msg args
[(write-byte b p)
(if (and (fixnum? b) ($fx<= 0 b) ($fx<= b 255))
(if (output-port? p)
(let ([idx ($port-index p)])
(if ($fx< idx ($port-size p))
(begin
($bytevector-set! ($port-buffer p) idx b)
($set-port-index! p ($fxadd1 idx)))
(if open?
(let ([bytes (do-write-buffer fd port-name
($port-buffer p) idx 'write-char)])
($set-port-index! p 0)
($write-byte b p))
(error 'write-byte "port is closed" p))))
(error 'write-byte "not an output-port" p))
(error 'write-byte "not a byte" b))]
[(write-char c p)
(if (char? c)
(if (output-port? p)
(let ([b ($char->fixnum c)])
(if ($fx<= b 255)
($write-byte b p)
(error 'write-char
"BUG: multibyte write of not implemented" c)))
(error 'write-char "not an output-port" p))
(error 'write-char "not a character" c))]
[(flush-output-port p)
(if (output-port? p)
(if open?
(let ([bytes (do-write-buffer fd port-name
($port-buffer p)
($port-index p)
'flush-output-port)])
($set-port-index! p 0))
(error 'flush-output-port "port is closed" p))
(error 'flush-output-port "not an output-port" p))]
[(close-port p)
(when open?
(flush-output-port p)
($set-port-size! p 0)
(set! open? #f)
(unless (foreign-call "ikrt_close_file" fd)
(error 'close-output-port "cannot close" port-name)))]
[(port-name p) port-name]
[else (error 'output-file-handler
"unhandled message" (cons msg args))])))
output-file-handler))
(define (option-id x)
(case x
[(error) 0]
[(replace) 1]
[(truncate) 2]
[(append) 3]
[else (error 'open-output-file "not a valid mode" x)]))
(define $open-output-file
(lambda (filename options)
(close-ports)
(let ([fd/error
(foreign-call "ikrt_open_output_file"
(string->utf8 filename)
(option-id options))])
(if (fixnum? fd/error)
(let ([port
(make-output-port
(make-output-file-handler fd/error filename)
($make-bytevector 4096))])
(guardian port)
port)
(error 'open-output-file "cannot open file" filename fd/error)))))
(define *standard-output-port* #f)
(define *standard-error-port* #f)
(define *current-output-port* #f)
(define standard-output-port
(lambda () *standard-output-port*))
(define standard-error-port
(lambda () *standard-error-port*))
(define console-output-port
(lambda () *standard-output-port*))
(define current-output-port
(case-lambda
[() *current-output-port*]
[(p)
(if (output-port? p)
(set! *current-output-port* p)
(error 'current-output-port "not an output port" p))]))
(define open-output-file
(case-lambda
[(filename)
(if (string? filename)
($open-output-file filename 'error)
(error 'open-output-file "not a string" filename))]
[(filename options)
(if (string? filename)
($open-output-file filename options)
(error 'open-output-file "not a string" filename))]))
(define with-output-to-file
(lambda (name proc . args)
(unless (string? name)
(error 'with-output-to-file "not a string" name))
(unless (procedure? proc)
(error 'with-output-to-file "not a procedure" proc))
(let ([p (apply open-output-file name args)]
[shot #f])
(call-with-values
(lambda ()
(parameterize ([current-output-port p])
(proc)))
(case-lambda
[(v) (close-output-port p) v]
[v*
(close-output-port p)
(apply values v*)])))))
(define call-with-output-file
(lambda (name proc . args)
(unless (string? name)
(error 'call-with-output-file "not a string" name))
(unless (procedure? proc)
(error 'call-with-output-file "not a procedure" proc))
(let ([p (apply open-output-file name args)])
(call-with-values (lambda () (proc p))
(case-lambda
[(v) (close-output-port p) v]
[v*
(close-output-port p)
(apply values v*)])))))
(set! *standard-output-port*
(make-output-port
(make-output-file-handler 1 '*stdout*)
($make-bytevector 4096)))
(set! *current-output-port* *standard-output-port*)
(set! *standard-error-port*
(make-output-port
(make-output-file-handler 2 '*stderr*)
($make-bytevector 4096))) )