宏扩展后的未定义函数

cb0*_*cb0 2 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)
Run Code Online (Sandbox Code Playgroud)

为了简单起见,我减少了我的问题功能.所以我有这个page功能:

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

这将产生所需的输出

*(0xcb0::page "foo")

<script>
   <script type='text/javascript' href='foo'></script>
</script>
Run Code Online (Sandbox Code Playgroud)

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

(defmacro js-source-file (filename)
  `(:script :type "text/javascript" :href ,filename)))
Run Code Online (Sandbox Code Playgroud)

这按预期工作:

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

(:SCRIPT :TYPE "text/javascript" :HREF "foo")
Run Code Online (Sandbox Code Playgroud)

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

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

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

*(0xcb0::page "foo")

The function :SCRIPT is undefined.
   [Condition of type UNDEFINED-FUNCTION]
Run Code Online (Sandbox Code Playgroud)

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

PS我知道Lisp中宏的主题对于像我这样的初学者来说可能会非常累人.但目前我无法理解这应该有效但事实并非如此!

jki*_*ski 7

问题是宏从最外层到最内层的位置直观地扩展了一点点.例如:

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

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

(foobar (do-twice (format t "qwerty")))
Run Code Online (Sandbox Code Playgroud)

输出将是

Foo: (DO-TWICE (FORMAT T "qwerty"))
Run Code Online (Sandbox Code Playgroud)

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"))
Run Code Online (Sandbox Code Playgroud)

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

(defun js-source-file (filename stream)
  (with-html-output (stream nil :prologue nil :indent t)
    (:script :type "text/javascript" :href filename))))

(defun page (test)
  (with-output-to-string (str)
    (with-html-output (str nil :prologue nil :indent t)
      (:script
       (js-source-file test str)))))
Run Code Online (Sandbox Code Playgroud)