reddit / emacs

Macro Help: Expanding seems different than calling

mellowmoonling · 2022-06-13 14:12
I'll preface this question with the fact that I'm very new to writing macros, I usually avoid them when I can. I'm trying to write a little emacs widget and thought I found the perfect situation to use them. I want to generate a bunch of toggle widgets with slightly different notify functions. (the idea is to use them as a bit field to allow the user to toggle them and calculate the hex number) The macro in question is this: (defmacro bw/gen-bit-toggle (index) `(widget-create 'toggle :on "1" :off "0" :format "%[%v%]" :notify (lambda (this-widget changed-widget &optional change-event) (let ((this-bit-index ,index)) (message (format "Bit Index Changing: %d" this-bit-index)) (message (format "Current Hex Value %d" (string-to-number (widget-value bw/hex-value) 16))) (if (equal (widget-value this-widget) t) (widget-value-set bw/hex-value (format "%x" (logior (string-to-number (widget-value bw/hex-value) 16) (lsh 1 this-bit-index)))) (widget-value-set bw/hex-value (format "%x" (logand (string-to-number (widget-value bw/hex-value) 16) (lognot (lsh 1 this-bit-index))))) )) (widget-setup) )) ) It should generate a toggle widget that shows "1" for on and "0" for off. The notify function will then take that bit, create a bit mask and either set or clear the bit in another variable that is displayed elsewhere in the widget. This works if I basically take the expanded macro code and manually use it. However, I want to have up to 64 of these widgets. I feel like this is a great use case for macros. I want to have essentially a list of these widgets that I can access by indexing their element in the list.When I expand the macro in ielm it looks like this: ELISP> (macroexpand '(bw/gen-bit-toggle 1)) (widget-create 'toggle :on "1" :off "0" :format "%[%v%]" :notify (lambda (this-widget changed-widget &optional change-event) (let ((this-bit-index 1)) (message (format "This Widget: %s" (widget-value this-widget))) (message (format "Bit Index Changing: %d" this-bit-index)) (message (format "Current Hex Value %d" (string-to-number (widget-value bw/hex-value) 16))) (if (equal (widget-value this-widget) t) (widget-value-set bw/hex-value (format "%x" (logior (string-to-number (widget-value bw/hex-value) 16) (lsh 1 this-bit-index)))) (widget-value-set bw/hex-value (format "%x" (logand (string-to-number (widget-value bw/hex-value) 16) (lognot (lsh 1 this-bit-index))))))) (widget-setup))) The "this-bit-index" variable in the let binding is really the only part that is different from each widget notify function. When I "call" (probably not the correct term, invoke?) the macro it looks like this: ELISP> (bw/gen-bit-toggle 1) (toggle :on "1" :off "0" :format "%[%v%]" :notify (closure (t) (this-widget changed-widget &optional change-event) (let ((this-bit-index 1)) (message (format "Bit Index Changing: %d" this-bit-index)) (message (format "Current Hex Value %d" (string-to-number (widget-value bw/hex-value) 16))) (if (equal (widget-value this-widget) t) (widget-value-set bw/hex-value (format "%x" (logior (string-to-number (widget-value bw/hex-value) 16) (lsh 1 this-bit-index)))) (widget-value-set bw/hex-value (format "%x" (logand (string-to-number (widget-value bw/hex-value) 16) (lognot (lsh 1 this-bit-index))))))) (widget-setup)) The expanded macro looks like what I want. However, when it is called it takes a different form and seems to lop off the "widget-create" function call. The intention is to use this macro multiple times to have a list of these widget objects. I feel like I'm really close but I'm at the point where I'm randomly adding quotes, backticks, and commas and getting nowhere. Does anyone see issues with the macro definition?
_viz_ · 2022-06-13 14:20
You expanded the macro, why not try eval-ing the expansion next? If you do that, the last block is simply what `widget-create' returns. EDIT: Clarified comment.
PropagandaOfTheDude · 2022-06-13 15:03
I don't see why you need a macro at all. `widget-create` is an ordinary function, and `index` is just an integer. There's no reason to rewrite the source text. Just make sure that you're operating in a lexical binding context, so the callback function can find `index` later. (defun bw/gen-bit-toggle (index) (widget-create 'toggle :on "1" :off "0" :format "%[%v%]" :notify #'(lambda (this-widget changed-widget &optional change-event) (message (format "Bit Index Changing: %d" index)) (message (format "Current Hex Value %d" (string-to-number (widget-value bw/hex-value) 16))) (let* ((current-bits (string-to-number (widget-value bw/hex-value) 16)) (updated-bits (if (equal (widget-value this-widget) t) (logior current-bits (lsh 1 index)) (logand current-bits (lognot (lsh 1 index)))))) (widget-value-set bw/hex-value (format "%x" updated-bits))) (widget-setup))))
mellowmoonling · 2022-06-13 17:11
ah that's a really good idea. I think I was so set on doing it with a macro I hadn't thought of it any other way. Thanks!
mellowmoonling · 2022-06-13 17:11
That's a good idea. I'm going to go the route that u/PropagandaOfTheDude suggested and just use a function.
7890yuiop · 2022-06-13 21:32
> Expanding seems different than calling I haven't looked at the code, but just to be clear, expanding a macro *is* different to calling it. Expanding a macro only produces the code which would be evaluated, if you were to call it. Calling a macro causes it to be expanded, and then the expanded code is evaluated. n.b. macro expansion typically happens at either byte-compilation time or at load time -- so by the time your code is actually running, there usually aren't any macros remaining; you're just executing the code which was expanded at some prior time.