🚀

Emacs Lisp でプリントデバッグをする (5)

2022/11/12に公開

※ xprint関数およびxdumpマクロは、Emacsのバッファに対する操作等を行うEmacs Lispコード(マクロ)の任意の位置から呼び出せて、マクロの処理を一切邪魔することがありませんので、インタラクティブな操作を行うプログラムの開発・デバッグに向いています。また、xmessageマクロは、message関数を少し便利に拡張しています。

この前以下の記事を書きましたが

Emacs Lisp でプリントデバッグをする (4) - Qiita

message関数好きの方から要望があったので、xmessageマクロを追加しました。*xprint*バッファが出現しませんのでさらに安心してお使いいただけます。xmessage関数の第一引数が整数(定数に限る定数またはシンボル(変数))場合には、messageの出力が都度再表示されてスリープ(ウェイト)します。
詳しくは、xmessage の説明に示したサンプルをご覧ください。


関数/マクロの説明

xprint/xdump

xprint, xdump が呼び出されると自動的に *xprint* バッファが表示されてそこにデバッグしたい値や変数を表示させることができます。
xprint は評価後の値のみ表示されるのに対して、xdump は「変数の名前 := 値」という風に表示されます。

(xprint 1 (+ 2 3)) ==> 1 5

(progn
  (setq a 123 b "abc xyz")
  (xprint "label-1" a b (list a b))
  (xdump "label-2" a b (list a b))
  )
⇓
"label-1" 123 "abc xyz" (123 "abc xyz")
"label-2" a := 123 b := "abc xyz" (list a b) := (123 "abc xyz")

xclear

(xclear) ==> *xprint* バッファをクリアします。

xclear 関数はプログラムの任意の位置から呼び出せます。interactive指定付きなので、M-x xclear でも呼び出せます。global-set-key してもよいでしょう。
(Windowsの場合は、Alt-x x c ENTER で呼び出せます)

xsleep

指定したミリ秒間実行を停止(スリープ)します。(また強制的にEmacsの画面再描画を促します。)

1. Emacs は処理するGUIイベントが空になるまで画面の再描画をしませんので、xprint/xdump を呼んですぐに(リアルタイムで)表示させたい場合には、

(xprint 123)
(xsleep 0)

のように xsleep を呼ぶ必要があります。

2. 表示がながれてしまうので、ディレイを入れたい場合にもxsleepは使えます。(指定がミリ秒ということに留意ください)

xsleepサンプル
(progn
  (xclear)
  (dotimes (i 10)
    (xprint i)
    (xsleep 2000)
    )
  )

image.png

xmessage

;; ミニバッファにいきなり「i=49」とだけ表示される
(dotimes (i 50)
  (message "i=%d" i))


;; xmessageマクロも第一引数に整数(スリープするミリ秒)を指定しないと、やはりミニバッファにいきなり「i=49」とだけ表示される
(dotimes (i 50)
  (xmessage "i=%d" i))

;; 「i=0」から「i=49」まで、200ミリ秒(0.2秒)ずつ再表示およびスリープしながらカウントアップが見れる(第一引数に整数定数を指定)
(dotimes (i 50)
  (xmessage 200 "i=%d" i))

xsleep を開発・デバッグに使うときのシナリオ

my-sleep-test.el
(defcustom *my-sleep* nil "スリープする時間")

(defun my-test ()
  (interactive)
  (dotimes (i 50)
    (xmessage *my-sleep* "i=%d" i)))

*my-sleep* という変数を定義することでデバッグしたい時だけディレイをかけることができます:

  • defcustom でスリープを制御する変数 *my-sleep* を nil として定義(第三引数のコメントも必須)
  • xmessage の第一引数に *my-sleep* を指定しておく
  • M-x my-test でマクロを実行 ⇒ ミニバッファにいきなり「i=49」とだけ表示される
  • M-x set-variable を実行し、「*my-sleep*」 と入力後「200」を入力。
  • 再度 M-x my-test でマクロを実行 ⇒ ミニバッファのメッセージがディレイ付きでカウントアップするのが確認できる
  • デバッグが終わったら M-x set-variable を実行し、「*my-sleep*」 と入力後「nil」を入力。⇒ディレイがなくなる

使い方

xprint.el を .emacs または .emacs.d のあるディレクトリ(Emacsが認識するホームディレクトリ)に置いて、以下のコードで起動時にロードされるようにします。

※ xprint.el を使って開発したマクロを公開する場合には、xprint/xdump/xclear 文を全部削除してください。また、xprint の機能ごとリリースしたい場合は、xprint/xdump/xclear/xsleep/xmessage をリネームしたバージョンを、あなたの .el ファイルに同梱してください。

※ xprint.el の関数はすべて x から始まるので、「(x」を検索すれば簡単に見つかると思います。

~/.emacs (or ~/.emacs.d/init.el)
(load "~/xprint.el")
xprint.el
;;;; xprint.el v1.0.1                ;;;;
;;;; Last Modified: 2022/12/15 17:08 ;;;;

(defun xprint (&rest args)
  (prog1 args
    (let ((msg ""))
      (dotimes (i (length args))
        (if (zerop i) nil
          (setq msg (concat msg " "))
          )
        (setq msg (concat msg (format "%S" (nth i args))))
        )
      (if noninteractive (message "%s" msg)
        (let ((cb (current-buffer))
              (cw (selected-window)))
          (if (equal (buffer-name) "*xprint*") nil
            (switch-to-buffer-other-window "*xprint*")
            (goto-char (point-max))
            (insert msg)
            (insert "\n")
            (let ((wins (window-list)))
              (dolist (win wins)
                (select-window win)
                (when (equal (buffer-name) "*xprint*")
                  (goto-char (point-max))
                  (cond
                   ((pos-visible-in-window-p (point)) nil)
                   ((< (point) (window-start)) (recenter 0))
                   (t (recenter -1)))
                  )
                )
              )
            (select-window cw)
            (switch-to-buffer cb)
            )
          )
        )
      )
    )
  )

(defmacro xdump (&rest list)
  (let ((exp '(xprint)))
    (dolist (x list)
      (if (and (not (consp x)) (not (symbolp x))) (push x exp)
        (push (list 'quote x) exp)
        (push := exp)
        (push x exp)
        )
      )
    (reverse exp)
    )
  )

(defun xclear ()
  (interactive)
  (let ((cb (current-buffer))
        (cw (selected-window)))
    (let ((wins (window-list)))
      (dolist (win wins)
        (select-window win)
        (when (equal (buffer-name) "*xprint*")
          (delete-window win)
          )
        )
      )
    (condition-case nil
        (kill-buffer "*xprint*")
      (error nil))
    (condition-case nil
        (select-window cw)
      (error nil))
    nil)
  )

(defun xsleep (millisec)
  (when millisec
    (sit-for 0)
    (sleep-for 0 millisec)
    )
  )

(defmacro xmessage (&rest list)
  (let ((sleep nil))
    (if (and (not (integerp (nth 0 list))) (not (symbolp (nth 0 list)))) nil
      (setq sleep (pop list))
      )
    (if (not sleep)
        `(message ,@list)
      `(progn (message ,@list) (xsleep ,sleep))
      )
    )
  )

以下に xprint と xdump の出力の違いをデモした際のスクリーンショットを示します:

image.png

何かお気づきの点がございましたら、コメント等で教えていただけると助かります。

Discussion