# HG changeset patch # User Steve Losh # Date 1617928191 14400 # Node ID c5fc3602d0001a1088c131190d22010cbe410ac1 # Parent f9fec2ff0ff5e91811372d62e0722c8c62c1c6d2 Add do-file diff -r f9fec2ff0ff5 -r c5fc3602d000 package.lisp --- a/package.lisp Thu Apr 08 20:29:23 2021 -0400 +++ b/package.lisp Thu Apr 08 20:29:51 2021 -0400 @@ -202,7 +202,8 @@ :multiple-value-bind* :do-repeat :do-range - :do-irange)) + :do-irange + :do-file)) (defpackage :losh.math diff -r f9fec2ff0ff5 -r c5fc3602d000 src/control-flow.lisp --- a/src/control-flow.lisp Thu Apr 08 20:29:23 2021 -0400 +++ b/src/control-flow.lisp Thu Apr 08 20:29:51 2021 -0400 @@ -516,3 +516,49 @@ ,(recur (rest ranges))))))))) +(let ((eof (gensym "EOF"))) + (defmacro do-file + ((symbol path &rest open-options &key (reader '#'read-line) &allow-other-keys) + &body body) + "Iterate over the contents of `file` using `reader`. + + During iteration, `symbol` will be set to successive values read from the + file by `reader`. + + `reader` can be any function that conforms to the usual reading interface, + i.e. anything that can handle `(read-foo stream eof-error-p eof-value)`. + + Any keyword arguments other than `:reader` will be passed along to `open`. + If `nil` is used for one of the `:if-…` options to `open` and this results + in `open` returning `nil`, no iteration will take place. + + An implicit block named `nil` surrounds the iteration, so `return` can be + used to terminate early. + + Returns `nil` by default. + + Examples: + + (do-file (line \"foo.txt\") + (print line)) + + (do-file (form \"foo.lisp\" :reader #'read :external-format :EBCDIC-US) + (when (eq form :stop) + (return :stopped-early)) + (print form)) + + (do-file (line \"does-not-exist.txt\" :if-does-not-exist nil) + (this-will-not-be-executed)) + + " + (let ((open-options (alexandria:remove-from-plist open-options :reader))) + (with-gensyms (stream) + (once-only (path reader) + `(when-let ((,stream (open ,path :direction :input ,@open-options))) + (unwind-protect + (do ((,symbol + (funcall ,reader ,stream nil ',eof) + (funcall ,reader ,stream nil ',eof))) + ((eq ,symbol ',eof)) + ,@body) + (close ,stream))))))))