Last active
May 1, 2021 06:17
-
-
Save sogaiu/9e4c7e47353c43a18c5dc44b240c55c9 to your computer and use it in GitHub Desktop.
step debugger notes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
possible results | |
* janet source step debugger | |
* margaret source step debugger | |
* babashka source step debugger | |
* location or creation of clear description of edebug | |
* location or creation of clear description of cider debug | |
* location or creation of clear description of sly stepper | |
--- | |
* edebug in emacs | |
https://github.com/emacs-mirror/emacs/blob/master/lisp/emacs-lisp/edebug.el | |
* cider debugger | |
https://docs.cider.mx/cider/debugging/debugger.html#debugger-internals | |
https://github.com/clojure-emacs/cider/blob/master/cider-debug.el | |
* sly-stepper in sly | |
https://github.com/joaotavora/sly-stepper | |
https://zenodo.org/record/3742759 | |
(A portable, annotation-based, visual stepper for Common Lisp) | |
--- | |
edebug | |
apart from comments in edebug.el have not found docs / description | |
;; Install edebug read and eval functions. | |
(edebug-install-read-eval-functions) | |
; | |
(defun edebug-install-read-eval-functions () | |
(interactive) | |
(add-function :around load-read-function #'edebug--read) | |
(advice-add 'eval-defun :override #'edebug-eval-defun)) | |
; | |
;;; Redefine read and eval functions | |
;; read is redefined to maybe instrument forms. | |
;; eval-defun is redefined to check edebug-all-forms and edebug-all-defs. | |
(defun edebug--read (orig &optional stream) | |
"Read one Lisp expression as text from STREAM, return as Lisp object. | |
If STREAM is nil, use the value of `standard-input' (which see). | |
STREAM or the value of `standard-input' may be: | |
a buffer (read from point and advance it) | |
a marker (read from where it points and advance it) | |
a function (call it with no arguments for each character, | |
call it with a char as argument to push a char back) | |
a string (takes text from string, starting at the beginning) | |
t (read text line using minibuffer and use it). | |
This version, from Edebug, maybe instruments the expression. But the | |
STREAM must be the current buffer to do so. Whether it instruments is | |
also dependent on the values of the option `edebug-all-defs' and | |
the option `edebug-all-forms'." | |
(or stream (setq stream standard-input)) | |
(if (eq stream (current-buffer)) | |
(edebug-read-and-maybe-wrap-form) | |
(funcall (or orig #'read) stream))) | |
; | |
;; We should somehow arrange to be able to do this | |
;; without actually replacing the eval-defun command. | |
(defun edebug-eval-defun (edebug-it) | |
"Evaluate the top-level form containing point, or after point. | |
If the current defun is actually a call to `defvar', then reset the | |
variable using its initial value expression even if the variable | |
already has some other value. (Normally `defvar' does not change the | |
variable's value if it already has a value.) Treat `defcustom' | |
similarly. Reinitialize the face according to `defface' specification. | |
With a prefix argument, instrument the code for Edebug. | |
Setting option `edebug-all-defs' to a non-nil value reverses the meaning | |
of the prefix argument. Code is then instrumented when this function is | |
invoked without a prefix argument. | |
If acting on a `defun' for FUNCTION, and the function was instrumented, | |
`Edebug: FUNCTION' is printed in the minibuffer. If not instrumented, | |
just FUNCTION is printed. | |
If not acting on a `defun', the result of evaluation is displayed in | |
the minibuffer." | |
(interactive "P") | |
(let* ((edebugging (not (eq (not edebug-it) (not edebug-all-defs)))) | |
(edebug-result) | |
(form | |
(let ((edebug-all-forms edebugging) | |
(edebug-all-defs (eq edebug-all-defs (not edebug-it)))) | |
(edebug-read-top-level-form)))) | |
;; This should be consistent with `eval-defun-1', but not the | |
;; same, since that gets a macroexpanded form. | |
(cond ((and (eq (car form) 'defvar) | |
(cdr-safe (cdr-safe form))) | |
;; Force variable to be bound. | |
(makunbound (nth 1 form))) | |
((and (eq (car form) 'defcustom) | |
(default-boundp (nth 1 form))) | |
;; Force variable to be bound. | |
;; FIXME: Shouldn't this use the :setter or :initializer? | |
(set-default (nth 1 form) (eval (nth 2 form) lexical-binding))) | |
((eq (car form) 'defface) | |
;; Reset the face. | |
(setq face-new-frame-defaults | |
(assq-delete-all (nth 1 form) face-new-frame-defaults)) | |
(put (nth 1 form) 'face-defface-spec nil) | |
(put (nth 1 form) 'face-documentation (nth 3 form)) | |
;; See comments in `eval-defun-1' for purpose of code below | |
(setq form (prog1 `(prog1 ,form | |
(put ',(nth 1 form) 'saved-face | |
',(get (nth 1 form) 'saved-face)) | |
(put ',(nth 1 form) 'customized-face | |
,(nth 2 form))) | |
(put (nth 1 form) 'saved-face nil))))) | |
(setq edebug-result (eval (eval-sexp-add-defvars form) lexical-binding)) | |
(if (not edebugging) | |
(prog1 | |
(prin1 edebug-result) | |
(let ((str (eval-expression-print-format edebug-result))) | |
(if str (princ str)))) | |
edebug-result))) | |
--- | |
cider | |
CIDER works in several steps as it instruments your code: | |
1. First, CIDER walks through the code, adding metadata to forms and | |
symbols that identify their position (coordinate) in the code. | |
2. Then, it macroexpands everything to get rid of macros. | |
3. Then, it walks through the code again, instrumenting it. | |
* CIDER understands all existing special forms and takes care not | |
to instrument where it’s not supposed to. For instance, CIDER | |
does not instrument the arglist of fn* or the left-side of a | |
let-binding. | |
* Wherever it finds the previously-injected metadata, assuming that | |
location is valid for instrumentation, it wraps the form or | |
symbol in a macro called breakpoint-if-interesting. | |
4. When the resulting code actually gets compiled, the Clojure | |
compiler will expand the breakpoint-if-interesting macros. This | |
macro decides whether the return value of the form or symbol is | |
actually something the user might want to see. If it is, the form | |
or symbol gets wrapped in a breakpoint macro, otherwise it’s | |
returned as is. | |
5. The breakpoint macro takes the coordinate information that was | |
provided in step 1. and sends it over to Emacs (the front-end). It | |
also sends the return value of the form and a prompt of available | |
commands. Emacs then uses this information to show the value of | |
actual code forms and prompt for the next action. | |
A few example forms that don’t have interesting return values (and so | |
are not wrapped in a breakpoint): | |
In (fn [x] (inc x)) the return value is a function object and | |
carries no information. Note that this is not the same as the | |
return value when you call this function (which is | |
interesting). Also, even those this form is not wrapped in a | |
breakpoint, the forms inside it are ((inc x) and x). | |
Similarly, in a form like (map inc (range 10)), the symbol inc | |
points to a function in clojure.core. That’s also irrelevant | |
(unless it’s being shadowed by a local, but the debugger can | |
identify that). | |
--- | |
sly | |
Our proposed portable stepper system for SLY/Emacs can be broken down | |
into three main components: | |
(1) A non-intrusive source code annotation system, called | |
“stickers”. This system primarily allows “interesting” Common | |
Lisp forms to be designated by the user. On compilation, the | |
annotated code is transmitted to the Common Lisp compiler, and | |
executes equivalently to non-annotated code; | |
(2) A source-tracking reader, i.e. a process by which a stream of | |
characters containing source code forms is read into a symbolic | |
expression representing the form, whilst recording the positions | |
of the start and end characters of each sub-form; | |
(3) A specialized code walker, a process by which an arbitrary | |
Common Lisp form can be traversed at compilation-time to | |
determine the syntactic value of each of its sub-forms as | |
processed by the compiler after the macro-expanding phase. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment