Книга: Practical Common Lisp

The Public API

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.[321]

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 MACROLETs, 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.

Оглавление книги

Оглавление статьи/книги

Генерация: 1.087. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз