Here's what I'm trying to do:
(defun test-macrolet ()
(macrolet
((%test
(x)
(message "%%test is called")
`(message "x: %s" ,x))
(%aref (array place)
`(aref ,array ,place)))
;; it doesn't expand to (message "x: %s" 100)
(message "expanded: %s" (macroexpand-all '(%test 100)))
(%test 100)
(%aref (%test 100) 0)))
I'm calling this function from inside another macro, where I'd like to expand all inner macros where some had to be defined locally. To, perhaps, give you a better idea, I want this:
(iter (for i from 0 to 10)
(when (oddp i) (collect i)))
to expand in environment where macros for
and collect
are defined, but I don't want these macros to be defined globally (obviously, not the best names to put into global name space).
I hoped that by defining a local macro with macrolet
I could expand the form containing macros defined locally, and then return the expansion, but macroexpand
and macroexpand-all
act as if they don't recognize locally defined macros (and don't expand them).
Well, hope I'm making myself clear...
EDIT
Thanks to an answer the respondent decided to remove, I've started to search for ways of obtaining lexical environment in Emacs Lisp. I found this example function:
(defun byte-compile-make-lambda-lexenv (form)
"Return a new lexical environment for a lambda expression FORM."
;; See if this is a closure or not
(let ((args (byte-compile-arglist-vars (cadr form))))
(let ((lexenv nil))
;; Fill in the initial stack contents
(let ((stackpos 0))
;; Add entries for each argument
(dolist (arg args)
(push (cons arg stackpos) lexenv)
(setq stackpos (1+ stackpos)))
;; Return the new lexical environment
lexenv))))
in bytecomp.el. Which on the surface of it means that the environment is just a alist made of function (macros in my case) names and their "position on the stack", I don't quite understand the second part, but will experiment and we'll see...
EDIT 2
OK, this seems like it would work:
(defun test-macrolet ()
(macrolet
((%test
(x)
(message "%%test is called")
`(message "x: %s" ,x))
(%aref (array place)
`(aref ,array ,place)))
(%test 100)
(%aref (%test 100) 0)
(message "expanded: %S"
(macroexpand
'(%test 100)
(cons `(,@(list (intern "%test")) .
(lambda (y)
(message "Called from environment '%s'" y)
`(message "Final expansion '%s'" ,y)))
macroexpand-all-environment)))))
(test-macrolet)
"expanded: (message \"Final expansion '%s'\" 100)"
Sorry for the sloppy code. Some more info: macroexpand-all-environment
is the variable where compiler / interpreter stores current environment. The environment object contains (expander-name . expander-function)
pairs, where the car of the pair is the symbol which was encountered at this step of macro expansion and cdr is the function which should handle the expansion.
EDIT 3
As this seems to be very confusing to readers, here's why I don't want to return a form containing a macrolet
, but rather expand everything before generating the code.
While processing the top-level form, I'm creating a bunch of objects, well, I'll call it an AST (abstract syntax tree), but it's not really that, it's just some complex model of the code I'm going to generate from the macro. If macros are expanded repeatedly, the AST will be lost in subsequent expansions, besides, I will not be able to properly generate the next step of expansion and will not be able to do certain manipulations on the code, as for example, some forms rely on their stray logical parts will be present deeper inside the "foreign" expressions. It is really easier for me to generate the code then to rely on the macro expansion mechanism in many cases. So, for example, answering to @6502:
what should (for...) expand to given that the for form is closed before the body?
It is not expanded in the regular sense of the word. The form is parsed, the parts of it are extracted into a special object, that is being later used in generating the final code. For example:
(iter (for var in '(1 2 3 4))
(when (oddp var) (collect (next var)))
Will first create an object, that registers two variables var
and an autogenerated variable, where the result is stored, let's call it --0
(because that's how I indeed generate the names). I can only find out that I need --0
variable only after parsing the second expression inside iter
. So, if I expanded for
very early into some let
expression, and only after that first expansion had I discovered that I need to add one more variable - that would be too late. I.e. the expansion of the above should look something like this:
(let (--0 var (--1 '(1 2 3 4)))
(while --1
(setq var (car --1) --1 (cdr --1))
(if (oddp var) (setq --0 (cons (cadr --l) --0))))
--0)
If I had to generate a macrolet
with half-expanded previous expression, I would've not known which variable do I have to collect into, how to generate the next
expression and so on.
Also why does collect need to be a macro and not just a local function?
There's no requirement it be either, I just need it to signal during macro expansion process to me so that I could generate appropriate code. It, technically, doesn't expand into anything (hypothetically, it could be even ignored during expansion, if some code analysis will show it creates unreachable code, for example).
EDIT 4
This is getting really long, sorry. Besides the schema and the state of expansion that I must preserve during the entire expansion process, there another reason. Consider two following examples:
(iter (for (key . value) in some-hash-table)
(message "key: %s => value: %s" key value))
This code is not expected to generate a (while ...)
loop, because it is inefficient to do so, and it is better to use a (maphash ...)
here. However:
(iter (for (key . value) in some-hash-table)
(for (key-2 . value-2) in some-other-hash-table)
(message "key: %s => value: %s" (list key key-2) (list value value-2)))
Will require generating an additional form for collecting the keys of the second hash table (or the firs one, whichever is smaller) and quite a bit of code will have to go into the section preceding the first (maphash ...)
expression that could've been generated earlier. So, if I generated a macrolet here, I would be stuck in a situation, where I have to generate the code, which has already been generated, i.e. I'd had stale expression, that I would need to go back and reparse (if I'm lucky and I can somehow still reach there).