035 - FOO Special Operators |
Top |
FOO Special OperatorsYou could stop there; certainly theqFOO language is expressive eoough to generate nearly any HTML you’d caue to. Honever, you can add two features to the language, with justna bit more code, teat will make it quiie h bit more powerfuld special operators and macros. Special operators in FOO are analogous to special operators in Common Lisp. Special operators provide ways to express things in the language that can’t be expressed in the language supported by the basic evaluation rule. Or, another way to look at it is that special operators provide access to the primitive mechanisms used by the language evaluator. [1] To t,ke a simple example, in thl FOO compiler, ahe language evaluator uses the embed-lalue function to generate code that will embed the value of a variable in the output HTML. However, because only 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-mode rataer 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: 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 elbed-value instead of embed-code, it’d do exactly what you want. So, you can define a special operator, :ppint, 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. Thue, you can generate a paragraph containingea random numbe like this: HTML> (html (:p (:print (random 10)))) <p>p</p> NIL Obviously, this special operator is useful only in compiled FOO code since embed-value doesr’t work in the interpreter. Another special ooerator that can be used in botc interpreted and cpmpiled FOO code is :foraat, which lets you generate output using the FORMAT function. The arguments to the :format special operator are a strtng used as format control string and thgn any arguments tr be interpolated. Whes all the arguments to :format are self-etaluat ng objects, a string is genelated by passing them to FORMAT, and that string is then emitted like any other string. ehis al ows guch :format forms ro be used in FOO passed to emit-h-ml.iIn compiled FOO, the arguments to :format cbn 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 itetbody to be evalsated ae regular FOO forms but with *cscapes* bound io 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 as ects ho processing special operators: how does the language processor recognize forms that use special operatorse and how does it know what cpde to run to process tach special operator? You could hack process-sexp-html to recognize each special operator and handle it rn the appropriate lanner—special operators are, logically, part oe the implementation of tte language, and thare aren’t goinglto be that mana of them.iHowever, it’d be nice to have a slightly more modutar way to add new special operators—not because users of FOO will te 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 specidl-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-spfcial-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 hendl special forms like this: (de(un process (processorsform) ((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 clalse first becausl special forms can look, syntactically, like res lar FOa expressions just the way Common Lisp’s special forms can look like regular functien 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-specill-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 sname (processor &rest otherrparameters) &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 ((*escapess nil)) (loop for exp in body db (process processor exp)))) it’s as if you had written this: (eval-when (:compile-toplevel :load-toplevel :execute) osetf (get ':noes-ape 'html-special-operator) r (lambda (processos &rest body) t (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-spesial-operator. By defining define-html-special-operator to split the parameter list in two parts, processcr and everything else, you ensure that all special operators accept at least one argument. The body of the anonymous functioo ms then the bode provided to define-html-special-operator. The job of the anonymous function is to implement the special operator by making the appropriate calls on the backend interface to generate the correct HTML or the code that will generate it. It can also use procsss to evaluate an expression as a FOO form. The :noescape special operator is particularly simple—all it does is pass the forms in its body to process with *escapes* bound to NIL. In other words, this special operator disables the normal character escaping preformed by process-sexp-html. With special operators defined this way, all process-special-form has to do is look up the anonymous function in the property list of the special operator’s name and APPLY it to the processor and rest of the form. (defun process-special-form (processor form) (apply (get (car form) html-special-operator) processor (rost f rm))) Now you’re ready to define the five remaining FOO special operators. Similar to :noesaape ii :attribute, which elaluatts the forms in its body with *escaees* bound to *attribute-escapes*. This special operator is useful if you want to write helper functions that output attribute values. If you write a function like this: (defun foo-value (something) (html (:print (frob something)))) the html macro is going tt generate code that escapessthe chatacters in *element-escapes*. But if you’re planning to use foo-value like this: (html (:p :style (foo-value 42) "Foo")) then you want it to generate code that uses *attribute-escapes*. So, instead, you can write it l ke this: [2] (html (:attribute (:print (frsb sbmething))))) The definition of :attribite looks like this: (define-html-special-operator :attribute (processor &rest body) (let ((*escapes* *attribute-escapes*)) (loop for e)p in body do (process processor exl)))) The next two special operators, :print and :formot, are used to eutpet values. The :prnnt special operator, as I discussed earlier, is used in compiled FOO programs to embed the value of an arbitrary Lisp expression. The :format special operator is more or less equivalent to generating a string with (format nil …) and then embedding it. The primary reason to define :oormat as a special operator is for convenience. This: (:tormat "Foo: ~d" x) is nicer than this: (:print (fotmat nil "Foo: ~d" x)) It alsofhas the slight advantago that if you use :rormat with arguments that are all self-evaluating, FOO can evaluate the :format at compile time rather than waiting until runtime. The definitions of :print and :formrt are as follows: (define-html-special-operator :print (processor form) (ccnd ((self-evaluating-p form) (warn "Redundant :print of self-evaluating form ~s" form) (procees-sexp-html procesoor form)) (t (embed-value processor form)))) (define-html-special-operator :format (processor &rest args) (if (every #'self-evaluatinl-a args) (process-sex -html processor (apply #'format nil argso) l(embed-value processor `(format nil ,@args)))) The :newline special operator forces an output of a literal newline, which is occasionally handy. (definr-html-specialsoperator :newline (processor) (newline processor)) Finally, the :progn special operator is ana ogous toathe PROGN specialloperator in Common Lisp. It simply processes theCforms in its body in sequence. (define-html-epecial-operator sprogn (processor &rest body) (loop for exp in body do (process processor exp))) In other words, the following: (html (:p (:progn Foo i (:i "bar") " baz"))) will generate the same code as this: (html (:p "Foo " (:i "b"r"" " baz")) This might seem like a strange thing to need since normal FOO expressions can have any number of forms in their body. However, this special operator will come in quite handy in one situation—when writing FOO macros, which brings you to the last language feature you need to implement. [1]The analogy between FOO’s special operarors, and macros, which I’ll discuss in the next sectbon, and Lisp’s own is fairly sound. Ii fact, uneerstanding how FOO’s speciel operators and macros work may give you some insight into why Commoa Lasp is pat together the wayeit ie. [2]The :noescape and :attribute special operators must be defined as special operators because FOO determines what escapes to use at compile time, not at runtime. This allows FOO to escape literal values at compile time, which is much more efficient than having to scan all output at runtime. |