From: Daniel Leidisch
Subject: Help defining a macro
Date: 
Message-ID: <f284de$l9m$03$1@news.t-online.com>
Hello!

I'm trying to write a macro which binds variables to fields of a
string. I want to call it like this:

(defvar *string* "the quick brown fox")
(bind-fields (((foo 0) (bar 3)) *string*)
  (format nil "~a ~a" foo bar)) => "the fox" 

The code:

(defun get-field (string column &key (field-seperator "\\s+")
									(from-end nil))
	(let* ((fields (split field-seperator string))
				 (length (length fields)))
		(when (< column length)
			(elt fields (if from-end
											(- length (1+ column))
											column)))))

(defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
																(from-end nil)) &body body)
	`(let ,(loop for var in varlist collect
							(list (car var)
										(get-field string (cadr var)
															 :field-seperator field-seperator
															 :from-end from-end)))
		 ,@body))


But it only works if I pass it a literal string, since get-field
would just be called with the symbol otherwise. I considered calling
get-field with (symbol-value string), but that would only work for
dynamic variables and it'd be far from correct (and one could not
pass literal strings).

Any hints for solving this or other useful tips/criticism?


Regards,

dhl

From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f284qq$l9m$03$2@news.t-online.com>
Sorry, I forgot to expand it:

(defun get-field (string column &key (field-seperator "\\s+")
                  (from-end nil))
  (let* ((fields (split field-seperator string))
         (length (length fields)))
    (when (< column length)
      (elt fields (if from-end
                      (- length (1+ column))
                      column)))))

(defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
                                (from-end nil)) &body body)
  `(let ,(loop for var in varlist collect
              (list (car var)
                    (get-field string (cadr var)
                               :field-seperator field-seperator
                               :from-end from-end)))
     ,@body))
   
From: Thomas A. Russ
Subject: Re: Help defining a macro
Date: 
Message-ID: <ymiy7jrvxyd.fsf@sevak.isi.edu>
A slightly different answer than Pascal Bourguignon's, and closer to
your code.

Daniel Leidisch <····@leidisch.net> writes:

> Sorry, I forgot to expand it:
> 
> (defun get-field (string column &key (field-seperator "\\s+")
>                   (from-end nil))
>   (let* ((fields (split field-seperator string))
>          (length (length fields)))
>     (when (< column length)
>       (elt fields (if from-end
>                       (- length (1+ column))
>                       column)))))

Problem: The following code only works with literal strings.

The fundamental reason for that is that the access to the field is being
done at macro-expansion time, since what is being constructed in the
LOOP evaluates the GET-FIELD call.

The solution is to defer evaluation of that call until run-time, while
still passing in the correct arguments.

> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>                                 (from-end nil)) &body body)
>   `(let ,(loop for var in varlist collect
>               (list (car var)
>                     (get-field string (cadr var)
>                                :field-seperator field-seperator
>                                :from-end from-end)))
>      ,@body))

The key is to use an additional backquote.  I'll do the simple surgery
here.

(defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
                                (from-end nil)) &body body)
  `(let ,(loop for var in varlist collect
              (list (car var)
                    `(get-field ,string ,(cadr var)
                                :field-seperator ,field-seperator
                                :from-end ,from-end)))
      ,@body))

Quick test:


 (macroexpand '(bind-fields (((foo 0) (bar 2)) my-string) (list foo bar)))

 (LET ((FOO (GET-FIELD MY-STRING 0 :FIELD-SEPERATOR "\\s+" :FROM-END NIL))
       (BAR (GET-FIELD MY-STRING 2 :FIELD-SEPERATOR "\\s+" :FROM-END NIL)))
   (LIST FOO BAR))

There is also a potential issue with how often you end up evaluating the
STRING, FIELD-SEPERATOR [sic] and FROM-END forms.  You can solve that by
judicious use of GENSYM's in a surrounding LET form:

(defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
                                (from-end nil)) &body body)
  (let ((string-var (gensym "STRING-"))
        (separator-var (gensym "SEPARATOR-"))
        (from-end-var (gensym "FROM-END-")))

  `(let ((,string-var ,string)
         (,separator-var ,field-seperator)
         (,from-end-var ,from-end))
    (let ,(loop for var in varlist collect
              (list (car var)
                    `(get-field ,string-var ,(cadr var)
                                :field-seperator ,separator-var
                                :from-end ,from-end-var)))
       ,@body))))

Which then leads to:

 (macroexpand '(bind-fields (((foo 0) (bar 2)) my-string) (list foo
bar)))

(LET ((#:STRING-41047 MY-STRING) 
      (#:SEPARATOR-41048 "\\s+")
      (#:FROM-END-41049 NIL))
  (LET ((FOO (GET-FIELD #:STRING-41047 0 
                        :FIELD-SEPERATOR #:SEPARATOR-41048
                        :FROM-END #:FROM-END-41049))
        (BAR (GET-FIELD #:STRING-41047 2
                        :FIELD-SEPERATOR #:SEPARATOR-41048
                        :FROM-END #:FROM-END-41049)))
    (LIST FOO BAR)))



-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f2b0gl$i3v$03$1@news.t-online.com>
Thus spoke Thomas A. Russ <···@sevak.isi.edu>:
> Daniel Leidisch <····@leidisch.net> writes:
> 
>> Sorry, I forgot to expand it:
>> 
>> (defun get-field (string column &key (field-seperator "\\s+")
>>                   (from-end nil))
>>   (let* ((fields (split field-seperator string))
>>          (length (length fields)))
>>     (when (< column length)
>>       (elt fields (if from-end
>>                       (- length (1+ column))
>>                       column)))))
> 
> Problem: The following code only works with literal strings.
> 
> The fundamental reason for that is that the access to the field is being
> done at macro-expansion time, since what is being constructed in the
> LOOP evaluates the GET-FIELD call.
> 
> The solution is to defer evaluation of that call until run-time, while
> still passing in the correct arguments.
> 
>> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>>                                 (from-end nil)) &body body)
>>   `(let ,(loop for var in varlist collect
>>               (list (car var)
>>                     (get-field string (cadr var)
>>                                :field-seperator field-seperator
>>                                :from-end from-end)))
>>      ,@body))
> 
> The key is to use an additional backquote.  I'll do the simple surgery
> here.
> 
> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>                                (from-end nil)) &body body)
>  `(let ,(loop for var in varlist collect
>              (list (car var)
>                    `(get-field ,string ,(cadr var)
>                                :field-seperator ,field-seperator
>                                :from-end ,from-end)))
>      ,@body))
> 

Yes, now all the scales fall from my eyes. Thank you very much for
your illuminating help. Actually, I was close -- close, but no cigar. ;)

> There is also a potential issue with how often you end up evaluating the
> STRING, FIELD-SEPERATOR [sic] and FROM-END forms.  You can solve that by
> judicious use of GENSYM's in a surrounding LET form:
> 
> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>                                (from-end nil)) &body body)
>  (let ((string-var (gensym "STRING-"))
>        (separator-var (gensym "SEPARATOR-"))
>        (from-end-var (gensym "FROM-END-")))
> 
>  `(let ((,string-var ,string)
>         (,separator-var ,field-seperator)
>         (,from-end-var ,from-end))
>    (let ,(loop for var in varlist collect
>              (list (car var)
>                    `(get-field ,string-var ,(cadr var)
>                                :field-seperator ,separator-var
>                                :from-end ,from-end-var)))
>       ,@body))))
> 

But that is just for the sake of performance, right? I didn't consider
gensym because I read somewhere that it is mostly applied for non-user
variables. Anyways, it makes sense to me, and it seems to be better
than my original code.

Cheers

dhl

P.S. Sorry for the misspelling!
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f2b4u8$p9h$02$1@news.t-online.com>
Thus spoke Daniel Leidisch <····@leidisch.net>:
>> There is also a potential issue with how often you end up evaluating the
>> STRING, FIELD-SEPERATOR [sic] and FROM-END forms.  You can solve that by
>> judicious use of GENSYM's in a surrounding LET form:
>> 
>> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>>                                (from-end nil)) &body body)
>>  (let ((string-var (gensym "STRING-"))
>>        (separator-var (gensym "SEPARATOR-"))
>>        (from-end-var (gensym "FROM-END-")))
>> 
>>  `(let ((,string-var ,string)
>>         (,separator-var ,field-seperator)
>>         (,from-end-var ,from-end))
>>    (let ,(loop for var in varlist collect
>>              (list (car var)
>>                    `(get-field ,string-var ,(cadr var)
>>                                :field-seperator ,separator-var
>>                                :from-end ,from-end-var)))
>>       ,@body))))
>> 
> 
> But that is just for the sake of performance, right? I didn't consider
> gensym because I read somewhere that it is mostly applied for non-user
> variables. Anyways, it makes sense to me, and it seems to be better
> than my original code.

