source: trunk/abcl/contrib/abcl-stepper/abcl-stepper.lisp

Last change on this file was 15709, checked in by Mark Evenson, 11 months ago

Working stepper for ABCL as a contrib!

Some characteristics:

  • For intepreted code, it won't step into compiled code
  • It is ready to use from a plain REPL and from SLIME.
  • ':?' will help a minimal help
  • Can inspect variables and symbols in the current package with 'i'
  • ':c' will resume the evaluation until the end without the stepper
  • ':s' will resume the evaluation until the next form to be analyzed
  • ':sn' will to step to the next form
  • case-insensitive when inspecting
  • ':l' will print the local bindings
  • ':q' will skip the current stepping evaluation and return NIL
  • ':b' will add a breakpoint to a symbol to use with next (n)
  • ':r' will remove an existent symbol breakpoint to use with next (n)
  • ':d' will remove all existent symbol breakpoints to use with next (n)
  • ':w' allows to watch a symbol binding
  • ':u' allows to (un)watch a symbol binding
  • ':bt' shows the current backtrace

fix

File size: 13.8 KB
Line 
1;;; This file is part of ABCL contrib
2;;;
3;;; Copyright (C) 2023 Alejandro Zamora Fonseca <ale2014.zamora@gmail.com>
4
5;;; This program is free software; you can redistribute it and/or
6;;; modify it under the terms of the GNU General Public License
7;;; as published by the Free Software Foundation; either version 2
8;;; of the License, or (at your option) any later version.
9;;;
10;;; This program is distributed in the hope that it will be useful,
11;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13;;; GNU General Public License for more details.
14;;;
15;;; You should have received a copy of the GNU General Public License
16;;; along with this program; if not, write to the Free Software
17;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18;;;
19;;; As a special exception, the copyright holders of this library give you
20;;; permission to link this library with independent modules to produce an
21;;; executable, regardless of the license terms of these independent
22;;; modules, and to copy and distribute the resulting executable under
23;;; terms of your choice, provided that you also meet, for each linked
24;;; independent module, the terms and conditions of the license of that
25;;; module.  An independent module is a module which is not derived from
26;;; or based on this library.  If you modify this library, you may extend
27;;; this exception to your version of the library, but you are not
28;;; obligated to do so.  If you do not wish to do so, delete this
29;;; exception statement from your version.
30
31(defpackage #:abcl-stepper
32  (:use :cl)
33  (:nicknames #:stepper)
34  (:shadow #:step)
35  (:export #:step
36           #:start
37           #:stop
38           #:*stepper-stop-packages*
39           #:*stepper-stop-symbols*))
40
41(in-package #:abcl-stepper)
42
43(defparameter *stepper-stop-packages* nil
44  "List of packages in which the stepper will stop in its external symbols")
45
46(defparameter *stepper-stop-symbols* nil
47  "List of symbols in which the stepper will stop")
48
49(defparameter *stepper-watch-symbols* nil
50  "List of symbols in which will be printed in every step")
51
52(defparameter *step-next-table* (make-hash-table)
53  "Used for the feature step-next, show the number of steps that have been completed")
54
55(defparameter *step-next-counter* -1
56  "Indicates if the feature step-next is active by showing the current step to be completed")
57
58(defun clear-step-next ()
59  (setf *step-next-counter* -1)
60  (setf *step-next-table* (make-hash-table)))
61
62(defun set-step-counter-completed (current-step-counter)
63  ;; mark the counter for steps as completed
64  ;; and force the printing of pending output
65  (setf (gethash current-step-counter *step-next-table*) t))
66
67(defmacro without-active-stepping (&body body)
68  `(progn (sys:%set-stepping-task-on)
69          (multiple-value-prog1 (progn ,@body)
70            (sys:%set-stepping-task-off))))
71
72(defun print-stepper-str (string newline)
73  "Prints a line using the java method 'System.out.println'"
74  (without-active-stepping
75    (princ string)
76    (if newline (terpri))
77    (unless (in-slime-repl-p)
78      (finish-output))))
79
80(defun pprint-stepper-str (string)
81  "Pretty prints a line using the java method 'System.out.println'"
82  (print-stepper-str (with-output-to-string (s)
83                       (pprint string s))
84                     t))
85
86(defun pprint-form-to-step (symbol args step-count)
87  (print-stepper-str "" t)
88  (print-stepper-str "We are in the stepper mode" t)
89  (print-stepper-str (format nil "Evaluating step ~a -->" step-count) nil)
90  (print-stepper-str
91   (with-output-to-string (s)
92     (pprint `(,symbol ,@args) s))
93   t))
94
95(defun add-breakpoint ()
96  (print-stepper-str "Type the name of the symbol to use as a breakpoint with next (n): " nil)
97  (let* ((symbol-str (without-active-stepping (read-line)))
98         (symbol (ignore-errors (without-active-stepping (read-from-string symbol-str)))))
99    ;; ensure we found the symbol
100    (unless symbol
101      (print-stepper-str (format nil "Couldn't find the symbol ~a" symbol-str) t))
102    (pushnew symbol *stepper-stop-symbols*)))
103
104(defun remove-breakpoint ()
105  (print-stepper-str "Type the name of the breakpoint symbol to remove: " nil)
106  (let* ((symbol-str (without-active-stepping (read-line)))
107         (symbol (ignore-errors (without-active-stepping (read-from-string symbol-str)))))
108    ;; ensure we found the symbol
109    (unless symbol
110      (print-stepper-str (format nil "Couldn't find the symbol ~a" symbol-str) t))
111    (setf *stepper-stop-symbols* (remove symbol *stepper-stop-symbols*))))
112
113(defun remove-all-breakpoints ()
114  (setf *stepper-stop-symbols* nil)
115  (print-stepper-str "Removed all symbol breakpoints" t))
116
117(defun lookup-symbol (symbol env &optional var-description)
118  (let* ((lookup-method (java:jmethod "org.armedbear.lisp.Environment" "lookup" "org.armedbear.lisp.LispObject"))
119         (symbol-lookup (java:jcall-raw lookup-method env symbol)))
120    (cond ((or (not (java:java-object-p symbol-lookup))
121               (not (java:jnull-ref-p symbol-lookup)))
122           (print-stepper-str
123            (if var-description
124                (format nil "~a=~a" symbol symbol-lookup)
125                (format nil "~a" symbol-lookup))
126            t))
127          ((boundp symbol)
128           (print-stepper-str
129            (if var-description
130                (format nil "~a=~a" symbol (symbol-value symbol))
131                (format nil "~a" (symbol-value symbol)))
132            t))
133          (t
134           (print-stepper-str (format nil "Couldn't find a value for symbol ~a" symbol) t)))))
135
136(defun inspect-variable (env)
137  (print-stepper-str "Type the name of the symbol: " nil)
138  (let* ((symbol-str (without-active-stepping (read-line)))
139         (symbol (ignore-errors (without-active-stepping (read-from-string symbol-str)))))
140    ;; ensure we found the symbol
141    (unless symbol
142      (print-stepper-str (format nil "Couldn't find the symbol ~a" symbol-str) t)
143      (return-from inspect-variable))
144    ;; let's try to retrieve the value from the symbol
145    (lookup-symbol symbol env)))
146
147(defun print-stepper-help ()
148  (print-stepper-str "Type ':l' to see the values of bindings on the local environment" t)
149  (print-stepper-str "Type ':c' to resume the evaluation until the end without the stepper" t)
150  (print-stepper-str "Type ':n' to resume the evaluation until the next form previously selected to step in" t)
151  (print-stepper-str "Type ':s' to step into the form" t)
152  (print-stepper-str "Type ':sn' to step to the next form" t)
153  (print-stepper-str "Type ':i' to inspect the current value of a variable or symbol" t)
154  (print-stepper-str "Type ':b' to add a symbol as a breakpoint to use with next (n)" t)
155  (print-stepper-str "Type ':r' to remove a symbol used as a breakpoint with next (n)" t)
156  (print-stepper-str "Type ':d' to remove all breakpoints used with next (n)" t)
157  (print-stepper-str "Type ':w' to print the value of a binding in all the steps (watch)" t)
158  (print-stepper-str "Type ':u' to remove a watched binding (unwatch)" t)
159  (print-stepper-str "Type ':bt' to show the backtrace" t)
160  (print-stepper-str "Type ':q' to quit the evaluation and return NIL" t))
161
162(defun pprint-list-locals (locals)
163  (loop :for pair :in locals
164        :do (print-stepper-str (format nil "~a=~a" (car pair) (cdr pair)) t)))
165
166(defun insert-watch-symbol ()
167  (print-stepper-str "Type the name of the symbol to watch: " nil)
168  (let* ((symbol-str (without-active-stepping (read-line)))
169         (symbol (ignore-errors (without-active-stepping (read-from-string symbol-str)))))
170    ;; ensure we found the symbol
171    (unless symbol
172      (print-stepper-str (format nil "Couldn't find the symbol ~a" symbol-str) t)
173      (return-from insert-watch-symbol))
174    (pushnew symbol *stepper-watch-symbols*)))
175
176(defun remove-watch-symbol ()
177  (print-stepper-str "Type the name of the symbol to (un)watch : " nil)
178  (let* ((symbol-str (without-active-stepping (read-line)))
179         (symbol (ignore-errors (without-active-stepping (read-from-string symbol-str)))))
180    ;; ensure we found the symbol
181    (unless symbol
182      (print-stepper-str (format nil "Couldn't find the symbol ~a" symbol-str) t)
183      (return-from remove-watch-symbol))
184    (setf *stepper-watch-symbols* (remove symbol *stepper-watch-symbols*))))
185
186(defun step-in-symbol-p (fun object delimited-stepping)
187  "Decides if the stepper will be applied to the OBJECT being evaluated and manages the internal
188states of the stepper"
189  (cond
190    ((or
191      (and (consp object)
192           (or (eq fun #'system::%subseq)
193               (equal object '(BLOCK SUBSEQ (SYSTEM::%SUBSEQ SEQUENCE SYSTEM::START SYSTEM::END)))
194               (equal object '(BLOCK LENGTH (SYSTEM::%LENGTH SEQUENCE)))
195               (eq fun #'system::%length)))
196      (and (consp object)
197           (eq (car object)
198               'CL:MULTIPLE-VALUE-PROG1)
199           (equal (car (last object))
200                  '(system:%set-delimited-stepping-off)))
201      (equal fun #'sys:%set-stepper-off))
202     ;; we don't step the expansion of 'step' macro
203     nil)
204    ((and (/= *step-next-counter* -1)
205          (gethash *step-next-counter* *step-next-table*))
206     (clear-step-next)
207     t)
208    ((and (/= *step-next-counter* -1)
209          (not (gethash *step-next-counter* *step-next-table*)))
210     nil)
211    (delimited-stepping
212     ;; Analyze next symbols
213     (sys:%set-stepper-off)
214     (let* ((function-name
215              (or (ignore-errors (nth-value 2 (function-lambda-expression fun)))
216                  (ignore-errors (car object))))
217            (stop-at-symbol-p-value
218              (and function-name (stop-at-symbol-p function-name))))
219       (sys:%set-stepper-on)
220       (when stop-at-symbol-p-value
221         (sys:%set-delimited-stepping-off)
222         t)))
223    (t t)))
224
225(defun stop-at-symbol-p (symbol)
226  "Indicates if the stepper need to stop at the current symbol"
227  (or (find symbol *stepper-stop-symbols* :test 'eq)
228      (some (lambda (package)
229                (do-external-symbols (s (find-package package))
230                  (if (eq s symbol)
231                      (return t))))
232            *stepper-stop-packages*)))
233
234(defun list-locals (env)
235  (print-stepper-str "Showing the values of variable bindings." t)
236  (print-stepper-str "From inner to outer scopes:" t)
237  (pprint-list-locals (sys:environment-all-variables env))
238  (print-stepper-str "Showing the values of function bindings." t)
239  (print-stepper-str "From inner to outer scopes:" t)
240  (pprint-list-locals (sys:environment-all-functions env)))
241
242(defun print-watched-symbols (env)
243  (when *stepper-watch-symbols*
244    (print-stepper-str "Watched bindings:" t)
245    (loop :for watch-symbol :in *stepper-watch-symbols*
246           :do (lookup-symbol watch-symbol env t))))
247
248(defun handle-user-interaction (env)
249  (let ((leave-prompt nil)
250        (unexpected-input-user nil)
251        (char-input-user nil))
252    (loop :until leave-prompt
253          :do (unless unexpected-input-user
254                (print-stepper-str "Type ':?' for a list of options" t)
255                (without-active-stepping (print-watched-symbols env)))
256              (without-active-stepping
257                (setf char-input-user (read))
258                (clear-input))
259              (case char-input-user
260                ((:? :help)
261                 (without-active-stepping (print-stepper-help)))
262                ((:l :locals)
263                 (without-active-stepping (list-locals env)))
264                ((:c :continue)
265                 (sys:%set-stepper-off)
266                 (setf leave-prompt t))
267                ((:sn :step-next)
268                 (setf *step-next-counter* (sys:%get-step-counter))
269                 (setf leave-prompt t))
270                ((:n :next)
271                 (sys:%set-delimited-stepping-on)
272                 (setf leave-prompt t))
273                ((:s :step) (setf leave-prompt t))
274                ((:q :quit)
275                 (sys:%set-stepper-off)
276                 (sys:%set-delimited-stepping-off)
277                 (sys:%return-from-stepper))
278                ((:i :inspect)
279                  (without-active-stepping (inspect-variable env)))
280                ((:b :br+ :add-breakpoint)
281                 (without-active-stepping (add-breakpoint)))
282                ((:r :br- :remove-breakpoint)
283                 (without-active-stepping (remove-breakpoint)))
284                ((:d :br! :delete-breakpoints)
285                 (without-active-stepping (remove-all-breakpoints)))
286                ((:w :watch)
287                 (without-active-stepping (insert-watch-symbol)))
288                ((:u :unwatch)
289                 (without-active-stepping (remove-watch-symbol)))
290                ((:bt :backtrace)
291                 (without-active-stepping
292                   ;; we avoid the first 2 entries of the backtrace
293                   ;; because they are constant and unrelated to the code
294                   ;; being stepped
295                   (pprint-stepper-str (subseq (sys:backtrace) 2))))
296                (otherwise (setf unexpected-input-user t))))))
297
298(defun in-slime-repl-p ()
299  "Determines if we are in Slime/Sly connection"
300  (some (lambda (c)
301          (and (find-package c)
302               (symbol-value (find-symbol "*EMACS-CONNECTION*" c))))
303        '(:swank :slynk)))
304
305(defun start ()
306  (print-stepper-str "This function activates the stepper." t)
307  (print-stepper-str "Remember to deactivate it after the end of the execution using (stepper:stop)." t)
308  (print-stepper-str "To clean its internal flags" t)
309  (sys:%initialize-step-counter)
310  (sys:%initialize-step-block)
311  (sys:%set-stepper-on))
312
313(defun stop ()
314  "Stops the stepper"
315  (sys:%set-stepper-off)
316  (clear-step-next)
317  (sys:%set-delimited-stepping-off)
318  (sys:%set-stepping-task-off))
319
320(defmacro step (form)
321  (let ((stepper-block (gensym)))
322    `(let ()
323       (block ,stepper-block
324         (sys:%initialize-step-counter)
325         (sys:%initialize-step-block)
326         (sys:%set-stepper-on)
327         (multiple-value-prog1 ,form
328           (sys:%set-stepper-off)
329           (clear-step-next)
330           (sys:%set-delimited-stepping-off))))))
331
332(provide :abcl-stepper)
Note: See TracBrowser for help on using the repository browser.