emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

debug-warn macro [was: Re: Emacs memory usage]


From: Adam Porter
Subject: debug-warn macro [was: Re: Emacs memory usage]
Date: Sat, 28 Nov 2020 19:30:33 -0600
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux)

In case it would be help anyone in this context, this macro is useful
when it's necessary to use print-style debugging in several functions
with output displayed in one place.  It handles a few chores for the
programmer, like getting and printing the function name and values of
variables or other expressions:

https://github.com/alphapapa/emacs-package-dev-handbook#debug-warn-macro

Plain-text export from Org follows (I hope the lines don't get wrapped):

----

This macro simplifies print-style debugging by automatically including
the names of the containing function and argument forms, rather than
requiring the programmer to write `format' strings manually.  If
`warning-minimum-log-level' is not `:debug' at expansion time, the macro
expands to nil, which the byte-compiler eliminates, therefore the macro
has no overhead at runtime when not debugging.  When debugging, the
expanded form also returns nil so, e.g. it may be used in a conditional
in place of nil.  The macro is tangled to `epdh.el', but it may also be
added directly to source files.

For example, when used like:

┌────
│ (eval-and-compile
│   (setq-local warning-minimum-log-level :debug))
│ 
│ (defun argh (var)
│   (debug-warn (current-buffer) "This is bad!" (point) var)
│   var)
│ 
│ (argh 1)
└────

This warning would be shown in the `*Warnings*' buffer:

┌────
│ Debug (argh): (CURRENT-BUFFER):*scratch* This is bad! (POINT):491845 VAR:1
└────

But if `warning-minimum-log-level' had any other value, and the buffer
were recompiled, there would be no output, and none of the arguments to
the macro would be evaluated at runtime.

It may even be used in place of comments.  For example, instead of:

┌────
│ (if (foo-p thing)
│     ;; It's a foo: frob it.
│     (frob thing)
│   ;; Not a foo: flub it.
│   (flub thing))
└────

You could write:

┌────
│ (if (foo-p thing)
│     (progn
│       (debug-warn "It's a foo: frob it." thing)
│       (frob thing))
│   (debug-warn "Not a foo: flub it." thing)
│   (flub thing))
└────

The macro:

┌────
│ (cl-defmacro epdh/debug-warn (&rest args)
│   "Display a debug warning showing the runtime value of ARGS.
│ The warning automatically includes the name of the containing
│ function, and it is only displayed if `warning-minimum-log-level'
│ is `:debug' at expansion time (otherwise the macro expands to nil
│ and is eliminated by the byte-compiler).  When debugging, the
│ expanded form also returns nil so, e.g. it may be used in a
│ conditional in place of nil.
│ 
│ Each of ARGS may be a string, which is displayed as-is, or a
│ symbol, the value of which is displayed prefixed by its name, or
│ a Lisp form, which is displayed prefixed by its first symbol.
│ 
│ Before the actual ARGS arguments, you can write keyword
│ arguments, i.e. alternating keywords and values.  The following
│ keywords are supported:
│ 
│   :buffer BUFFER   Name of buffer to pass to `display-warning'.
│   :level  LEVEL    Level passed to `display-warning', which see.
│                    Default is :debug."
│   (pcase-let* ((fn-name (with-current-buffer
│                             (or byte-compile-current-buffer (current-buffer))
│                           ;; This is a hack, but a nifty one.
│                           (save-excursion
│                             (beginning-of-defun)
│                             (cl-second (read (current-buffer))))))
│                (plist-args (cl-loop while (keywordp (car args))
│                                     collect (pop args)
│                                     collect (pop args)))
│                ((map (:buffer buffer) (:level level)) plist-args)
│                (level (or level :debug))
│                (string (cl-loop for arg in args
│                                 concat (pcase arg
│                                          ((pred stringp) "%S ")
│                                          ((pred symbolp)
│                                           (concat (upcase (symbol-name arg)) 
":%S "))
│                                          ((pred listp)
│                                           (concat "(" (upcase (symbol-name 
(car arg)))
│                                                   (pcase (length arg)
│                                                     (1 ")")
│                                                     (_ "...)"))
│                                                   ":%S "))))))
│     (when (eq :debug warning-minimum-log-level)
│       `(progn
│          (display-warning ',fn-name (format ,string ,@args) ,level ,buffer)
│          nil))))
└────




reply via email to

[Prev in Thread] Current Thread [Next in Thread]