R6RS でファイルに追記する

プログラミング言語 Scheme ではどういうわけか既存のファイルに追記する方法が用意されていない。 処理系によっては独自に用意している場合もあるが、移植性に配慮して R6RS の範囲内で書いてみることにした。

#!r6rs
(library (io-append)
  (export open-file-output-port/append-mode)
  (import (rnrs))
  
  (define skip-buffer-size 1024)

  (define (file-skip-to-eof port)
    (define bv (make-bytevector skip-buffer-size))
    (do () (eof-object? (get-bytevector-n! port bv 0 skip-buffer-size))))

  (define (file-size filename)
    (call-with-port 
        (open-file-input-port filename
                              (file-options no-fail no-truncate)
                              (buffer-mode block)
                              #f)
      (lambda(port)
        (file-skip-to-eof port)
        (port-position port))))

  (define open-file-output-port/append-mode
    (case-lambda
     ((filename opt mode trans)
      (define pos (file-size filename))
      (define port (open-file-output-port filename opt mode #f))
      (set-port-position! port pos)
      (if trans (transcoded-port port trans) port))
     ((filename opt mode)
      (open-file-output-port/append-mode filename opt mode #f))
     ((filename opt)
      (open-file-output-port/append-mode filename opt (buffer-mode block)))
     ((filename)
      (open-file-output-port/append-mode filename (file-options)))))
)

R6RS におけるファイル内の位置はバイナリポートでは正の整数であり、テキストポートでは処理系定義のオブジェクトである。 ファイルサイズを所得してからファイルサイズの位置、すなわちファイル終端へ移動するという手順をとっている都合上、最初はバイナリポートとして開き、必要であれば transcoded-port でテキストポートへ切替えるようにしている。

以下のような要領で使う。

#!r6rs
(import (rnrs)
        (io-append))

;; ファイルに foo と書き込む
(call-with-output-file "test.txt"
  (lambda(port)
    (display "foo\n" port)))

;; 追記モードで開いたファイルに bar と書き込む
(call-with-port (open-file-output-port/append-mode
                 "test.txt"
                 (file-options no-fail no-truncate)
                 (buffer-mode line)
                 (make-transcoder (utf-8-codec) (eol-style lf)))
  (lambda(port)
    (display "bar\n" port)))

何故か Larceny のバージョン 0.98 では期待する出力にならなかった。

Document ID: 422071b5b8241b39da23d9dacc3a896b