From: Vijay L
Subject: Re: doubts on a macro generating HTML
Date: 
Message-ID: <1eaf81aa.0212130537.58d028c1@posting.google.com>
Peter Seibel <·····@javamonkey.com> wrote in message news:<··············@localhost.localdomain>...
> ······@lycos.com (Vijay L) writes:
> 
[snip] [snip]
> > 
> > AFAIK there is one bug in my code: If OUTPUT-STREAM is NIL, the output
> > generated is incorrect, for eg.
> > 
> > (let ((stream nil))
> >   (output->html (head) stream
> >     (output->html (body) stream
> >       (format stream "Back in the U.S.S.R.<BR>Dear Prudence"))))
> > => "Back in the U.S.S.R.<BR>Dear Prudence"
> > It seems to work for all other streams. Can I assume that NIL does not
> > *really* constitute a stream and that my program's failure to produce
> > _the right output_ (ie the output I expect) is not *really* a failure?
> > If I *am* wrong, I'd greatly appreciate it if somebody could direct me
> > to the right reference.
> 
> Well, you're right about one thing--nil isn't really a stream:
> 
> CL-USER(1370): (streamp nil)
> NIL
> 
> Similarly:
> 
> CL-USER(1371): (streamp t)
> NIL
> 
> I got faked out by this recently too--read the documentation for
> FORMAT in the CLHS carefully, the destination argument is:
> 
>   destination -- nil, t, a stream, or a string with a fill pointer.
> 
> Which just means that FORMAT happens to do some special things if
> passed nil or t rather than a stream as described in Section 22.3
> Formatted Output. To wit: "If destination is nil, format creates and
> returns a string containing the output from control-string" and "If
> destination is t, the output is sent to standard output."
> 
> However, you have a problem, with your html-open function: when you
> call it with a nil output-stream, you're just generating a bunch of
> strings (one per call to FORMAT) and throwing them all away except the
> last one which is returned as the value of html-open. Note the
> difference in the output and return values of the following two calls:
> 
> HTML(1406): (html-open 'p nil nil nil)
> ">"
> HTML(1409): (html-open 'p t nil nil)
> <P>
> NIL
>
> You need to look in the CLHS for WITH-OUTPUT-TO-STRING.
I have, if you noticed, used WITH-OUTPUT-TO-STRING in one of my
examples above.

> > (2) My macro is still irritating to write in one aspect: I have to
> > specify OUTPUT-STREAM everytime. At first I thought of keeping a
> > special variable (say), *html-stream*, that would be set everytime I
> > was starting to write a html file. This would be specified with
> > another macro (say), START-HTML, like this:
> > (start-html output-stream
> >   (output->html ...))
> > While this is ok if my html file is going to write everything to only
> > one stream. I may (actually, I don't know that I will not) find myself
> > in some situation where I have to write to another html stream. Again,
> > this I can implement by treating the special variable as a stack, but
> > this seems ugly too.
> 
> Hmmm. I suspect you're using the word "special" in it's ordinary
> English sense. Because if you were using it in the Lisp sense of a
> variable with dynamic extent, etc. You'd be on the right track. If you
> DEFVAR'd *html-stream* it's a special variable and you can bind it
> with let and get your 'stack' for free.

No. I am using "special" in Lisp's special context. I have even used
Lisp's conventional naming style for my special variable. Kenny Tilton
has understood what I meant. What I meant by using wht special
variable as a stack was if the stream was nested, i would PUSH the
current OUTPUT-STREAM into the special variable (say) *HTML-STREAM*,
and expand the macro code into: (car *OP-STREAM*) where it now expands
into STREAM.

Here both you and Kenny say it is okay, therefore I shall use it.
Thanks for the tip.

 
> Anyway, here's how I might change your code (I took out the
> declarations because they were cluttering things up -- I suppose you
> can put them back in when you're closer to shipping. Or something.
> Anyway, I took 'em out.) 
> 
> Also, I'd consider renaming html-open and
> html-close to open-tag and close-tag since you can use the package
> prefix to get the HTML in there: html:open-tag, etc. 
I had been planning to put them into a package myself, but I've never
used them before, and was a little lazy to look it up just then.
Thanks for the code, both for the improvements, better names (OPEN-TAG
and CLOSE-TAG), and an introduction to packages :)

> With this code you can do:
> 
> (let ((*html-output* some-other-stream))
>    (output->html (head)
>      (output->html (body)
>        (format *html-output* "Back in the U.S.S.R.<BR>Dear Prudence"))))
> 
> Here you go:
> 
> (defpackage "HTML"
>     (:use "COMMON-LISP")
>   (:export "OPEN" "CLOSE" "OUTPUT->HTML"))
> 
> (in-package "HTML")
> 
> (defvar *html-output* *standard-output*)
> 
> (defun html-open (tag attribute-names attribute-values)
>   (format *html-output* "<~A" tag)
>   (loop for name  in attribute-names
>         and value in attribute-values do
>         (format *html-output* " ~A=~S" name value))
>   (format *html-output* ">"))
> 
> (defun html-close (tag)
>   (format *html-output* "</~A>" tag))
> 
> (defmacro output->html ((tag &rest pairs) &body body)
>   `(prog2
>     (html-open ',tag ',(mapcar #'first pairs)
>      (list ,@(mapcar #'second pairs)))
>     ;;when there are atleast 2 forms to be evaluated in BODY
>     ;;ensure that the last form is the one returned by PROG2
>     ,(if (not (null (cdr body))) 
> 	 `(progn ,@body) 
> 	 (car body))
>     (html-close ',tag)))
> 

Thank you very much for your time,

Vijay L