バッファを1行ずつ処理するマクロ

まあ、置換とかは replace-buffer とかで一発なんだが、もちょっと凝ったことをしたいときがある。
今まではバッファを1行づつ処理するときは、

(while (null (eobp))
    (let* ((beg (save-excursion (goto-bol) (point)))
           (end (save-excursion (goto-eol) (point)))
           (s (buffer-substring beg end)))
      (dbg-msgbox s)                            ; やりたい処理
      (forward-line)))

とかやっていた。
すごく冗長に感じていたし、カーソルを動かしたりするのがやるせなかった。行頭と行末のポイントが欲しいだけなのに、save-excursion するのとか、不必要に let* 使うのもちょっと変だと思っていた。まあ、いろいろキモかった。

もうちょっと読みやすいのを考えてみた。今回はマクロで。

;;; バッファを1行ずつ処理する
;;; e.g. (loop-at-buffer (line)
;;;        (dbg-msgbox line))
(defmacro loop-at-buffer ((var &optional buffer) &body body)
  (let ((gbuf (gensym))
        (gstream (gensym)))
    `(let* ((,gbuf (cond ((null ,buffer) (selected-buffer))
                         ((bufferp ,buffer) ,buffer)
                         (t (find-buffer ,buffer))))
            (,gstream (if ,gbuf (make-buffer-stream ,gbuf)
                        (error "\"~A\"という名前のバッファが見つかりません" ,buffer))))
       (loop
         (let ((,var (read-line ,gstream nil)))
           (unless ,var (return nil))
           ,@body)))))

これを使うと、上の例は↓こう書けるようになる。

(loop-at-buffer (line)
  (dbg-msgbox line))

わりといい感じになった。
パラメータは下記の2つ。

  • 第1パラメータは、バッファの1行を取り出した文字列を入れる変数を指定する。省略不可。
  • 第2パラメータは、回すバッファを指定する。省略時は現在のバッファになる。

ちなみに、マクロ内で使ってる make-buffer-stream は xyzzy の関数なんだが、引数に nil を渡すと適当になんかのバッファ(selected-buffer か?)に紐づいたストリームを返すみたいで、具合が悪い。その対策を入れてある。

・・・とここまでやっておいて、実は with-input-from-buffer があったことに気づく。
また車輪やってしまった。