5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-11-12

こちらの記事より新しい記事があります:


最新版の xprint.el は以下のGitHubリポジトリからダウンロードできます。

https://github.com/emacs-pkg/xprint/releases/latest

インストール方法 (xprint.el)

とりあえず使ってみたい(または最新の変更に追随したい)方は、.emacs または init.el に以下を追加しておくと自動的にロードされます。.emacs 内で xprint 等を呼びたい場合は、呼び出す前の時点で以下のコードを記述しておいてください。

~/.emacs or ~/.emacs.d/init.el
(unless (featurep 'get-feature)
  (defun get-feature (feature-name &optional url file-name)
    (if (featurep feature-name) t
      (unless url (setq url (format "https://github.com/emacs-pkg/%s/raw/main/%s.el"
                                    feature-name feature-name)))
      (unless file-name (setq file-name (format "%s.el" feature-name)))
      (let ((make-backup-files nil)
            (file-path (expand-file-name file-name user-emacs-directory)))
        (ignore-errors
          (url-copy-file url file-path 'ok-if-already-exists))
        (ignore-errors
          (load file-path nil 'nomessage))
        (featurep feature-name))))
  (get-feature 'get-feature))

(get-feature 'xprint)

straight.el でロードしたい場合は以下を .emacs または init.el に入れてください(但し git のインストールが必要です):

~/.emacs or ~/.emacs.d/init.el
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package '(xprint :type git :host github :repo "emacs-pkg/xprint"))
(require 'xprint)

Emacs Lisp のコード(マクロ)を開発する際に、プリント文を使ってデバッグしたい時があると思います。

(message "文字列")

でミニバッファに文字列を表示させることができますが。リアルタイムで印字履歴をモニターできると便利そうなので xprint という関数を作ってみました。

xprintを使ったマクロの例
(global-set-key (kbd "C-n")
                (lambda ()
                  (interactive)
                  (next-line)
                  (xprint (point))
                  ))

*scratch* バッファで上記のようなマクロを評価し、Ctrl-n を入力し続けると、自動的に *xprint* というバッファが表れて印字履歴を表示してくれます。(xprint (point)) という行から表示されています。

  • また、xprint 関数には複数の引数を指定できます。それぞれの値が空白で区切られて*xprint*バッファに出力されます。
  • xprint を使うと、まるでコンソールプログラムを作っているかのように動作を確認できます。

image.png

ソース添付

xprint.el
;;;; xprint.el v1.2.1                ;;;;
;;;; Last Modified: 2023/01/02 00:00 ;;;;

(require 'cl-lib)
(require 'cl-extra)

(defun xprint (&rest args)
  (let ((raw nil))
    (when (eq (car args) :raw)
      (setq raw t)
      (setq args (cdr args))
      )
    (prog1 args
      (let ((msg ""))
        (dotimes (i (length args))
          (if (zerop i) nil
            (setq msg (concat msg " "))
            )
          (setq msg (concat msg (format (if raw "%s" "%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*")
              )
            ;;(emacs-lisp-mode)
            (lisp-interaction-mode)
            (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 (and (symbolp x) (not (keywordp 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*")
          (ignore-errors (delete-window win))
          )
        )
      )
    (ignore-errors (kill-buffer "*xprint*"))
    (ignore-errors (select-window cw))
    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))
      )
    )
  )

(defmacro xformat (&rest list)
  `(xprint :raw (format ,@list))
  )

(defun xpp (x)
  (xprint :raw (xpp-to-string x))
  )

(defun xpp-to-string (form)
  (with-temp-buffer
    (cl-prettyprint form)
    (let ((str (buffer-string)))
      (replace-regexp-in-string "\\`[ \t\n\r]*\\|[ \t\n\r]*\\'" "" str)
      )
    )
  )

(defun xpand-macro-scan (form callback data)
  (cond
   ((symbolp form) (funcall callback form data))
   ((consp form)
    (cons
     (xpand-macro-scan (car form) callback data)
     (xpand-macro-scan (cdr form) callback data)))
   (t form)
   )
  )

(defun xpand-macro (form)
  (let ((result (macroexpand-all form))
        (hash (make-hash-table :test #'equal)))
    (xprint :raw "")
    (xprint :raw ";;; Expanding Macro:")
    (xprint
     :raw
     (xpp-to-string form)
     )
    (xprint :raw "    |")
    (xprint :raw "    |")
    (xprint :raw "    v")
    (xpand-macro-scan
     result
     #'(lambda (sym data)
         (let ((lst (gethash (symbol-name sym) data)))
           (when (not (member sym lst))
             (push sym lst)
             (puthash (symbol-name sym) lst data)
             )
           )
         )
     hash
     )
    (setq result
          (xpand-macro-scan
           result
           #'(lambda (sym data)
               (let ((lst (gethash (symbol-name sym) data)))
                 (if (= 1 (length lst)) sym
                   (intern (format "%s_%d" sym (length (member sym lst))))
                   )
                 )
               )
           hash
           )
          )
    (xprint
     :raw
     (xpp-to-string result)
     )
    result
    )
  )

(defmacro xpand (form)
  `(xpand-macro (quote ,form))
  )

(provide 'xprint)

問題などにお気づきの場合は、コメントでお知らせください。
以上です。

5
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?