From: Scott Alexander
Subject: A macro involving two sub-macros - where the 2nd macro needs results from the first
Date: 
Message-ID: <36A17650.851C1C0A@bellsouth.net>
Hello -

I'm having some trouble writing a macro that's supposed to expand into 2
forms (within a progn).

The problem is, the second form needs to use side-effects produced by
the first form.

The first form defines a class; the second form defines a [simplified]
CAPI interface (from Harlequin LispWorks) for the class.

For example, a call to the macro 'define-gui-class' might look like
this:

  (define-gui-class foo () (foo-size 0) (foo-weight 100))

which macroexpands into:

  (progn 
     (define-class foo nil (foo-size 0) (foo-weight 100)) 
     (define-class-editor foo))

(Note that both of these forms are themselves macro calls.)

Evaluating these two forms separately (outside a progn) works fine. But
when they're evaluated inside the progn, I get the error "Class FOO does
not exist."

(The first form uses the 'define-class' macro from
http://www.apl.jhu.edu/~hall/lisp/CLOS-Utilities.lisp which saves having
to type :accessor and :initform values for each slot.)

The first macro-call in the expansion:

  (define-class foo nil (foo-size 0) (foo-weight 100))

expands in turn into:

  (defclass foo (named-object)
     ((foo-size :accessor foo-size :initform 0 :initarg :foo-size)
      (foo-weight :accessor foo-weight :initform 100 :initarg
:foo-weight)))
           
as specified in file CLOS-utilities.lisp from
http://www.apl.jhu.edu/~hall/lisp/CLOS-Utilities.lisp

The second macro-call in the expansion

  (define-class-editor foo)

expands in turn into:

