The Public API
Now, at long last, you’re ready to implement the html
macro, the main entry point to the FOO compiler. The other parts of FOO’s public API are emit-html
and with-html-output
, which I discussed in the previous chapter, and define-html-macro
, which I discussed in the previous section. The define-html-macro
macro needs to be part of the public API because FOO’s users will want to write their own HTML macros. On the other hand, define-html-special-operator
isn’t part of the public API because it requires too much knowledge of FOO’s internals to define a new special operator. And there should be very little that can’t be done using the existing language and special operators.4
One last element of the public API, before I get to html
, is another macro, in-html-style
. This macro controls whether FOO generates XHTML or regular HTML by setting the *xhtml*
variable. The reason this needs to be a macro is because you’ll want to wrap the code that sets *xhtml*
in an **EVAL-WHEN**
so you can set it in a file and have it affect uses of the html
macro later in that same file.
(defmacro in-html-style (syntax)
(eval-when (:compile-toplevel :load-toplevel :execute)
(case syntax
(:html (setf *xhtml* nil))
(:xhtml (setf *xhtml* t)))))
Finally let’s look at html
itself. The only tricky bit about implementing html
comes from the need to generate code that can be used to generate both pretty and compact output, depending on the runtime value of the variable *pretty*
. Thus, html
needs to generate an expansion that contains an **IF**
expression and two versions of the code, one compiled with *pretty*
bound to true and one compiled with it bound to **NIL**
. To further complicate matters, it’s common for one html
call to contain embedded calls to html
, like this:
(html (:ul (dolist (item stuff)) (html (:li item))))
If the outer html
expands into an **IF**
expression with two versions of the code, one for when *pretty*
is true and one for when it’s false, it’s silly for nested html
forms to expand into two versions too. In fact, it’ll lead to an exponential explosion of code since the nested html
is already going to be expanded twice—once in the *pretty*
-is-true branch and once in the *pretty*
-is-false branch. If each expansion generates two versions, then you’ll have four total versions. And if the nested html
form contained another nested html
form, you’d end up with eight versions of that code. If the compiler is smart, it’ll eventually realize that most of that generated code is dead and will eliminate it, but even figuring that out can take quite a bit of time, slowing down compilation of any function that uses nested calls to html
.
Luckily, you can easily avoid this explosion of dead code by generating an expansion that locally redefines the html
macro, using **MACROLET**
, to generate only the right kind of code. First you define a helper function that takes the vector of ops returned by sexp->ops
and runs it through optimize-static-output
and generate-code
--the two phases that are affected by the value of *pretty*
--with *pretty*
bound to a specified value and that interpolates the resulting code into a **PROGN**
. (The **PROGN**
returns **NIL**
just to keep things tidy.).
(defun codegen-html (ops pretty)
(let ((*pretty* pretty))
`(progn ,@(generate-code (optimize-static-output ops)) nil)))
With that function, you can then define html
like this:
(defmacro html (&whole whole &body body)
(declare (ignore body))
`(if *pretty*
(macrolet ((html (&body body) (codegen-html (sexp->ops body) t)))
(let ((*html-pretty-printer* (get-pretty-printer))) ,whole))
(macrolet ((html (&body body) (codegen-html (sexp->ops body) nil)))
,whole)))
The **&whole**
parameter represents the original html
form, and because it’s interpolated into the expansion in the bodies of the two **MACROLET**
s, it will be reprocessed with each of the new definitions of html
, the one that generates pretty-printing code and the other that generates non-pretty-printing code. Note that the variable *pretty*
is used both during macro expansion and when the resulting code is run. It’s used at macro expansion time by codegen-html
to cause generate-code
to generate one kind of code or the other. And it’s used at runtime, in the **IF**
generated by the top-level html
macro, to determine whether the pretty-printing or non-pretty-printing code should actually run.