(defmacro html (html)
"A compiler for the literal HTML language."
`(write-sequence ,html *html-output*))
This "language" is highly expressive since it can express any HTML you could possibly want to generate.[311] On the other hand, this language doesn't win a lot of points for its concision because it gives you zero compression—its input is its output.
To design a language that gives you some useful compression without sacrificing too much expressiveness, you need to identify the details of the output that are either redundant or uninteresting. You can then make those aspects of the output implicit in the semantics of the language.
For instance, because of the structure of HTML, every opening tag is paired with a matching closing tag.[312] When you write HTML by hand, you have to write those closing tags, but you can improve the concision of your HTML-generating language by making the closing tags implicit.
Another way you can gain concision at a slight cost in expressiveness is to make the language processors responsible for adding appropriate whitespace between elements—blank lines and indentation. When you're generating HTML programmatically, you typically don't care much about which elements have line breaks before or after them or about whether different elements are indented relative to their parent elements. Letting the language processor insert whitespace according to some rule means you don't have to worry about it. As it turns out, FOO actually supports two modes—one that uses the minimum amount of whitespace, which allows it to generate extremely efficient code and compact HTML, and another that generates nicely formatted HTML with different elements indented and separated from other elements according to their role.
Another detail that's best moved into the language processor is the escaping of certain characters that have a special meaning in HTML such as <
, >
, and &
. Obviously, if you generate HTML by just printing strings to a stream, then it's up to you to replace any occurrences of those characters in the string with the appropriate escape sequences, <
, >
and &
. But if the language processor can know which strings are to be emitted as element data, then it can take care of automatically escaping those characters for you.
The FOO Language
So, enough theory. I'll give you a quick overview of the language implemented by FOO, and then you'll look at the implementation of the two FOO language processors—the interpreter, in this chapter, and the compiler, in the next.
Like Lisp itself, the basic syntax of the FOO language is defined in terms of forms made up of Lisp objects. The language defines how each legal FOO form is translated into HTML.
The simplest FOO forms are self-evaluating Lisp objects such as strings, numbers, and keyword symbols.[313] You'll need a function self-evaluating-p
that tests whether a given object is self-evaluating for FOO's purposes.
(defun self-evaluating-p (form)
(and (atom form) (if (symbolp form) (keywordp form) t)))
Objects that satisfy this predicate will be emitted by converting them to strings with PRINC-TO-STRING
and then escaping any reserved characters, such as <
, >
, or &
. When the value is being emitted as an attribute, the characters "
, and '
are also escaped. Thus, you can invoke the html
macro on a self-evaluating object to emit it to *html-output*
(which is initially bound to *STANDARD-OUTPUT*
). Table 30-1 shows how a few different self-evaluating values will be output.
Table 30-1. FOO Output for Self-Evaluating Objects
FOO Form | Generated HTML |
"foo" |
foo |
10 |
10 |
:foo |
FOO |
"foo & bar" |
foo & bar |
Of course, most HTML consists of tagged elements. The three pieces of information that describe each element are the tag, a set of attributes, and a body containing text and/or more HTML elements. Thus, you need a way to represent these three pieces of information as Lisp objects, preferably ones that the Lisp reader already knows how to read.[314] If you forget about attributes for a moment, there's an obvious mapping between Lisp lists and HTML elements: any HTML element can be represented by a list whose FIRST
is a symbol where the name is the name of the element's tag and whose REST
is a list of self-evaluating objects or lists representing other HTML elements. Thus:
<p>Foo</p> <==> (:p "Foo")
<p><i>Now</i> is the time</p> <==> (:p (:i "Now") " is the time")
Now the only problem is where to squeeze in the attributes. Since most elements have no attributes, it'd be nice if you could use the preceding syntax for elements without attributes. FOO provides two ways to notate elements with attributes. The first is to simply include the attributes in the list immediately following the symbol, alternating keyword symbols naming the attributes and objects representing the attribute value forms. The body of the element starts with the first item in the list that's in a position to be an attribute name and isn't a keyword symbol. Thus:
HTML> (html (:p "foo"))
<p>foo</p>
NIL
HTML> (html (:p "foo " (:i "bar") " baz"))
<p>foo <i>bar</i> baz</p>
NIL
HTML> (html (:p :style "foo" "Foo"))
<p style='foo'>Foo</p>
NIL
HTML> (html (:p :id "x" :style "foo" "Foo"))
<p id='x' style='foo'>Foo</p>
NIL
For folks who prefer a bit more obvious delineation between the element's attributes and its body, FOO supports an alternative syntax: if the first element of a list is itself a list with a keyword as its first element, then the outer list represents an HTML element with that keyword indicating the tag, with the REST
of the nested list as the attributes, and with the REST
of the outer list as the body. Thus, you could write the previous two expressions like this:
HTML> (html ((:p :style "foo") "Foo"))
<p style='foo'>Foo</p>
NIL
HTML> (html ((:p :id "x" :style "foo") "Foo"))
<p id='x' style='foo'>Foo</p>
NIL
The following function tests whether a given object matches either of these syntaxes:
(defun cons-form-p (form &optional (test #'keywordp))
(and (consp form)
(or (funcall test (car form))
(and (consp (car form)) (funcall test (caar form))))))
You should parameterize the test
function because later you'll need to test the same two syntaxes with a slightly different predicate on the name.
To completely abstract the differences between the two syntax variants, you can define a function, parse-cons-form
, that takes a form and parses it into three elements, the tag, the attributes plist, and the body list, returning them as multiple values. The code that actually evaluates cons forms will use this function and not have to worry about which syntax was used.
311
In fact, it's probably
312
Well, almost every tag. Certain tags such as IMG
and BR
don't. You'll deal with those in the section "The Basic Evaluation Rule."
313
In the strict language of the Common Lisp standard, keyword symbols aren't
314
The requirement to use objects that the Lisp reader knows how to read isn't a hard-and-fast one. Since the Lisp reader is itself customizable, you could also define a new reader-level syntax for a new kind of object. But that tends to be more trouble than it's worth.