(capi:define-interface FOO-EDITOR
   nil
   ((class-name :accessor class-name :initform 'FOO))
   (:panes
      (NAVIGATOR
      capi:push-button-panel
      :items
      '("|<" "<" ">" ">|" "+" "--" "ok" "x")
      :selection-callback
      'navigator-callback)
      (NAME capi:text-input-pane :title "name")
      (FOO-SIZE capi:text-input-pane :title "foo-size")
      (FOO-WEIGHT capi:text-input-pane :title "foo-weight"))
   (:layouts
      (FOO-SLOTS capi:column-layout '(NAVIGATOR NAME FOO-SIZE
FOO-WEIGHT)))
   (:default-initargs :title "class : foo"))
                       
as specified in the file define-class-editor.lisp (appended below).

Note that to create the list '(NAVIGATOR NAME FOO-SIZE FOO-WEIGHT) in
this 2nd macro-expansion, you need to know the slot-names of class FOO,
defined in the first macro-expansion.

These two forms evaluate fine separately - but when they're inside a
progn, the second form can't see the class created by the first form.

Clearly the problem is that this second macro-call needs to look at the
slot-names of the new class FOO, which doesn't seem to exist yet if
these two forms are both inside a progn.

Does anyone have any ideas on this? I'm reading Graham's "On Lisp" but I
think I'm still missing some subtlety here. Maybe I need a 'let' which
binds a gensym'ed variable based on a backquoted call to define-class,
and which has a body consisting of of a backquoted call to
define-class-editor... but at this point I get lost.

Any suggestions? 

- Scott Alexander
······@bellsouth.net

PS - If there's a better way of creating and editing class instances in
Harlequin (for Windows) please let me know. At the moment I'm using the
Object Inspector - which *does* let you right-click on a Slot and do
Object | Set... - this is okay but I'd like to extend it a bit. For
instance, for a slot with a :type specifier, if the :type is another
user-defined class, it would be greate to display a dropdown list of all
the instances of that class. 

Does Harlequin provide the source for all their browsers if you buy the
Professional Edition? (I just downloaded the Personal last week, afraid
to buy until I know what I'm doing...)

PPS - I love Harlequin's many browsers but I wish they had a grid-widget
like Allegro CL for Windows. It's very hard to edit something with a
bunch of slots if you're using an uneditable list-panel. I imagine a
grid-widget could be created via a foreign-function call to the Windows
API but hey, one reason I like Lisp is because I know nothing about the
Windows API, so I'm not the one to create a grid-widget class for
Windows... (And one reason I reluctantly always end up telling myself I
can't indulge in Lisp programming is the incomplete set of wrappers for
the built-in GUI objects provided by today's popular OS APIs.)


;;; = = = = = = = = = = = beginning of file : define-class-editor.lisp =
= = = = = = = 

(defun text-input-pane (slot-name)
   "makes an expression defining a capi:text-input-pane for slot-name"
  (if (atom slot-name)
      (list slot-name 'capi:text-input-pane
            :title (string-downcase (symbol-name slot-name)))
      (list (first slot-name) 'capi:text-input-pane
            :title (string-downcase (symbol-name (first slot-name))))))

(defun navigator-callback (data interface)
   "stub for a function that gets called after clicking on a navigator
button in the interface"
  (case data
    ("+" ())
    (t (capi:display-message 
                         "Pressed ~S" data))))

;; a panel of navigator buttons for the capi:interface
(defparameter *navigator* '(navigator capi:push-button-panel 
                                      :items '("|<" 
                                               "<" 
                                               ">" 
                                               ">|" 
                                               "+" 
                                               "--" 
                                               "OK"
                                               "X")
                                      :selection-callback
'navigator-callback))
                                      
;; given a class-name <class>, expands into a call to
capi:define-interface 
;; defining a graphical editor named <class>-EDITOR for this class
(defmacro define-class-editor (class)
  (let* ((interface-symbol (intern (concatenate 'string (symbol-name
class) "-EDITOR")))
         (slots (slot-names class))
         (panes-clause (cons *navigator* (mapcar #'text-input-pane
slots)))
         (layout-slots-symbol (intern (concatenate 'string (symbol-name
class) "-SLOTS")))
         (layout-slots-list (cons 'navigator slots)))

    `(capi:define-interface ,interface-symbol
         ()
         ((class-name :accessor class-name
                     :initform ',class))
         (:panes ,@panes-clause)
         (:layouts (,layout-slots-symbol
                    capi:column-layout ',layout-slots-list))
         (:default-initargs
          :title ,(string-downcase (concatenate 'string
                                                "class : "
                                                (symbol-name
class)))))))


;; the main macro:
;; use this instead of defclass or define-class to automatically create
;; a capi:interface for the newly defined class
(defmacro define-gui-class (new-class-name superclass-list &rest
slot-entries)
 `(progn
    (define-class ,new-class-name ,superclass-list ,@(when slot-entries
slot-entries))
    (define-class-editor ,new-class-name)))


;;; = = = = = = = = = = end of file : define-class-editor.lisp = = = = =
= = = 

*eof*

From: Barry Margolin
Subject: Re: A macro involving two sub-macros - where the 2nd macro needs results from the first
Date: 
Message-ID: <jofo2.289$oD6.20199@burlma1-snr1.gtei.net>
In article <·················@bellsouth.net>,
Scott Alexander  <······@bellsouth.net> wrote:
>For example, a call to the macro 'define-gui-class' might look like
>this:
>
>  (define-gui-class foo () (foo-size 0) (foo-weight 100))
>
>which macroexpands into:
>
>  (progn 
>     (define-class foo nil (foo-size 0) (foo-weight 100)) 
>     (define-class-editor foo))
>
>(Note that both of these forms are themselves macro calls.)
>
>Evaluating these two forms separately (outside a progn) works fine. But
>when they're evaluated inside the progn, I get the error "Class FOO does
>not exist."

That seems like a bug.  Common Lisp specifies that a top-level PROGN should
be treated identically to its forms appearing at top-level in the source
file.  This rule is there precisely for the benefit of macros like this.

However, I'm very doubtful that Harlequin has this bug.  It's likely
that many of their own macros expand into code like this, so they surely
would have noticed it.

>Note that to create the list '(NAVIGATOR NAME FOO-SIZE FOO-WEIGHT) in
>this 2nd macro-expansion, you need to know the slot-names of class FOO,
>defined in the first macro-expansion.

Aha, you're trying to access the objects created by one expansion in the
expander function of the second.  The problem you're having here is due to
environments.  When you're compiling a file, the compiler arranges for the
thing that a DEFxxx form creates to exist when you load the file, but
they're not put in the current environment that the second macro expander
can access.  You need to use EVAL-WHEN with the :COMPILE-TOPLEVEL keyword
to cause the FOO class to be defined in the compile-time environment.

-- 
Barry Margolin, ······@bbnplanet.com
GTE Internetworking, Powered by BBN, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Don't bother cc'ing followups to me.
From: Scott Alexander
Subject: Re: A macro involving two sub-macros - where the 2nd macro needs results from the first
Date: 
Message-ID: <36A2420D.3B1E249E@bellsouth.net>
Barry -

Thanks for explaining this.

I really doubt I've stumbled across a bug in Harlequin's implementation
- I'm very new to macros so I'm sure there's something wrong with the
way I'm writing this.

Now I'm trying to use eval-when but I'm not sure where to put it.

I've tried two versions - (1) replacing the progn with an eval-when, and
(2) wrapping the call to define-class in an eval-when:

(defmacro define-gui-class-ew-1 (new-class-name superclass-list 
	&rest slot-entries)
 `(progn
    (eval-when (:compile-toplevel :load-toplevel :execute)
      (define-class ,new-class-name ,superclass-list 
	,@(when slot-entries slot-entries)))
    (define-class-editor ,new-class-name)))

(defmacro define-gui-class-ew-2 (new-class-name superclass-list 
	&rest slot-entries)
 `(eval-when (:compile-toplevel :load-toplevel :execute)
    (define-class ,new-class-name ,superclass-list 
	,@(when slot-entries slot-entries))
    (define-class-editor ,new-class-name)))

These give the following macroexpansions:

;; (define-gui-class-ew-1 baz () baz-x baz-y)
(EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
  (DEFINE-CLASS BAZ NIL BAZ-X BAZ-Y)
  (DEFINE-CLASS-EDITOR BAZ))

;; (define-gui-class-ew-2 bbb () bbb-x bbb-y)
(PROGN
  (EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
    (DEFINE-CLASS BBB NIL BBB-X BBB-Y))
  (DEFINE-CLASS-EDITOR BBB))

and both produce the same error: "Class xxx not defined."

The calls to define-gui-class-xxx are being done in a Listener. 
The defmacro's are in a buffer which I'm simply evaluating in Harlequin. 

Should I be compiling this buffer instead of evaluating it? 

Does "execute" (as a keyword to eval-when) mean the same thing as
"evaluate"?

Is eval-when really the right thing to use here?

If it is, where should I be using it?

If I can't get this to work, it's no big deal, although it would be nice
to know how to get the second form in a two-form macro-expansion to be
able to look at results of the first form. I can always evaluate the
second form at a later time. 

- Scott Alexander
······@bellsouth.net


Barry Margolin wrote:
> 
> In article <·················@bellsouth.net>,
> Scott Alexander  <······@bellsouth.net> wrote:
> >For example, a call to the macro 'define-gui-class' might look like
> >this:
> >
> >  (define-gui-class foo () (foo-size 0) (foo-weight 100))
> >
> >which macroexpands into:
> >
> >  (progn
> >     (define-class foo nil (foo-size 0) (foo-weight 100))
> >     (define-class-editor foo))
> >
> >(Note that both of these forms are themselves macro calls.)
> >
> >Evaluating these two forms separately (outside a progn) works fine. But
> >when they're evaluated inside the progn, I get the error "Class FOO does
> >not exist."
> 
> That seems like a bug.  Common Lisp specifies that a top-level PROGN should
> be treated identically to its forms appearing at top-level in the source
> file.  This rule is there precisely for the benefit of macros like this.
> 
> However, I'm very doubtful that Harlequin has this bug.  It's likely
> that many of their own macros expand into code like this, so they surely
> would have noticed it.
> 
> >Note that to create the list '(NAVIGATOR NAME FOO-SIZE FOO-WEIGHT) in
> >this 2nd macro-expansion, you need to know the slot-names of class FOO,
> >defined in the first macro-expansion.
> 
> Aha, you're trying to access the objects created by one expansion in the
> expander function of the second.  The problem you're having here is due to
> environments.  When you're compiling a file, the compiler arranges for the
> thing that a DEFxxx form creates to exist when you load the file, but
> they're not put in the current environment that the second macro expander
> can access.  You need to use EVAL-WHEN with the :COMPILE-TOPLEVEL keyword
> to cause the FOO class to be defined in the compile-time environment.
> 
> --
> Barry Margolin, ······@bbnplanet.com
> GTE Internetworking, Powered by BBN, Burlington, MA
> *** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
> Don't bother cc'ing followups to me.
From: ········@poboxes.com
Subject: Re: A macro involving two sub-macros - where the 2nd macro needs results from the first
Date: 
Message-ID: <77uomu$2p7$1@nnrp1.dejanews.com>
In article <·················@bellsouth.net>,
  Scott Alexander <······@bellsouth.net> wrote:
(...)
> ;; (define-gui-class-ew-1 baz () baz-x baz-y)
> (EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
>   (DEFINE-CLASS BAZ NIL BAZ-X BAZ-Y)
>   (DEFINE-CLASS-EDITOR BAZ))
>
> ;; (define-gui-class-ew-2 bbb () bbb-x bbb-y)
> (PROGN
>   (EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
>     (DEFINE-CLASS BBB NIL BBB-X BBB-Y))
>   (DEFINE-CLASS-EDITOR BBB))
>
> and both produce the same error: "Class xxx not defined."

This is just a hint as I'm afraid I can't really go into this.

To me (without knowing details about DEFINE-CLASS and
DEFINE-CLASS-EDITOR), it looks like when DEFINE-CLASS-EDITOR
is _expanded_, it already needs the class to be defined.
Now the EVAL-WHEN will ensure the class is defined when
the DEFINE-CLASS form is compiled, but the macro expansion
of DEFINE-CLASS-EDITOR may occur before the compilation or
evaluation of DEFINE-CLASS (with or without EVAL-WHEN).

This may or may not be the answer, but one thing you could
try would be to see what happens if you have the macro
expansion of DEFINE-GUI-CLASS-EW evaluate DEFINE-CLASS.
This could shed some light on the source of the error.

Good luck,
Vassil.


Vassil Nikolov <········@poboxes.com> www.poboxes.com/vnikolov
(You may want to cc your posting to me if I _have_ to see it.)
   LEGEMANVALEMFVTVTVM  (Ancient Roman programmers' adage.)

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own    
From: Bruno Haible
Subject: Re: A macro involving two sub-macros - where the 2nd macro needs results from the first
Date: 
Message-ID: <78045i$1uo$1@news.u-bordeaux.fr>
Scott Alexander <······@bellsouth.net> wrote:
>
> The second macro-call in the expansion
>
>  (define-class-editor foo)
>
> expands in turn into:
>
> (capi:define-interface FOO-EDITOR
>    nil
>    ((class-name :accessor class-name :initform 'FOO))
>    (:layouts
>       (FOO-SLOTS capi:column-layout '(NAVIGATOR NAME FOO-SIZE FOO-WEIGHT)))
>    ...

Do you really need to produce the list (NAVIGATOR NAME FOO-SIZE FOO-WEIGHT)
at macroexpansion time? It looks like it would be sufficient to have it
at run time.

  (capi:define-interface FOO-EDITOR
     nil
     ((class-name :accessor class-name :initform 'FOO))
     (:layouts
       (FOO-SLOTS capi:column-layout (cons 'NAVIGATOR (slot-names 'FOO))))
     ...


                      Bruno                     http://clisp.cons.org/~haible/

---
"One day, computer power will eventually outstrip demand, and OS engineers
will be free to use friendly languages like LISP again. Until then, I think
we're stuck with C." -- Oliver Xymoron on the Linux kernel mailing list
<http://www.uwsg.indiana.edu/hypermail/linux/kernel/9901.1/0170.html>