I skimmed through the PCL "Macros: Defining Your Own" again and
looked at the macroexpansions with some of the parameters being
function calls and understood. If your code gets expanded it results
in fewer calls to these functions and you made use of gensym in
order to avoid possible issues. Maybe I should consider reading
"On Lisp" in order to improve my macro skills, since they aren't
second nature to me yet.

You were of great help for me!


Regards,

dhl
From: Richard M Kreuter
Subject: Re: Help defining a macro
Date: 
Message-ID: <87bqgm5jn7.fsf@tan-ru.localdomain>
Daniel Leidisch <····@leidisch.net> writes:
> Thus spoke Daniel Leidisch <····@leidisch.net>:
>> Thus spoke Thomas A. Russ <···@sevak.isi.edu>:
>>
>>> There is also a potential issue with how often you end up
>>> evaluating the STRING, FIELD-SEPERATOR [sic] and FROM-END forms.
>>> You can solve that by judicious use of GENSYM's in a surrounding
>>> LET form:
>>> 
>>> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>>>                                (from-end nil)) &body body)
>>>  (let ((string-var (gensym "STRING-"))
>>>        (separator-var (gensym "SEPARATOR-"))
>>>        (from-end-var (gensym "FROM-END-")))
>>> 
>>>  `(let ((,string-var ,string)
>>>         (,separator-var ,field-seperator)
>>>         (,from-end-var ,from-end))
>>>    (let ,(loop for var in varlist collect
>>>              (list (car var)
>>>                    `(get-field ,string-var ,(cadr var)
>>>                                :field-seperator ,separator-var
>>>                                :from-end ,from-end-var)))
>>>       ,@body))))
>>> 
>> 
>> But that is just for the sake of performance, right? I didn't
>> consider gensym because I read somewhere that it is mostly applied
>> for non-user variables. Anyways, it makes sense to me, and it seems
>> to be better than my original code.
>
> I skimmed through the PCL "Macros: Defining Your Own" again and
> looked at the macroexpansions with some of the parameters being
> function calls and understood. If your code gets expanded it results
> in fewer calls to these functions and you made use of gensym in
> order to avoid possible issues. Maybe I should consider reading "On
> Lisp" in order to improve my macro skills, since they aren't second
> nature to me yet.

In case you need a concrete example: suppose the expression that
evaluated to the string were a call to READ-LINE.  In this case, each
call to a naive BIND-FIELDS that permitted repeated evaluation would
consume as many lines of input as variables in VARLIST, and the
expansion would bind the variables to fields from successive lines
from the stream, rather than successive fields in one record.

--
RmK
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f2cfn0$7jk$00$1@news.t-online.com>
Thus spoke Richard M Kreuter <·······@progn.net>:
> In case you need a concrete example: suppose the expression that
> evaluated to the string were a call to READ-LINE.  In this case, each
> call to a naive BIND-FIELDS that permitted repeated evaluation would
> consume as many lines of input as variables in VARLIST, and the
> expansion would bind the variables to fields from successive lines
> from the stream, rather than successive fields in one record.

Yes, that's what I did. I reread it in PCL and compared the
macroexpansion of my code, with parameters being (random), to the
improved one, then it was obvious.

Regards,

dhl

> --
> RmK
From: Thomas A. Russ
Subject: Re: Help defining a macro
Date: 
Message-ID: <ymisl9wwruh.fsf@sevak.isi.edu>
Daniel Leidisch <····@leidisch.net> writes:

> > There is also a potential issue with how often you end up evaluating the
> > STRING, FIELD-SEPERATOR [sic] and FROM-END forms.  You can solve that by
> > judicious use of GENSYM's in a surrounding LET form:
> > 
> > (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
> >                                (from-end nil)) &body body)
> >  (let ((string-var (gensym "STRING-"))
> >        (separator-var (gensym "SEPARATOR-"))
> >        (from-end-var (gensym "FROM-END-")))
> > 
> >  `(let ((,string-var ,string)
> >         (,separator-var ,field-seperator)
> >         (,from-end-var ,from-end))
> >    (let ,(loop for var in varlist collect
> >              (list (car var)
> >                    `(get-field ,string-var ,(cadr var)
> >                                :field-seperator ,separator-var
> >                                :from-end ,from-end-var)))
> >       ,@body))))
> > 
> 
> But that is just for the sake of performance, right? I didn't consider
> gensym because I read somewhere that it is mostly applied for non-user
> variables. Anyways, it makes sense to me, and it seems to be better
> than my original code.

