A macro involving two sub-macros - where the 2nd macro needs results from the first 
Author Message
 A macro involving two sub-macros - where the 2nd macro needs results from the first

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.*-*-*.com/ ~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.*-*-*.com/ ~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

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))

         (: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

slot-entries))
    (define-class-editor ,new-class-name)))

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

*eof*



Thu, 05 Jul 2001 03:00:00 GMT  
 A macro involving two sub-macros - where the 2nd macro needs results from the first


Quote:
>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.

Quote:
>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.

--

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.



Thu, 05 Jul 2001 03:00:00 GMT  
 A macro involving two sub-macros - where the 2nd macro needs results from the first
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

    (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

    (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

Quote:



> >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.

> --

> 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.



Thu, 05 Jul 2001 03:00:00 GMT  
 A macro involving two sub-macros - where the 2nd macro needs results from the first

Quote:

> 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>



Fri, 06 Jul 2001 03:00:00 GMT  
 
 [ 5 post ] 

 Relevant Pages 

1. syntax-rules macros with sub-macros

2. macro -vs- macro/codeblock

3. Help with macros writing macros in Bigloo

4. Scheme macro source: rewriter-widget, a widely applicable macro interface

5. Macros defining macros with define-syntax

6. symbol-macros and regular macros

7. Question about a macro-defining macro

8. Macro-Defining Macros

9. macro macros

10. Local macro within a macro?

11. how much macro is too much macro

12. help sought for macro defining macro

 

 
Powered by phpBB® Forum Software