emacs-devel
[Top][All Lists]
Advanced

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

debugger-toggle-locals


From: Helmut Eller
Subject: debugger-toggle-locals
Date: Sat, 23 Nov 2013 09:57:17 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3.50 (gnu/linux)

I'd like to add a new debugger command `debugger-toggle-locals', that
displays the names and values of local variables of a stack frame.
This looks like so:

  Debugger entered--Lisp error: (void-variable d)
    foo(10 113)
    foo(9 114)
      x: 9
      y: 114
      z: (9 . 114)
    foo(8 115)

The command inserts the lines with x:, y: and z:. Pressing t a second
time deletes those lines again.

To do this I needed a new primitive `backtrace-locals' which extracts
the names and values from the specpdl, similar to backtrace-eval.  This
works for interpreted functions and compiled non-lexically-scoped
functions.  Compiled lexically scoped functions would need compiler
support, in the form of stackmaps.  Until somebody implements that, the
command only displays "no locals".

Helmut

diff --git a/lisp/emacs-lisp/debug.el b/lisp/emacs-lisp/debug.el
index 6c7a0d2..d8596aa 100644
--- a/lisp/emacs-lisp/debug.el
+++ b/lisp/emacs-lisp/debug.el
@@ -494,9 +494,13 @@ removes itself from that hook."
       (forward-line 1)
       (while (progn
               (forward-char 2)
-              (if (= (following-char) ?\()
-                  (forward-sexp 1)
-                (forward-sexp 2))
+              (cond ((debugger--locals-visible-p)
+                     (goto-char (next-single-char-property-change
+                                 (1+ (point)) 'debugger-locals-visible-p)))
+                    ((= (following-char) ?\()
+                     (forward-sexp 1))
+                    (t
+                     (forward-sexp 2)))
               (forward-line 1)
               (<= (point) opoint))
        (if (looking-at " *;;;")
@@ -558,6 +562,89 @@ The environment used is the one when entering the 
activation frame at point."
             (prin1 val t)
           (let ((str (eval-expression-print-format val)))
             (if str (princ str t))))))))
+
+(defun debugger--backtrace-base ()
+  "Return the function name that marks the top of the backtrace.
+See `backtrace-frame'."
+  (cond ((eq 'debug--implement-debug-on-entry
+            (cadr (backtrace-frame 1 'debug)))
+        'debug--implement-debug-on-entry)
+       (t 'debug)))
+
+(defun debugger--locals-visible-p ()
+  "Are the local variables of the current stack frame visible?"
+  (save-excursion
+    (move-to-column 2)
+    (get-text-property (point) 'debugger-locals-visible-p)))
+
+(defun debugger--insert-locals (locals)
+  "Insert the local variables LOCALS at point."
+  ;; A bit messy because we need to deal with lexical frames, i.e.
+  ;; the ones with the magic symbol 'internal-interpreter-environment.
+  (when (null locals)
+    (insert "\n    [no locals]"))
+  (dolist (s+v locals)
+    (let ((symbol (car s+v))
+         (value (cdr s+v))
+         (print-escape-newlines t)
+         (insert (lambda (s v)
+                   (insert "\n    ")
+                   (prin1 s (current-buffer))
+                   (insert ": ")
+                   (prin1 v (current-buffer)))))
+      (cond ((string= (symbol-name symbol) "internal-interpreter-environment")
+            (cond ((or (null value)
+                       (and (null (cdr value))
+                            (symbol (car value))))
+                   (insert "\n    [no locals]"))
+                  (t
+                   (dolist (s+v value)
+                     (unless (symbolp s+v)
+                       (let ((symbol (car s+v))
+                             (value (cdr s+v)))
+                         (funcall insert symbol value)))))))
+           (t
+            (funcall insert symbol value))))))
+
+(defun debugger--show-locals ()
+  "For the frame at point, insert locals and add text properties."
+  (let* ((nframe (debugger-frame-number 'skip-base))
+        (base (debugger--backtrace-base))
+        (locals (backtrace-locals nframe base))
+        (inhibit-read-only t))
+    (save-excursion
+      (let ((start (progn
+                    (beginning-of-line)
+                    (skip-chars-forward " ")
+                    (point))))
+       (end-of-line)
+       (debugger--insert-locals locals)
+       (add-text-properties start (point) '(debugger-locals-visible-p t))))))
+
+(defun debugger--hide-locals ()
+  "Delete local variables and remove the text property."
+  (let* ((col (current-column))
+        (end (progn
+               (move-to-column 2)
+               (next-single-char-property-change
+                (point) 'debugger-locals-visible-p)))
+        (start (previous-single-char-property-change
+                end 'debugger-locals-visible-p))
+        (inhibit-read-only t))
+    (goto-char start)
+    (end-of-line)
+    (remove-text-properties start end '(debugger-locals-visible-p))
+    (delete-region (point) end)
+    (move-to-column col)))
+
+(defun debugger-toggle-locals ()
+  "Show or hide local variables of the current stack frame."
+  (interactive)
+  (cond ((debugger--locals-visible-p)
+        (debugger--hide-locals))
+       (t
+        (debugger--show-locals))))
+
 
 (defvar debugger-mode-map
   (let ((map (make-keymap))
@@ -575,6 +662,7 @@ The environment used is the one when entering the 
activation frame at point."
     (define-key map "h" 'describe-mode)
     (define-key map "q" 'top-level)
     (define-key map "e" 'debugger-eval-expression)
+    (define-key map "t" 'debugger-toggle-locals)
     (define-key map " " 'next-line)
     (define-key map "R" 'debugger-record-expression)
     (define-key map "\C-m" 'debug-help-follow)
diff --git a/src/eval.c b/src/eval.c
index d3fcec5..a455d95 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -3576,6 +3576,53 @@ NFRAMES and BASE specify the activation frame to use, as 
in `backtrace-frame'.
      from the debugger.  */
   return unbind_to (count, eval_sub (exp));
 }
+
+DEFUN ("backtrace-locals", Fbacktrace_locals, Sbacktrace_locals, 1, 2, NULL,
+       doc: /* Return names and values of local variables of a stack frame.
+NFRAMES and BASE specify the activation frame to use, as in `backtrace-frame'.
+The result is an alist. */)
+  (Lisp_Object nframes, Lisp_Object base)
+{
+  union specbinding *frame = get_backtrace_frame (nframes, base);
+  union specbinding *olderframe =
+    get_backtrace_frame (make_number (XFASTINT (nframes) + 1), base);
+  ptrdiff_t distance = specpdl_ptr - olderframe;
+  Lisp_Object result = Qnil;
+  eassert (distance >= 0);
+
+  if (!backtrace_p (frame))
+    error ("Activation frame not found!");
+  if (!backtrace_p (olderframe))
+    error ("Activation frame not found!");
+
+  /* move values to specpdl */
+  backtrace_eval_unrewind (distance);
+
+  /* grab values */
+  {
+    union specbinding *tmp = frame;
+    for (; tmp > olderframe; tmp--)
+      {
+       switch (tmp->kind)
+         {
+         case SPECPDL_LET:
+         case SPECPDL_LET_DEFAULT:
+         case SPECPDL_LET_LOCAL:
+           {
+             Lisp_Object sym = specpdl_symbol (tmp);
+             Lisp_Object val = specpdl_old_value (tmp);
+             result = Fcons (Fcons (sym, val), result);
+           }
+         }
+      }
+  }
+
+  /* restore values from specpdl to orignal place */
+  backtrace_eval_unrewind (-distance);
+
+  return result;
+}
+
 
 void
 mark_specpdl (void)
@@ -3824,6 +3871,7 @@ alist of active lexical bindings.  */);
   defsubr (&Sbacktrace);
   defsubr (&Sbacktrace_frame);
   defsubr (&Sbacktrace_eval);
+  defsubr (&Sbacktrace_locals);
   defsubr (&Sspecial_variable_p);
   defsubr (&Sfunctionp);
 }

reply via email to

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