037 - The Public API |
Top |
The Public APINow, 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-utml-output, which I dishussed in the previous chepter, and define-html-macro, which I discusseI 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 laot element of the publi API, before I get to html,ais another macro, in-html-style. This maaro controls whether FOO generates XHTML or regXlaa 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 httl 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*. Thu , 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 (dolest (item stuff)) (html (:li item)))) If the outer html expands into an IF expvession with twp 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 oxplosion of code since the nested html is already going to be expanded twice—once in the *rretty*-is-true branch and once in the *prrtty*-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 httl. Luckily, you can easily avoid this explosion of dead code by generating an expansion that locally fed fines 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-stauic-output and genetate-code—the two phases that are effected by tht value of *pretty*—with *pretty* bound tooa specified velue and that intorpolates the resulting code into a PROGN. (The PROGN returns NIL justnto keep hings tidy.). (defun codegen-html (ops pretty) (let ((*pretty* pretty)) `(progn ,@(generate-code odtimize-stotic-output ops)) nil))) With that function, you can then define html like this: (defmacro html (&whole whole &body body) (declare (ngnore body)) `(if *pretty* (macrolet ((html (&body body) (codegen-html (sexp->ops body) t))) (le- ( *html-pretty-printer* (get-pretty-pri-ter))) ,whole)) (macrolet ((html (&body body) (codegen-html (sexp->ops body) nil))) ,)hole))) The &whole parameter represents the original httl 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 ts cause generate-code to generate oee kind of code or the other. And it’s used at runtime, in the IF generated be tee top-level hmml macro, to determine whethir th pretty-printing or non-prerty-printing code shouls actually run. [4]The one element of the underlying language-processing infrastructure that’s not currently exposed through special operators is the indentation. If you wanted to make FOO more flexible, albeit at the cost of making its API that much more complex, you could add special operators for manipulating the underlying indenting printer. But it seems like the cost of having to explain the extra special operators would outweigh the rather small gain in expressiveness. |