I've been thinking about a neat feature I think could be implemented in a pretty straightforward way to help with the debugging of macros. There would be a new minor mode for a buffer containing some random form. Selecting any given subform of the buffer's form would macroexpand that one form, in place. Of course, it would have to respect macrolet's and symbol-macrolet's (which isn't that hard, really).
I already have the code written (included below) to expand such forms (perhaps buggy, perhaps incomplete). (One thing I know it doesn't do is find and respect macrolets that aren't present in the form until after some expansion. I don't know that this is a big deal.)
What's missing is the hooks into slime and Emacs. I don't want to bother with these (because they'd be a lot more effort for me) until I find whether there are any similar features currently available for SLIME. So has anyone done anything like this yet? If not, I suppose SLIME would need another minor mode with some cool keybindings and code to figure out what to pass as the list-indexes variable based on the position in the buffer (perhaps the hardest part?).
Chris Capel
(defmacro expand (form &environment e) (list 'quote (macroexpand-1 form e)))
(defun get-list-to-expand (list list-indexes) (flet ((recurse () (get-list-to-expand (nth (car list-indexes) list) (cdr list-indexes)))) (if (null list-indexes) (list 'expand list) (let ((sym (find (car list) '(macrolet symbol-macrolet)))) (if sym (list sym (cadr list) (recurse)) (recurse))))))
(get-list-to-expand '(symbol-macrolet ((s a)) s) '(2))
(defun nsubst-list (source substitution list-indexes) (if (null list-indexes) substitution (progn (setf (nth (car list-indexes) source) (nsubst-list (nth (car list-indexes) source) substitution (cdr list-indexes))) source)))
(nsubst-list '(a b (c d)) '(e f) '(2 1))
(defun subst-all-tree (tree obj-a obj-b &key (test #'equal)) (mapcar (lambda (i) (if (consp i) (subst-all-tree i obj-a obj-b :test test) (if (funcall test obj-a i) obj-b i))) tree))
(subst-all-tree '(a b (c d)) 'd 'e)
(defun expand-part (list &rest list-indexes) (nsubst-list list (eval (get-list-to-expand list list-indexes)) list-indexes))
(expand-part '(macrolet ((x (m) `(y ,m))) (x z)) 2)
(expand-part '(let ((x 50)) (destructuring-bind (x y z) f (dolist (q y) x))) 2) ;; or "2 3)" for the dolist
Chris Capel pdf23ds@gmail.com writes:
I've been thinking about a neat feature I think could be implemented in a pretty straightforward way to help with the debugging of macros. There would be a new minor mode for a buffer containing some random form. Selecting any given subform of the buffer's form would macroexpand that one form, in place. Of course, it would have to respect macrolet's and symbol-macrolet's (which isn't that hard, really).
Suppose you have:
(defmacro bar (x) `(1+ ,x)) (defmacro foo (&rest body) '()) (defun baz (y) (foo (bar y)))
and now you select (bar y) and let it expand. What's the result?
I already have the code written (included below) to expand such forms (perhaps buggy, perhaps incomplete). (One thing I know it doesn't do is find and respect macrolets that aren't present in the form until after some expansion. I don't know that this is a big deal.)
I suspect that your code doesn't pass down the environment correctly. Every macro can potentially call macroexpand itself and I think you have to expand every macro and pass the proper environemnt. I think in the general case you need a full codewalker. Of course, a lot of useful stuff can be done with something much simpler.
[Isn't it shame that there's no portable codewalker for Common Lisp?]
What's missing is the hooks into slime and Emacs. I don't want to bother with these (because they'd be a lot more effort for me) until I find whether there are any similar features currently available for SLIME. So has anyone done anything like this yet?
Not that I know of. There are only the macroexpand{-1,-all} commands.
If not, I suppose SLIME would need another minor mode with some cool keybindings and code to figure out what to pass as the list-indexes variable based on the position in the buffer (perhaps the hardest part?).
I have the impression that your list-indexes are similar to what CMUCL calls "source-path". You could use the code in SLIME's source-path-parser. Basically you can pass in a string (or stream) and you get the corresponding sexp and a table which maps each (sub)form to the start and the end position in the string. So you could give it the toplevel expression from the buffer as a string, search the interval in the table which matches the buffer position best and you know which subform to expand. If you know the subform (comparable with eq) you can compute the source-path. But you probably don't need that, since you already have the subform.
Hm... sounds like a lot of work. Maybe there's something simpler.
Helmut.
On Wed, 13 Apr 2005 12:42:09 +0200, Helmut Eller wrote:
Chris Capel pdf23ds@gmail.com writes:
I already have the code written (included below) to expand such forms (perhaps buggy, perhaps incomplete). (One thing I know it doesn't do is find and respect macrolets that aren't present in the form until after some expansion. I don't know that this is a big deal.)
I suspect that your code doesn't pass down the environment correctly. Every macro can potentially call macroexpand itself and I think you have to expand every macro and pass the proper environemnt. I think in the general case you need a full codewalker. Of course, a lot of useful stuff can be done with something much simpler.
Do you mean that the environment passed to macroexpand-1 up there in my EXPAND isn't correct?
Now, what could be done to be more general is to actually do the full expansion for every parent form before expanding the selected form, but only show the selected form's expansion in the buffer. The challenge here would be keeping dibs on the selected form during the macroexpansion of its parents. Since the form isn't necessarily preserved in a way congruent with the users expectations (or even in a way we can locate it again after a call to macroexpand-1) this problem has no general solution.
Suppose you have:
(defmacro bar (x) `(1+ ,x)) (defmacro foo (&rest body) '()) (defun baz (y) (foo (bar y)))
and now you select (bar y) and let it expand. What's the result?
Yeah, my code was definitely built on the assumption that you know what you're doing when you try to start expanding stuff. There could be a warning somewhere that "You should only expand forms nested under other macros when those other macros include a ,@body somewhere and the forms you're expanding a part of that body. Otherwise, you're pretty much guaranteed to get something completely different than what will actually be compiled."
But since these are uncommon cases, and since macros with full code-walkers and transformers rarely have regular lisp macro calls that are eradicated by the time macroexpansion is complete (but instead some domain-specific language or parameter list that couldn't be meaningfully macroexpanded anyway) I think the potential for confusion is minimal.
Perhaps sufficient would be to allow the list of forms kept for environment purposes to be expanded from '(macrolet symbol-macrolet) by the user, as some sort of configuration. Of course, the configuration would include which parts of the form to keep (while it's the first two items of macrolet or symbol-macrolet, that's not general principle).
If not, I suppose SLIME would need another minor mode with some cool keybindings and code to figure out what to pass as the list-indexes variable based on the position in the buffer (perhaps the hardest part?).
I have the impression that your list-indexes are similar to what CMUCL calls "source-path". You could use the code in SLIME's source-path-parser. Basically you can pass in a string (or stream) and you get the corresponding sexp and a table which maps each (sub)form to the start and the end position in the string. So you could give it the toplevel expression from the buffer as a string, search the interval in the table which matches the buffer position best and you know which subform to expand. If you know the subform (comparable with eq) you can compute the source-path. But you probably don't need that, since you already have the subform.
Hm... sounds like a lot of work. Maybe there's something simpler.
Here's one idea I had. Emacs passes in the position of the point and a string representation of the whole buffer, which Lisp proceeds to read with a locally rebound #( read macro that keeps up with the position in the list during every recursive call to read in some global variable. When it gets to the position in the string stream where the point was in Emacs, it saves its current list position to some other variable, which is then passed to the expanders. But this sounds like a reimplementation of source-path-parser to me, so I doubt any effort would be saved.
Well, I'll look into getting a patch for this in the coming week, then.
Chris Capel