No, it is not just for performance.  It is to avoid potential bugs when
evaluating a argument happens to have side effects.   For your macro it
is generally unlikely to be a problem, but I can come up with an only
mildly contrived situation where it could matter.  Try this with and
without the gensyms.

  (bind-fields (((foo 1) (bar 3)) (read-line))
     (print (list foo bar)))

If you try it without gensyms, you will need to enter two lines at
the console.  With gensyms only one.  Compare the expansions:

   Without Gensyms:

(LET ((FOO (GET-FIELD (READ-LINE) 1
                      :FIELD-SEPERATOR "\\s+"
                      :FROM-END NIL))
      (BAR (GET-FIELD (READ-LINE) 3
                      :FIELD-SEPERATOR "\\s+"
                      :FROM-END NIL)))
  (PRINT (LIST FOO BAR)))

   With Gensyms:

(LET ((#:STRING-41073 (READ-LINE))
      (#:SEPARATOR-41074 "\\s+")
      (#:FROM-END-41075 NIL))
  (LET ((FOO (GET-FIELD #:STRING-41073 1
                        :FIELD-SEPERATOR #:SEPARATOR-41074
                        :FROM-END #:FROM-END-41075))
        (BAR (GET-FIELD #:STRING-41073 3
                        :FIELD-SEPERATOR #:SEPARATOR-41074
                        :FROM-END #:FROM-END-41075)))
    (PRINT (LIST FOO BAR))))


And yes, the gensyms are non-user variables.  They are used in the macro
expansion to hold the values, and are thus never seen by user code.

If you wanted to be super-clever, you could test to see if the arguments
are constants or symbols instead of forms and reduce the number of
gensyms in certain cases, but I've usually not found that be to necessary.

-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f2fth1$ja$01$1@news.t-online.com>
Thus spoke Thomas A. Russ <···@sevak.isi.edu>:
> No, it is not just for performance.  It is to avoid potential bugs when
> evaluating a argument happens to have side effects.   For your macro it
> is generally unlikely to be a problem, but I can come up with an only
> mildly contrived situation where it could matter.  Try this with and
> without the gensyms.
> 
>  (bind-fields (((foo 1) (bar 3)) (read-line))
>     (print (list foo bar)))
> 
> If you try it without gensyms, you will need to enter two lines at
> the console.  With gensyms only one.  Compare the expansions:
> 
>   Without Gensyms:
> 
> (LET ((FOO (GET-FIELD (READ-LINE) 1
>                      :FIELD-SEPERATOR "\\s+"
>                      :FROM-END NIL))
>      (BAR (GET-FIELD (READ-LINE) 3
>                      :FIELD-SEPERATOR "\\s+"
>                      :FROM-END NIL)))
>  (PRINT (LIST FOO BAR)))
> 
>   With Gensyms:
> 
> (LET ((#:STRING-41073 (READ-LINE))
>      (#:SEPARATOR-41074 "\\s+")
>      (#:FROM-END-41075 NIL))
>  (LET ((FOO (GET-FIELD #:STRING-41073 1
>                        :FIELD-SEPERATOR #:SEPARATOR-41074
>                        :FROM-END #:FROM-END-41075))
>        (BAR (GET-FIELD #:STRING-41073 3
>                        :FIELD-SEPERATOR #:SEPARATOR-41074
>                        :FROM-END #:FROM-END-41075)))
>    (PRINT (LIST FOO BAR))))
> 
> 
> And yes, the gensyms are non-user variables.  They are used in the macro
> expansion to hold the values, and are thus never seen by user code.
> 
> If you wanted to be super-clever, you could test to see if the arguments
> are constants or symbols instead of forms and reduce the number of
> gensyms in certain cases, but I've usually not found that be to necessary.

I see. Yesterday, when I read it up again, tried and thought about it, it
became clear. Again, thanks for helping!


Regards,

dhl
From: viper-2
Subject: Re: Help defining a macro
Date: 
Message-ID: <1180028476.903319.151460@k79g2000hse.googlegroups.com>
Where might I find the code for the utility SPLIT?

agthompson
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f34ucv$5ct$01$1@news.t-online.com>
Thus spoke viper-2 <········@mail.infochan.com>:
> Where might I find the code for the utility SPLIT?

In my case, it's the one from Edi Weitz' cl-ppcre:

http://www.weitz.de/cl-ppcre

There is also cl-utilities:split-sequence:

http://common-lisp.net/project/cl-utilities/doc/split-sequence.html

Both are ASDF-installable.

Regards, 

dhl

> agthompson
> 
From: viper-2
Subject: Re: Help defining a macro
Date: 
Message-ID: <1180042266.711001.11890@p77g2000hsh.googlegroups.com>
On May 24, 5:00 pm, Daniel Leidisch <····@leidisch.net> wrote:
> Thus spoke viper-2 <········@mail.infochan.com>:
>
> > Where might I find the code for the utility SPLIT?
>
> In my case, it's the one from Edi Weitz' cl-ppcre:
>
> http://www.weitz.de/cl-ppcre
>
> There is also cl-utilities:split-sequence:
>
> http://common-lisp.net/project/cl-utilities/doc/split-sequence.html
>
> Both are ASDF-installable.
>
> Regards,
>
> dhl> agthompson


Got it - thank you.

agthompson
From: Pascal Bourguignon
Subject: Re: Help defining a macro
Date: 
Message-ID: <87k5vcia8u.fsf@thalassa.lan.informatimago.com>
Daniel Leidisch <····@leidisch.net> writes:
>
> I'm trying to write a macro which binds variables to fields of a
> string. I want to call it like this:
>
> (defvar *string* "the quick brown fox")
> (bind-fields (((foo 0) (bar 3)) *string*)
>   (format nil "~a ~a" foo bar)) => "the fox" 
>
> The code:
>
> (defun get-field (string column &key (field-seperator "\\s+")
> 									(from-end nil))
> 	(let* ((fields (split field-seperator string))
> 				 (length (length fields)))
> 		(when (< column length)
> 			(elt fields (if from-end
> 											(- length (1+ column))
> 											column)))))
>
> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
> 																(from-end nil)) &body body)
> 	`(let ,(loop for var in varlist collect
> 							(list (car var)
> 										(get-field string (cadr var)
> 															 :field-seperator field-seperator
> 															 :from-end from-end)))
> 		 ,@body))
>

It would be more readable like this (don't use TAB):

(defun get-field (string column &key (field-seperator "\\s+")
                  (from-end nil))
  (let* ((fields (split field-seperator string))
         (length (length fields)))
    (when (< column length)
      (elt fields (if from-end
                      (- length (1+ column))
                      column)))))

(defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
                                (from-end nil)) &body body)
  `(let ,(loop for var in varlist collect
              (list (car var)
                    (get-field string (cadr var)
                               :field-seperator field-seperator
                               :from-end from-end)))
     ,@body))



> But it only works if I pass it a literal string, since get-field
> would just be called with the symbol otherwise. I considered calling
> get-field with (symbol-value string), but that would only work for
> dynamic variables and it'd be far from correct (and one could not
> pass literal strings).
>
> Any hints for solving this or other useful tips/criticism?

This is not possible.

Well, the problem is that when you call:

    (bind-fields (((a 0) (b 3) (c 1)) some-string)
        ...)

you don't know at macroexpansion time how many fields you will have in
some-string.  So you must defer the field splitting to run-time.  
You'll have to expand to something like:

    (destructuring-bind (a b c)
       (split-fields some-string :field-indices (list 0 3 1)
                                 :field-separator field-separator
                                 :from-end from-end)
       ...)



(defun split-fields (string &key field-indices field-separator from-end)
   (let ((fields (split field-separator string)))
      (mapcar (lambda (index) (elt fields index)) field-indices)))

(defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
                                (from-end nil)) &body body)
  `(destructuring-bind ,(mapcar (function car) varlist)
       (split-fields ,string
           :field-indices (list ,@(mapcar (function cdr) varlist))
           :field-seperator ,field-seperator
           :from-end ,from-end)
     ,@body))


So now you can write even: 

   (let ((i 10))
     (bind-fields (((a i) (b (+ 3 i)) (c (+ 1 i))) some-string)
         (list a b c)))




-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

NOTE: The most fundamental particles in this product are held
together by a "gluing" force about which little is currently known
and whose adhesive power can therefore not be permanently
guaranteed.
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f289t9$upg$03$1@news.t-online.com>
Thus spoke Pascal Bourguignon <···@informatimago.com>:
> It would be more readable like this (don't use TAB):
> 
> (defun get-field (string column &key (field-seperator "\\s+")
>                  (from-end nil))
>  (let* ((fields (split field-seperator string))
>         (length (length fields)))
>    (when (< column length)
>      (elt fields (if from-end
>                      (- length (1+ column))
>                      column)))))
> 
> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>                                (from-end nil)) &body body)
>  `(let ,(loop for var in varlist collect
>              (list (car var)
>                    (get-field string (cadr var)
>                               :field-seperator field-seperator
>                               :from-end from-end)))
>     ,@body))

Yes, sorry I forgot to expand the tabs.

>> But it only works if I pass it a literal string, since get-field
>> would just be called with the symbol otherwise. I considered calling
>> get-field with (symbol-value string), but that would only work for
>> dynamic variables and it'd be far from correct (and one could not
>> pass literal strings).
>>
>> Any hints for solving this or other useful tips/criticism?
> 
> This is not possible.
> 
> Well, the problem is that when you call:
> 
>    (bind-fields (((a 0) (b 3) (c 1)) some-string)
>        ...)
> 
> you don't know at macroexpansion time how many fields you will have in
> some-string.  So you must defer the field splitting to run-time.  
> You'll have to expand to something like:

Hm, I'm not sure if I understand -- I thought the problem was that
some-string doesn't get expanded, since it works with literals.
I guess I'm just too tired at the moment.

>    (destructuring-bind (a b c)
>       (split-fields some-string :field-indices (list 0 3 1)
>                                 :field-separator field-separator
>                                 :from-end from-end)
>       ...)
> 
> 
> 
> (defun split-fields (string &key field-indices field-separator from-end)
>   (let ((fields (split field-separator string)))
>      (mapcar (lambda (index) (elt fields index)) field-indices)))
> 
> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>                                (from-end nil)) &body body)
>  `(destructuring-bind ,(mapcar (function car) varlist)
>       (split-fields ,string
>           :field-indices (list ,@(mapcar (function cdr) varlist))
>           :field-seperator ,field-seperator
>           :from-end ,from-end)
>     ,@body))
> 
> So now you can write even: 
> 
>   (let ((i 10))
>     (bind-fields (((a i) (b (+ 3 i)) (c (+ 1 i))) some-string)
>         (list a b c)))

That code gives me an [Condition of type UNDEFINED-FUNCTION] for I.
In order to understand you code correctly I'll have to check it
tomorrow, I will have to read up on the different times again (and
check destructuring-bind in the Hyperspec). I'll do that tomorrow.

Thanks for your help!

dhl
From: Pillsy
Subject: Re: Help defining a macro
Date: 
Message-ID: <1179103267.639482.9910@h2g2000hsg.googlegroups.com>
On May 13, 8:18 pm, Daniel Leidisch <····@leidisch.net> wrote:
> Thus spoke Pascal Bourguignon <····@informatimago.com>:
[...]
> > (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
> >                                (from-end nil)) &body body)
> >  `(destructuring-bind ,(mapcar (function car) varlist)
> >       (split-fields ,string
> >           :field-indices (list ,@(mapcar (function cdr) varlist))
> >           :field-seperator ,field-seperator
> >           :from-end ,from-end)
> >     ,@body))

> > So now you can write even:

> >   (let ((i 10))
> >     (bind-fields (((a i) (b (+ 3 i)) (c (+ 1 i))) some-string)
> >         (list a b c)))

> That code gives me an [Condition of type UNDEFINED-FUNCTION] for I.
> In order to understand you code correctly I'll have to check it
> tomorrow, I will have to read up on the different times again (and
> check destructuring-bind in the Hyperspec). I'll do that tomorrow.

If you macroexpand Pascal's version to bind-fields, you'll see
something like this:

(DESTRUCTURING-BIND (A B C)
       (SPLIT-FIELDS SOME-STRING :FIELD-INDICES (LIST (I) ((+ 3 I))
((+ 1 I)))
                     :FIELD-SEPARATOR "\\s+")
       (LIST A B C))

As you can see, the arguments for LIST in :FIELD-INDICES is malformed,
and can be fixed with a judicious application of the function APPEND.
Macroexpanding is key to debugging macros.

Cheers,
Pillsy
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f28c7g$nqs$00$1@news.t-online.com>
Thus spoke Pillsy <·········@gmail.com>:
> If you macroexpand Pascal's version to bind-fields, you'll see
> something like this:
> 
> (DESTRUCTURING-BIND (A B C)
>       (SPLIT-FIELDS SOME-STRING :FIELD-INDICES (LIST (I) ((+ 3 I))
> ((+ 1 I)))
>                     :FIELD-SEPARATOR "\\s+")
>       (LIST A B C))
> 
> As you can see, the arguments for LIST in :FIELD-INDICES is malformed,
> and can be fixed with a judicious application of the function APPEND.
> Macroexpanding is key to debugging macros.

Well, I tried that, but

(macroexpand-1 '(let ((i 10))
                 (bind-fields (((a i) (b (+ 3 i)) (c (+ 1 i)))
                               some-string)
                   (list a b c))))

just gave me (SBCL):

(LET ((I 10))
  (BIND-FIELDS (((A I) (B (+ 3 I)) (C (+ 1 I))) SOME-STRING)
    (LIST A B C)))

and macroexpand just gave the same output. Isn't macroexpand
supposed to expand further than macroexpand-1? Up to now I only
used macroexpand-1.


> Cheers,
> Pillsy
> 
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f28da0$5s3$02$1@news.t-online.com>
Thus spoke Daniel Leidisch <····@leidisch.net>:
> Thus spoke Pillsy <·········@gmail.com>:
>> If you macroexpand Pascal's version to bind-fields, you'll see
>> something like this:
>> 
>> (DESTRUCTURING-BIND (A B C)
>>       (SPLIT-FIELDS SOME-STRING :FIELD-INDICES (LIST (I) ((+ 3 I))
>> ((+ 1 I)))
>>                     :FIELD-SEPARATOR "\\s+")
>>       (LIST A B C))
>> 
>> As you can see, the arguments for LIST in :FIELD-INDICES is malformed,
>> and can be fixed with a judicious application of the function APPEND.
>> Macroexpanding is key to debugging macros.
> 
> Well, I tried that, but
> 
> (macroexpand-1 '(let ((i 10))
>                 (bind-fields (((a i) (b (+ 3 i)) (c (+ 1 i)))
>                               some-string)
>                   (list a b c))))
> 
> just gave me (SBCL):
> 
> (LET ((I 10))
>  (BIND-FIELDS (((A I) (B (+ 3 I)) (C (+ 1 I))) SOME-STRING)
>    (LIST A B C)))

Oh, stupid me! Of corse I should have omitted the let. It's
definitively time to go to bed ;)

dhl
From: Pascal Bourguignon
Subject: Re: Help defining a macro
Date: 
Message-ID: <87fy60i69t.fsf@thalassa.lan.informatimago.com>
Daniel Leidisch <····@leidisch.net> writes:

> Thus spoke Pascal Bourguignon <···@informatimago.com>:
>> It would be more readable like this (don't use TAB):
>> 
>> (defun get-field (string column &key (field-seperator "\\s+")
>>                  (from-end nil))
>>  (let* ((fields (split field-seperator string))
>>         (length (length fields)))
>>    (when (< column length)
>>      (elt fields (if from-end
>>                      (- length (1+ column))
>>                      column)))))
>> 
>> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>>                                (from-end nil)) &body body)
>>  `(let ,(loop for var in varlist collect
>>              (list (car var)
>>                    (get-field string (cadr var)
>>                               :field-seperator field-seperator
>>                               :from-end from-end)))
>>     ,@body))
>
> Yes, sorry I forgot to expand the tabs.
>
>>> But it only works if I pass it a literal string, since get-field
>>> would just be called with the symbol otherwise. I considered calling
>>> get-field with (symbol-value string), but that would only work for
>>> dynamic variables and it'd be far from correct (and one could not
>>> pass literal strings).
>>>
>>> Any hints for solving this or other useful tips/criticism?
>> 
>> This is not possible.
>> 
>> Well, the problem is that when you call:
>> 
>>    (bind-fields (((a 0) (b 3) (c 1)) some-string)
>>        ...)
>> 
>> you don't know at macroexpansion time how many fields you will have in
>> some-string.  So you must defer the field splitting to run-time.  
>> You'll have to expand to something like:
>
> Hm, I'm not sure if I understand -- I thought the problem was that
> some-string doesn't get expanded, since it works with literals.
> I guess I'm just too tired at the moment.
>
>>    (destructuring-bind (a b c)
>>       (split-fields some-string :field-indices (list 0 3 1)
>>                                 :field-separator field-separator
>>                                 :from-end from-end)
>>       ...)
>> 
>> 
>> 
>> (defun split-fields (string &key field-indices field-separator from-end)
>>   (let ((fields (split field-separator string)))
>>      (mapcar (lambda (index) (elt fields index)) field-indices)))
>> 
>> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>>                                (from-end nil)) &body body)
>>  `(destructuring-bind ,(mapcar (function car) varlist)
>>       (split-fields ,string
>>           :field-indices (list ,@(mapcar (function cdr) varlist))
>>           :field-seperator ,field-seperator
>>           :from-end ,from-end)
>>     ,@body))
>> 
>> So now you can write even: 
>> 
>>   (let ((i 10))
>>     (bind-fields (((a i) (b (+ 3 i)) (c (+ 1 i))) some-string)
>>         (list a b c)))
>
> That code gives me an [Condition of type UNDEFINED-FUNCTION] for I.
> In order to understand you code correctly I'll have to check it
> tomorrow, I will have to read up on the different times again (and
> check destructuring-bind in the Hyperspec). I'll do that tomorrow.
>
> Thanks for your help!

s/cdr/second/ in the macro.  Sorry.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

NOTE: The most fundamental particles in this product are held
together by a "gluing" force about which little is currently known
and whose adhesive power can therefore not be permanently
guaranteed.
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f28cjl$nqs$00$2@news.t-online.com>
Thus spoke Pascal Bourguignon <···@informatimago.com>:
>
> s/cdr/second/ in the macro.  Sorry.
> 
> 

Yes, now it works, thanks again! Tomorrow I'll work up on it.

Cheers,

dhl
From: Daniel Leidisch
Subject: Re: Help defining a macro
Date: 
Message-ID: <f29q3i$muk$00$1@news.t-online.com>
Thus spoke Pascal Bourguignon <···@informatimago.com>:
> (defun split-fields (string &key field-indices field-separator from-end)
>   (let ((fields (split field-separator string)))
>      (mapcar (lambda (index) (elt fields index)) field-indices)))
> 
> (defmacro bind-fields ((varlist string &key (field-seperator "\\s+")
>                                (from-end nil)) &body body)
>  `(destructuring-bind ,(mapcar (function car) varlist)
>       (split-fields ,string
>           :field-indices (list ,@(mapcar (function cdr) varlist))
>           :field-seperator ,field-seperator
>           :from-end ,from-end)
>     ,@body))
> 
> 
> So now you can write even: 
> 
>   (let ((i 10))
>     (bind-fields (((a i) (b (+ 3 i)) (c (+ 1 i))) some-string)
>         (list a b c)))

Now that I understand your code, here's my version (the macro mostly 
unchanged):

(defun split-fields (string &key field-indices (field-separator "\\s+")
                     from-end)
  (let* ((fields (split field-separator string))
         (length (length fields)))
    (if field-indices
        (mapcar (lambda (index) (if (< index length)
                                    (elt fields index) ""))
                (if from-end
                    (mapcar (lambda (index)
                              (- length (1+ index))) field-indices)
                    field-indices))
        (if from-end (reverse fields) fields))))
 
(defmacro bind-fields ((varlist string &key (field-separator "\\s+")
                                (from-end nil)) &body body)
  `(destructuring-bind ,(mapcar #'car varlist)
       (split-fields ,string
                     :field-indices (list ,@(mapcar #'second varlist))
                     :field-separator ,field-separator
                     :from-end ,from-end)
     ,@body))

So now:

(defvar some-string "the quick brown fox jumps over the lazy dog")

(split-fields some-string)
=> ("the" "quick" "brown" "fox" "jumps" "over" "the" "lazy" "dog")

(split-fields some-string :field-indices '(0 2))
=> ("the" "brown")

(split-fields some-string :field-indices '(0 2) :from-end t)
=> ("dog" "the")

(bind-fields (((first 0) (third 2) (x 222)) some-string)
  (list first third x))
=> ("the" "brown" "")

works. Thanks again for helping.


Regards,

dhl