(defun emit/no-newlines (ip string &key (start 0) end)
(indent-if-necessary ip)
(write-sequence string (out ip) :start start :end end)
(unless (zerop (- (or end (length string)) start))
(setf (beginning-of-line-p ip) nil)))
The helper indent-if-necessary
checks beginning-of-line-p
and indenting-p
to determine whether it needs to emit indentation and, if they're both true, emits as many spaces as indicated by the value of indentation
. Code that uses the indenting-printer
can control the indentation by manipulating the indentation
and indenting-p
slots. Incrementing and decrementing indentation
changes the number of leading spaces, while setting indenting-p
to NIL
can temporarily turn off indentation.
(defun indent-if-necessary (ip)
(when (and (beginning-of-line-p ip) (indenting-p ip))
(loop repeat (indentation ip) do (write-char #\Space (out ip)))
(setf (beginning-of-line-p ip) nil)))
The last two functions in the indenting-printer
API are emit-newline
and emit-freshline
, which are both used to emit a newline character, similar to the ~%
and ~& FORMAT
directives. That is, the only difference is that emit-newline
always emits a newline, while emit-freshline
does so only if beginning-of-line-p
is false. Thus, multiple calls to emit-freshline
without any intervening emit
s won't result in a blank line. This is handy when one piece of code wants to generate some output that should end with a newline while another piece of code wants to generate some output that should start on a newline but you don't want a blank line between the two bits of output.
(defun emit-newline (ip)
(write-char #\Newline (out ip))
(setf (beginning-of-line-p ip) t))
(defun emit-freshline (ip)
(unless (beginning-of-line-p ip) (emit-newline ip)))
With those preliminaries out of the way, you're ready to get to the guts of the FOO processor.
HTML Processor Interface
Now you're ready to define the interface that'll be used by the FOO language processor to emit HTML. You can define this interface as a set of generic functions because you'll need two implementations—one that actually emits HTML and another that the html
macro can use to collect a list of actions that need to be performed, which can then be optimized and compiled into code that emits the same output in a more efficient way. I'll call this set of generic functions the backend interface. It consists of the 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 |
вернуться
315 Another, more purely object-oriented, approach would be to define two classes, perhaps |