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