Vsevolod
2008-07-25 20:28:00 UTC
Hi,
Once again (hopefully, for the last time) I want to address the
question "to use or not to use", so to say, macroexpansion inside
w-h-o form, and moreover, how to do it right.
I have experimented with the previously proposed solution of
introducing additional EMB keyword and have found, that it as well has
some drawbacks. More precisely, they exist because of the nature of
the w-h-o macro itself, which uses special processing for the forms,
contained inside HTM: they are processed before any function
application, that is why if you EMBed some macros, which use HTM
keyword, which should output to their own streams, the result can be
emitted in the unexpected order. To illustrate this vague statement, I
can show the piece of the code, that combines cl-who and parenscript
forms to make a currency selection form with some javascript backing:
(defmacro cur-sel ()
(w/uniqs (cur)
(let ((sel-str `(:select :id "currency"
(dolist (,cur *currencies*)
(htm (:option :selected (string= ,cur *default-currency*)
(str ,cur)))))))
`(htm
(emb (ps-script* (write-htm (:a :class "toggle" :onclick (ps-inline*
`(lambda ()
,(write-htm ,sel-str)))
(str (ie-val *default-currency*))))))
(:noscript ,sel-str)))))
(defmacro write-htm (form)
``(.write document
,(who
,form)))
The macro CUR-SEL, that is intended to itself be EMBedded, uses
WRITE-HTM to emit HTML forms to string, that will in turn be an
argument to javascript document.write(). But, as after macroexpansion
of sel-str HTM forms appear as arguments to WRITE-HTM, the output of
the containing w-h-o forms goes to the stream, bound by the topmost
w-h-o and not the innermost, as it's supposed...
Anyway, after some re-thinking of a problem, I've come to a
conclusion, that my approach was not the most natural one. Now, I
believe, that the more lispy way is to view w-h-o macro as solely a
good processor for static pseudo-html data (like '(:p some text (:br)
(:a :href ....), and on top of it build macros, which will allow
eventually to add macroexpansion ability.
My initial target was to be able to use macros with pseudo-html forms,
like in the following simple example:
(def-who-macro (arg)
`(:p some text (:br) (:a :href ,arg)))
inside w-h-o body the same way you would use an ordinary macro, but
I'd got carried away with modifying the w-h-o macro itself to allow
for such usage. It turned out, that it's not hard to implement the
separated approach. My variant is below:
(defmacro def-who-macro (name (&rest args) pseudo-html-form)
"Produces functions for generating pseudo-html forms,
which can be embedded in WHO-PAGEs"
`(defun ,name (,@args)
,pseudo-html-form))
(defmacro def-who-page (name (&optional stream) pseudo-html-form)
"Creates a function to generate an HTML page with the use of
WITH-HTML-OUTPUT macro, in which pseudo-html forms, constructed with
DEF-WHO-MACRO, can be embedded. If STREAM is nil/not supplied
WITH-HTML-OUTPUT-TO-STRING to a throwaway string will be used,
otherwise -- WITH-HTML-OUTPUT"
`(macrolet ((who-tmp ()
`(,@',(if stream `(with-html-output (,stream nil :prologue t))
`(with-html-output-to-string (,(gensym) nil :prologue t)))
,,pseudo-html-form)))
(defun ,name ()
(who-tmp))))
(defmacro who (pseudo-html-form)
"An analog of DEF-WHO-PAGE, which not only creates an
w-h-o pseudo-HTML to HTML translation function, but as well
runs it and emits the obtained HTML as a string"
`(macrolet ((who-tmp ()
`(with-html-output-to-string (,(gensym))
,,pseudo-html-form)))
(who-tmp)))
And the example usage can be:
(def-who-macro ps-script (&body body)
`(:script :type "text/javascript"
(fmt "~%// <![CDATA[~%")
(str (js:ps ,@body))
(fmt "~%// ]]>~%")))
(def-who-page demo-page ()
`(:html (:head ,(:ps-script
(defun hi ()
(alert "Hello world!"))))
(:body (:input :type "button" :value "Click here..."))))
The only catch is that DEF-WHO-MACRO actually generates not a macro in
Lisp terms, but a function, which constructs lists of pseudo-HTML
forms.
I hope, the given code will be useful.
Best regards,
Vsevolod Dyomkin
Once again (hopefully, for the last time) I want to address the
question "to use or not to use", so to say, macroexpansion inside
w-h-o form, and moreover, how to do it right.
I have experimented with the previously proposed solution of
introducing additional EMB keyword and have found, that it as well has
some drawbacks. More precisely, they exist because of the nature of
the w-h-o macro itself, which uses special processing for the forms,
contained inside HTM: they are processed before any function
application, that is why if you EMBed some macros, which use HTM
keyword, which should output to their own streams, the result can be
emitted in the unexpected order. To illustrate this vague statement, I
can show the piece of the code, that combines cl-who and parenscript
forms to make a currency selection form with some javascript backing:
(defmacro cur-sel ()
(w/uniqs (cur)
(let ((sel-str `(:select :id "currency"
(dolist (,cur *currencies*)
(htm (:option :selected (string= ,cur *default-currency*)
(str ,cur)))))))
`(htm
(emb (ps-script* (write-htm (:a :class "toggle" :onclick (ps-inline*
`(lambda ()
,(write-htm ,sel-str)))
(str (ie-val *default-currency*))))))
(:noscript ,sel-str)))))
(defmacro write-htm (form)
``(.write document
,(who
,form)))
The macro CUR-SEL, that is intended to itself be EMBedded, uses
WRITE-HTM to emit HTML forms to string, that will in turn be an
argument to javascript document.write(). But, as after macroexpansion
of sel-str HTM forms appear as arguments to WRITE-HTM, the output of
the containing w-h-o forms goes to the stream, bound by the topmost
w-h-o and not the innermost, as it's supposed...
Anyway, after some re-thinking of a problem, I've come to a
conclusion, that my approach was not the most natural one. Now, I
believe, that the more lispy way is to view w-h-o macro as solely a
good processor for static pseudo-html data (like '(:p some text (:br)
(:a :href ....), and on top of it build macros, which will allow
eventually to add macroexpansion ability.
My initial target was to be able to use macros with pseudo-html forms,
like in the following simple example:
(def-who-macro (arg)
`(:p some text (:br) (:a :href ,arg)))
inside w-h-o body the same way you would use an ordinary macro, but
I'd got carried away with modifying the w-h-o macro itself to allow
for such usage. It turned out, that it's not hard to implement the
separated approach. My variant is below:
(defmacro def-who-macro (name (&rest args) pseudo-html-form)
"Produces functions for generating pseudo-html forms,
which can be embedded in WHO-PAGEs"
`(defun ,name (,@args)
,pseudo-html-form))
(defmacro def-who-page (name (&optional stream) pseudo-html-form)
"Creates a function to generate an HTML page with the use of
WITH-HTML-OUTPUT macro, in which pseudo-html forms, constructed with
DEF-WHO-MACRO, can be embedded. If STREAM is nil/not supplied
WITH-HTML-OUTPUT-TO-STRING to a throwaway string will be used,
otherwise -- WITH-HTML-OUTPUT"
`(macrolet ((who-tmp ()
`(,@',(if stream `(with-html-output (,stream nil :prologue t))
`(with-html-output-to-string (,(gensym) nil :prologue t)))
,,pseudo-html-form)))
(defun ,name ()
(who-tmp))))
(defmacro who (pseudo-html-form)
"An analog of DEF-WHO-PAGE, which not only creates an
w-h-o pseudo-HTML to HTML translation function, but as well
runs it and emits the obtained HTML as a string"
`(macrolet ((who-tmp ()
`(with-html-output-to-string (,(gensym))
,,pseudo-html-form)))
(who-tmp)))
And the example usage can be:
(def-who-macro ps-script (&body body)
`(:script :type "text/javascript"
(fmt "~%// <![CDATA[~%")
(str (js:ps ,@body))
(fmt "~%// ]]>~%")))
(def-who-page demo-page ()
`(:html (:head ,(:ps-script
(defun hi ()
(alert "Hello world!"))))
(:body (:input :type "button" :value "Click here..."))))
The only catch is that DEF-WHO-MACRO actually generates not a macro in
Lisp terms, but a function, which constructs lists of pseudo-HTML
forms.
I hope, the given code will be useful.
Best regards,
Vsevolod Dyomkin