Lisp のストリームあれこれ

peek-char の便利な機能

ストリームから文字を peek する際,peek-char の第 1 引数に t を指定すると空白文字をスキップしてくれる.次に read-char を実行すると,スキップ後の 1 文字を返す.

(with-input-from-string (in "   1 2 3 4 5")
  (format t "Peek: ~s  Read: ~s~%" (peek-char t in) (read-char in))     ; (1)
  (format t "Peek: ~s  Read: ~s~%" (peek-char nil in) (read-char in))   ; (2)
  (format t "Peek: ~s  Read: ~s~%" (peek-char nil in) (read-char in)))  ; (3)
;=> Peek: #\1  Read: #\1
;   Peek: #\   Read: #\
;   Peek: #\2  Read: #\2
1 peek-char の第 1 引数は t なので先頭の空白文字はスキップされ #\1 が返る.read-char は直前の peek-char が返したスキップ後の #\1 を返す.
2 peek-char の第 1 引数は nil なので入力文字列の 1 と 2 の間のスペース文字が返される.
3 3 行目の read-char でストリームのポインタが 1 つ前進し入力文字列の 2 を指しているので 4 行目の peek-char の第 1 引数が nil でも #\2 が返る.

ストリームから文字列を作成するときのバッファリング

字句解析などでストリームからキャラクタを読み込む際,読み込む文字列の長さが未確定の場合は伸縮可能な CHARACTER 型の配列に vector-push-extend で文字をプッシュしていく.その際,配列が全て文字列で埋まるように長さ 0 の配列を用意し,そこに文字をプッシュする必要はない.Lisp は fill-pointer で管理している長さまでしか文字列として認識しないので,初期化した配列より短い文字列でも問題ない.したがって,用意する配列は,バッファとして十分な大きさを用意すれば良い.

以下のコードはバファとして長さ 100 の配列を用意しているが,文字列を表示させると読み込んだ分しか表示されていない.また,同じ 3 文字から成る別の文字列と比較しても,等しい文字列として認識されているのがわかる.

(let ((str (make-array 100                         ; 初期バッファサイズ
                       :element-type 'character
                       :fill-pointer 0
                       :adjustable t)))
   (with-input-from-string (in "今日は晴れ.明日は雨.")
      (dotimes (i 3)                               ; 最初の3文字のみ
         (vector-push-extend (read-char in) str))) ; バッファに読み込む
   (format t "~a" str)
   (string= "今日は" str))
;=> 今日は
;   T

一方,fill-pointer の無い simple-string をバッファに使用した以下の例では,バッファの大きさ全てが文字列として扱われることになる.

(let ((str (make-string 10)))                      ; 初期バッファサイズ
   (with-input-from-string (in "今日は晴れ.明日は雨.")
      (dotimes (i 3)                               ; 最初の3文字のみ
         (setf (schar str i) (read-char in))))     ; バッファに読み込む
   (format t "~a" str)
   (string= "今日は" str))
;=> 今日は^@^@^@^@^@^@^@
    NIL