abcl
abcl copied to clipboard
First working stepper for ABCL!
Hi guys
I just completed a working stepper for ABCL (more details below)
See #264
I think it can help ABCL users even knowing that is an initial attempt. I've been starting to working in make it compatible with Sly/Slime but it was not trivial and I couldn't complete a successful integration for now (if someone can do it would be awesome).
If you have some time, please take a look at it, and try to test it. An maybe we could merge it if you think is ready enougth to be our first stepper.
It can be of course improved, but IMO it can be seen as a working starting point.
I'm attaching a simple session here.
Happy hacking!
Some characteristics:
- For intepreted code
- For plain REPL only, it is still not working with Sly/Slime When called inside Sly/Slime it will only show print a message and return the form without any stepping
- ? will print 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
- case-insensitive when inspecting
Now the stepper allows the inspection of symbols and bindings in other packages too Attaching another session
;; .abclrc
;; (defparameter *some-var* 1)
;; (defun test ()
;; (let ((*some-var* nil)
;; (x 3))
;; (list *some-var* 3)))
Trés cool!
I'm gonna try rewriting the presence of steppenwolf
as a fset
able place: should be cleaner for the lexical transcriptions.
Thanks so much for the contribution.
Hi @easye
Glad to know you liked it.
Good luck with the rewriting process
And let me know if you need any help :)
Added new features to the stepper
-
See locals bindings (l): The l will show the local bindings for variables and function in the current environment passed to the current form to evaluate
-
Quit the stepper (q): The quit q feature will abort the evaluation in the stepper and return NIL. This is useful to avoid running the remaining forms in the code when the user wants to leave the stepper, specially if the rest of the program is doing costly operations.
-
Go to the next (n) symbol:
The next n feature allow to stop the stepper only when the interpreter is analyzing one of the symbols specified in the list of sys::stepper-stop-symbols or any of the exported symbols presented in any of the list of packages specified in sys::stepper-stop-packages. These variables will have initially the value NIL and if they are not modified, next will behave exactly as continue. It is useful when we want to step large or complex code and avoid stepping every form in order to jump only to the interested ones.
@alejandrozf Is this mature enough to include in 1.9.1? Last time I looked, it was only working for interpreted code? Since we don't currently have a good way to switch the implementation to a "pure interpreted" mode I worry that including this would confuse people to thinking something wasn't functional.
Would it be possible to package the stepper as something in ABCL-CONTRIB? Your current implementation needs Java, but I think it could be packaged as static classes within code not included in abcl.jar
but rather abcl-contrib.jar
. Currently, ABCL-CONTRIB is advertised as "Pure Common Lisp", but there is no real reason it couldn't contain Java implementation as well. (for those curious about why the split between two jars: 1) licensing and 2) bounding the basic ANSI implementation from its extensions for reasons of 1).)
Hi @easye
Yes, I think it is functional enougth to be released. I've been testing it these days, it would be amazing if someone else can do some more testing too. It could be improved with more features and integrations incrementally.
When I said that is for interpreted code I mean that it is built inside the main evaluator method in Lisp.java (eval), which is an interpreter, and if we try to debug a compiled function it won't show any useful step info, but if we use on forms directly or with an interpreted function then we can have the advantages of the stepping process.
For example:
CL-USER(1): (defun test (n)
(cond ((oddp n) (print "Odd number"))
((evenp n) (print "Even number"))))
TEST
CL-USER(2): (step (test 7))
We are in the stepper mode
Evaluating:
TEST
With args:
(7)
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
BLOCK
With args:
(TEST (COND ((ODDP N) (PRINT "Odd number")) ((EVENP N) (PRINT "Even number"))))
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
COND
With args:
(((ODDP N) (PRINT "Odd number")) ((EVENP N) (PRINT "Even number")))
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
ODDP
With args:
(N)
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
PRINT
With args:
("Odd number")
Type '?' for a list of options
c
"Odd number"
"Odd number"
CL-USER(3): (compile 'test)
TEST
NIL
NIL
CL-USER(4): (step (test 7))
We are in the stepper mode
Evaluating:
TEST
With args:
(7)
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
SYSTEM:%SET-STEPPER-OFF
With args:
NIL
Type '?' for a list of options
s
"Odd number"
"Odd number"
CL-USER(5):
For use it with ASDF systems we can use asdf:load-source-op:
CL-USER(5): (asdf:operate 'asdf:load-source-op :alexandria)
#<ASDF/LISP-ACTION:LOAD-SOURCE-OP >
#<ASDF/PLAN:SEQUENTIAL-PLAN {245AD6EA}>
CL-USER(6): (step (alexandria:plist-hash-table '(:a 1 :b 2 :c 3)))
We are in the stepper mode
Evaluating:
ALEXANDRIA:PLIST-HASH-TABLE
With args:
('(:A 1 :B 2 :C 3))
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
QUOTE
With args:
((:A 1 :B 2 :C 3))
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
BLOCK
With args:
(ALEXANDRIA:PLIST-HASH-TABLE (LET ((ALEXANDRIA::TABLE (APPLY #'MAKE-HASH-TABLE ALEXANDRIA::HASH-TABLE-INITARGS))) (DO ((ALEXANDRIA::TAIL ALEXANDRIA::PLIST (CDDR ALEXANDRIA::TAIL))) ((NOT ALEXANDRIA::TAIL)) (LET ((#:KEY14153 (CAR ALEXANDRIA::TAIL)) (#:HASH-TABLE14154 ALEXANDRIA::TABLE)) (MULTIPLE-VALUE-BIND (#:VALUE14155 #:PRESENTP14156) (GETHASH #:KEY14153 #:HASH-TABLE14154) (IF #:PRESENTP14156 (VALUES #:VALUE14155 #:PRESENTP14156) (VALUES (SYSTEM:PUTHASH #:KEY14153 #:HASH-TABLE14154 (CADR ALEXANDRIA::TAIL)) NIL))))) ALEXANDRIA::TABLE))
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
LET
With args:
(((ALEXANDRIA::TABLE (APPLY #'MAKE-HASH-TABLE ALEXANDRIA::HASH-TABLE-INITARGS))) (DO ((ALEXANDRIA::TAIL ALEXANDRIA::PLIST (CDDR ALEXANDRIA::TAIL))) ((NOT ALEXANDRIA::TAIL)) (LET ((#:KEY14153 (CAR ALEXANDRIA::TAIL)) (#:HASH-TABLE14154 ALEXANDRIA::TABLE)) (MULTIPLE-VALUE-BIND (#:VALUE14155 #:PRESENTP14156) (GETHASH #:KEY14153 #:HASH-TABLE14154) (IF #:PRESENTP14156 (VALUES #:VALUE14155 #:PRESENTP14156) (VALUES (SYSTEM:PUTHASH #:KEY14153 #:HASH-TABLE14154 (CADR ALEXANDRIA::TAIL)) NIL))))) ALEXANDRIA::TABLE)
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
APPLY
With args:
(#'MAKE-HASH-TABLE ALEXANDRIA::HASH-TABLE-INITARGS)
Type '?' for a list of options
s
We are in the stepper mode
Evaluating:
FUNCTION
With args:
(MAKE-HASH-TABLE)
Type '?' for a list of options
c
#<EQL HASH-TABLE 3 entries, 11 buckets {33172E22}>
CL-USER(7):
And I don't know how to make it a contrib, I implemented it thinking in a core addition, because it is modifying main parts of the implementation like the evaluator and other .java files. To make that change I would need some help.
@easye I don't see the need to separate out the java support code from the main java code. However I would suggest only leaving in Java the minimal parts necessary to support stepping. So, for example, pull out any of the printing and interaction from handleStepping, similarly pprintListLocals and defer that to commonlisp code that's in contrib. The rule would be, if it can be done in commonlisp then do it in commonlisp.
@easye @alanruttenberg I followed your suggestions and created another PR #568 making this stepper a contrib and migrating to Lisp the printing and reading functions. Let me know what do you think when you have some time. I'm keeping the first PR #507 because I made a document based on it
I had to confess that I'm still thinking that the stepper should be a core addition and not a contrib because cl:step is part of CL standard and also I've been analyzing steppers in other CL implementations similar to ABCL like CLISP & ECL and what they have are behaving quite similar to this one, in the sense that they are functional only for interpreted code.
CLISP:
alessio@ThinkBook-15-G2-ITL:~/projects$ clisp -ansi
i i i i i i i ooooo o ooooooo ooooo ooooo
I I I I I I I 8 8 8 8 8 o 8 8
I \ `+' / I 8 8 8 8 8 8
\ `-+-' / 8 8 8 ooooo 8oooo
`-__|__-' 8 8 8 8 8
| 8 o 8 8 o 8 8
------+------ ooooo 8oooooo ooo8ooo ooooo 8
Welcome to GNU CLISP 2.49.93+ (2018-02-18) <http://clisp.org/>
Copyright (c) Bruno Haible, Michael Stoll 1992-1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2018
Type :h and hit Enter for context help.
[1]> (defparameter *some-var* 1)
*SOME-VAR*
[2]> (defun test ()
(let ((*some-var* nil)
(x 3))
(list *some-var* 3)))
TEST
[3]> (step (test))
step 1 --> (TEST)
Step 1 [4]> :s
step 2 --> (LET ((*SOME-VAR* NIL) (X 3)) (LIST *SOME-VAR* 3))
Step 2 [5]> :s
step 3 --> NIL
Step 3 [6]> :s
step 3 ==> value: NIL
step 3 --> 3
Step 3 [7]> :s
step 3 ==> value: 3
step 3 --> (LIST *SOME-VAR* 3)
Step 3 [8]> :s
step 4 --> *SOME-VAR*
Step 4 [9]> :s
step 4 ==> value: NIL
step 4 --> 3
Step 4 [10]> :s
step 4 ==> value: 3
step 3 ==> value: (NIL 3)
step 2 ==> value: (NIL 3)
step 1 ==> value: (NIL 3)
(NIL 3)
[11]> (compile 'test)
WARNING: in TEST : variable X is not used.
Misspelled or missing IGNORE declaration?
TEST ;
1 ;
NIL
[12]> (step (test))
step 1 --> (TEST)
Step 1 [13]> :s
step 1 ==> value: (NIL 3)
(NIL 3)
[14]>
ECL
alessio@ThinkBook-15-G2-ITL:~/projects$ ecl
ECL (Embeddable Common-Lisp) 21.2.1 (git:UNKNOWN)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2013 Juan J. Garcia-Ripoll
Copyright (C) 2018 Daniel Kochmanski
Copyright (C) 2021 Daniel Kochmanski and Marius Gerbershagen
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.
Top level in: #<process TOP-LEVEL 0x7fac1bd12f80>.
> (defparameter *some-var* 1)
*SOME-VAR*
> (defun test ()
(let ((*some-var* nil)
(x 3))
(list *some-var* 3)))
TEST
> (step (test))
(TEST) -:s
(BLOCK TEST ...) -:s
(LET (# #) ...) -:s
(LIST *SOME-VAR* ...) -:s
(NIL 3)
> (compile 'test)
;;; Loading #P"/usr/lib/x86_64-linux-gnu/ecl-21.2.1/cmp.fas"
;;; OPTIMIZE levels: Safety=2, Space=0, Speed=3, Debug=0
;;;
;;; End of Pass 1.
TEST
NIL
NIL
> (step (test))
(TEST) -:s
(NIL 3)
>
I had to confess that I'm still thinking that the stepper should be a core addition and not a contrib
Yes, I think eventually it can be a core contribution, but it would be nice to "incubate" it as a contrib until it (hopefully) works under compiled code as well. In any event, I have started to work on figuring out how to start packaging your code as a contrib, so stay tuned for that.
Ok, @easye got it. I think you can use then the PR #568 version directly (where the stepper is already packaged as a contrib), unless there are some other stages I'm not aware to make it a full contrib.
Supseded by <https://github.com/armedbear/abcl/pull/596>