#:g1: ストリームをreadしてその文字列表現を取り出す

Posted 2020-07-26 16:49:52 GMT

ストリームを読み込んで、一つのS式を得るにはreadを使えば良いのですが、その文字列表現を得るのは結構面倒という話をSNSで見掛けました。

いや、with-output-to-string等を使えばreadの結果を文字列として取り出すのは簡単じゃないかなと思ったのですが、これでは上手く行かない状況があるのかもしれません。

;;; 単純な例
(setq *print-circle* T)

(with-output-to-string (out) (with-input-from-string (in "#0=(0 1 2 3 . #0#)") (print (read in) out))) → " #1=(0 1 2 3 . #1#) "

例えば、存在しないパッケージを含んだ表現を読み込むとエラーになる場合であったり、

(with-output-to-string (out)
  (with-input-from-string (in "(foo:bar baz)")
    (print (read in) out)))
!! Error: Reader cannot find package FOO.

コメントを読み飛ばしたくない場合であったり、

(with-output-to-string (out)
  (with-input-from-string (in "(foo bar #|baz|#)")
    (print (read in) out)))
→ "
(FOO BAR) "

しかし、これらはreadの挙動ではないので、readした結果の文字列ではない気がしますが……。

とはいえ、Lispのプリティプリンタ等を作成する場合等でCommon Lispのreadをうまいこと流用しつつ都合良くreadの標準の挙動を越えた結果が欲しい場合もあります。

make-echo-stream というマイナー機能

上述のように、コメントを読み飛ばしたくない場合や、存在しないパッケージは無視してシンボルのトークンとして読み込みたい場合、元ストリームのecho-streamを作成した状況で、*read-suppress*をTにしてreadを動かし、echo-streamに軌跡を出力するという技が使えます。

具体的には、

(defun read-to-string (&optional (stream *standard-input*)
                                 (eof-error-p T)
                                 eof-value
                                 recursivep
                                 (junk-allowed T))
  (with-output-to-string (outstring)
    (let* ((stream (make-echo-stream stream outstring))
           (*read-suppress* junk-allowed))
      (read stream eof-error-p eof-value recursivep))))

こんな感じのものを作成します。

(setq *print-circle* nil)
(dolist (xpr '("#0=(0 1 2 3 . #0#)"
               "(foo:bar baz)"
               "(foo bar #|baz|#
;; comment

)")) (with-input-from-string (in xpr) (print (read-to-string in)))) ▻ ▻ "#0=(0 1 2 3 . #0#)" ▻ "(foo:bar baz)" ▻ "(foo bar #|baz|#;; comment ▻ ▻ )" → NIL

解説

まず、make-echo-streamですが、read系の関数が読み取ったものを出力するというストリームです。エラーログを出力する場面等で便利な気はしますが、結構マイナーな機能です。
HyperSpecでも読み取ったものを文字列として返す例が紹介されています。

次に*read-suppress*ですが、元来これは、#-#+を処理するための機能であり、Lispのトークンとして読み込めるレベルのものを適切に無視することが可能です。

これらを組み合せるとreadエラーは回避しつつLispのトークンとして読み込み、文字列として出力することが可能です。

参照


HTML generated by 3bmd in LispWorks Personal Edition 7.1.2

comments powered by Disqus