いよいよ本番突入です。
最初にその1を読んでおいてください。
CLtL2の27章は Guy Steele, JR.ではなくRichard Watersさんが書いたものを、ほとんどそのまま使っています。だからなのか、もともとプリティプリントが難しいのか、ちょっと見ではなかなか理解しがたいものがあります。
私は細かい仕様をグダグダ見せられるよりも、最初は例題を見るほうが分かりやすいと思っていますので、いい例題がないかと探しましたが、なかなか実例がないですね。XMLだったらSWCLOSのプログラムでもいいのですが、ちょっと最初からはしきいが高いし、プリティプリント関数ではなくフォーマット文制御でやってますから使えません。結局、この本に載っている pprint-let がいいのではないかと、説明のためのコメントを追加して以下に示します。
(defun pprint-let (list)
;; (let ({<var> | (<var> [<init-form> ])}*) {<declaration>}* {<form>}* )
(pprint-logical-block (nil list :prefix "(" :suffix ")") ; (1)
(write (pprint-pop)) ; let
(pprint-exit-if-list-exhausted)
(write-char #\space)
;; ({<var> | (<var> [<init-form> ])}*)
(pprint-logical-block (nil (pprint-pop) :prefix "(" :suffix ")") ; (2)
(pprint-exit-if-list-exhausted)
;; <var> | (<var> [<init-form> ])
(loop (pprint-logical-block (nil (pprint-pop) :prefix "(" :suffix ")") ; (3)
(pprint-exit-if-list-exhausted)
;; pop <var> and then <init-form> if exists
(loop (write (pprint-pop))
(pprint-exit-if-list-exhausted)
(write-char #\space)
(pprint-newline :linear)))
(pprint-exit-if-list-exhausted)
(write-char #\space)
(pprint-newline :fill)))
(pprint-indent :block 1)
;; ({<declaration>}* {<form>}* )
(loop (pprint-exit-if-list-exhausted)
(write-char #\space)
(pprint-newline :linear)
;; <form>
(write (pprint-pop)))))
ここで最初のコメントには let 文の構文を示しておきました。最初に注目してもらいたいのはpprint-logical-block
です。ここには三つのpprint-logical-block
がありますが、pprint-logical-block
は_stream-symbol_と_object_を引数にとります。_stream-symbol_が nil のときは*standard-output*
を示すそうです。最初のpprint-logical-block
は関数パラメータのlist
ですが、その他は(pprint-pop)
です。
さて、pprint-logical-block
の主な役割はプリティプリントの論理ブロックを定義することです。pprint-pop
は、レキシカルな現在の論理ブロック中の印刷中のリストからその要素をポップします。ですから、このプログラムでは最初論理ブロックは let 文全体ですが、直後の(write (pprint-pop))
で let がポップされて印刷され、2番目のpprint-logical-block
ではポップされたlet変数リスト部が新たな論理ブロックとしてネストされます。次の loop 文ではlet変数リスト部が順番に一つずつ取り出されて順に処理されます。ないものをポップしようとすると何が起こるのかマニュアルの記述からははっきりしませんが、そうならないように(pprint-exit-if-list-exhausted)
がそれぞれ絶妙な位置においてあります。(pprint-exit-if-list-exhausted)
はポップするべきものがなかったら、その論理ブロックを終了し、ひとつ上の論理ブロックに戻ります。
pprint-logical-block
で論理ブロックに入る時や出る時にある特殊な文字を印刷したければ、ここにあるように:prefix
や:suffix
キーワードパラメータで指示します。ということは、jsonだったらここは"{"や"}"になるということだし、jsonやXMLのようなものはどこをプリティプリントの論理ブロックとして定義するのかを考えるということになりますね。ただし、pprint-logical-block
で与えられるべきものはあくまでもリストということが前提ですが。
let変数リスト部が処理終われば、次は本体部の印刷ですが、その前に(pprint-indent :block 1)
していますね、これで1文字分のインデンテーションがつけられて、それ以後はそのインデンテーションが有効になります。1文字分というのはこの場合はlet
の書き出しの位置からなので、(let
からは2文字分になります。
プリティプリントらしいところは、さらに(pprint-newline :linear)
や(pprint-newline :fill)
から生じます。pprint-newline
はある方式でもって、もし必要なら、条件が満たされるなら改行する、さもなければ改行しない、というものですが、それには必ず改行する:mandatory以外に、:linear、:miser、:fillの三つがあります。
厳密にはマニュアルを読んでほしいのですが、アバウトに理解するためにこんなことをしてみました。
cg-user(42): (setq foo
'(nil (1st) (1st 2nd) (1st 2nd 3rd) (1st 2nd 3rd 4th) (1st 2nd 3rd 4th 5th)
(1st 2nd 3rd 4th 5th 6th) (1st 2nd 3rd 4th 5th 6th 7th)
(1st 2nd 3rd 4th 5th 6th 7th 8th) (1st 2nd 3rd 4th 5th 6th 7th 8th 9th)))
そしてこの foo を、印刷のウィンドウ幅を変化させながら、pprint-fill、pprint-linearで印刷してみます。たとえば・・・
cg-user(13): (pprint-fill nil foo)
(nil (1st) (1st 2nd) (1st 2nd 3rd) (1st 2nd 3rd 4th) (1st 2nd 3rd 4th 5th)
(1st 2nd 3rd 4th 5th 6th) (1st 2nd 3rd 4th 5th 6th 7th)
(1st 2nd 3rd 4th 5th 6th 7th 8th) (1st 2nd 3rd 4th 5th 6th 7th 8th 9th))
nil
cg-user(14): (pprint-linear nil foo)
(nil
(1st)
(1st 2nd)
(1st 2nd 3rd)
(1st 2nd 3rd 4th)
(1st 2nd 3rd 4th 5th)
(1st 2nd 3rd 4th 5th 6th)
(1st 2nd 3rd 4th 5th 6th 7th)
(1st 2nd 3rd 4th 5th 6th 7th 8th)
(1st 2nd 3rd 4th 5th 6th 7th 8th 9th))
nil
pprint-fill
は文字通り、常に行いっぱいに要素を印刷しようとします。pprint-linear
はすべてを一行に印刷できるならするけど、さもなければこのように全部を別々の行で印刷します。では:miserは?
pprint-miser
がないのでよくわかりませんが、「論理ブロックの書きだされる場所から右マージンまでの長さが*print-miser-width*
の値以下になったとき」なんて書いてありますね。詳細はこの次にまた説明することにして、とりあえずここでは以下に進みます。
それでは次のような例を作って、Windowsの印刷幅をいろいろ変えてpprint-let
を試してください。印刷幅の大小によって、このようになります。
cg-user(45): (setq foo
'(let (var1 (var2 val2) (var3 val3))
(form1)
(form2)
(form3)))
cg-user(59): (pprint-let foo)
(let (var1 (var2 val2) (var3 val3)) (form1) (form2) (form3))
cg-user(60): (pprint-let foo)
(let (var1 (var2 val2) (var3 val3))
(form1)
(form2)
(form3))
cg-user(62): (pprint-let foo)
(let (var1
(var2 val2)
(var3 val3))
(form1)
(form2)