following eight generic functions:

(defgeneric raw-string (processor string &optional newlines-p))

(defgeneric newline (processor))

(defgeneric freshline (processor))

(defgeneric indent (processor))

(defgeneric unindent (processor))

(defgeneric toggle-indenting (processor))

(defgeneric embed-value (processor value))

(defgeneric embed-code (processor code))

While several of these functions have obvious correspondence to indenting-printer functions, it's important to understand that these generic functions define the abstract operations that are used by the FOO language processors and won't always be implemented in terms of calls to the indenting-printer functions.

That said, perhaps the easiest way to understand the semantics of these abstract operations is to look at the concrete implementations of the methods specialized on html-pretty-printer, the class used to generate human-readable HTML.

The Pretty Printer Backend

You can start by defining a class with two slots—one to hold an instance of indenting-printer and one to hold the tab width—the number of spaces you want to increase the indentation for each level of nesting of HTML elements.

(defclass html-pretty-printer ()

((printer :accessor printer :initarg :printer)

(tab-width :accessor tab-width :initarg :tab-width :initform 2)))

Now you can implement methods specialized on html-pretty-printer on the eight generic functions that make up the backend interface.

The FOO processors use the raw-string function to emit strings that don't need character escaping, either because you actually want to emit normally reserved characters or because all reserved characters have already been escaped. Usually raw-string is invoked with strings that don't contain newlines, so the default behavior is to use emit/no-newlines unless the caller specifies a non-NIL newlines-p argument.

(defmethod raw-string ((pp html-pretty-printer) string &optional newlines-p)

(if newlines-p

(emit (printer pp) string)

(emit/no-newlines (printer pp) string)))

The functions newline, freshline, indent, unindent, and toggle-indenting implement fairly straightforward manipulations of the underlying indenting-printer. The only wrinkle is that the HTML pretty printer generates pretty output only when the dynamic variable *pretty* is true. When it's NIL, you should generate compact HTML with no unnecessary whitespace. So, these methods, with the exception of newline, all check *pretty* before doing anything:[315]

(defmethod newline ((pp html-pretty-printer))

(emit-newline (printer pp)))

(defmethod freshline ((pp html-pretty-printer))

(when *pretty* (emit-freshline (printer pp))))

(defmethod indent ((pp html-pretty-printer))

(when *pretty*

(incf (indentation (printer pp)) (tab-width pp))))

(defmethod unindent ((pp html-pretty-printer))

(when *pretty*

(decf (indentation (printer pp)) (tab-width pp))))

(defmethod toggle-indenting ((pp html-pretty-printer))

(when *pretty*

(with-slots (indenting-p) (printer pp)

(setf indenting-p (not indenting-p)))))

Finally, the functions embed-value and embed-code are used only by the FOO compiler—embed-value is used to generate code that'll emit the value of a Common Lisp expression, while embed-code is used to embed a bit of code to be run and its result discarded. In the interpreter, you can't meaningfully evaluate embedded Lisp code, so the methods on these functions always signal an error.

(defmethod embed-value ((pp html-pretty-printer) value)

(error 'Can't embed values when interpreting. Value: ~s' value))

(defmethod embed-code ((pp html-pretty-printer) code)

(error 'Can't embed code when interpreting. Code: ~s' code))

Using Conditions to Have Your Cake and Eat It Too

An alternate approach would be to use EVAL to evaluate Lisp expressions in the interpreter. The problem with this approach is that EVAL has no access to the lexical environment. Thus, there's no way to make something like this work:

(let ((x 10)) (emit-html '(:p x)))

when x is a lexical variable. The symbol x that's passed to emit- html at runtime has no particular connection to the lexical variable named with the same symbol. The Lisp compiler arranges for references to x in the code to refer to the variable, but after the code is compiled, there's no longer necessarily any association between the name x and that variable. This is the main reason that when you think EVAL is the solution to your problem, you're probably wrong.

However, if x was a dynamic variable, declared with DEFVAR or DEFPARAMETER (and likely named *x* instead of x), EVAL could get at its value. Thus, it might be useful to allow the FOO interpreter to use EVAL in some situations. But it's a bad idea to always use EVAL. You can get the best of both worlds by combining the idea of using EVAL with the condition

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

0

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

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