Skip to content

Instantly share code, notes, and snippets.

@sogaiu
Last active May 1, 2021 06:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sogaiu/9e4c7e47353c43a18c5dc44b240c55c9 to your computer and use it in GitHub Desktop.
Save sogaiu/9e4c7e47353c43a18c5dc44b240c55c9 to your computer and use it in GitHub Desktop.
step debugger notes
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