-
Notifications
You must be signed in to change notification settings - Fork 144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Threading macro refactoring #616
base: master
Are you sure you want to change the base?
Conversation
I'll have a better look at this later, but shouldn't be called Some more questions:
|
With autodoc the thread-first macro messes it up a little:
![2023-08-22-162829_363x148_scrot](https://github.com/joaotavora/sly/assets/122528427/5e43f4c6-1414-4f9c-b492-67a6c69699bd)
It doesn't know that the threading macro will insert an argument in the
first position, so it bolds one argument before the one we're really
on. I think it's kind of expected autodoc won't always work perfectly when
macros can rewrite code, for example yesterday I was using lquery and within
It would be neat if it worked by macro-expanding the code first :-)
In ELisp I tried to do that for the completion of local variables,
see `elisp--local-variables`. It's hackish (and dangerous), tho, so it
doesn't really address the "won't always work perfectly".
|
Interesting, how would that work? How would you find where the current point(cursor) location corresponds to in the macroexpanded version? |
Interesting, how would that work? How would you find where the current
point(cursor) location corresponds to in the macroexpanded version?
As mentioned, you can check how I did it: `C-h f RET
elisp--local-variables RET` and then follow the link to its source code.
Basically I take the string between the start of the current sexp and
point, add "elisp--witness--lisp" and the right number of closing
parens, and then macro-expand the corresponding sexp and look for
the "elisp--witness--lisp" symbol in the result.
It can fail in a wonderful variety of ways, of course.
Stefan
|
As Stefan implied with macroexpand, in part by running "user code", i.e. by executing arbitrary user code that is under analysis. This strategy isn't new, and it's used by Elisp's flymake backend or in some select situations by SLY and SLIME too. But there's a conceptual difference between doing this macroexpansion/execution when the user specifically asks to and doing it in the background, while the user is innocently navigating code. I've once deleted a full directory in my $HOME just because I had a miswritten macro with a
Hi @monnier In fact I think it's failing right now while editing Debugger entered--Lisp error: (wrong-number-of-arguments (2 . 2) 1)
#f(compiled-function (arg1 arg2 &rest rest) "Destructure OBJECT, binding VARS in BODY.\nVARS is ([(INTERFACE)] SYMS...)\nHonor `eglot-strict-mode'." #<bytecode 0x1e46fc1e69c1011c>)(((TypeHierarchyItem) name elisp--witness--lisp))
apply(#f(compiled-function (arg1 arg2 &rest rest) "Destructure OBJECT, binding VARS in BODY.\nVARS is ([(INTERFACE)] SYMS...)\nHonor `eglot-strict-mode'." #<bytecode 0x1e46fc1e69c1011c>) ((TypeHierarchyItem) name elisp--witness--lisp))
macroexpand-1((eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp)) nil)
macroexp-macroexpand((eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp)) nil)
macroexp--expand-all((eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp)))
macroexp--all-forms((lambda (n1 n2) (eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp))) 2)
macroexp--expand-all((cl-function (lambda (n1 n2) (eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp)))))
macroexp--all-forms((--cl-node-equal-- (cl-function (lambda (n1 n2) (eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp))))) 1)
macroexp--all-clauses(((--cl-node-equal-- (cl-function (lambda (n1 n2) (eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp)))))) 1)
macroexp--expand-all((cl-flet ((node-equal (n1 n2) (eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp))))))
macroexp--all-forms(((cl-flet ((node-equal (n1 n2) (eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp)))))))
macroexp--expand-all((let* ((srv (eglot-current-server)) (hierarchy (hierarchy-new)) (roots (jsonrpc-request srv preparer (eglot--TextDocumentPositionParams)))) (cl-flet ((node-equal (n1 n2) (eglot--dbind ((TypeHierarchyItem) name elisp--witness--lisp)))))))
macroexp--all-forms((lambda (provider preparer methods) (eglot--server-capable-or-lose provider) (let* ((srv (eglot-current-server)) (hierarchy (hierarchy-new)) (roots (jsonrpc-request srv preparer (eglot--TextDocumentPositionParams)))) (cl-flet ((node-equal (n1 n2) (eglot--dbind (... name elisp--witness--lisp))))))) 2)
macroexp--expand-all(#'(lambda (provider preparer methods) (eglot--server-capable-or-lose provider) (let* ((srv (eglot-current-server)) (hierarchy (hierarchy-new)) (roots (jsonrpc-request srv preparer (eglot--TextDocumentPositionParams)))) (cl-flet ((node-equal (n1 n2) (eglot--dbind ...)))))))
macroexp--all-forms((defalias 'eglot--hierarchy-helper #'(lambda (provider preparer methods) (eglot--server-capable-or-lose provider) (let* ((srv (eglot-current-server)) (hierarchy (hierarchy-new)) (roots (jsonrpc-request srv preparer ...))) (cl-flet ((node-equal ... ...)))))) 1)
#f(compiled-function (form func) #<bytecode -0xa784b5aded04feb>)(((defalias 'eglot--hierarchy-helper #'(lambda (provider preparer methods) (eglot--server-capable-or-lose provider) (let* ((srv ...) (hierarchy ...) (roots ...)) (cl-flet (...)))))) defalias)
macroexp--expand-all((defun eglot--hierarchy-helper (provider preparer methods) (eglot--server-capable-or-lose provider) (let* ((srv (eglot-current-server)) (hierarchy (hierarchy-new)) (roots (jsonrpc-request srv preparer (eglot--TextDocumentPositionParams)))) (cl-flet ((node-equal (n1 n2) (eglot--dbind (... name elisp--witness--lisp))))))))
macroexpand-all((defun eglot--hierarchy-helper (provider preparer methods) (eglot--server-capable-or-lose provider) (let* ((srv (eglot-current-server)) (hierarchy (hierarchy-new)) (roots (jsonrpc-request srv preparer (eglot--TextDocumentPositionParams)))) (cl-flet ((node-equal (n1 n2) (eglot--dbind (... name elisp--witness--lisp))))))))
elisp--local-variables() |
by SLY and SLIME too. But there's a conceptual difference between doing
this macroexpansion/execution when the user specifically asks to and doing
it in the background, while the user is innocently navigating code.
There's a difference in degree, maybe, but in both cases it's indeed
quite dangerous :-(
I've once deleted a full directory in my $HOME just because I had
a miswritten macro with a `delete` call and I was using Elisp flymake.
I hadn't even saved the file.
Yup, we really need some way to confine the execution of the macros, and
there's currently no way to do that :-(
> It can fail in a wonderful variety of ways, of course.
Hi @monnier In fact I think it's failing right now while editing `lisp/progmodes/eglot.el` :-)
```elisp
Debugger entered--Lisp error: (wrong-number-of-arguments (2 . 2) 1)
#f(compiled-function (arg1 arg2 &rest rest) "Destructure OBJECT, binding
Hmm... looks like I pushed an incomplete patch, indeed.
Should be fixed now.
Stefan
|
I include a brief description of what threading macros are in case anyone hasn't seen them, as they aren't standard in common lisp, but are popular in clojure, and pipe operator which is the same thing is popular in other langs.
Threading macros let you write a series of functions reading from top to bottom instead of inside out. For example instead of:
you can write:
->>
substitutes each value as the last argument of each form in turn, while->
substitutes it as the first argument, so:This pull adds a series of refactoring commands that will automatically rewrite code into thread-first or thread-last style, or unwind a thread macro into "normal" code. It is 95% copied from clojure-mode which in turn got it from clj-refactor (as I note in authors section of the code), which are also like sly licensed in GPL so it shouldn't be a problem. This introduces 5 new interactive functions:
sly-unwind: unwind threading macro once, ie turn
(-> a b c)
into(-> (b a) c)
sly-unwind-all: fully unwind, ie turn
(-> a b c)
into(c (b a))
sly-thread-first-all: turn
(+ (- (* (/ 1) 2) 3) 4)
into:sly-thread-last-all: turn
(print (remove-if #'evenp (mapcar #'1+ '(1 2 3 4 5))))
into:and sly-thread: which increases the threading by one, turning:
into:
and introduces one new variable: sly-threading-macro which defaults to "->" or can be set to "~>" which is used by some CL threading macro libraries.
In the future more refactoring commands could be copied from clj-refactor (applied to CL), or original ones added.
I'm new at lisp and emacs, and never really done professional programming, this was my first time writing unit tests. so any feedback is welcome on how to make it high quality and mergeable