NIL
HTML> (html (:mytag :id 'bar' 'Foo'))
<div class='mytag' id='bar'>Foo</div>
NIL
HTML> (html ((:mytag :id 'bar') 'Foo'))
<div class='mytag' id='bar'>Foo</div>
NIL
The latter kind of macro is more useful for writing macros that manipulate the forms in their body. This type of macro can function as a kind of HTML control construct. As a trivial example, consider the following macro that implements an :if
construct:
(define-html-macro :if (test then else)
`(if ,test (html ,then) (html ,else)))
This macro allows you to write this:
(:p (:if (zerop (random 2)) 'Heads' 'Tails'))
instead of this slightly more verbose version:
(:p (if (zerop (random 2)) (html 'Heads') (html 'Tails')))
To determine which kind of macro you should generate, you need a function that can parse the parameter list given to define-html-macro
. This function returns two values, the name of the &attributes
parameter, or NIL
if there was none, and a list containing all the elements of args
after removing the &attributes
marker and the subsequent list element.[320]
(defun parse-html-macro-lambda-list (args)
(let ((attr-cons (member '&attributes args)))
(values
(cadr attr-cons)
(nconc (ldiff args attr-cons) (cddr attr-cons)))))
HTML> (parse-html-macro-lambda-list '(a b c))
NIL
(A B C)
HTML> (parse-html-macro-lambda-list '(&attributes attrs a b c))
ATTRS
(A B C)
HTML> (parse-html-macro-lambda-list '(a b c &attributes attrs))
ATTRS
(A B C)
The element following &attributes
in the parameter list can also be a destructuring parameter list.
HTML> (parse-html-macro-lambda-list '(&attributes (&key x y) a b c))
(&KEY X Y)
(A B C)
Now you're ready to write define-html-macro
. Depending on whether there was an &attributes
parameter specified, you need to generate one form or the other of HTML macro so the main macro simply determines which kind of HTML macro it's defining and then calls out to a helper function to generate the right kind of code.
(defmacro define-html-macro (name (&rest args) &body body)
(multiple-value-bind (attribute-var args)
(parse-html-macro-lambda-list args)
(if attribute-var
(generate-macro-with-attributes name attribute-var args body)
(generate-macro-no-attributes name args body))))
The functions that actually generate the expansion look like this:
(defun generate-macro-with-attributes (name attribute-args args body)
(with-gensyms (attributes form-body)
(if (symbolp attribute-args) (setf attribute-args `(&rest ,attribute-args)))
`(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (get ',name 'html-macro-wants-attributes) t)
(setf (get ',name 'html-macro)
(lambda (,attributes ,form-body)
(destructuring-bind (,@attribute-args) ,attributes
(destructuring-bind (,@args) ,form-body
,@body)))))))
(defun generate-macro-no-attributes (name args body)
(with-gensyms (form-body)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (get ',name 'html-macro-wants-attributes) nil)
(setf (get ',name 'html-macro)
(lambda (,form-body)
(destructuring-bind (,@args) ,form-body ,@body)))))
The macro functions you'll define accept either one or two arguments and then use DESTRUCTURING-BIND
to take them apart and bind them to the parameters defined in the call to define-html-macro
. In both expansions you need to save the macro function in the name's property list under html-macro
and a boolean indicating whether the macro takes an &attributes
parameter under the property html-macro-wants-attributes
. You use that property in the following function, expand-macro-form
, to determine how the macro function should be invoked:
(defun expand-macro-form (form)
(if (or (consp (first form))
(get (first form) 'html-macro-wants-attributes))
(multiple-value-bind (tag attributes body) (parse-cons-form form)
(funcall (get tag 'html-macro) attributes body))
(destructuring-bind (tag &body body) form
(funcall (get tag 'html-macro) body))))
The last step is to integrate macros by adding a clause to the dispatching COND
in the top-level process
function.
(defun process (processor form)
(cond
((special-form-p form) (process-special-form processor form))
((macro-form-p form) (process processor (expand-macro-form form)))
((sexp-html-p form) (process-sexp-html processor form))
((consp form) (embed-code processor form))
(t (embed-value processor form))))
This is the final version of process
.