From: Andrew Cooke
Subject: Macros that create an environment
Date: 
Message-ID: <85sup5$ms7$1@nnrp1.deja.com>
Hi,

I am writing a collection of macros that lets the user specify a
circuit of connected components (with feedback).  I am aiming for
something like:

(clocked-components 20
  (add-component a and b c)
  (add-component b or a 1)
  (add-component c and a b)
  (add-output b))

This would return the output from the or gate called b whose inputs
are 1 and the output from the and gate a (the inputs for a are the
outputs from b and c, etc.) for 20 clock cycles.

(The reasons for wanting to do this are complicated and it is not as
stupid as it may appear, so bear with me)

I'd like to translate this, via macros (clocked-components,
add-component and add-output are probably all macros) to code like:

(let ((a (make-instance 'and))
      (b (make-instance 'or))
      (c (make-instance 'and)))
  (set-input a b c)
  (set-input b a 1)
  (set-input c a b)
  (.. code to calculate output for 20 clicks ...))

I already have classes and functions that execute the code above, what
is troubling me is going from the input form described earlier, As far
as I can see, clocked-components has to create an environment in which
the other macros can work.  For example, it must provide variables to
store the components and connections.

So clocked-components must create an environment and then call eval on
the inner body (is there an alternative? - I could parse the body using
my own code, but that would either be a lot of work, or stop me using
general Lisp expressions).

However, eval only uses dynamic scope, so clocked-components cannot
look like:

(defmacro clocked-filters (count &body body)
  (let ((component-list)
        (...))
    (eval body)
    (... build code ...)))

(defmacro add-component (name type in1 in2)
  (setf component-list (push (list name type in1 in2) component-list)))

because component-list is lexical in scope (I think - is this
correct?).

But if I use dynamic scope, I am worried that I will not be able to
nest code.  Dynamic code seems to force me to use global variables.
This seems very ugly.

How do I implement code like this?  I feel like I am missing something
obvious.

Thanks,
Andrew


Sent via Deja.com http://www.deja.com/
Before you buy.

From: Tim Bradshaw
Subject: Re: Macros that create an environment
Date: 
Message-ID: <ey3g0vxq4jw.fsf@cley.com>
* Andrew Cooke wrote:

> So clocked-components must create an environment and then call eval on
> the inner body (is there an alternative? - I could parse the body using
> my own code, but that would either be a lot of work, or stop me using
> general Lisp expressions).

The standard trick is for the outer macro to expand to something that
does a MACROLET (or FLET / LABELS) for the interesting names, so
within the scope of the outer macro those names do something special.
An example of this kind of thing in standard CL is CALL-NEXT-METHOD,
a closer example is probably the kind of COLLECTING/COLLECT pair:

	(collecting ... (collect x))

where COLLECT is locally defined to have access to some secret
accumulator extablished by COLLECTING.

--tim
From: Andrew Cooke
Subject: Re: Macros that create an environment
Date: 
Message-ID: <85uiev$pva$1@nnrp1.deja.com>
Excellent!  Thank-you very much.

Andrew

In article <···············@cley.com>,
  Tim Bradshaw <···@cley.com> wrote:
> * Andrew Cooke wrote:
>
> > So clocked-components must create an environment and then call eval
on
> > the inner body (is there an alternative? - I could parse the body
using
> > my own code, but that would either be a lot of work, or stop me
using
> > general Lisp expressions).
>
> The standard trick is for the outer macro to expand to something that
> does a MACROLET (or FLET / LABELS) for the interesting names, so
> within the scope of the outer macro those names do something special.
> An example of this kind of thing in standard CL is CALL-NEXT-METHOD,
> a closer example is probably the kind of COLLECTING/COLLECT pair:
>
> 	(collecting ... (collect x))
>
> where COLLECT is locally defined to have access to some secret
> accumulator extablished by COLLECTING.
>
> --tim
>


Sent via Deja.com http://www.deja.com/
Before you buy.
From: Andrew Cooke
Subject: Re: Macros that create an environment
Date: 
Message-ID: <85vql4$nmg$1@nnrp1.deja.com>
In article <···············@cley.com>,
  Tim Bradshaw <···@cley.com> wrote:
> The standard trick is for the outer macro to expand to something that
> does a MACROLET (or FLET / LABELS) for the interesting names, so
> within the scope of the outer macro those names do something special.
> An example of this kind of thing in standard CL is CALL-NEXT-METHOD,
> a closer example is probably the kind of COLLECTING/COLLECT pair:
>
> 	(collecting ... (collect x))
>
> where COLLECT is locally defined to have access to some secret
> accumulator extablished by COLLECTING.

Hi,

I celebrated too quickly.  If I try:

(defmacro start (x &body body)
  (let ((msg "hello"))
    (macrolet ((inner (y) `(progn (print ,msg) (print y)))
               (end () `(print "end")))
      `(progn ,body (end)))))

(pprint (macroexpand
  '(start 1
     (print "body 1")
     (inner "inner")
     (print "body 2")))

all I get is:

(progn (print "body 1") (inner "inner") (print "body 2") (end))

So the macrolet doesn't seem to be used.  The same thing occurs even
with a macroexpand outsice the backquoted code in the final line of the
defmacro above.

I'm not surprised, as macro expansion seems to depend on global
definitions, but then what is macrolet for?  Also, is the implied
closure that includes the strig "hellO" in the inner macro going to
work?

Sorry for banging away at this - I can't find anything similar in on
Lisp.

Andrew


Sent via Deja.com http://www.deja.com/
Before you buy.
From: Michael Hudson
Subject: Re: Macros that create an environment
Date: 
Message-ID: <m3wvp879mp.fsf@atrus.jesus.cam.ac.uk>
Andrew Cooke <······@andrewcooke.free-online.co.uk> writes:

> In article <···············@cley.com>,
>   Tim Bradshaw <···@cley.com> wrote:
> > The standard trick is for the outer macro to expand to something that
> > does a MACROLET (or FLET / LABELS) for the interesting names, so
> > within the scope of the outer macro those names do something special.
> > An example of this kind of thing in standard CL is CALL-NEXT-METHOD,
> > a closer example is probably the kind of COLLECTING/COLLECT pair:
> >
> > 	(collecting ... (collect x))
> >
> > where COLLECT is locally defined to have access to some secret
> > accumulator extablished by COLLECTING.
> 
> Hi,
> 
> I celebrated too quickly.  If I try:
> 
> (defmacro start (x &body body)
>   (let ((msg "hello"))
>     (macrolet ((inner (y) `(progn (print ,msg) (print y)))
>                (end () `(print "end")))
>       `(progn ,body (end)))))
> 
> (pprint (macroexpand
>   '(start 1
>      (print "body 1")
>      (inner "inner")
>      (print "body 2")))
> 
> all I get is:
> 
> (progn (print "body 1") (inner "inner") (print "body 2") (end))
> 
> So the macrolet doesn't seem to be used.  The same thing occurs even
> with a macroexpand outsice the backquoted code in the final line of the
> defmacro above.

No, if you're doing what I think you are (not certain) you need to
expand into the macrolet; after all the stuff inside the backquote
isn't evaluated. 

Is this what you want? (I added a ·@' as well)

[28]> (defmacro start (x &body body)
  (let ((msg "hello"))
    `(macrolet ((inner (y) `(progn (print ,',msg) (print ,,'y)))
                (end () `(print "end")))
       (progn ,@body (end)))))
start
[29]> (pprint (macroexpand
         '(start 1
                 (print "body 1")
                 (inner "inner")
                 (print "body 2"))))
(lisp:macrolet
  ((inner (y) `(progn (print "hello") (print ,y))) (end nil `(print "end")))
  (progn (print "body 1") (inner "inner") (print "body 2") (end))
)

[30]> (start 1
       (print "body 1")
       (inner "inner")
       (print "body 2"))
"body 1" 
"hello" 
"inner" 
"body 2" 
"end" 
"end"

I had fun and games getting the ,', and ,,' stuff right, but if you
think about what's actually going on, it does make sense.

Looking at the expansion of with-slots may help (tho' that uses
symbol-macrolet).

Make sure you export "INNER" (and probably "END") from the package in
which you define 'start - I got very confused by that one.

HTH,
Michael
From: Andrew Cooke
Subject: Re: Macros that create an environment
Date: 
Message-ID: <8617d9$nfo$1@nnrp1.deja.com>
In article <············@nnrp1.deja.com>,
  Andrew Cooke <······@andrewcooke.free-online.co.uk> wrote:
[...]
> So the macrolet doesn't seem to be used.  The same thing occurs even
> with a macroexpand outsice the backquoted code in the final line of
the
> defmacro above.

Thanks for the replies to this.  I need to read them again and think,
but it looks like that clears it up.

Thanks again,
Andrew



Sent via Deja.com http://www.deja.com/
Before you buy.
From: Tim Bradshaw
Subject: Re: Macros that create an environment
Date: 
Message-ID: <ey3ln5o8b16.fsf@cley.com>
* Andrew Cooke wrote:

> I celebrated too quickly.  If I try:

> (defmacro start (x &body body)
>   (let ((msg "hello"))
>     (macrolet ((inner (y) `(progn (print ,msg) (print y)))
>                (end () `(print "end")))
>       `(progn ,body (end)))))

> (pprint (macroexpand
>   '(start 1
>      (print "body 1")
>      (inner "inner")
>      (print "body 2")))

> all I get is:

> (progn (print "body 1") (inner "inner") (print "body 2") (end))

There are two bugs here.  One is that the MACROLET must be in the
expansion of the macro, something like this:

    (defmacro start (&body bod)
      (let ((msg "hello"))
	`(macrolet ((inner (y)
		      `(progn (print ,',msg) (print ,y)))
		    (end () `(print "end")))
	   ,@bod
	   (end))))


The other (not really a bug) is that you may be confused about what
MACROEXPAND does.  What it does is expand the form it is given until
it is no longer a `macro form' -- that is until the operator is no
longer defined as a macro.  It does *not* walk into the form and
expand all macros.  So if I have a macro M-A which expands to (M-B
..) and M-B is a macro, I'll get the expansion of (M-B ...), but
macros other than those in the operator position won't get expanded.

So for (start (inner "foo")), MACROEXPAND gives you (in Allegro, which
does funny things with backquotes...):

    (macrolet ((inner (y)
		 (excl::bq-list `progn (excl::bq-list `print '"hello")
				(excl::bq-list `print y)))
	       (end nil `(print "end")))
      (inner "foo")
      (end))

--tim
From: Phil Stubblefield
Subject: Re: Macros that create an environment
Date: 
Message-ID: <3884F6C6.B28172EA@rpal.rockwell.com>
Andrew Cooke <······@andrewcooke.free-online.co.uk> wrote:
> 
> I am writing a collection of macros that lets the user specify a
> circuit of connected components (with feedback).  I am aiming for
> something like:
> 
> (clocked-components 20
>   (add-component a and b c)
>   (add-component b or a 1)
>   (add-component c and a b)
>   (add-output b))
> 
> [...]

Knowing nothing else about the problem, I would probably code the
solution something like the following.

#||
(with-clocked-components (20)
  (:component a (and b c))
  (:component b (or a 1))
  (:component c (and a b))
  (:output b))
||#

First, note that I prepended WITH- to the name of the top-level
macro, primarily to follow existing Lisp convention with respect to
macros that bind variables.  (But also because Emacs will indent it
correctly with no further effort!)  I added parentheses around the
counter so that optional or keyword parameters can be added later
without modifying existing code.  I changed the contents of the body
into keyword clauses to make it clear that the body is *not* a set
of forms to be evaluated.  Finally, I parenthesized the definition
of each component so that it more closely follows Lisp syntax.
Apologies if I've misinterpreted the original problem.

Having decided on the syntax, we need to decide on the expansion.
After playing around with a few ideas, here's what I came up with:

#||
(simulate-circuit (make-circuit
                   ;; Component specifications, each of the form
                   ;; (<name> <type> . <input-names>).
                   '((a and b c)
                     (b or  a 1)
                     (c and a b))
                   ;; The output component name.
                   'b)
                  :cycles 20)
||#

Note that I have made two design decisions here.  First, I chose
*not* to expand into code that uses MACROLET or friends.  Instead,
the macro just bundles up the appropriate arguments and expands into
a call to a functional interface, which I named SIMULATE-CIRCUIT.
An advantage of this approach is that users of the macro have to be
recompiled only if the macro *syntax* changes, but not if the
*implementation* changes.  In other words, if your simulations are
too slow, then you can speed them up by changing the implementation
of SIMULATE-CIRCUIT, but you don't have to recompile all the places
where the macro is used.

Second, note that I separated the definitions into two layers:  one
that deals with names (WITH-CLOCKED-COMPONENTS, MAKE-CIRCUIT), and
another that deals with objects (SIMULATE-CIRCUIT).  This mirrors
the separation in CLOS and many other systems.  An advantage is that
the object layer no longer needs to deal with naming issues, which
can often simplify the code considerably.

Now that we have the macro syntax and expansion, the code follows.
It is completely untested!  Feel free to use any of this as you see
fit.  Comments are solicited!

------------------------------------------------------------------------

;;; Clocked components.
;;;
;;; This code is in the public domain.

(in-package "CL-USER")

(defmacro with-clocked-components ((cycles) &body clauses)
  "Defines a circuit using the given component clauses, then simulates
   it for the given number of cycles and returns the output."
  (multiple-value-bind (component-specifications output-name)
      (parse-with-clocked-components-body clauses)
    `(simulate-circuit
      (make-circuit ',component-specifications ',output-name)
      :cycles ,cycles)))

(declaim (declaration values))                  ;if needed

(defun parse-with-clocked-components-body (clauses)
  "Parses the body of a WITH-CLOCKED-COMPONENTS macro, checking for
   errors, and returns as multiple values: (1) an alist mapping
   component names to specifications, and (2) the name of the output
   component."
  (declare (values component-specifications output))
  (let ((component-specifications '())
        (output nil))
    (dolist (clause clauses)
      (ecase (first clause)
        (:component
         (destructuring-bind (name definition) (rest clause)
           (push (cons name definition) component-specifications)))
        (:output
         (destructuring-bind (name) (rest clause)
           (if (null output)
               (setq output name)
             (error "Multiple :OUTPUT clauses present."))))))
    (when (null output)
      (error "No :OUTPUT clause present."))
    (values (nreverse component-specifications)
            output)))

(defun make-circuit (component-specifications output-name)
  "Creates a circuit using the given component specifications.  Each
   alist entry if of the form (<name> <type> . <input-names>)."
  (let ((components '()))
    (flet ((find-component (name)
             (or (find name components :key #'component-name)
                 (error "There is no component named '~A'." name))))
      (declare (dynamic-extent #'find-component))
      ;; Create the component objects.
      (dolist (specification component-specifications)
        (destructuring-bind (name type . input-names) specification
          (declare (ignore input-names))
          (push (make-component name type) components)))
      ;; Add the connections.
      (dolist (component components)
        (let ((specification (assoc (component-name component)
                                    component-specifications)))
          (dolist (input-name (cddr specification))
            (let ((input (etypecase input-name
                           (symbol
                            (find-component input-name))
                           (number
                            input-name))))
              (component-add-input input component)))))
      ;; Create and return the circuit.
      (make-instance 'circuit
        :components (nreverse components)
        :output (find-component output-name)))))

(defun simulate-circuit (circuit &key (cycles 1))
  "Simulates the circuit for the given number of cycles, returning its
   output."
  (dotimes (cycle cycles)
    ;; Simulate...
    )
  (circuit-output circuit))

------------------------------------------------------------------------


Phil Stubblefield
Rockwell Palo Alto Laboratory                               206/655-3204
http://www.rpal.rockwell.com/~phil                ····@rpal.rockwell.com