symbols are passed to embed-value, there's no way, in the language I've described so far, to embed the value of an arbitrary Common Lisp expression; the process function passes cons cells to embed-code rather than embed-value, so the values returned are ignored. Typically this is what you'd want, since the main reason to embed Lisp code in a FOO program is to use Lisp control constructs. However, sometimes you'd like to embed computed values in the generated HTML. For example, you might like this FOO program to generate a paragraph tag containing a random number:

(:p (random 10))

But that doesn't work because the code is run and its value discarded.

HTML> (html (:p (random 10)))

<p></p>

NIL

In the language, as you've implemented it so far, you could work around this limitation by computing the value outside the call to html and then embedding it via a variable.

HTML> (let ((x (random 10))) (html (:p x)))

<p>1</p>

NIL

But that's sort of annoying, particularly when you consider that if you could arrange for the form (random 10) to be passed to embed-value instead of embed-code, it'd do exactly what you want. So, you can define a special operator, :print, that's processed by the FOO language processor according to a different rule than a normal FOO expression. Namely, instead of generating a <print> element, it passes the form in its body to embed-value. Thus, you can generate a paragraph containing a random number like this:

HTML> (html (:p (:print (random 10))))

<p>9</p>

NIL

Obviously, this special operator is useful only in compiled FOO code since embed-value doesn't work in the interpreter. Another special operator that can be used in both interpreted and compiled FOO code is :format, which lets you generate output using the FORMAT function. The arguments to the :format special operator are a string used as a format control string and then any arguments to be interpolated. When all the arguments to :format are self-evaluating objects, a string is generated by passing them to FORMAT, and that string is then emitted like any other string. This allows such :format forms to be used in FOO passed to emit-html. In compiled FOO, the arguments to :format can be any Lisp expressions.

Other special operators provide control over what characters are automatically escaped and to explicitly emit newline characters: the :noescape special operator causes all the forms in its body to be evaluated as regular FOO forms but with *escapes* bound to NIL, while :attribute evaluates the forms in its body with *escapes* bound to *attribute-escapes*. And :newline is translated into code to emit an explicit newline.

So, how do you define special operators? There are two aspects to processing special operators: how does the language processor recognize forms that use special operators, and how does it know what code to run to process each special operator?

You could hack process-sexp-html to recognize each special operator and handle it in the appropriate manner—special operators are, logically, part of the implementation of the language, and there aren't going to be that many of them. However, it'd be nice to have a slightly more modular way to add new special operators—not because users of FOO will be able to but just for your own sanity.

Define a special form as any list whose CAR is a symbol that's the name of a special operator. You can mark the names of special operators by adding a non- NIL value to the symbol's property list under the key html-special- operator. So, you can define a function that tests whether a given form is a special form like this:

(defun special-form-p (form)

(and (consp form) (symbolp (car form)) (get (car form) 'html-special-operator)))

The code that implements each special operator is responsible for taking apart the rest of the list however it sees fit and doing whatever the semantics of the special operator require. Assuming you'll also define a function process-special-form, which will take the language processor and a special form and run the appropriate code to generate a sequence of calls on the processor object, you can augment the top-level process function to handle special forms like this:

(defun process (processor form)

(cond

((special-form-p form) (process-special-form processor form))

((sexp-html-p form) (process-sexp-html processor form))

((consp form) (embed-code processor form))

(t (embed-value processor form))))

You must add the special-form-p clause first because special forms can look, syntactically, like regular FOO expressions just the way Common Lisp's special forms can look like regular function calls.

Now you just need to implement process-special-form. Rather than define a single monolithic function that implements all the special operators, you should define a macro that allows you to define special operators much like regular functions and that also takes care of adding the html-special-operator entry to the property list of the special operator's name. In fact, the value you store in the property list can be a function that implements the special operator. Here's the macro:

(defmacro define-html-special-operator (name (processor &rest other-parameters) &body body)

`(eval-when (:compile-toplevel :load-toplevel :execute)

(setf (get ',name 'html-special-operator)

(lambda (,processor ,@other-parameters) ,@body))))

This is a fairly advanced type of macro, but if you take it one line at a time, there's nothing all that tricky about it. To see how it works, take a simple use of the macro, the definition of the special operator :noescape, and look at the macro expansion. If you write this:

(define-html-special-operator :noescape (processor &rest body)

(let ((*escapes* nil))

(loop for exp in body do (process processor exp))))

it's as if you had written this:

(eval-when (:compile-toplevel :load-toplevel :execute)

(setf (get ':noescape 'html-special-operator)

(lambda (processor &rest body)

(let ((*escapes* nil))

(loop for exp in body do (process processor exp))))))

The EVAL-WHEN special operator, as I discussed in Chapter 20, ensures that the effects of code in its body will be made visible during compilation when you compile with COMPILE-FILE. This matters if you want to use define-html-special- operator in a file and then use the just-defined special operator in that same file.

Then the SETF expression sets the property html-special- operator on the symbol :noescape to an anonymous function with the same parameter list as was specified in define-html-special-operator. By defining define-html-special- operator to split the parameter list in two parts, processor and everything else, you ensure that all special operators accept at least one argument.

The body of the anonymous function is then the body provided to define-html-special- operator. The job of the anonymous function is to implement the special operator by making the

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату