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 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.
(defun parse-cons-form (sexp)
(if (consp (first sexp))
(parse-explicit-attributes-sexp sexp)
(parse-implicit-attributes-sexp sexp)))
(defun parse-explicit-attributes-sexp (sexp)
(destructuring-bind ((tag &rest attributes) &body body) sexp
(values tag attributes body)))
(defun parse-implicit-attributes-sexp (sexp)
(loop with tag = (first sexp)
for rest on (rest sexp) by #'cddr
while (and (keywordp (first rest)) (second rest))
when (second rest)
collect (first rest) into attributes and
collect (second rest) into attributes
end
finally (return (values tag attributes rest))))
Now that you have the basic language specified, you can think about how you're actually going to implement the language processors. How do you get from a series of FOO forms to the desired HTML? As I mentioned previously, you'll be implementing two language processors for FOO: an interpreter that walks a tree of FOO forms and emits the corresponding HTML directly and a compiler that walks a tree and translates it into Common Lisp code that'll emit the same HTML. Both the interpreter and compiler will be built on top of a common foundation of code, which provides support for things such as escaping reserved characters and generating nicely indented output, so it makes sense to start there.
The first bit of the foundation you'll need to lay is the code that knows how to escape characters with a special meaning in HTML. There are three such characters, and they must not appear in the text of an element or in an attribute value; they are <
, >
, and &
. In element text or attribute values, these characters must be replaced with the <
, >
;, and &
. Similarly, in attribute values, the quotation marks used to delimit the value must be escaped, '
with '
and '
with "
. Additionally, any character can be represented by a numeric character reference entity consisting of an ampersand, followed by a sharp sign, followed by the numeric code as a base 10 integer, and followed by a semicolon. These numeric escapes are sometimes used to embed non-ASCII characters in HTML.
The Package |
Since FOO is a low-level library, the package you develop it in doesn't rely on much external code—just the usual dependency on names from the
|
The following function accepts a single character and returns a string containing a character reference entity for that character:
(defun escape-char (char)
(case char