宏扩展后的未定义函数

时间:2016-01-30 13:57:15

标签: macros common-lisp cl-who

我正在学习Common Lisp并希望使用lisp和web开发。我目前的问题来自一个简单的想法,迭代我想要包括的所有JavaScript文件。我使用SBCL和Quicklisp进行快速启动。问题可能与我正在使用的cl-who包有关。

所以我宣布了我的包裹并开始这样:

(defpackage :0xcb0
  (:use :cl :cl-who :hunchentoot :parenscript))
(in-package :0xcb0)

为了简单起见,我减少了我的问题功能。所以我有page函数:

(defun page (test)
  (with-html-output-to-string
    (*standard-output* nil :prologue nil :indent t)
    (:script
     (:script :type "text/javascript" :href test))))

这将产生所需的输出

*(0xcb0::page "foo")

<script>
   <script type='text/javascript' href='foo'></script>
</script>

现在我已经创建了一个生成:script标签的宏。

(defmacro js-source-file (filename)
  `(:script :type "text/javascript" :href ,filename)))

这可以按预期工作:

*(macroexpand-1 '(0XCB0::js-source-file "foo"))

(:SCRIPT :TYPE "text/javascript" :HREF "foo")

但是,如果我将其包含在我的page函数中:

(defun page (test)
  (with-html-output-to-string
    (*standard-output* nil :prologue nil :indent t)
    (:script
     (js-source-file "foo"))))

...在定义新的undefined function: :SCRIPT函数时会给我一个样式警告(page)。此外,page函数在执行时会产生此错误:

*(0xcb0::page "foo")

The function :SCRIPT is undefined.
   [Condition of type UNDEFINED-FUNCTION]

为什么嵌入式宏js-source-file按预期工作,因为它会产生所需的输出,但在另一个函数中调用时会失败?

P.S。我知道Lisp中的宏主题对于像我这样的初学者来说可能会非常耗费精力。但是目前我无法理解这应该有效但事实并非如此!

2 个答案:

答案 0 :(得分:7)

问题在于,从最外层到最内层,宏直观地扩展了一点点。例如:

(defmacro foobar (quux)
  (format t "Foo: ~s~%" quux))

(defmacro do-twice (form)
  `(progn
     ,form
     ,form))

(foobar (do-twice (format t "qwerty")))

输出

Foo: (DO-TWICE (FORMAT T "qwerty"))

foobar永远不会看到do-twice的扩张。您可以通过macroexpand

自己致电foobar来避免此问题
(defmacro foobar (quux)
  (format t "Foo: ~s~%" (macroexpand quux)))

(foobar (do-twice (format t "qwerty")))
; => Foo: (PROGN (FORMAT T "qwerty") (FORMAT T "qwerty"))

由于您使用的是第三方宏,这可能不是一个好的解决方案。我认为最好的选择是在js-source-file中自己生成标记。我对[{1}}不熟悉,但这似乎在我的快速测试中起作用:

cl-who

答案 1 :(得分:4)

除了other good answer之外,我还将介绍with-html-output的具体情况。这来自 cl-who 手册的Syntax and Semantics部分。

首先请注意,如果您自己宏观扩展了调用,则可以看到with-html-output建立了macrolet绑定,例如strhtmftmesc ... htm宏不带任何参数(除了正文)并扩展为with-html-output形式,其具有相同的参数,具有词法封闭的with-html-output宏。 为了修复代码,您可以按如下方式修改宏:

(defmacro js-source-file (filename)
  `(htm (:script :type "text/javascript" :href ,filename)))

然后:

  1. with-html-output扩展为包含(js-source-file ...)
  2. 的树
  3. 您的宏已展开并生成(htm (:script ...))表单。
  4. 然后,扩展macrolet以生成内部(with-html-output ...)形式。
  5. 内部with-html-output已展开并处理(:script ...)
  6. 您必须选择是否要在此处使用宏或函数。函数通常不内联,可以在运行时轻松重新定义。宏在理论上也可以在运行时扩展,但在大多数实现(和默认配置)中,您必须重新编译取决于宏的任何函数。您也可以让宏调用辅助函数。