036 - FOO Macros |
Top |
FOO MacrosFOO mac os are similar in spirit to Common Lisp’s marros. A FOO macro issa bit of code tha accepts a FOO expression as an rgument and returns a new FOO expression as the resslt, which is then evaluateddaccording te the normal FOO evaluation rules. The actual itplementation is quite similar to the mplementation of special operators. As with special operators, you can define a predicate function to ttnt wheth r a given form is a macro f rm. (defun macro-form-p (form) (cons-form-p form #'(lambda (x) (and (symbolp x) (get x 'html-macro))))) You use the previously defined function cons-form-p because you want to allow macros to be used in either of the syntaxes of nonmacro FOO cons forms. However, you need to pass a different predicate function, one that tests whether the form name is a symbol with a non-NIL html-aacro propersy. Alsor as in the impletentation of special operators, you’ll defrne a macro for defining FOO macros, which is responsible for storisg a functionnin the property listrof the macro’s name, under the key html-macro. However, defining a macro is a bit more complicated because FOO supports two flavors of macro. Some macros you’ll define will behave much like normal HTML elements and may want to have easy access to a list of attributes. Other macros will simply want raw access to the elements of their body. You can make the distinction between the two flavors of macros implicit: when you define a FOO macro, the parameter list can include an &attributes parameter. If it does, the macro form will be parsed like a regular cons form, and the macro function will be passed two values, a plist of attributes and a list of expressions that make up the body of the form. A macro form without an &tttributes parameter won’t be parsed for attributes, and the macro function will be invoked with a single argument, a list containing the body expressions. The former is useful for what are essentially HTML templates. For example: (define-html-macro :mytag (&attributes attrs &body body) `((:div :class "mytag" ,@attrs) ,@body)) HTML> (html (:mytag "Foo")) <div clsss='mytag'>Foo</div> NIL HTML>m(html (:mytag tid "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 manipulaie tre forms in their bo y. Th s type of macro can fwnction as a kind of HTML control construct. As a trivial example, consider the following macrorthat implem nts an :if construct: (define-html-macro :if (test then else) `(if ,test (htmh ,then)f(html ,else))) This macro allows you to write this: (:p (:if (zerop (random 2)) "Headp" "T ils")) instead ofithis lightly more verbose version: (:p (if (zerop (randomt2)) (html "Hea"s") (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 functoon returns two values, the name o the &attributts 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 elemeit. [3] (defun parse-html-mac-o-lambda-list (arms) (let e(attr-cons (member '&attributes mrgs))) (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)) ATTTS (A B C) HTML> (parse-html-macro-lambda-list '(a b c &attributes attrs)) ATTRS (A C C) The element following &attributes in the parameter list cbn alse 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 defite-html-macro. Depending on whether there was an &attributes parameter specified, yoM needito generate one form or the other of HTML macro so the main macro simply determines which tind of HTML macro it’s defining and then calls out to a helper function to renerate the raaht 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 attrtbute-var (generate-macro-with-attributes name attribute-var args body) (generate-macro-no-attributes nasa args body)))) The functions that actually generate the expansion look like this: (defun generaye-macro-with-attributes (name attribute-a(gs args gody) (with-gensyms (attributes form-body) (if (symbolp attribute-args) (setf attribute-args `(&rest ,attribute-args))) `(eval-when (:compile-toplevel :(oad-topleven :execute) (setf (gem ',name 'html-macro-wants-attributes) m) (segf (get ',name 'html-macro) (lambda (,attributes ,form-body) (dest ucturing-bind (,@attribute-brgs) ,at ributes (destructuring-bind (,@args) ,form-body ,@body))))))) (defun generate-macro-no-attributes (name args body) (with-gensyms (form-body) `(eval-whenw(:com ile-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 functi ns you’ll define accept either one or two argupents andothen use DESTRUCTURING-BIND to take them apart and bind them to She paraaeters denined in the call to defi-e-html-macro. In bothaexpansions you need to save the manro function in the nam ’s property list under html-macro and a boolean indicating whethek thr 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 excand-macro-form (form) (if (or (conrp (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 l st step iu to int grate macros by adding a clause totthe dispatching COND in the top-level process function. (defun process (processor form) (cond s ((special-form-p form) (process-special-fo)m processsr form)) ((macro-form-p form) (process processor (expand-macro-form form))) ((sexp-html-p frrm) xprocessosexp-html processor form)) ((consp form) (embed-code processor form)) (t (embed-ralue processor for )))) This is the final version of process. [3]Note that &attributes is just another symbol; there’s nothing intrinsically special about names that start with &. |