这是我正在尝试做的事情:
(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)))
我正在从另一个宏中调用这个函数,我想扩展所有内部宏,其中一些必须在本地定义。或许,为了给你一个更好的主意,我想要这个:
(iter (for i from 0 to 10)
(when (oddp i) (collect i)))
在已定义宏for
和collect
的环境中进行扩展,但我不希望全局定义这些宏(显然,不是放入全局名称空间的最佳名称)。
我希望通过使用macrolet
定义本地宏,我可以展开包含本地定义的宏的表单,然后返回扩展,但macroexpand
和macroexpand-all
表现得好像他们没有不识别本地定义的宏(并且不扩展它们)。
嗯,希望我能让自己明白......
修改
由于受访者决定删除答案,我开始寻找在Emacs Lisp中获取词汇环境的方法。我找到了这个示例函数:
(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))))
在bytecomp.el中。从表面上看,这意味着环境只是一个由功能(我的情况下是宏)名称及其“堆叠位置”组成的列表,我不太了解第二部分,但会进行实验,我们将会见...
编辑2
好的,这似乎可行:
(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)"
对于草率的代码感到抱歉。更多信息:macroexpand-all-environment
是编译器/解释器存储当前环境的变量。环境对象包含(expander-name . expander-function)
对,其中该对的汽车是宏扩展步骤中遇到的符号,cdr是应该处理扩展的函数。
编辑3
因为这对读者来说似乎很混乱,所以我不想返回包含macrolet
的表单,而是在生成代码之前扩展所有内容。
在处理顶级表单时,我正在创建一堆对象,好吧,我将其称为AST(抽象语法树),但实际上并非如此,它只是代码的一些复杂模型我将从宏生成。如果宏被重复扩展,AST将在后续扩展中丢失,此外,我将无法正确生成下一步扩展,并且无法对代码进行某些操作,例如,某些表单依赖他们的流浪逻辑部分将更深入地呈现在“外国”表达中。在很多情况下,我更容易生成代码,然后依赖宏扩展机制。所以,例如,回答@ 6502:
如果for表单在正文之前关闭,那么(for ...)应该展开什么?
它没有在常规的意义上扩展。解析表单,将其中的部分提取到特殊对象中,稍后将其用于生成最终代码。例如:
(iter (for var in '(1 2 3 4))
(when (oddp var) (collect (next var)))
首先创建一个对象,它注册两个变量var
和一个自动生成的变量,其中存储结果,让我们称之为--0
(因为这就是我确实生成名称的方式)。我只能在解析--0
内的第二个表达式后才发现我需要iter
变量。所以,如果我很早就将for
扩展到某个let
表达式,并且只有在第一次扩展之后才发现我需要再添加一个变量 - 这太晚了。即上面的扩展应该是这样的:
(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)
如果我必须使用半扩展的前一个表达式生成macrolet
,我将不知道我必须收集哪个变量,如何生成next
表达式等等。
为什么collect需要是一个宏而不仅仅是一个本地函数?
也没有要求它,我只是需要它在宏扩展过程中向我发出信号,以便我可以生成适当的代码。从技术上讲,它不会扩展到任何东西(假设,在扩展期间甚至可以忽略它,例如,如果某些代码分析会显示它会创建无法访问的代码)。
编辑4
这真的很长,抱歉。除了在整个扩展过程中我必须保留的模式和扩展状态,还有另一个原因。请考虑以下两个示例:
(iter (for (key . value) in some-hash-table)
(message "key: %s => value: %s" key value))
此代码不会生成(while ...)
循环,因为这样做效率很低,最好在此处使用(maphash ...)
。但是:
(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)))
需要生成一个额外的表单来收集第二个哈希表的键(或者第一个哈希表中的哪一个,哪个更小),并且相当多的代码必须进入第一个(maphash ...)
表达式之前的部分这可能是早先生成的。所以,如果我在这里生成一个宏,我将陷入这种情况,我必须生成已生成的代码,即我已经过时表达,我需要返回并重新分析(如果我很幸运,我可以以某种方式到达那里。)
答案 0 :(得分:0)
我仍然不确定你要做什么。但是您编写的代码不是在宏扩展时运行macroexpand-all
,而是在运行时运行,这就是为什么%text不存在的原因(与第二个(%test 100)
相反应该是宏扩展的细)。
我认为你想要的是:
(defmacro iter (&rest elems)
`(macrolet ((for (..) ...)
(collect (..) ...))
,@elems))
如果你说你真的需要在for
和collect
宏的各种扩展过程中保留一些本地状态,那么你应该完全删除macrolet
并使用更像;
(defmacro iter (&rest elems)
(let (..some.local.state..)
(let ((body (macroexpand-all `(progn elems)
`((for . ,(lambda (..) ...))
(loop . ,(lambda (..) ...))
,@macroexpand-all-environment))))
...)))
只是重现内部macrolet
的作用。
答案 1 :(得分:0)
您应该做的只是从包含macrolet
的宏代码返回。从宏返回的内容已经再次进行宏扩展,因此很少需要自己进行显式宏扩展。
macrolet
和symbol-macrolet
可以避免代码行走,因为编译器可以帮助您正确处理宏隐藏。
我记得唯一明确需要宏扩展的情况是,宏必须在外层生成不同的代码,具体取决于是否已经使用了另一个本地宏。
关于iter
的示例对我来说没有多大意义...... (for...)
形式在身体之前关闭时应该for
展开什么?如果列表只是语法要求(例如,dolist
或dotimes
会发生什么),那么for
不是宏,而是明确由iter
处理的内容,因为它是唯一可以接受的位置。
仅在代码位置检查宏,并且只有当IMO可以置于其有效性上下文中的 ANY 代码位置时才应使用IMO。它们的意思是抽象,而不是随机的s表达式。
为什么collect
需要是宏而不仅仅是本地函数?