Edebug & lexical scope

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Edebug & lexical scope

Sergey Mozgovoy
Hi,

Currently (Emacs 24.0.97) Edebug cannot evaluate lexical variables when it is suspended (by "e" or "M-:"). To be more precise: when you have "lexical-binding" variable set to non-nil in a buffer, and you instrument a defun from this buffer, and then debug it with Edebug, and you're suspended at some stop point inside this defun, you cannot see the value of a (lexical) variable.

Example:

(defun solve-square-equation (a b c)
  (if (= a 0.0)
      (/ (- c) b)
    (let ((D (- (* b b) (* 4 (* a c)))))
      (cond
        ((= D 0.0)
         (/ (- b) (* 2 a)))
        ((< D 0.0)
         nil)
        (t
         (let ((sqD (sqrt D)))
           (list (/ (+ (- b) sqD) (* 2 a))
                 (/ (- (- b) sqD) (* 2 a)))))))))


It is impossible to see the value of D when the program is stopped in any of the cond's branches.

The reason for that is simple: when you are asking Edebug to evaluate something, it does so by calling `eval', and the latter just binds Qinternal_interpreter_environment (internal variable holding the current lexical environment, or nil in dynamic scoping) to "(t)" (provided that lexical scoping is on). So the values of all lexical variables are lost.

Nevertheless, when you press "G" or "c", the execution resumes and those lexical variables are perfectly accessible.  By all evidence, it is just due to the fact that Qinternal_interpreter_environment gets unbound, and takes on its previous value (which is what we would also like to access when evaluating by "M-:").

It seems that I've managed to make M-: evaluate lexical variables. I've created the following C subroutine; it does the same job as `eval', but first of all it scans "specpdl" (the stack of bindings of symbols) searching for the most recent value of Qinternal_interpreter_environment which is not eq to nil. (That is, we want to find out the most recent lexical environment to which Qinternal_interpreter_environment was bound by some code upward the stack.) If that value is found, Qinternal_interpreter_environment gets bound to it again, and usual `eval_sub' is called. Otherwise, `eval_sub' is called as well, but this would be in dynamic environment now.

DEFUN ("eval-in-most-recent-lex-env", Seval_in_mo....) (Lisp_Object form)
  {
    struct specbinding* ptr = specpdl_ptr;
    while (ptr != specpdl)
      {
        if (EQ (ptr->symbol, Qinternal_interpreter_environment) &&
            !EQ (ptr->old_value, Qnil))
         {
           break;
         }
       --ptr;
      }
    if (ptr != specpdl)
      {
        int count = SPECPDL_INDEX ();
        specbind (Qinternal_interpreter_environment, ptr->old_value);
        return unbind_to (count, eval_sub (form));
      }
    return eval_sub (form);
  }

The second step is pretty easy: there's a function named `edebug-eval'. One should just change it to call `eval-in-most-recent-lex-env' rather than `eval'.

That seems to work fine in Emacs 24.0.97 pretest.

I'd be very glad to hear any critics or comments.



Best regards,
  egnarts-ms.
Reply | Threaded
Open this post in threaded view
|

Re: Edebug & lexical scope

Stefan Monnier
> Currently (Emacs 24.0.97) Edebug cannot evaluate lexical variables when it
> is suspended (by "e" or "M-:").

Indeed.  The same holds for the normal (backtrace-style) debugger.

> It seems that I've managed to make M-: evaluate lexical variables.  I've
> created the following C subroutine; it does the same job as `eval', but
> first of all it scans "specpdl" (the stack of bindings of symbols) searching
> for the most recent value of Qinternal_interpreter_environment which is not
> eq to nil.

Thanks.  This is a good way to fix the problem (tho it still doesn't
solve the issue for the case of the normal debugger where you want to
look at the local variable of a lexical-binding byte-code routine,
because in that case the var names are purely gone).

The only problem with it is that it relies on all the stack frames (until
the interesting ones) using dynamic binding.  E.g. if/when we switch
edebug to lexical-binding we'll have a problem.

Also in the case of the normal debugger, we would generally want to be
able to get the value of a variable in a particular stack frame.

So it would be better to have an "eval-in-frame" which receives some
description of the stack frame to use (and of course, more primitives to
get a handle of the current stack frame as well as the Nth previous one).


        Stefan

Reply | Threaded
Open this post in threaded view
|

Re: Edebug & lexical scope

Sergey Mozgovoy
Stefan,

thank you very much for your reply. It was important for me to know comments of a competent person.

Stefan Monnier wrote
Also in the case of the normal debugger, we would generally want to be
able to get the value of a variable in a particular stack frame.

So it would be better to have an "eval-in-frame" which receives some
description of the stack frame to use (and of course, more primitives to
get a handle of the current stack frame as well as the Nth previous one).
AFAIK, currently it is not possible to see values of lexical variables in a particular stack frame, in normal debugger, right ? We can just press "e" and evaluate something, but during this evaluation all the dynamic variables will hold the same values as they hold in the topmost stack frame (where we're suspended).
Reply | Threaded
Open this post in threaded view
|

Re: Edebug & lexical scope

Stefan Monnier
> AFAIK, currently it is not possible to see values of lexical variables in a
> particular stack frame, in normal debugger, right ? We can just press "e"
> and evaluate something, but during this evaluation all the dynamic variables
> will hold the same values as they hold in the topmost stack frame (where
> we're suspended).

Indeed, there is no current feature to execute in a particular
stack frame.  In most cases this doesn't matter much in the case of
dynamic scoping; even in those cases where a later binding hides an
earlier one it's very frequent that both bindings have the exact same
value (typically an argument passed through a few levels of function
calls, with the same name at each stage).

With lexical scoping, this becomes more important.
Actually a good eval-in-stack-frame might even want to first undo the
corresponding let-bindings as well as save-excursions and friends, but
it would require more significant changes.


        Stefan