From f80015b831b3631f4707908ee0744b44f838fd33 Mon Sep 17 00:00:00 2001 From: okamsn Date: Sat, 9 Mar 2024 17:54:38 -0500 Subject: [PATCH] Make `always`, `never`, and `thereis` like other accumulation commands. Closes #145. - Limit the commands to only one condition. - Implement the commands using `loopy--defaccumulation`. - Correct the documentation string of `loopy-result` to mention the boolean commands. - Fix errors in tests `custom-command-always-pass` and `custom-command-always-fail`. These tests were accidentally running the built-in `always` command, not the tested version. --- doc/loopy-doc.org | 83 ++++++++-------------------- doc/loopy.texi | 101 ++++++++++------------------------ loopy-commands.el | 137 ++++++++++++---------------------------------- loopy-vars.el | 19 +++++-- tests/tests.el | 116 ++++++++++++++++++++++++++++++--------- 5 files changed, 191 insertions(+), 265 deletions(-) diff --git a/doc/loopy-doc.org b/doc/loopy-doc.org index 1c17e0876..e6abed8c1 100644 --- a/doc/loopy-doc.org +++ b/doc/loopy-doc.org @@ -3454,61 +3454,26 @@ them to be so, you are recommended to use =accum-opt= on those variables. :END: {{{dfn(Boolean commands)}}} are used to test whether a condition holds true -during the loop. They work like a combination of iteration and accumulation -commands, in that values are stored in ~loopy-result~ and that they can -terminate the loop. +during the loop. They work like a combination of accumulation commands +([[#accumulation-commands]]) and early-exit commands ([[#exiting-the-loop-early]]), in +that values are by default stored in ~loopy-result~ and that they can terminate +the loop without forcing a return value. -Be aware of the following: - -- Currently, unlike accumulation commands, there is no non-keyword way to - specify a variable. The first argument (the only required argument) of each - boolean command is a condition to check. - -- Like accumulation commands, the keyword =:into= can be used the specify a - variable other than ~loopy-result~. - -- The =always= and =never= commands must use the same variable to work - together correctly. By default, the both use ~loopy-result~. - -- These commands exit the loop without forcing a return value - ([[#exiting-the-loop-early]]). Therefore, optimized accumulation variables can be - finalized even when the loop ends, as happens with the =leave= command. - However, because the boolean commands already use ~loopy-result~, such - optimized accumulation variables must be created with the special macro - argument =accum-opt= and must be used explicitly, as in the below example. - - -#+begin_src emacs-lisp - ;; => (nil (1 3 5)) - (loopy (accum-opt coll) - (list i '(1 3 5 6 9)) - (always (cl-oddp i)) - (collect coll i) - (finally-return loopy-result coll)) - - ;; Works similarly, but forces the `nil' return value. - ;; Returns the collection if `always' doesn't trigger an exit. - ;; Attempting similar with CL's `iterate' will signal an error. - ;; - ;; => nil - (cl-loop for i in '(1 3 5 6 9) - always (cl-oddp i) - collect i) -#+end_src - -#+attr_texinfo: :tag Warn -#+begin_quote -Using the command =thereis= for a variable is incompatible with using the -commands =always= and =never= on that same variable, as this would create -conflicting initial values for the implicit return value (both using -~loopy-result~). -#+end_quote +#+ATTR_TEXINFO: :tag Note +#+BEGIN_QUOTE +Due to how the commands work, there are restrictions to how their target +variables can be used. First, the =always= and =never= commands must use the +same variable to work together correctly. Second, using the command =thereis= +with the same variable as =always= (and/or =never=) is an error, as this would +create conflicting initial values for the implicit return value. +#+END_QUOTE #+findex: always -- =(always EXPR &key into)= :: Check the result of the condition =EXPR=. If the - condition evaluates to ~nil~, end the loop. Otherwise, the loop returns the - final value of the condition or ~t~ if the command is never run. +- =(always [VAR] EXPR &key into)= :: Check the result of the condition =EXPR=. + If the condition evaluates to ~nil~, end the loop. If the command was run, + return the value of the condition via =VAR=. Otherwise, if the command was + never run, return ~t~ via =VAR=. The steps are thus: 1. The variable (by default, ~loopy-result~) is initially bound to ~t~, using @@ -3547,15 +3512,15 @@ conflicting initial values for the implicit return value (both using #+END_SRC #+findex: never -- =(never EXPR &key into)= :: Check the condition =EXPR=. If the condition is - ever non-nil, then the loop is exited and returns ~nil~. Otherwise the loop - returns ~t~. +- =(never [VAR] EXPR &key into)= :: Check the condition =EXPR=. If the + condition is ever non-~nil~, then the loop is exited and returns ~nil~ via + =VAR=. Otherwise the loop returns ~t~ via =VAR=. The steps are thus: 1. The variable (by default, ~loopy-result~) is initialized to ~t~ and used as the loop's implicit return value. 2. The value of the condition is checked. - 3. If the condition is non-nil, then the variable is set to ~nil~ + 3. If the condition is non-~nil~, then the variable is set to ~nil~ and the loop is exited. @@ -3588,15 +3553,15 @@ conflicting initial values for the implicit return value (both using #+end_src #+findex: thereis -- =(thereis EXPR &key into)= :: Check the result of the condition =EXPR=. If - the condition evaluates to a non-~nil~ value, the loop returns that value. - Otherwise, the loop returns nil. +- =(thereis [VAR] EXPR &key into)= :: Check the result of the condition =EXPR=. + If the condition evaluates to a non-~nil~ value, the loop returns that value + via =VAR=. Otherwise, the loop returns ~nil~ via =VAR=. The steps are thus: 1. The variable (by default, ~loopy-result~) is initialized to ~nil~ and used as the implicit return value of the loop. 2. The value of the condition is stored in the variable. - 3. If the value of the variable is non-nil, the loop exits. + 3. If the value of the variable is non-~nil~, the loop exits. #+BEGIN_SRC emacs-lisp diff --git a/doc/loopy.texi b/doc/loopy.texi index f31ea7d84..00f5ede5b 100644 --- a/doc/loopy.texi +++ b/doc/loopy.texi @@ -705,7 +705,7 @@ You should keep in mind that commands are evaluated in order. This means that attempting something like the below example might not do what you expect, as @samp{i} is assigned a value from the list after collecting @samp{i} into @samp{coll}. -@float Listing,org668e9bc +@float Listing,orgbbf188f @lisp ;; => (nil 1 2) (loopy (collect coll i) @@ -887,7 +887,7 @@ the flag @samp{dash} provided by the package @samp{loopy-dash}. Below are two examples of destructuring in @code{cl-loop} and @code{loopy}. -@float Listing,orgcba0f7c +@float Listing,org7e0ff41 @lisp ;; => (1 2 3 4) (cl-loop for (i . j) in '((1 . 2) (3 . 4)) @@ -902,7 +902,7 @@ Below are two examples of destructuring in @code{cl-loop} and @code{loopy}. @caption{Destructuring values in a list.} @end float -@float Listing,org87acf60 +@float Listing,org92a276c @lisp ;; => (1 2 3 4) (cl-loop for elem in '((1 . 2) (3 . 4)) @@ -1122,7 +1122,7 @@ to @code{nil} or, if specified, a default value. Additionally, one may bind a variable to record whether the list was long enough to contain the optional value. -As in a CL @code{lambda-list}, the variable has the one of the following forms: +As in a CL @code{lambda} list, the variable has the one of the following forms: @itemize @item @@ -3727,69 +3727,28 @@ them to be so, you are recommended to use @samp{accum-opt} on those variables. @section Checking Conditions @dfn{Boolean commands} are used to test whether a condition holds true -during the loop. They work like a combination of iteration and accumulation -commands, in that values are stored in @code{loopy-result} and that they can -terminate the loop. +during the loop. They work like a combination of accumulation commands +(@ref{Accumulation}) and early-exit commands (@ref{Early Exit}), in +that values are by default stored in @code{loopy-result} and that they can terminate +the loop without forcing a return value. -Be aware of the following: - -@itemize -@item -Currently, unlike accumulation commands, there is no non-keyword way to -specify a variable. The first argument (the only required argument) of each -boolean command is a condition to check. - -@item -Like accumulation commands, the keyword @samp{:into} can be used the specify a -variable other than @code{loopy-result}. - -@item -The @samp{always} and @samp{never} commands must use the same variable to work -together correctly. By default, the both use @code{loopy-result}. - -@item -These commands exit the loop without forcing a return value -(@ref{Early Exit}). Therefore, optimized accumulation variables can be -finalized even when the loop ends, as happens with the @samp{leave} command. -However, because the boolean commands already use @code{loopy-result}, such -optimized accumulation variables must be created with the special macro -argument @samp{accum-opt} and must be used explicitly, as in the below example. -@end itemize - - -@lisp -;; => (nil (1 3 5)) -(loopy (accum-opt coll) - (list i '(1 3 5 6 9)) - (always (cl-oddp i)) - (collect coll i) - (finally-return loopy-result coll)) - -;; Works similarly, but forces the `nil' return value. -;; Returns the collection if `always' doesn't trigger an exit. -;; Attempting similar with CL's `iterate' will signal an error. -;; -;; => nil -(cl-loop for i in '(1 3 5 6 9) - always (cl-oddp i) - collect i) -@end lisp - -@quotation Warn -Using the command @samp{thereis} for a variable is incompatible with using the -commands @samp{always} and @samp{never} on that same variable, as this would create -conflicting initial values for the implicit return value (both using -@code{loopy-result}). +@quotation Note +Due to how the commands work, there are restrictions to how their target +variables can be used. First, the @samp{always} and @samp{never} commands must use the +same variable to work together correctly. Second, using the command @samp{thereis} +with the same variable as @samp{always} (and/or @samp{never}) is an error, as this would +create conflicting initial values for the implicit return value. @end quotation @findex always @table @asis -@item @samp{(always EXPR &key into)} -Check the result of the condition @samp{EXPR}. If the -condition evaluates to @code{nil}, end the loop. Otherwise, the loop returns the -final value of the condition or @code{t} if the command is never run. +@item @samp{(always [VAR] EXPR &key into)} +Check the result of the condition @samp{EXPR}. +If the condition evaluates to @code{nil}, end the loop. If the command was run, +return the value of the condition via @samp{VAR}. Otherwise, if the command was +never run, return @code{t} via @samp{VAR}. The steps are thus: @enumerate @@ -3836,10 +3795,10 @@ remain @code{t}. @findex never @table @asis -@item @samp{(never EXPR &key into)} -Check the condition @samp{EXPR}. If the condition is -ever non-nil, then the loop is exited and returns @code{nil}. Otherwise the loop -returns @code{t}. +@item @samp{(never [VAR] EXPR &key into)} +Check the condition @samp{EXPR}. If the +condition is ever non-@code{nil}, then the loop is exited and returns @code{nil} via +@samp{VAR}. Otherwise the loop returns @code{t} via @samp{VAR}. The steps are thus: @enumerate @@ -3849,7 +3808,7 @@ the loop's implicit return value. @item The value of the condition is checked. @item -If the condition is non-nil, then the variable is set to @code{nil} +If the condition is non-@code{nil}, then the variable is set to @code{nil} and the loop is exited. @end enumerate @end table @@ -3885,10 +3844,10 @@ the same variable. @findex thereis @table @asis -@item @samp{(thereis EXPR &key into)} -Check the result of the condition @samp{EXPR}. If -the condition evaluates to a non-@code{nil} value, the loop returns that value. -Otherwise, the loop returns nil. +@item @samp{(thereis [VAR] EXPR &key into)} +Check the result of the condition @samp{EXPR}. +If the condition evaluates to a non-@code{nil} value, the loop returns that value +via @samp{VAR}. Otherwise, the loop returns @code{nil} via @samp{VAR}. The steps are thus: @enumerate @@ -3898,7 +3857,7 @@ as the implicit return value of the loop. @item The value of the condition is stored in the variable. @item -If the value of the variable is non-nil, the loop exits. +If the value of the variable is non-@code{nil}, the loop exits. @end enumerate @end table @@ -4588,7 +4547,7 @@ using the @code{let*} special form. This method recognizes all commands and their aliases in the user option @code{loopy-aliases}. -@float Listing,org748b4ce +@float Listing,org6a2630b @lisp ;; => ((1 2 3) (-3 -2 -1) (0)) (loopy-iter (arg accum-opt positives negatives other) diff --git a/loopy-commands.el b/loopy-commands.el index eb6f0575b..3d3a36f10 100644 --- a/loopy-commands.el +++ b/loopy-commands.el @@ -2667,119 +2667,52 @@ This function is called by `loopy--expand-optimized-accum'." ;;;;; Boolean Commands ;;;;;; Always -(cl-defun loopy--parse-always-command - ((&whole cmd _ condition &rest other-conditions-or-var)) - "Parse a command of the form `(always CONDITION [CONDITIONS] &key into)'. +(loopy--defaccumulation always + "Parse a command of the form `(always VAR CONDITION &key into)'. -If any condition is nil, `loopy' should immediately return nil. -Otherwise, `loopy' should return the final value of CONDITIONS, +If CONDITION is nil, `loopy' should immediately return nil. +Otherwise, `loopy' should return the final value of CONDITION, or t if the command is never evaluated." - (let* ((var 'loopy-result) - (other-conditions nil) - (final-two-cond (last other-conditions-or-var 2))) - - (if (eq :into (cl-first final-two-cond)) - (setq var (cl-second final-two-cond) - other-conditions (butlast other-conditions-or-var 2)) - (setq other-conditions other-conditions-or-var)) - - (when other-conditions - (warn "Loopy: `always': Use of multiple conditions is deprecated. -This command's behavior will be changed to be (always [VAR] CONDITION &key into), -like accumulation commands. -Warning trigger: %s" cmd)) - - (loopy--check-accumulation-compatibility - loopy--loop-name var 'boolean-always-never cmd) - - `((loopy--accumulation-vars (,var t)) - (loopy--implicit-return ,var) - (loopy--main-body (setq ,var - ,(if other-conditions - `(and ,condition ,@other-conditions) - condition))) - ,@(cl-destructuring-bind (main-body rest) - (loopy--extract-main-body (loopy--parse-leave-command 'ignored-arg)) - (cons `(loopy--main-body (unless ,var ,@main-body)) - rest))))) + :category boolean-always-never + :explicit `((loopy--accumulation-vars (,var t)) + (loopy--implicit-return ,var) + (loopy--main-body (setq ,var ,val)) + ,@(loopy--bind-main-body (main-body rest) + (loopy--parse-leave-command 'ignored-arg) + (cons `(loopy--main-body (unless ,var ,@main-body)) + rest)))) ;;;;;; Never -(cl-defun loopy--parse-never-command - ((&whole cmd _ condition &rest other-conditions-or-var)) - "Parse a command of the form `(never CONDITION [CONDITIONS] &key into)'. +(loopy--defaccumulation never + "Parse a command of the form `(never VAR CONDITION &key into)'. -If any condition is t, `loopy' should immediately return nil. +If CONDITION is t, `loopy' should immediately return nil. Otherwise, `loopy' should return t." - ;; Unlike `always', we don't set `loopy-result' to the conditions' values, - ;; since they are expected to all be `nil' anyway. - (let* ((var 'loopy-result) - (other-conditions nil) - (final-two-cond (last other-conditions-or-var 2))) - - (if (eq :into (cl-first final-two-cond)) - (setq var (cl-second final-two-cond) - other-conditions (butlast other-conditions-or-var 2)) - (setq other-conditions other-conditions-or-var)) - - (when other-conditions - (warn "Loopy: `never': Use of multiple conditions is deprecated. -This command's behavior will be changed to be (never [VAR] CONDITION &key into), -like accumulation commands. -Warning trigger: %s" cmd)) - - (loopy--check-accumulation-compatibility - loopy--loop-name var 'boolean-always-never cmd) - - `((loopy--accumulation-vars (,var t)) - (loopy--implicit-return ,var) - ,@(cl-destructuring-bind (main-body rest) - (loopy--extract-main-body (loopy--parse-leave-command 'ignored-arg)) - (cons `(loopy--main-body (when ,(if other-conditions - `(or ,condition ,@other-conditions) - condition) - (setq ,var nil) - ,@main-body)) - rest))))) + :category boolean-always-never + :explicit `((loopy--accumulation-vars (,var t)) + (loopy--implicit-return ,var) + ,@(loopy--bind-main-body (main-body rest) + (loopy--parse-leave-command 'ignored-arg) + (cons `(loopy--main-body (when ,val + (setq ,var nil) + ,@main-body)) + rest)))) ;;;;;; Thereis -(cl-defun loopy--parse-thereis-command - ((&whole cmd _ condition &rest other-conditions-or-var)) - "Parse the `thereis' command as (thereis CONDITION [CONDITIONS] &key into). +(loopy--defaccumulation thereis + "Parse the `thereis' command as (thereis VAR CONDITION &key into). -If any condition is non-nil, its value is immediately returned +If CONDITION is non-nil, its value is immediately returned and the loop is exited. Otherwise the loop continues and nil is returned." - (let* ((var 'loopy-result) - (other-conditions nil) - (final-two-cond (last other-conditions-or-var 2))) - - (if (eq :into (cl-first final-two-cond)) - (setq var (cl-second final-two-cond) - other-conditions (butlast other-conditions-or-var 2)) - (setq other-conditions other-conditions-or-var)) - - (when other-conditions - (warn "Loopy: `thereis': Use of multiple conditions is deprecated. -This command's behavior will be changed to be (thereis [VAR] CONDITION &key into), -like accumulation commands. -Warning trigger: %s" cmd)) - - (loopy--check-accumulation-compatibility - loopy--loop-name var 'boolean-thereis cmd) - - `((loopy--accumulation-vars (,var nil)) - (loopy--implicit-return ,var) - (loopy--main-body (setq ,var ,(if other-conditions - `(and ,condition ,@other-conditions) - condition))) - ,@(cl-destructuring-bind (main-body rest) - (loopy--extract-main-body (loopy--parse-leave-command 'ignored-arg)) - (cons `(loopy--main-body (when ,(if other-conditions - `(or ,condition ,@other-conditions) - condition) - ,@main-body)) - rest))))) - + :category boolean-thereis + :explicit `((loopy--accumulation-vars (,var nil)) + (loopy--implicit-return ,var) + (loopy--main-body (setq ,var ,val)) + ,@(loopy--bind-main-body (main-body rest) + (loopy--parse-leave-command 'ignored-arg) + (cons `(loopy--main-body (when ,val ,@main-body)) + rest)))) ;;;;; Exiting and Skipping ;;;;;; Leave diff --git a/loopy-vars.el b/loopy-vars.el index 7d5aa503d..5a9502474 100644 --- a/loopy-vars.el +++ b/loopy-vars.el @@ -475,14 +475,21 @@ returned by the macro if no other value is returned.") ;;;;; Loop Command Variables (defvar loopy-result nil - "The result of using implicit accumulation commands in `loopy'. + "The default variable used by accumulation and boolean commands in `loopy'. -All accumulation commands with no given variable (such -as `(collect my-val)') will accumulate into `loopy-result'. +When a variable is not specified for accumulation commands (such +as `collect') or for boolean commands (such as `always'), those +commands will use `loopy-result' for their operation and set it +as the implicit return value of the loop. -While `loopy-result' is an implied return value, it need not be -the only implied value, and can still be returned in a list with -other implied return values, if any.") +When optimized accumulation commands use `loopy-result', the +variables value is finalized after the loop ends. Therefore, in +that situation, its value shouldn't be used while the loop is +running. + +This variable can be safely referenced in code produced by +special macro arguments (such as `finally-return') after the loop +runs and its value is finalized.") (defvar loopy--generalized-vars nil "A list of symbols and macro expansions explicitly named in loop commands. diff --git a/tests/tests.el b/tests/tests.el index 28ad0e112..f6c6d466f 100644 --- a/tests/tests.el +++ b/tests/tests.el @@ -5206,9 +5206,18 @@ Not multiple of 3: 7" (loopy-deftest always-var :result 4 - :body ((list i '(1 2 3)) - (always i (numberp i) (1+ i) :into test-var) - (finally-return test-var)) + :multi-body t + :body [((list i '(1 2 3)) + (always (and i (numberp i) (1+ i)) :into test-var) + (finally-return test-var)) + + ((list i '(1 2 3)) + (always test-var (and i (numberp i) (1+ i))) + (finally-return test-var)) + + ((list i '(1 2 3)) + (always (and i (numberp i) (1+ i))) + (finally-return loopy-result))] :loopy t :iter-keyword (list always) :iter-bare ((list . listing) @@ -5257,32 +5266,79 @@ Not multiple of 3: 7" (loopy-deftest never-var :result t - :body ((list i '(1 2 3)) - (never (not (numberp i)) nil :into test-var) - (finally-return test-var)) + :multi-body t + :body [((list i '(1 2 3)) + (never (not (numberp i)) :into test-var) + (finally-return test-var)) + + ((list i '(1 2 3)) + (never test-var (not (numberp i))) + (finally-return test-var)) + + ((list i '(1 2 3)) + (never (not (numberp i))) + (finally-return loopy-result))] :loopy t :iter-keyword (list never) :iter-bare ((list . listing) (never . never))) ;;;;; Thereis -(loopy-deftest thereis-pass +(loopy-deftest thereis-val + :doc "Make sure `thereis' sets the value correctly." :result 6 - :body ((list i '(1 2 3 4 5 6)) - (thereis (and (> i 5) i))) + :multi-body t + :body [((list i '(1 2 3 4 5 6 7 8 9)) + (thereis (and (> i 5) i))) + + ((list i '(1 2 3 4 5 6 7 8 9)) + (thereis var (and (> i 5) i)) + (finally-return var))] :loopy t :iter-keyword (list thereis) :iter-bare ((list . listing) (thereis . thereis))) +(loopy-deftest thereis-pass + :doc "Make sure `thereis' ends the loop early when the conditions is non-nil." + :result '(6 (1 2 3 4 5 6)) + :multi-body t + :body [((list i '(1 2 3 4 5 6 7 8 9)) + (collect coll i) + (thereis (and (> i 5) i)) + (finally-return loopy-result coll)) + + ((list i '(1 2 3 4 5 6 7 8 9)) + (collect i) + (thereis var (and (> i 5) i)) + (finally-return var loopy-result))] + :loopy t + :iter-keyword (list thereis collect) + :iter-bare ((list . listing) + (thereis . thereis) + (collect . collecting))) + (loopy-deftest thereis-fail + :doc "Make sure `thereis' does not end the loop early and does not set the value." :result nil - :body ((list i '(1 2 3 4 5 6)) - (thereis (> i 7))) + :multi-body t + :body [((list i '(1 2 3 4 5 6)) + (thereis (> i 7))) + + ((list i '(1 2 3 4 5 6)) + (thereis var (> i 7)) + (finally-return var)) + + ((list i '(1 2 3 4 5 6)) + (collect i) + (thereis var (> i 7)) + (finally-return (not (and (eq var nil) + (equal loopy-result '(1 2 3 4 5 6))))))] :loopy t - :iter-keyword (list thereis) + :iter-keyword (list thereis collect) :iter-bare ((list . listing) - (thereis . thereis))) + (thereis . thereis) + (collect . collecting))) (loopy-deftest thereis-always-same-var :error loopy-incompatible-accumulations @@ -5292,7 +5348,10 @@ Not multiple of 3: 7" (thereis i)) ((list i '(1 2 3)) (always i :into test) - (thereis i :into test))] + (thereis i :into test)) + ((list i '(1 2 3)) + (always test i) + (thereis test i))] :loopy t :iter-keyword (list thereis always) :iter-bare ((list . listing))) @@ -5305,7 +5364,10 @@ Not multiple of 3: 7" (thereis i)) ((list i '(1 2 3)) (never i :into test) - (thereis i :into test))] + (thereis i :into test)) + ((list i '(1 2 3)) + (never test i) + (thereis test i))] :loopy t :iter-keyword (list thereis never) :iter-bare ((list . listing))) @@ -5502,7 +5564,7 @@ Not multiple of 3: 7" :doc "Wrapping with another eval to make sure variables are set by expansion time. Also tests that post-conditions work as expected." :wrap ((x . `(cl-labels ((my--loopy-always-command-parser ((_ &rest conditions)) - "Parse a command of the form `(always [CONDITIONS])'. + "Parse a command of the form `(my-always [CONDITIONS])'. If any condition is `nil', `loopy' should immediately return nil. Otherwise, `loopy' should return t." ;; Return t if loop completes successfully. @@ -5519,24 +5581,24 @@ Otherwise, `loopy' should return t." for condition in conditions collect `(loopy--post-conditions ,condition))))) (let ((loopy-command-parsers - (map-insert loopy-command-parsers 'target-sum + (map-insert loopy-command-parsers 'my-always #'my--loopy-always-command-parser)) - (loopy-iter-bare-commands (cons 'always + (loopy-iter-bare-commands (cons 'my-always loopy-iter-bare-commands))) (eval (quote ,x) t))))) :result t :body ((list i (number-sequence 1 9)) - (always (< i 10))) + (my-always (< i 10) (< i 20))) :loopy t - :iter-keyword (list always) + :iter-keyword (list my-always) :iter-bare ((list . listing) - (always . always))) + (my-always . my-always))) (loopy-deftest custom-command-always-fail :doc "Wrapping with another eval to make sure variables are set by expansion time. Also tests that post-conditions work as expected." :wrap ((x . `(cl-labels ((my--loopy-always-command-parser ((_ &rest conditions)) - "Parse a command of the form `(always [CONDITIONS])'. + "Parse a command of the form `(my-always [CONDITIONS])'. If any condition is `nil', `loopy' should immediately return nil. Otherwise, `loopy' should return t." ;; Return t if loop completes successfully. @@ -5553,19 +5615,19 @@ Otherwise, `loopy' should return t." for condition in conditions collect `(loopy--post-conditions ,condition))))) (let ((loopy-command-parsers - (map-insert loopy-command-parsers 'target-sum + (map-insert loopy-command-parsers 'my-always #'my--loopy-always-command-parser)) - (loopy-iter-bare-commands (cons 'always + (loopy-iter-bare-commands (cons 'my-always loopy-iter-bare-commands))) (eval (quote ,x) t))))) :result nil :body ((list i (number-sequence 1 9)) (list j '(2 4 6 8 9)) - (always (< i 10) (cl-evenp j))) + (my-always (< i 10) (cl-evenp j))) :loopy t - :iter-keyword (list always) + :iter-keyword (list my-always) :iter-bare ((list . listing) - (always . always))) + (my-always . my-always))) ;;; Repeated evaluation of macro