Gauche で transcript-on

Gauche は今では R7RS 準拠の Scheme 処理系を名乗っているが、それ以前は R5RS に準拠していた。 しかし、 R5RS の仕様の内で Gauche (の作者) が意図的に無視していた箇所がある。 そのひとつが transcript-on / transcript-off だ。 これらは処理系との対話をファイルに記録する機能の開始・終了を指示する手続きであり、 Gauche では提供されなかった。 Emacs などの開発環境を利用していればその機能で記録を残すことが出来るので言語処理系として提供する必要がなかったということもあるのだろう。

そのかわり read-eval-print-loop という手続きが提供されていて、カスタマイズされた repl を作ることが出来るようになっている。

Gauche ユーザリファレンス: read-eval-print-loop

読み込み手続きや印字手続きがその本来の動作のついでにファイルに記録するようにすれば、処理系が transcript-on / transcript-off 手続きを提供するより自由度の高い記録処理が可能だ。 Gauche を対話モードで起動したときにも内部的には read-eval-print-loop 手続きが使われている。

さて、そこで今回は Gauchetranscript-on / transcript-off を追加することを考えた。 .gaucherc に追加して利用することを想定している。 .gaucherc については Gauche のドキュメントを参照して欲しい。

Gauche ユーザリファレンス: インタラクティブな開発

以下が実装だ。

(with-module gauche.interactive
  (define %repl-print (with-module gauche.internal %repl-print))
  (define (my-printer . vals) (apply %repl-print vals))
  (define (my-reader) (%reader))
  (define (my-prompter) (%prompter))
  (define-in-module user (read-eval-print-loop :optional (reader #f)
                                               (evaluator #f)
                                               (printer #f)
                                               (prompter #f))
    (let ([reader (or reader my-reader)]
          [evaluator (or evaluator (with-module gauche.interactive %evaluator))]
          [prompter (or prompter my-prompter)]
          [printer (or printer my-printer)])
      ((with-module gauche read-eval-print-loop)
       reader evaluator printer prompter)))

  (define (%transcript-off) (error "Transcript mode not yet."))

  (define-in-module gauche transcript-off %transcript-off)

  (define-in-module gauche (transcript-on filename)
    (unless (eq? transcript-off %transcript-off)
      (error "Already in transcript mode."))
    (let ((port (open-output-file filename :buffering :line))
          (old-printer %repl-print)
          (old-reader %reader)
          (old-prompter %prompter))
      (set! %repl-print
            (lambda vals
              (for-each (lambda(e) (write e port) (newline port)) vals)
              (apply old-printer vals)))
      (set! %reader
            (lambda ()
              (rlet1 s (old-reader)
                (write s port)
                (newline port))))
      (set! %prompter
            (lambda ()
              (rlet1 s (with-output-to-string old-prompter)
                (display s) (flush)
                (display s port))))
      (set! transcript-off
            (lambda()
              (close-output-port port)
              (set! %repl-print old-printer)
              (set! %reader old-reader)
              (set! %prompter old-prompter)
              (set! transcript-off %transcript-off)
              (undefined)))
      (undefined)))
  )

では利用してみよう。

saito ~
$ gosh
gosh> (transcript-on "test.txt")
#<undef>
gosh> (+ 1 2)
3
gosh> (sin 0.5)
0.479425538604203
gosh> (transcript-off)
#<undef>
gosh> (exit)

saito ~
$ cat test.txt
#<undef>
gosh> (+ 1 2)
3
gosh> (sin 0.5)
0.479425538604203
gosh> (transcript-off)

transcript-on を実行してから transcript-off を実行するまでの対話がファイルに書き込まれているのがわかる。 ここでは repl に与えた式と戻り値を記録しているので displaywrite の出力結果は記録されないが必要であれば捕捉することは可能だろう。

Document ID: a06002e461443df32d67dd0a18838c33