1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
;;; loop.el --- friendly imperative loop structures
;; Copyright (C) 2013 Wilfred Hughes
;; Author: Wilfred Hughes <me@wilfred.me.uk>
;; Version: 1.3
;; Keywords: loop, while, for each, break, continue
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Emacs lisp is missing loop structures familiar to users of newer
;; languages. This library adds a selection of popular loop structures
;; as well as break and continue.
;; Future ideas:
;; * Named loops so you can break/continue outer loops
(defmacro loop-while (condition &rest body)
"Repeatedly evaluate BODY while CONDITION is non-nil."
(declare (indent defun) (debug (form &rest form)))
`(catch 'loop-break
(while ,condition
(catch 'loop-continue
,@body))))
(defmacro loop-do-while (condition &rest body)
"Evaluate BODY, then repeatedly BODY while CONDITION is non-nil."
(declare (indent defun) (debug (form &rest form)))
(let ((is-first-iteration-var (make-symbol "first-iteration-p")))
`(catch 'loop-break
(progn
(catch 'loop-continue
,@body)
(while ,condition
(catch 'loop-continue
,@body))))))
(defmacro loop-until (condition &rest body)
"Repeatedly evaluate BODY until CONDITION is non-nil."
(declare (indent defun) (debug (form &rest form)))
`(loop-while (not ,condition) ,@body))
;; todo: support vectors and strings
(defmacro loop-for-each (var list &rest body)
"For every item in LIST, evaluate BODY with VAR bound to that item."
(declare (indent defun) (debug (symbolp form &rest form)))
(let ((list-var (make-symbol "list")))
`(catch 'loop-break
(let ((,list-var ,list)
(,var))
(while ,list-var
(catch 'loop-continue
(setq ,var (car ,list-var))
(setq ,list-var (cdr ,list-var))
,@body))))))
(defun loop--last-line-p ()
"Return non-nil if point is on the last line in the buffer."
(looking-at (rx (0+ not-newline) buffer-end)))
(defun loop--current-line ()
"Return the current line that contains point."
(save-excursion
(let ((line-start (progn (beginning-of-line) (point)))
(line-end (progn (end-of-line) (point))))
(buffer-substring line-start line-end))))
(defmacro loop-for-each-line (&rest body)
"Execute BODY for every line in the buffer.
Point is placed at the start of the line on each iteration.
Inside BODY, `it' is bound to the contents of the current line."
(declare (indent 0) (debug (&rest form)))
`(save-excursion
(catch 'loop-break
(goto-char (point-min))
;; Execute body on all but the last line.
(while (not (loop--last-line-p))
(catch 'loop-continue
(save-excursion
(let ((it (loop--current-line)))
,@body)))
(forward-line))
;; Execute body on the last line.
(catch 'loop-continue
(let ((it (loop--current-line)))
,@body)))))
(defsubst loop-break ()
"Terminate evaluation of a `loop-while', `loop-do-while', or `loop-for-each' block.
If there are nested loops, breaks out of the innermost loop."
(throw 'loop-break nil))
(defun loop-continue ()
"Skip the rest of the current `loop-while', `loop-do-while', or
`loop-for-each' block and continue to the next iteration. If there
are nested loops, applies to the innermost loop."
(throw 'loop-continue nil))
(defun loop-return (value)
"Terminate evaluation of a `loop-while', `loop-do-while', or `loop-for-each' block.
The return value from the loop is VALUE."
(throw 'loop-break value))
(provide 'loop)
;;; loop.el ends here
|