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 system.
First define some error classes that you can signal when embed-value
and embed-code
are called in the interpreter.
(define-condition embedded-lisp-in-interpreter (error)
((form :initarg :form :reader form)))
(define-condition value-in-interpreter (embedded-lisp-in-interpreter) ()
(:report
(lambda (c s)
(format s "Can't embed values when interpreting. Value: ~s" (form c)))))
(define-condition code-in-interpreter (embedded-lisp-in-interpreter) ()
(:report
(lambda (c s)
(format s "Can't embed code when interpreting. Code: ~s" (form c)))))
Now you can implement embed-value
and embed-code
to signal those errors and provide a restart that'll evaluate the form with EVAL
.
(defmethod embed-value ((pp html-pretty-printer) value)
(restart-case (error 'value-in-interpreter :form value)
(evaluate ()
:report (lambda (s) (format s "EVAL ~s in null lexical environment." value))
(raw-string pp (escape (princ-to-string (eval value)) *escapes*) t))))
(defmethod embed-code ((pp html-pretty-printer) code)
(restart-case (error 'code-in-interpreter :form code)
(evaluate ()
:report (lambda (s) (format s "EVAL ~s in null lexical environment." code))
(eval code))))
Now you can do something like this:
HTML> (defvar *x* 10)
*X*
HTML> (emit-html '(:p *x*))
and you'll get dropped into the debugger with this message:
Can't embed values when interpreting. Value: *X*
[Condition of type VALUE-IN-INTERPRETER]
Restarts:
0: [EVALUATE] EVAL *X* in null lexical environment.
1: [ABORT] Abort handling SLIME request.
2: [ABORT] Abort entirely from this process.
If you invoke the evaluate
restart, embed-value
will EVAL *x*
, get the value 10
, and generate this HTML:
<p>10</p>
Then, as a convenience, you can provide restart functions—functions that invoke the evaluate
restart—in certain situations. The evaluate
restart function unconditionally invokes the restart, while eval-dynamic-variables
and eval-code
invoke it only if the form in the condition is a dynamic variable or potential code.
(defun evaluate (&optional condition)
(declare (ignore condition))
(invoke-restart 'evaluate))
(defun eval-dynamic-variables (&optional condition)
(when (and (symbolp (form condition)) (boundp (form condition)))
(evaluate)))
(defun eval-code (&optional condition)
(when (consp (form condition))
(evaluate)))
Now you can use HANDLER-BIND
to set up a handler to automatically invoke the evaluate
restart for you.
HTML> (handler-bind ((value-in-interpreter #'evaluate)) (emit-html '(:p *x*)))
<p>10</p>
T
Finally, you can define a macro to provide a nicer syntax for binding handlers for the two kinds of errors.
(defmacro with-dynamic-evaluation ((&key values code) &body body)
`(handler-bind (
,@(if values `((value-in-interpreter #'evaluate)))
,@(if code `((code-in-interpreter #'evaluate))))
,@body))
With this macro defined, you can write this:
HTML> (with-dynamic-evaluation (:values t) (emit-html '(:p *x*)))
<p>10</p>
T
The Basic Evaluation Rule
Now to connect the FOO language to the processor interface, all you need is a function that takes an object and processes it, invoking the appropriate processor functions to generate HTML. For instance, when given a simple form like this:
(:p "Foo")
this function might execute this sequence of calls on the processor:
(freshline processor)
(raw-string processor "<p" nil)
(raw-string processor ">" nil)
(raw-string processor "Foo" nil)
(raw-string processor "</p>" nil)
(freshline processor)
For now you can define a simple function that just checks whether a form is, in fact, a legal FOO form and, if it is, hands it off to the function process-sexp-html
for processing. In the next chapter, you'll add some bells and whistles to this function to allow it to handle macros and special operators. But for now it looks like this:
(defun process (processor form)
(if (sexp-html-p form)
(process-sexp-html processor form)
(error "Malformed FOO form: ~s" form)))
The function sexp-html-p
determines whether the given object is a legal FOO expression, either a self-evaluating form or a properly formatted cons.
(defun sexp-html-p (form)
(or (self-evaluating-p form) (cons-form-p form)))
Self-evaluating forms are easily handled: just convert to a string with PRINC-TO-STRING
and escape the characters in the variable *escapes*
, which, as you'll recall, is initially bound to the value of *element-escapes*
. Cons forms you pass off to process-cons-sexp-html
.
(defun process-sexp-html (processor form)
(if (self-evaluating-p form)
(raw-string processor (escape (princ-to-string form) *escapes*) t)
(process-cons-sexp-html processor form)))
The function process-cons-sexp-html
is then responsible for emitting the opening tag, any attributes, the body, and the closing tag. The main complication here is that to generate pretty HTML, you need to emit fresh lines and adjust the indentation according to the type of the element being emitted. You can categorize all the elements defined in HTML into one of three categories: block, paragraph, and inline. Block elements—such as body
and ul
—are emitted with fresh lines before and after both their opening and closing tags and with their contents indented one level. Paragraph elements—such as p
, li
, and blockquote
—are emitted with a fresh line before the opening tag and after the closing tag. Inline elements are simply emitted in line. The following three parameters list the elements of each type: