From: Erik Winkels
Subject: Reading lines from file macro
Date: 
Message-ID: <87k71jhazr.fsf@xs4all.nl>
Hi,

I never really made any macro's since I started programming in CL.
However, one idiom I use a lot when using CLISP for shell scripts is
reading successive lines from a file and that couldn't be fit into a
defun.

This is my first stab at such a macro:

    (defmacro with-lines-from-file ((var filename) &body body)
      `(with-open-file (stream ,filename)
         (loop for line = (read-line stream nil nil)
               while line
               do (let ((,var line))
                    ,@body))))

How would an experienced macrologist write such a macro?  I suppose
one would at least use gensyms instead of 'line' and 'filename'?
Would read-lines-from-file be a better name?

(And, yes, I'll start reading On Lisp real soon now I've seen the
 light :)


Thanks,
Erik.

From: Steven E. Harris
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <q67oeqvb0ow.fsf@L75001820.us.ray.com>
Erik Winkels <·······@xs4all.nl> writes:

> How would an experienced macrologist write such a macro?

I'm no "macrologist," but I did come up with these and refined them
with some help from others on this newsgroup:


(defun call-with-lines (stream function)
  (loop for line = (read-line stream nil)
        while line
        do (funcall function line)))


(defmacro dolines ((var stream) &body body)
  `(block nil
    (let ((.f. #'(lambda (,var)
                   (declare (ignorable ,var))
                   ,@body)))
      (declare (dynamic-extent .f.))
      (funcall #'call-with-lines ,stream .f.))))


I had originally written dolines as the core loop, with
call-with-lines dependent on dolines, but others convinced me that
layering it this way is better.�

Note that these only deal with already open streams, so an extra layer
of wrapping is necessary to include the file opening details. Don't
forget that you may want to forward on same keyword arguments to
with-open-file such as :element-type or :external-format. The defaults
for :direction and :if-does-not-exist are likely correct for your
usage.


Footnotes: 
� http://groups.google.com/groups?threadm=xcvu1502qb6.fsf%40famine.OCF.Berkeley.EDU
  (and other private correspondence)

-- 
Steven E. Harris        :: ········@raytheon.com
Raytheon                :: http://www.raytheon.com
From: Espen Vestre
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <kw8yhzoaly.fsf@merced.netfonds.no>
Erik Winkels <·······@xs4all.nl> writes:

>     (defmacro with-lines-from-file ((var filename) &body body)
>       `(with-open-file (stream ,filename)
>          (loop for line = (read-line stream nil nil)
>                while line
>                do (let ((,var line))
>                     ,@body))))

Congratulations on using LOOP for one of its most pretty applications
:-) (The stubborn loop-ignorants usually write some ugly
DO-replacement for the nice "for line = ... while line"-idiom)

I guess you can just glue in the VAR, can't you?

I think I'd prefer this slight modification, then:

(defmacro with-lines-from-file ((var filename) &body body)
  (let ((stream (gensym)))
   `(with-open-file (,stream ,filename)
       (loop for ,var = (read-line ,stream nil nil)
             while ,var
             do ,@body))))

> How would an experienced macrologist write such a macro?  I suppose
> one would at least use gensyms instead of 'line' and 'filename'?

I guess you mean 'line' and 'stream'? See above.

> Would read-lines-from-file be a better name?

No :-)

-- 
  (espen)
From: Marco Antoniotti
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <nj36c.106$IJ5.80828@typhoon.nyu.edu>
Hi

All very nice, but it conses a lot.  And READ-SEQUENCE does not help. 
We need a

   READ-LINE* buffer &optional stream eof-error-p eof-value recursive-p

returning BUFFER and the length of the line read.  Some implementations 
have something like it.

Cheers

Marco




Espen Vestre wrote:
> Erik Winkels <·······@xs4all.nl> writes:
> 
> 
>>    (defmacro with-lines-from-file ((var filename) &body body)
>>      `(with-open-file (stream ,filename)
>>         (loop for line = (read-line stream nil nil)
>>               while line
>>               do (let ((,var line))
>>                    ,@body))))
> 
> 
> Congratulations on using LOOP for one of its most pretty applications
> :-) (The stubborn loop-ignorants usually write some ugly
> DO-replacement for the nice "for line = ... while line"-idiom)
> 
> I guess you can just glue in the VAR, can't you?
> 
> I think I'd prefer this slight modification, then:
> 
> (defmacro with-lines-from-file ((var filename) &body body)
>   (let ((stream (gensym)))
>    `(with-open-file (,stream ,filename)
>        (loop for ,var = (read-line ,stream nil nil)
>              while ,var
>              do ,@body))))
> 
> 
>>How would an experienced macrologist write such a macro?  I suppose
>>one would at least use gensyms instead of 'line' and 'filename'?
> 
> 
> I guess you mean 'line' and 'stream'? See above.
> 
> 
>>Would read-lines-from-file be a better name?
> 
> 
> No :-)
> 
From: Barry Margolin
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <barmar-998250.16473617032004@comcast.ash.giganews.com>
In article <···················@typhoon.nyu.edu>,
 Marco Antoniotti <·······@cs.nyu.edu> wrote:

> Hi
> 
> All very nice, but it conses a lot.  And READ-SEQUENCE does not help. 
> We need a
> 
>    READ-LINE* buffer &optional stream eof-error-p eof-value recursive-p
> 
> returning BUFFER and the length of the line read.  Some implementations 
> have something like it.

The macros suggested cons one string for every line read, that's hardly 
"a lot".  If the application doesn't need to retain these strings long 
term then they'll become garbage quickly, and a good ephemeral GC will 
clean them up quickly.  I expect that the overhead of the garbage will 
not be as much as the overhead of the disk I/O.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Marco Antoniotti
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <yDj6c.109$IJ5.83075@typhoon.nyu.edu>
Barry Margolin wrote:

> In article <···················@typhoon.nyu.edu>,
>  Marco Antoniotti <·······@cs.nyu.edu> wrote:
> 
> 
>>Hi
>>
>>All very nice, but it conses a lot.  And READ-SEQUENCE does not help. 
>>We need a
>>
>>   READ-LINE* buffer &optional stream eof-error-p eof-value recursive-p
>>
>>returning BUFFER and the length of the line read.  Some implementations 
>>have something like it.
> 
> 
> The macros suggested cons one string for every line read, that's hardly 
> "a lot".  If the application doesn't need to retain these strings long 
> term then they'll become garbage quickly, and a good ephemeral GC will 
> clean them up quickly.  I expect that the overhead of the garbage will 
> not be as much as the overhead of the disk I/O.
> 

Still READ-LINE* seems to me a like a good idea.

Marco
From: Lars Brinkhoff
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <851xnrinx4.fsf@junk.nocrew.org>
Erik Winkels <·······@xs4all.nl> writes:
>     (defmacro with-lines-from-file ((var filename) &body body)
>       `(with-open-file (stream ,filename)
>          (loop for line = (read-line stream nil nil)
>                while line
>                do (let ((,var line))
>                     ,@body))))
> How would an experienced macrologist write such a macro?  I suppose
> one would at least use gensyms instead of 'line' and 'filename'?
> Would read-lines-from-file be a better name?

Iterative macros conventionally start with "do".  I'd like to split
the macro in two:

  (defmacro do-file-lines ((var file &rest options) &body body)
    (let ((stream (gensym)))
      `(with-open-file (,stream ,file ,@options)
         (do-stream-lines (,var ,stream)
           ,@body))))

  (defmacro do-stream-lines ((var stream-form) &body body)
    (let ((stream (gensym)))
      `(loop with ,stream = ,stream-form
             for ,var = (read-line ,stream nil nil)
             while ,var
             do (progn ,@body))))

-- 
Lars Brinkhoff,         Services for Unix, Linux, GCC, HTTP
Brinkhoff Consulting    http://www.brinkhoff.se/
From: Jock Cooper
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <m3lllzpd89.fsf@jcooper02.sagepub.com>
Lars Brinkhoff <·········@nocrew.org> writes:

> Erik Winkels <·······@xs4all.nl> writes:
>[snip]
> 
>   (defmacro do-stream-lines ((var stream-form) &body body)
>     (let ((stream (gensym)))
>       `(loop with ,stream = ,stream-form
>              for ,var = (read-line ,stream nil nil)
>              while ,var
>              do (progn ,@body))))
> 

Is there a reason why you put that PROGN in the DO part of the LOOP?
From: Lars Brinkhoff
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <85wu5jgvuw.fsf@junk.nocrew.org>
Jock Cooper <·····@mail.com> writes:
> Lars Brinkhoff <·········@nocrew.org> writes:
> > (defmacro do-stream-lines ((var stream-form) &body body)
> >   (let ((stream (gensym)))
> >     `(loop with ,stream = ,stream-form
> >            for ,var = (read-line ,stream nil nil)
> >            while ,var
> >            do (progn ,@body))))
> Is there a reason why you put that PROGN in the DO part of the LOOP?

I believe ... do ,@body ... would fail if one of the immediate
subforms of body were a symbol.

Hmm, perhaps (tagbody ,@body) would be more in line with other
iterative macros.  And I forgot about the usual result form.

It's somewhat tempting to use do*:

  (defmacro do-stream-lines ((var stream-form &optional result) &body body)
    (let ((stream (gensym)))
      `(do* ((,stream ,stream-form)
             (,var #1=(read-line ,stream nil nil) #1#))
            ((null ,var)
             ,result)
         ,@body)))

-- 
Lars Brinkhoff,         Services for Unix, Linux, GCC, HTTP
Brinkhoff Consulting    http://www.brinkhoff.se/
From: Steven M. Haflich
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <q787c.12775$Lk3.6571@newssvr27.news.prodigy.com>
Lars Brinkhoff wrote:

> Jock Cooper <·····@mail.com> writes:
> 
>>Lars Brinkhoff <·········@nocrew.org> writes:
>>
>>>(defmacro do-stream-lines ((var stream-form) &body body)
>>>  (let ((stream (gensym)))
>>>    `(loop with ,stream = ,stream-form
>>>           for ,var = (read-line ,stream nil nil)
>>>           while ,var
>>>           do (progn ,@body))))
>>
>>Is there a reason why you put that PROGN in the DO part of the LOOP?
> 
> I believe ... do ,@body ... would fail if one of the immediate
> subforms of body were a symbol.

The problem is that an atomic symbol form could possibly be a
symbol-macro, whereas this possibility is explicitly not considered
for top-level subforms of a tagbody (or loop, etc.).

> Hmm, perhaps (tagbody ,@body) would be more in line with other
> iterative macros.  And I forgot about the usual result form.

What's missing is a specification for the macro that clarifies
whether or not the body is a tagbody.  It usually isn't important,
but it's nice to write macros that cover these things and which
are fully specified.  I can think of code examples where this would
matter, but they are untypical sorts of things.

If you rename the macro do-stream-lines, then IMO it should _not_
employ the wrapping progn and should use tagbody instead.  That
would make the body compatible with all the other do iterator
bodies, which are implicit tagbodies and can emply tags.

A last language lawyer point:  Why can't ooe moit the tagbody and
just append the ,@body forms at top level of the loop?  The reason
is that the many loop keywords have reserved meanings at top level
of a loop but could be used as tag names in any of the do macros.
From: Lars Brinkhoff
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <85d676wtwv.fsf@junk.nocrew.org>
"Steven M. Haflich" <·················@alum.mit.edu> writes:
> >>Lars Brinkhoff <·········@nocrew.org> writes:
> >>>(defmacro do-stream-lines ((var stream-form) &body body)
> >>>  (let ((stream (gensym)))
> >>>    `(loop with ,stream = ,stream-form
> >>>           for ,var = (read-line ,stream nil nil)
> >>>           while ,var
> >>>           do (progn ,@body))))
> > Hmm, perhaps (tagbody ,@body) would be more in line with other
> > iterative macros.  And I forgot about the usual result form.
> If you rename the macro do-stream-lines, then IMO it should _not_
> employ the wrapping progn and should use tagbody instead.  That
> would make the body compatible with all the other do iterator
> bodies, which are implicit tagbodies and can emply tags.

That was my intent.  But there would still be something missing that
other do iterator macros can handle in the body: declarations.
Another version of the macro using DO* in the body (posted in this
thread) fixes that too.

-- 
Lars Brinkhoff,         Services for Unix, Linux, GCC, HTTP
Brinkhoff Consulting    http://www.brinkhoff.se/
From: Steven M. Haflich
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <umy7c.27010$qN3.3605@newssvr29.news.prodigy.com>
Lars Brinkhoff wrote:

> That was my intent.  But there would still be something missing that
> other do iterator macros can handle in the body: declarations.
> Another version of the macro using DO* in the body (posted in this
> thread) fixes that too.

Agreed, but if this is the definition to which you refer

   (defmacro do-stream-lines ((var stream-form &optional result) &body body)
     (let ((stream (gensym)))
       `(do* ((,stream ,stream-form)
              (,var #1=(read-line ,stream nil nil) #1#))
             ((null ,var)
              ,result)
          ,@body)))

then it would be more like do/do* if the result were a &body rather than
an &optional :

   (defmacro do-stream-lines ((var stream-form &body result) &body body)
     (let ((stream (gensym)))
       `(do* ((,stream ,stream-form)
              (,var #1=(read-line ,stream nil nil) #1#))
             ((null ,var)
              ,@result)
          ,@body)))

Making these details the same makes the language easier to remember.

By the way, I don't like using reader circularities in source code.
You will run into unexpected trouble if you try to combine multiple
separate definitions inside an eval-when or #+debug(progn ...) etc.
From: Lars Brinkhoff
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <858yhtuq8t.fsf@junk.nocrew.org>
"Steven M. Haflich" <·················@alum.mit.edu> writes:
> it would be more like do/do* if the result were a &body rather than
> an &optional :
> 
>    (defmacro do-stream-lines ((var stream-form &body result) &body body)
>      (let ((stream (gensym)))
>        `(do* ((,stream ,stream-form)
>               (,var #1=(read-line ,stream nil nil) #1#))
>              ((null ,var)
>               ,@result)
>           ,@body)))
> 
> Making these details the same makes the language easier to remember.

I was trying to mimic dolist, dotimes, do-symbols, etc all of which
have a single optional result form.

-- 
Lars Brinkhoff,         Services for Unix, Linux, GCC, HTTP
Brinkhoff Consulting    http://www.brinkhoff.se/
From: Rahul Jain
Subject: Re: Reading lines from file macro
Date: 
Message-ID: <87fzc4nuda.fsf@nyct.net>
Erik Winkels <·······@xs4all.nl> writes:

> Hi,
>
> I never really made any macro's since I started programming in CL.
> However, one idiom I use a lot when using CLISP for shell scripts is
> reading successive lines from a file and that couldn't be fit into a
> defun.

(scan-file file-name #'read-line)

;)

-- 
Rahul Jain
·····@nyct.net
Professional Software Developer, Amateur Quantum Mechanicist