在宏环境中强制扩展宏

时间:2012-11-23 14:47:47

标签: emacs macros lisp elisp

这是我正在尝试做的事情:

(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)))

在已定义宏forcollect的环境中进行扩展,但我不希望全局定义这些宏(显然,不是放入全局名称空间的最佳名称)。

我希望通过使用macrolet定义本地宏,我可以展开包含本地定义的宏的表单,然后返回扩展,但macroexpandmacroexpand-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 ...)表达式之前的部分这可能是早先生成的。所以,如果我在这里生成一个宏,我将陷入这种情况,我必须生成已生成的代码,即我已经过时表达,我需要返回并重新分析(如果我很幸运,我可以以某种方式到达那里。)

2 个答案:

答案 0 :(得分:0)

我仍然不确定你要做什么。但是您编写的代码不是在宏扩展时运行macroexpand-all,而是在运行时运行,这就是为什么%text不存在的原因(与第二个(%test 100)相反应该是宏扩展的细)。

我认为你想要的是:

(defmacro iter (&rest elems)
  `(macrolet ((for (..) ...)
              (collect (..) ...))
     ,@elems))

如果你说你真的需要在forcollect宏的各种扩展过程中保留一些本地状态,那么你应该完全删除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的宏代码返回。从宏返回的内容已经再次进行宏扩展,因此很少需要自己进行显式宏扩展。

在大多数情况下,

macroletsymbol-macrolet可以避免代码行走,因为编译器可以帮助您正确处理宏隐藏。

我记得唯一明确需要宏扩展的情况是,宏必须在外层生成不同的代码,具体取决于是否已经使用了另一个本地宏。

关于iter的示例对我来说没有多大意义...... (for...)形式在身体之前关闭时应该for展开什么?如果列表只是语法要求(例如,dolistdotimes会发生什么),那么for不是宏,而是明确由iter处理的内容,因为它是唯一可以接受的位置。

仅在代码位置检查宏,并且只有当IMO可以置于其有效性上下文中的 ANY 代码位置时才应使用IMO。它们的意思是抽象,而不是随机的s表达式。

为什么collect需要是宏而不仅仅是本地函数?