Subject: Re: Keywords and CL-WHO
From: rpw3@rpw3.org (Rob Warnock)
Date: Thu, 21 Sep 2006 23:13:21 -0500
Newsgroups: comp.lang.lisp
Message-ID: <-q-dnQuGAbx8_47YnZ2dnUVZ_o-dnZ2d@speakeasy.net>
Ken Tilton  <kentilton@gmail.com> wrote:
+---------------
| Rob Warnock wrote:
| > But... but... it's the &BODY argument of a *macro*. Macro arguments
| > *aren't* evaluated!
| 
| Of course not. I was speaking loosely. The question is simply "Can i use 
| (intern "HTML" :keyword) instead of :html?"
+---------------

The answer, at least for CL-WHO, HTOUT, AllegroServe's HTML, and
maybe some others, is "No". [At least, not without reaching *deep*
under the covers and playing with the internals of the implementation.]

+---------------
| Here, try this:
| Does cl-who expand into code such that a given argument gets evaluated, 
| or does it quote that argument in the expansion?
+---------------

Neither, really, or a mixture of both, depending on your viewpoint.
CL-WHO is a *compiler* from "HTML-designating forms" into "HTML-emitting
Common Lisp code". That is, the &BODY argument of WITH-HTML-OUTPUT
expands into pure "inline" Lisp code, primarily WRITE-STRINGs with
constant [well, computed at macroexpansion time] strings as arguments.
A form such as (:FOO "some text") gets compiled into:

    (write-string "<foo>some text</foo>")

while ((:FOO :ATTR1 "val1" :ATTR2 "val2") "some more text") gets
compiled into:

    (write-string "<foo attr1='val1' attr2='val2'>some more text</foo>")

and (:FOO subforms... ) gets compiled into:

    (progn (write-string "<foo>")
	   ,@(translation-of subforms...)
	   (write-string "</foo>"))

Moreover, CL-WHO tries very hard to aggregate all adjacent output
constant strings into as few strings as possible, e.g.:

    > (macroexpand
       '(cl-who:with-html-output (*standard-output* nil :prologue nil)
          (:html (:head (:title "A small test page"))
		 (:body (:h1 "Test Page")
			"This is only a test."))))

    (LET ((*STANDARD-OUTPUT* *STANDARD-OUTPUT*))
      (PROGN
       NIL
       (WRITE-STRING
	"<html><head><title>A small test page</title></head><body><h1>Test Page</h1>This is only a test.</body></html>"
	*STANDARD-OUTPUT*)))
    T
    >

See? The *entire* "keyword tree" is gone, replaced by a single
constant string.

Obviously, when one allows Lisp code into the template, the same
degree of collapsing can't be done, but it tries as best it can:

    > (macroexpand
       '(cl-who:with-html-output (*standard-output* nil :prologue nil)
          (:html
	    (:head (:title "A small test page"))
	    (:body (:h1 "Test Page")
		   (loop for i from 1 to 5 do
		     (htm "This is test line #" (fmt "~d" i) (:br)))
		   "And this is more text after the LOOP."))))

    (LET ((*STANDARD-OUTPUT* *STANDARD-OUTPUT*))
      (PROGN
       NIL
       (WRITE-STRING
	"<html><head><title>A small test page</title></head><body><h1>Test Page</h1>"
	*STANDARD-OUTPUT*)
       (LOOP FOR I FROM 1 TO 5 DO
	 (PROGN
	  (WRITE-STRING "This is test line #" *STANDARD-OUTPUT*)
	  (FORMAT *STANDARD-OUTPUT* "~d" I)
	  (WRITE-STRING "<br />" *STANDARD-OUTPUT*)))
       (WRITE-STRING "And this is more text after the LOOP.</body></html>"
		     *STANDARD-OUTPUT*)))
    T
    >

Does this clarify the issue somewhat?  [If so, feel free to stop here.]

If not, I'll elaborate an example from my previous reply:

    > (macroexpand
       '(cl-who:with-html-output (*standard-output* nil :prologue nil)
          (:html
	    (:head (:title "A small test page"))
	    (:body
	      (:h1 "Test Page")
	      (:foo "A FOO with " (:bar "a sample BAR") " in it")))))

    (LET ((*STANDARD-OUTPUT* *STANDARD-OUTPUT*))
      (PROGN
       NIL
       (WRITE-STRING
	"<html><head><title>A small test page</title></head><body><h1>Test Page</h1><foo>A FOO with <bar>a sample BAR</bar> in it</foo></body></html>"
	*STANDARD-OUTPUT*)))
    T
    >

O.k.? Nothing special about ":FOO", it's just another keyword that
gets transformed into matching starting & ending tag strings.

But when you replace it with a *non*-keyword, then CL-WHO correctly
just passes it through [as it is documented to], on the assumption
that it is Lisp code that you want to execute:

    > (macroexpand
       '(cl-who:with-html-output (*standard-output* nil :prologue nil)
          (:html
	    (:head (:title "A small test page"))
	    (:body
	      (:h1 "Test Page")
	      ((intern "FOO" :keyword)
	       "A FOO with " (:bar "a sample BAR") " in it")))))

    (LET ((*STANDARD-OUTPUT* *STANDARD-OUTPUT*))
      (PROGN
       NIL
       (WRITE-STRING
	"<html><head><title>A small test page</title></head><body><h1>Test Page</h1>"
	*STANDARD-OUTPUT*)
        ((INTERN "FOO" :KEYWORD) "A FOO with " (:BAR "a sample BAR") " in it")
       (WRITE-STRING "</body></html>" *STANDARD-OUTPUT*)))
    T
    >

Now ask yourself what's going to happen when the CL system tries
to evaluate that ((INTERN "FOO" :KEYWORD) "A FOO with " ...) form.
*Ka-BLOOEY!*   [CMUCL calls it "Error: Illegal function call."]


-Rob

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607