emacs-config/elpa/loop-20160813.1407/loop.el
2020-04-28 12:47:35 +02:00

122 lines
4.2 KiB
EmacsLisp

;;; loop.el --- friendly imperative loop structures
;; Copyright (C) 2013 Wilfred Hughes
;; Author: Wilfred Hughes <me@wilfred.me.uk>
;; Version: 1.4
;; Package-Version: 20160813.1407
;; 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