c5fc3602d000

Add do-file
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Thu, 08 Apr 2021 20:29:51 -0400
parents f9fec2ff0ff5
children 4e5867a99afe
branches/tags (none)
files package.lisp src/control-flow.lisp

Changes

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