Выбрать главу

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.

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.