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.
The End of the Line
As usual, you could keep working with this code to enhance it in various ways. One interesting avenue to pursue is to use the underlying output generation framework to emit other kinds of output. In the version of FOO you can download from the book's Web site, you'll find some code that implements CSS output that can be integrated into HTML output in both the interpreter and compiler. That's an interesting case because CSS's syntax can't be mapped to s-expressions in such a trivial way as HTML's can. However, if you look at that code, you'll see it's still possible to define an s-expression syntax for representing the various constructs available in CSS.
A more ambitious undertaking would be to add support for generating embedded JavaScript. Done right, adding JavaScript support to FOO could yield two big wins. One is that after you define an s-expression syntax that you can map to JavaScript syntax, then you can start writing macros, in Common Lisp, to add new constructs to the language you use to write client-side code, which will then be compiled to JavaScript. The other is that, as part of the FOO s-expression JavaScript to regular JavaScript translation, you could deal with the subtle but annoying differences between JavaScript implementations in different browsers. That is, the JavaScript code that FOO generates could either contain the appropriate conditional code to do one thing in one browser and another in a different browser or could generate different code depending on which browser you wanted to support. Then if you use FOO in dynamically generated pages, it could use information about the User-Agent making the request to generate the right flavor of JavaScript for that browser.
But if that interests you, you'll have to implement it yourself since this is the end of the last practical chapter of this book. In the next chapter I'll wrap things up, discussing briefly some topics that I haven't touched on elsewhere in the book such as how to find libraries, how to optimize Common Lisp code, and how to deliver Lisp applications.
32. Conclusion: What's Next?
I hope by now you're convinced that the title of this book isn't an oxymoron. However, it's quite likely there's some area of programming that's of great practical importance to you that I haven't discussed at all. For instance, I haven't said anything about how to develop graphical user interfaces (GUIs), how to connect to relational databases, how to parse XML, or how to write programs that act as clients for various network protocols. Similarly, I haven't discussed two topics that will become important when you write real applications in Common Lisp: optimizing your Lisp code and packaging your application for delivery.
I'm obviously not going to cover all these topics in depth in this final chapter. Instead, I'll give you a few pointers you can use to pursue whichever aspect of Lisp programming interests you most.