I'm studying Common Lisp and want to play with lisp and web development. My current problem comes from a simple idea to iterate over all javascript files i want to include. I use SBCL and Quicklisp for fast startup. The problem could be related to the cl-who
package I'm using.
So I've declared my package and started like this:
(defpackage :0xcb0
(:use :cl :cl-who :hunchentoot :parenscript))
(in-package :0xcb0)
To keep it simple I reduced my problem function. So I have this page
function:
(defun page (test)
(with-html-output-to-string
(*standard-output* nil :prologue nil :indent t)
(:script
(:script :type "text/javascript" :href test))))
This will produce the desired output
*(0xcb0::page "foo")
<script>
<script type='text/javascript' href='foo'></script>
</script>
Now my I've created a macro which produces :script
tags.
(defmacro js-source-file (filename)
`(:script :type "text/javascript" :href ,filename)))
This works as expected:
*(macroexpand-1 '(0XCB0::js-source-file "foo"))
(:SCRIPT :TYPE "text/javascript" :HREF "foo")
However if I include this into my page
function:
(defun page (test)
(with-html-output-to-string
(*standard-output* nil :prologue nil :indent t)
(:script
(js-source-file "foo"))))
...it will give me a style warning (undefined function: :SCRIPT
) when defining the new page
function. Also, the page
function produces this error when executed:
*(0xcb0::page "foo")
The function :SCRIPT is undefined.
[Condition of type UNDEFINED-FUNCTION]
Why does the embedded macro js-source-file
works as expected, in that it produces the desired output, but fails when is called within another function?
P.S. I know the topic of macros in Lisp can be quite exhausting for a beginner like me. But currently I can't wrap my head around the fact that this should work but doesn't!
The problem is that macros are expanded a bit counter intuitively in order from outmost to inmost. For example:
(defmacro foobar (quux)
(format t "Foo: ~s~%" quux))
(defmacro do-twice (form)
`(progn
,form
,form))
(foobar (do-twice (format t "qwerty")))
The output will be
Foo: (DO-TWICE (FORMAT T "qwerty"))
foobar
never sees the expansion of do-twice
. You could avoid the problem by calling macroexpand
yourself in foobar
:
(defmacro foobar (quux)
(format t "Foo: ~s~%" (macroexpand quux)))
(foobar (do-twice (format t "qwerty")))
; => Foo: (PROGN (FORMAT T "qwerty") (FORMAT T "qwerty"))
Since you're using a third party macro, that probably isn't a good solution. I think the best option is to generate the markup yourself in js-source-file
. I'm not familiar with cl-who
, but this seemed to work in my quick test:
(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)))))