branch: elpa/loopy
commit 873ec3d2771d83de222832ef41d50f5b6ca6d756
Author: okamsn <28612288+oka...@users.noreply.github.com>
Commit: GitHub <nore...@github.com>

    General code cleanup (#241)
    
    - Stop using `loopy--valid-external-at-targets`.
    
      - Instead of having a list of approved targets, use all targets that 
aren't
        `loopy--main-body` or `loopy--latter-body` as external and valid.
    
      - Remove variable `loopy--valid-external-at-targets`
        and function `loopy--valid-external-at-target-p`.
    
      - Update `loopy--process-instruction` to no longer call
        `loopy--valid-external-at-target-p`.
    
      - Add test `loopy-at-set` to check that `loopy--other-vars` are handled
        correctly, as they were not listed in 
`loopy--valid-external-at-targets`.
    
      - Update changelog to mention bug.
    
    - Remove unused function `loopy--flags`.
    
    - Remove obsolete alias `loopy--accumulation-final-updates` for
     `loopy--vars-final-updates`.  This has been obsolete since 2022-11.
    
    - Fix typo in documentation string of `loopy--accumulation-places`.
    
    - Add `loopy--other-vars` to `loopy--command-bound-p` check.
    
    - Remove unused function `loopy--special-macro-argument-p`.
    
    - Move body of `loopy--known-loop-name-p` into 
`loopy--check-target-loop-name`.
      That latter function is the only user of the first function.
      Remove function `loopy--known-loop-name-p`.
    
    - Use `loopy--normalize-position-name` in accumulation commands.
    
      - Replace use of `loopy--normalize-symbol` in accumulation commands
        and explicit accumulation constructors.
    
      - Check validity in `loopy--normalize-symbol` instead of explicitly in 
each
        constructor.
    
      - Add use for explicit version of `concat` and `union`.
    
    - Remove commented-out lines applying default flags from Loopy and
      Iter Flag processor.
    
    - Remove unused function `loopy--ensure-valid-bindings`.
    
    - Use `loopy--with-protected-stack` in `loopy`.
    
    - Remove unused declarations.
    
    - Make sure `accum-opt` SMA normalizes the position symbols of optimized
      sequences.
    
    - Fix typo in documentation string of `loopy--defiteration`.
    
    - Fix reasoning error in `loopy-seq--make-pcase-pattern`.
    
    - Remove unused function `loopy--car-equal-car` and alias
      `loopy--car-equals-car`.
    
    - Remove unused functions `loopy--count-while` and `loopy--count-until`.
    
    - Move body of `loopy--convert-iteration-vars-to-other-vars`
      into `loopy--destructure-for-other-command`.  This was the only use of
      `loopy--convert-iteration-vars-to-other-vars`.  Delete
      `loopy--convert-iteration-vars-to-other-vars`.
    
    - Remove unused functions `loopy--substitute-using` and
      `loopy--substitute-using-if`.  They were only used by
      `loopy--convert-iteration-vars-to-other-vars`, which we've now deleted.
    
    - Replaceable uses of `loopy--extract-main-body` with 
`loopy--bind-main-body`.
    
      - Move the content of `loopy--extract-main-body` into 
`loopy--bind-main-body`.
    
      - Remove `loopy--extract-main-body`.
    
    - Remove some old TODOs.
    
    - Fix some typos in doc strings.
---
 CHANGELOG.md              |   3 +-
 lisp/loopy-commands.el    | 180 +++++++++++++++++-----------------------------
 lisp/loopy-destructure.el |  10 ++-
 lisp/loopy-instrs.el      |  53 +++++---------
 lisp/loopy-iter.el        |   2 +-
 lisp/loopy-misc.el        |  58 ---------------
 lisp/loopy-seq.el         |   2 +-
 lisp/loopy-vars.el        |  80 ++++++++-------------
 lisp/loopy.el             |  73 +++++++++----------
 tests/misc-tests.el       |   2 -
 tests/tests.el            |  27 ++++++-
 11 files changed, 175 insertions(+), 315 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2dc5e85a8..927a1671e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ For Loopy Dash, see <https://github.com/okamsn/loopy-dash>.
 ### Bug Fixes
 
 - `when` and `unless` now correctly work when aliased ([#234], [#240]).
+- Fix variable scoping when using `set` with `at` ([#241]).
 
 ### Breaking Changes
 
@@ -44,7 +45,7 @@ For Loopy Dash, see <https://github.com/okamsn/loopy-dash>.
 [#234]: https://github.com/okamsn/loopy/issues/234
 [#237]: https://github.com/okamsn/loopy/PR/237
 [#240]: https://github.com/okamsn/loopy/PR/240
-
+[#241]: https://github.com/okamsn/loopy/PR/241
 
 ## 0.14.0
 
diff --git a/lisp/loopy-commands.el b/lisp/loopy-commands.el
index e117da3be3..1cc585b393 100644
--- a/lisp/loopy-commands.el
+++ b/lisp/loopy-commands.el
@@ -85,9 +85,7 @@
 (require 'subr-x)
 (require 'stream)
 
-(declare-function loopy--bound-p "loopy")
 (declare-function loopy--process-instructions "loopy")
-(declare-function loopy--process-instruction "loopy")
 (defvar loopy--in-sub-level)
 
 ;;;; Helpful Functions
@@ -275,8 +273,8 @@ BODY is one or more commands to be grouped by a `progn' 
form.
 This command is suitable for using as the first sub-command in an
 `if' command."
   (let ((loopy--in-sub-level t))
-    (cl-destructuring-bind (progn-body rest)
-        (loopy--extract-main-body (loopy--parse-loop-commands body))
+    (loopy--bind-main-body (progn-body rest)
+        (loopy--parse-loop-commands body)
       ;; Return the instructions.
       (cons `(loopy--main-body (progn ,@progn-body))
             rest))))
@@ -300,19 +298,16 @@ the loop literally (not even in a `progn')."
 - IF-TRUE is the first sub-command of the `if' command.
 - IF-FALSE are all the other sub-commands."
   (let ((loopy--in-sub-level t))
-    (pcase-let ((`(,if-true-main-body ,true-rest)
-                 (loopy--extract-main-body (loopy--parse-loop-command 
if-true)))
-                (`(,if-false-main-body ,false-rest)
-                 (loopy--extract-main-body (loopy--parse-loop-commands 
if-false))))
-
-      ;; Handle if we need to wrap multiple main-body expressions.
-      (setq if-true-main-body (macroexp-progn if-true-main-body))
-
-      ;; Return the full instruction list.
-      `((loopy--main-body
-         (if ,condition ,if-true-main-body ,@if-false-main-body))
-        ,@true-rest
-        ,@false-rest))))
+    (loopy--bind-main-body (if-true-main-body true-rest)
+        (loopy--parse-loop-command if-true)
+      (loopy--bind-main-body (if-false-main-body false-rest)
+          (loopy--parse-loop-commands if-false)
+        ;; Return the full instruction list.
+        `((loopy--main-body (if ,condition
+                                ,(macroexp-progn if-true-main-body)
+                              ,@if-false-main-body))
+          ,@true-rest
+          ,@false-rest)))))
 
 ;;;;;; Cond
 (cl-defun loopy--parse-cond-command ((_ &rest clauses))
@@ -326,13 +321,12 @@ command are inserted into a `cond' special form."
   (let ((loopy--in-sub-level t)
         (cond-body nil)
         (rest-instructions nil))
-    (cl-loop for clause in clauses
-             for (main-body rest) = (loopy--extract-main-body
-                                     (loopy--parse-loop-commands
-                                      (cl-rest clause)))
-             do
-             (push (cons (cl-first clause) main-body) cond-body)
-             (push rest rest-instructions))
+    (dolist (clause clauses)
+      (loopy--bind-main-body (main-body rest)
+          (loopy--parse-loop-commands
+           (cl-rest clause))
+        (push (cons (cl-first clause) main-body) cond-body)
+        (push rest rest-instructions)))
     (cons `(loopy--main-body (cond ,@(nreverse cond-body)))
           (apply #'append (nreverse rest-instructions)))))
 
@@ -357,7 +351,7 @@ command are inserted into a `cond' special form."
 ;;;;; Iteration
 (cl-defmacro loopy--defiteration
     (name doc-string &key keywords (required-vals 1) other-vals instructions)
-  "Define an interation command parser for NAME.
+  "Define an iteration command parser for NAME.
 
 An iteration command made with this macro has the layout of
 \(command-name variable-name value [values] [keys]).  That is,
@@ -1781,8 +1775,8 @@ second pass of macro expansion."
         plist
       (if (null fn)
           (signal 'loopy-accum-constructor-missing (list name plist))
-        (cl-destructuring-bind (main-body other-instrs)
-            (loopy--extract-main-body (funcall fn plist))
+        (loopy--bind-main-body (main-body other-instrs)
+            (funcall fn plist)
           (loopy--process-instructions
            `((loopy--at-instructions (,loop ,@(remq nil other-instrs)))))
           (macroexp-progn main-body))))))
@@ -1794,13 +1788,11 @@ LOOP is the current loop.  VAR is the accumulation 
variable.
 PLACE is one of `start' or `end'.  VALUE is the integer by which
 to increment the count (default 1)."
   (loopy--check-target-loop-name loop)
+  (loopy--check-position-name place)
   (cl-symbol-macrolet ((loop-map (map-elt loopy--accumulation-places loop)))
     (unless (map-elt loop-map var)
       (setf (map-elt loop-map var)
             (list (cons 'start 0) (cons 'end 0))))
-    (setq place (loopy--normalize-symbol place))
-    (when (eq place 'beginning) (setq place 'start))
-    (loopy--check-position-name place)
     (cl-incf (map-elt (map-elt loop-map var) place) value)))
 
 ;;;;;; Commands
@@ -2036,11 +2028,7 @@ you can use in the instructions:
   :explicit
   (loopy--plist-bind ( :test (test (quote #'equal)) :key key :at (pos 'end))
       opts
-    (setq pos (loopy--normalize-symbol pos))
-    (when (eq pos 'beginning) (setq pos 'start))
-    (unless (memq pos '(start beginning end))
-      (signal 'loopy-bad-position-command-argument (list pos cmd)))
-
+    (setq pos (loopy--normalize-position-name pos))
     (if (memq var loopy--optimized-accum-vars)
         (progn
           (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2049,31 +2037,27 @@ you can use in the instructions:
                                         :var ,var :val ,val
                                         :test ,test :key ,key :at ,pos
                                         :opt-accum-fn 
loopy--construct-accum-adjoin)))))
-
       (loopy--check-accumulation-compatibility loopy--loop-name var 'list cmd)
       `((loopy--accumulation-vars (,var nil))
-        ,@(cond
-           ((member pos '(start beginning 'start 'beginning))
-            (loopy--instr-let-const* ((test-val test)
-                                      (key-val key))
-                loopy--accumulation-vars
-              `((loopy--main-body
-                 ,(cl-once-only ((adjoin-value val))
-                    `(unless (loopy--member-p ,var ,adjoin-value
-                                              :test ,test-val :key ,key-val)
-                       (cl-callf2 cons ,adjoin-value ,var)))))))
-           ((member pos '(end nil 'end))
-            (loopy--produce-adjoin-end-tracking var val :test test :key key))
-           (t
-            (signal 'loopy-bad-position-command-argument (list pos cmd))))
+        ,@(pcase pos
+            ('start
+             (loopy--instr-let-const* ((test-val test)
+                                       (key-val key))
+                 loopy--accumulation-vars
+               `((loopy--main-body
+                  ,(cl-once-only ((adjoin-value val))
+                     `(unless (loopy--member-p ,var ,adjoin-value
+                                               :test ,test-val :key ,key-val)
+                        (cl-callf2 cons ,adjoin-value ,var)))))))
+            ((or 'end 'nil)
+             (loopy--produce-adjoin-end-tracking var val :test test :key key))
+            (_
+             (signal 'loopy-bad-position-command-argument (list pos cmd))))
         (loopy--vars-final-updates (,var . nil)))))
   :implicit
   (loopy--plist-bind ( :test (test (quote #'equal)) :key key :at (pos 'end))
       opts
-    (setq pos (loopy--normalize-symbol pos))
-    (when (eq pos 'beginning) (setq pos 'start))
-    (unless (memq pos '(start beginning end))
-      (signal 'loopy-bad-position-command-argument (list pos cmd)))
+    (setq pos (loopy--normalize-position-name pos))
     (loopy--update-accum-place-count loopy--loop-name var pos)
     `((loopy--main-body
        (loopy--optimized-accum '( :cmd ,cmd :name ,name
@@ -2089,7 +2073,6 @@ you can use in the instructions:
                        :var var :val val
                        :at (pos 'end))
       plist
-    (setq pos (loopy--get-quoted-symbol pos))
     (map-let (('start start)
               ('end end))
         (loopy--get-accum-counts loop var 'append)
@@ -2119,10 +2102,7 @@ you can use in the instructions:
   :explicit
   (loopy--plist-bind (:at (pos 'end))
       opts
-    (setq pos (loopy--normalize-symbol pos))
-    (when (eq pos 'beginning) (setq pos 'start))
-    (unless (memq pos '(start beginning end))
-      (signal 'loopy-bad-position-command-argument (list pos cmd)))
+    (setq pos (loopy--normalize-position-name pos))
     (if (memq var loopy--optimized-accum-vars)
         (progn
           (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2134,24 +2114,18 @@ you can use in the instructions:
                                         :opt-accum-fn 
loopy--construct-accum-append)))))
       (loopy--check-accumulation-compatibility loopy--loop-name var 'list cmd)
       `((loopy--accumulation-vars (,var nil))
-        ,@(cond
-           ;; TODO: Is there a better way of appending to the beginning
-           ;;       of a list?
-           ((member pos '(start beginning 'start 'beginning))
+        ,@(pcase pos
+            ;; TODO: Is there a better way of appending to the beginning
+            ;;       of a list?
             ;; `append' doesn't copy the last argument.
-            `((loopy--main-body (setq ,var (append ,val ,var)))))
-           ((member pos '(end 'end))
-            (loopy--produce-multi-item-end-tracking var val))
-           (t
-            (signal 'loopy-bad-position-command-argument (list pos cmd))))
+            ('start `((loopy--main-body (setq ,var (append ,val ,var)))))
+            ('end (loopy--produce-multi-item-end-tracking var val))
+            (_ (signal 'loopy-bad-position-command-argument (list pos cmd))))
         (loopy--vars-final-updates (,var . nil)))))
   :implicit
   (loopy--plist-bind (:at (pos 'end))
       opts
-    (setq pos (loopy--normalize-symbol pos))
-    (when (eq pos 'beginning) (setq pos 'start))
-    (unless (memq pos '(start beginning end))
-      (signal 'loopy-bad-position-command-argument (list pos cmd)))
+    (setq pos (loopy--normalize-position-name pos))
     (loopy--update-accum-place-count loopy--loop-name var pos)
     `((loopy--accumulation-vars (,var nil))
       (loopy--main-body
@@ -2165,7 +2139,6 @@ you can use in the instructions:
   "Construct an optimized `collect' accumulation from PLIST."
   (loopy--plist-bind ( :cmd cmd :loop loop :var var :val val :at (pos 'end))
       plist
-    (setq pos (loopy--get-quoted-symbol pos))
     `((loopy--accumulation-vars (,var nil))
       ,@(map-let (('start start)
                   ('end end))
@@ -2192,10 +2165,7 @@ you can use in the instructions:
   :keywords (at)
   :explicit (loopy--plist-bind ( :at (pos (quote 'end)))
                 opts
-              (setq pos (loopy--normalize-symbol pos))
-              (when (eq pos 'beginning) (setq pos 'start))
-              (unless (memq pos '(start beginning end))
-                (signal 'loopy-bad-position-command-argument (list pos cmd)))
+              (setq pos (loopy--normalize-position-name pos))
               (if (memq var loopy--optimized-accum-vars)
                   (progn
                     (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2218,10 +2188,7 @@ you can use in the instructions:
 
   :implicit (loopy--plist-bind ( :at (pos 'end))
                 opts
-              (setq pos (loopy--normalize-symbol pos))
-              (when (eq pos 'beginning) (setq pos 'start))
-              (unless (memq pos '(start beginning end))
-                (signal 'loopy-bad-position-command-argument (list pos cmd)))
+              (setq pos (loopy--normalize-position-name pos))
               (loopy--update-accum-place-count loopy--loop-name var pos)
               `((loopy--main-body
                  (loopy--optimized-accum
@@ -2263,6 +2230,7 @@ This function is called by 
`loopy--expand-optimized-accum'."
   :keywords (at)
   :explicit (loopy--plist-bind (:at (pos 'end))
                 opts
+              (setq pos (loopy--normalize-position-name pos))
               (if (memq var loopy--optimized-accum-vars)
                   (progn
                     (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2288,10 +2256,7 @@ This function is called by 
`loopy--expand-optimized-accum'."
                   (loopy--vars-final-updates (,var . nil)))))
   :implicit (loopy--plist-bind (:at (pos 'end))
                 opts
-              (setq pos (loopy--normalize-symbol pos))
-              (when (eq pos 'beginning) (setq pos 'start))
-              (unless (memq pos '(start beginning end))
-                (signal 'loopy-bad-position-command-argument (list pos cmd)))
+              (setq pos (loopy--normalize-position-name pos))
               (loopy--update-accum-place-count loopy--loop-name var pos)
               `((loopy--accumulation-vars (,var nil))
                 (loopy--main-body
@@ -2465,10 +2430,7 @@ EXPR is the value to bind to VAR."
   :keywords (at)
   :explicit (loopy--plist-bind (:at (pos 'end))
                 opts
-              (setq pos (loopy--normalize-symbol pos))
-              (when (eq pos 'beginning) (setq pos 'start))
-              (unless (memq pos '(start beginning end))
-                (signal 'loopy-bad-position-command-argument (list pos cmd)))
+              (setq pos (loopy--normalize-position-name pos))
               (if (memq var loopy--optimized-accum-vars)
                   (progn
                     (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2490,10 +2452,7 @@ EXPR is the value to bind to VAR."
                   (loopy--vars-final-updates (,var . nil)))))
   :implicit (loopy--plist-bind (:at (pos 'end))
                 opts
-              (setq pos (loopy--normalize-symbol pos))
-              (when (eq pos 'beginning) (setq pos 'start))
-              (unless (memq pos '(start beginning end))
-                (signal 'loopy-bad-position-command-argument (list pos cmd)))
+              (setq pos (loopy--normalize-position-name pos))
               (loopy--update-accum-place-count loopy--loop-name var pos)
               `((loopy--accumulation-vars (,var nil))
                 (loopy--main-body (loopy--optimized-accum
@@ -2559,10 +2518,7 @@ This function is used by 
`loopy--expand-optimized-accum'."
   :explicit
   (loopy--plist-bind (:at (pos 'end) :key key :test (test (quote #'equal)))
       opts
-    (setq pos (loopy--normalize-symbol pos))
-    (when (eq pos 'beginning) (setq pos 'start))
-    (unless (memq pos '(start beginning end))
-      (signal 'loopy-bad-position-command-argument (list pos cmd)))
+    (setq pos (loopy--normalize-position-name pos))
     (if (memq var loopy--optimized-accum-vars)
         (progn
           (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2597,10 +2553,7 @@ This function is used by 
`loopy--expand-optimized-accum'."
   :implicit
   (loopy--plist-bind (:at (pos 'end) :key key :test (test (quote #'equal)))
       opts
-    (setq pos (loopy--normalize-symbol pos))
-    (when (eq pos 'beginning) (setq pos 'start))
-    (unless (memq pos '(start beginning end))
-      (signal 'loopy-bad-position-command-argument (list pos cmd)))
+    (setq pos (loopy--normalize-position-name pos))
     (loopy--update-accum-place-count loopy--loop-name var pos)
     `((loopy--accumulation-vars (,var nil))
       (loopy--implicit-return ,var)
@@ -2753,6 +2706,7 @@ This function is used by `loopy--expand-optimized-accum'."
   :explicit
   (loopy--plist-bind (:at (pos 'end) :key key :test (test (quote #'equal)))
       opts
+    (setq pos (loopy--normalize-position-name pos))
     (if (memq var loopy--optimized-accum-vars)
         (progn
           (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2787,10 +2741,7 @@ This function is used by 
`loopy--expand-optimized-accum'."
   :implicit
   (loopy--plist-bind (:at (pos 'end) :key key :test (test (quote #'equal)))
       opts
-    (setq pos (loopy--normalize-symbol pos))
-    (when (eq pos 'beginning) (setq pos 'start))
-    (unless (memq pos '(start beginning end))
-      (signal 'loopy-bad-position-command-argument (list pos cmd)))
+    (setq pos (loopy--normalize-position-name pos))
     (loopy--update-accum-place-count loopy--loop-name var pos)
     `((loopy--accumulation-vars (,var nil))
       (loopy--implicit-return ,var)
@@ -2833,10 +2784,7 @@ This function is called by 
`loopy--expand-optimized-accum'."
   :keywords (at)
   :explicit (loopy--plist-bind (:at (pos 'end))
                 opts
-              (setq pos (loopy--normalize-symbol pos))
-              (when (eq pos 'beginning) (setq pos 'start))
-              (unless (memq pos '(start beginning end))
-                (signal 'loopy-bad-position-command-argument (list pos cmd)))
+              (setq pos (loopy--normalize-position-name pos))
               (if (memq var loopy--optimized-accum-vars)
                   (progn
                     (loopy--update-accum-place-count loopy--loop-name var pos)
@@ -2861,10 +2809,7 @@ This function is called by 
`loopy--expand-optimized-accum'."
                   (loopy--vars-final-updates (,var . nil)))))
   :implicit (loopy--plist-bind (:at (pos 'end))
                 opts
-              (setq pos (loopy--normalize-symbol pos))
-              (when (eq pos 'beginning) (setq pos 'start))
-              (unless (memq pos '(start beginning end))
-                (signal 'loopy-bad-position-command-argument (list pos cmd)))
+              (setq pos (loopy--normalize-position-name pos))
               (loopy--update-accum-place-count loopy--loop-name var pos)
               `((loopy--accumulation-vars (,var nil))
                 (loopy--main-body
@@ -3067,8 +3012,13 @@ Return a list of instructions for initializing the 
variables and
 destructuring into them in the loop body.
 
 A wrapper around `loopy--destructure-for-iteration-command'."
-  (loopy--convert-iteration-vars-to-other-vars
-   (loopy--destructure-for-iteration-command var value-expression)))
+  (cl-loop
+   for binding in (loopy--destructure-for-iteration-command var 
value-expression)
+   if (eq (car binding) 'loopy--iteration-vars)
+   collect (cons 'loopy--other-vars (cdr binding))
+   else
+   collect binding
+   end))
 
 (cl-defun loopy--parse-destructuring-accumulation-command-default
     ((name var val &rest args))
diff --git a/lisp/loopy-destructure.el b/lisp/loopy-destructure.el
index 1ec1b1e585..a14e175143 100644
--- a/lisp/loopy-destructure.el
+++ b/lisp/loopy-destructure.el
@@ -260,7 +260,6 @@ Type is one of `list' or `array'."
           (puthash var-seq val loopy--get-var-groups-cache)
           val))))
 
-;; TODO: Turn these into records?
 (defun loopy--get-&optional-spec (form)
   "Get the spec of the `&optional' variable FORM as (VAR DEFAULT SUPPLIED 
LEN)."
   (let ((var)
@@ -390,7 +389,7 @@ Type is one of `list' or `array'."
   "Wrapper macro for compatibility with obsoletion of `pcase--flip'.
 
 FN is the function.  ARG2 is the argument to move to the second
-postion of the call to FN in the pattern."
+position of the call to FN in the pattern."
   (static-if (>= emacs-major-version 30)
       `(,fn _ ,arg2)
     `(pcase--flip ,fn ,arg2)))
@@ -1033,10 +1032,9 @@ an error should be signaled if the pattern doesn't 
match."
                                                                    #'cdr
                                                                  #'cadr))
                                                              v)))
-                                     (seq-let (main-body other-instructions)
-                                         (loopy--extract-main-body
-                                          (loopy--parse-loop-command
-                                           `(,name ,destr-var ,destr-val 
,@args)))
+                                     (loopy--bind-main-body (main-body 
other-instructions)
+                                         (loopy--parse-loop-command
+                                          `(,name ,destr-var ,destr-val 
,@args))
                                        ;; Just push the other instructions, but
                                        ;; gather the main body expressions.
                                        (dolist (instr other-instructions)
diff --git a/lisp/loopy-instrs.el b/lisp/loopy-instrs.el
index 3896e021c6..4631bba192 100644
--- a/lisp/loopy-instrs.el
+++ b/lisp/loopy-instrs.el
@@ -115,45 +115,24 @@ binding exists."
              (reverse bindings)
              :initial-value (macroexp-progn body)))
 
+(cl-defmacro loopy--bind-main-body ((main-exprs other-instrs) value &rest body)
+  "Bind MAIN-EXPRS and OTHER-INSTRS for those items in VALUE for BODY.
 
-(defun loopy--extract-main-body (instructions)
-  "Extract main-body expressions from INSTRUCTIONS.
-
-This returns a list of two sub-lists:
-
-1. A list of expressions (not instructions) that are meant to be
-   use in the main body of the loop.
-
-2. A list of instructions for places other than the main body.
-
-The lists will be in the order parsed (correct for insertion)."
-  (let ((wrapped-main-body)
-        (other-instructions))
-    (dolist (instruction instructions)
-      (if (eq (cl-first instruction) 'loopy--main-body)
-          (push (cl-second instruction) wrapped-main-body)
-        (push instruction other-instructions)))
-
-    ;; Return the sub-lists.
-    (list (nreverse wrapped-main-body) (nreverse other-instructions))))
-
-;; We find ourselves doing this pattern a lot.
-(cl-defmacro loopy--bind-main-body ((main-expr other-instrs) value &rest body)
-  "Bind MAIN-EXPR and OTHER-INSTRS for those items in VALUE for BODY."
+MAIN-EXPR is a list of main-body expressions (not instructions).
+OTHER-INSTRS is a list of the remaining instructions."
   (declare (indent 2))
-  `(cl-destructuring-bind (,main-expr ,other-instrs)
-       (loopy--extract-main-body ,value)
-     ,@body))
-
-(defun loopy--convert-iteration-vars-to-other-vars (instructions)
-  "Convert instructions for `loopy--iteration-vars' to `loopy--other-vars'.
-
-INSTRUCTIONS is a list of instructions, which don't all have to be
-for `loopy--iteration-vars'."
-  (loopy--substitute-using-if
-   (cl-function (lambda ((_ init)) (list 'loopy--other-vars init)))
-   (lambda (x) (eq (car x) 'loopy--iteration-vars))
-   instructions))
+  (let ((main-temp (gensym "main-temp"))
+        (other-temp (gensym "other-temp"))
+        (instruction (gensym "instr")))
+    `(let ((,main-temp nil)
+           (,other-temp nil))
+       (dolist (,instruction ,value)
+         (if (eq (cl-first ,instruction) 'loopy--main-body)
+             (push (cl-second ,instruction) ,main-temp)
+           (push ,instruction ,other-temp)))
+       (let ((,main-exprs (nreverse ,main-temp))
+             (,other-instrs (nreverse ,other-temp)))
+         ,@body))))
 
 (provide 'loopy-instrs)
 ;;; loopy-instrs.el ends here
diff --git a/lisp/loopy-iter.el b/lisp/loopy-iter.el
index 10b941dc82..da806bcbe2 100644
--- a/lisp/loopy-iter.el
+++ b/lisp/loopy-iter.el
@@ -383,7 +383,6 @@ Returns BODY without the `%s' argument."
   ;;
   ;; 1. Flags in `loopy-default-flags'.
   ;; 2. Flags in the `flag' macro argument, which can undo the first group.
-  ;; (mapc #'loopy--apply-flag loopy-default-flags)
   (mapc #'loopy--apply-flag arg-value))
 
 (loopy-iter--def-special-processor without
@@ -393,6 +392,7 @@ Returns BODY without the `%s' argument."
   (pcase-dolist ((or `(,var ,pos) var) arg-value)
     (push var loopy--optimized-accum-vars)
     (when pos
+      (setq pos (loopy--normalize-position-name pos))
       (loopy--update-accum-place-count loopy--loop-name var pos 1.0e+INF))))
 
 (loopy-iter--def-special-processor wrap
diff --git a/lisp/loopy-misc.el b/lisp/loopy-misc.el
index 7a907c52e6..6dac28f8de 100644
--- a/lisp/loopy-misc.el
+++ b/lisp/loopy-misc.el
@@ -256,39 +256,6 @@
 
 
 ;;;; List Processing
-(defalias 'loopy--car-equals-car #'loopy--car-equal-car)
-(defun loopy--car-equal-car (a b)
-  "Check whether the `car' of A equals the `car' of B."
-  (equal (car a) (car b)))
-
-;; Similar to `seq--count-successive'.
-(defun loopy--count-while (pred list)
-  "Count the number of items while PRED is true in LIST.
-
-This function returns 0 if PRED is immediately false.
-PRED is a function taking one argument: the item.
-
-For example, applying `cl-evenp' on (2 4 6 7) returns 3."
-  ;; Could be done with `cl-position-if-not', except that
-  ;; we want to return the length of the lists if
-  ;; no counterexample found.
-  (cl-loop for i in list
-           while (funcall pred i)
-           sum 1))
-
-(defun loopy--count-until (pred list)
-  "Count the number of items until PRED is true in LIST.
-
-This function returns 0 if PRED is immediately true.
-PRED is a function taking one argument: the item.
-
-For example, applying `cl-oddp' on (2 4 6 7) returns 3."
-  ;; Could be done with `cl-position-if', except that
-  ;; we want to return the length of the lists if
-  ;; no counterexample found.
-  (cl-loop for i in list
-           until (funcall pred i)
-           sum 1))
 
 (defmacro loopy--plist-bind (bindings plist &rest body)
   "Bind values in PLIST to variables in BINDINGS, surrounding BODY.
@@ -313,31 +280,6 @@ keywords and variables are separate."
        ,plist
      ,@body))
 
-(cl-defun loopy--substitute-using (new seq &key test)
-  "Copy SEQ, substituting elements using output of function NEW.
-
-NEW receives the element as its only argument.
-
-If given predicate function TEST, replace only elements
-satisfying TEST.  This testing could also be done in NEW."
-  ;; In testing, `cl-map' seems the fastest way to do this.
-  (cl-map (if (listp seq) 'list 'array)
-          (if test
-              (lambda (x)
-                (if (funcall test x)
-                    (funcall new x)
-                  x))
-            (lambda (x) (funcall new x)))
-          seq))
-
-(cl-defun loopy--substitute-using-if (new test seq)
-  "Copy SEQ, substituting elements satisfying TEST using output of NEW.
-
-NEW receives the element as its only argument.
-
-Unlike `loopy--substitute-using', the test is required."
-  (loopy--substitute-using new seq :test test))
-
 
 ;;;; Loop Tag Names
 (defun loopy--produce-non-returning-exit-tag-name (&optional loop-name)
diff --git a/lisp/loopy-seq.el b/lisp/loopy-seq.el
index 7fed5fa2e1..28bc99cf56 100644
--- a/lisp/loopy-seq.el
+++ b/lisp/loopy-seq.el
@@ -77,7 +77,7 @@
   (cons 'seq
         (seq-map (lambda (elt)
                    (if (seqp elt)
-                       (seq--make-pcase-patterns elt)
+                       (loopy-seq--make-pcase-pattern elt)
                      elt))
                  args)))
 
diff --git a/lisp/loopy-vars.el b/lisp/loopy-vars.el
index 1dfacc3201..891a192e56 100644
--- a/lisp/loopy-vars.el
+++ b/lisp/loopy-vars.el
@@ -456,11 +456,6 @@ Each item is of the form (FLAG . FLAG-ENABLING-FUNCTION).")
 
 This is used to check for errors with the `at' command.")
 
-(defvar loopy--flags nil
-  "Symbols/flags whose presence changes the behavior of `loopy'.
-
-NOTE: This functionality might change in the future.")
-
 (defvar loopy--with-vars nil
   "With Forms are variables explicitly created using the `with' keyword.
 
@@ -514,21 +509,6 @@ are `(loopy--at-instructions (LOOP-NAME INSTRUCTION 
INSTRUCTION ...))'.
 
 These instructions are removed when that loop expansion is complete.")
 
-(defvar loopy--valid-external-at-targets
-  ;; Iteration vars currently needed for `expr'.
-  ;;
-  ;; TODO: We should probably change what the variables are named
-  '( loopy--iteration-vars
-     loopy--accumulation-vars
-     loopy--vars-final-updates
-     loopy--skip-used
-     loopy--non-returning-exit-used
-     loopy--implicit-return)
-  "Valid targets for instructions pushed upwards by the `at' command.
-
-Instructions not in this list are interpreted by the current
-loop.")
-
 ;;;;; Loop Body Settings
 (defvar loopy--pre-conditions nil
   "The list of expressions that determine whether the `while' loop 
starts/loops.
@@ -673,8 +653,6 @@ list much easier.  When using multiple accumulation 
commands, it
 is important that such commands use the same variable to keep
 track of the end of the list.")
 
-(define-obsolete-variable-alias 'loopy--accumulation-final-updates
-  'loopy--vars-final-updates "2022-11")
 (defvar loopy--vars-final-updates nil
   "Alist of actions to perform on variables after the loop ends.
 
@@ -712,7 +690,7 @@ command) create for themselves a new, local top level.")
   "Where some accumulation commands are placing values.
 
 This variable keeps track some of the accumulation variables in a
-loop and how there being used.  This allows for optimizing some
+loop and how they are being used.  This allows for optimizing some
 kinds accumulations.
 
 Generally, this is used with commands that produce lists, such as
@@ -792,8 +770,9 @@ This list is mainly fed to the macro 
`loopy--wrap-variables-around-body'."))
 (defun loopy--with-bound-p (var-name)
   "Whether VAR-NAME is bound in `loopy--with-vars' or `loopy--without-vars'.
 
-Some iteration commands can produce more efficient code if there
-is no request for a specific initialization value."
+Some iteration commands (e.g., `reduce') will change their behavior
+depending on whether the accumulation variable is given an initial
+value."
   (or (cl-loop for (var val) in loopy--with-vars
                when (eq var var-name)
                return (cons 'with val))
@@ -805,7 +784,10 @@ is no request for a specific initialization value."
   "Whether VAR-NAME was bound by a command (and not a special macro argument).
 
 The variable can exist in `loopy--iteration-vars',
-`loopy--accumulation-vars', or `loopy--generalized-vars'."
+`loopy--accumulation-vars', `loopy--other-vars' (for commands like
+`set'), or `loopy--generalized-vars'.
+
+Re-initializing an iteration variable is an error."
   (or (cl-loop for (var val) in loopy--iteration-vars
                when (eq var var-name)
                return (cons 'iteration val))
@@ -814,7 +796,10 @@ The variable can exist in `loopy--iteration-vars',
                return (cons 'accumulation val))
       (cl-loop for (var val) in loopy--generalized-vars
                when (eq var var-name)
-               return (cons 'generalized val))))
+               return (cons 'generalized val))
+      (cl-loop for (var val) in loopy--other-vars
+               when (eq var var-name)
+               return (cons 'other val))))
 
 (defun loopy--bound-p (var-name)
   "Check if VAR-NAME (a symbol) is already bound for the macro.
@@ -833,35 +818,33 @@ Accumulation commands can operate on the same variable, 
and we
   don't want that variable to appear more than once as an implied return."
   (member expression loopy--implicit-return))
 
-(defun loopy--special-macro-argument-p (symbol arguments-list)
-  "Whether SYMBOL is a special macro argument (including aliases).
-
-Special macro arguments are listed in ARGUMENTS-LIST
-or `loopy-aliases'."
-  (memq symbol (append arguments-list
-                       (let ((results))
-                         (dolist (alias loopy-aliases)
-                           (when (memq (cdr alias) arguments-list)
-                             (push (car alias) results)))
-                         results))))
-
-(defun loopy--known-loop-name-p (target)
-  "Whether TARGET is a known loop name."
-  (memq target loopy--known-loop-names))
-
 (defun loopy--check-target-loop-name (target)
   "Signal an error whether TARGET is not a valid loop name."
-  (unless (loopy--known-loop-name-p target)
+  (unless (memq target loopy--known-loop-names)
     (signal 'loopy-unknown-loop-target (list target))))
 
 (defun loopy--check-position-name (pos)
   "Error if POS is not an accepted symbol describing how to add to a sequence.
 
+Accepted places are the quoted symbols `start' or `end'.  The place
+`beginning' is assumed to have been transformed by the function
+`loopy--normalize-position-name' into `start' before calling
+`loopy--check-position-name'.
+
 For example, the `collect' command can add items at the beginning or end
 of a sequence."
-  (unless (member pos '(start end beginning))
+  (unless (member pos '(start end))
     (signal 'loopy-bad-position-command-argument (list pos))))
 
+(defun loopy--normalize-position-name (pos)
+  (pcase pos
+    ((or 'beginning '(quote beginning) 'start '(quote start))
+     'start)
+    ((or 'end '(quote end))
+     'end)
+    (_
+     (signal 'loopy-bad-position-command-argument (list pos)))))
+
 (defmacro loopy--wrap-variables-around-body (&rest body)
   "Wrap variables in `loopy--variables' in `let*' bindings around BODY."
   (macroexp-let* (mapcar (lambda (x) (list x nil))
@@ -874,12 +857,5 @@ of a sequence."
       (funcall func)
     (error "Loopy: Flag not defined: %s" flag)))
 
-(defun loopy--valid-external-at-target-p (target)
-  "Check if variable TARGET is valid for an `at' command.
-
-This predicate checks for presence in the list
-`loopy--valid-external-at-targets'."
-  (memq target loopy--valid-external-at-targets))
-
 (provide 'loopy-vars)
 ;;; loopy-vars.el ends here
diff --git a/lisp/loopy.el b/lisp/loopy.el
index a96882c310..e6dbcd2d5e 100644
--- a/lisp/loopy.el
+++ b/lisp/loopy.el
@@ -155,10 +155,6 @@ this means that an explicit \"nil\" is always required."
                (= 2 (length binding)))
     (error "Invalid binding in `loopy' expansion: %s" binding)))
 
-(defun loopy--ensure-valid-bindings (bindings)
-  "Ensure BINDINGS valid according to `loopy--validate-binding'."
-  (mapc #'loopy--validate-binding bindings))
-
 (defun loopy--destructure-for-with-vars (bindings)
   "Destructure BINDINGS into bindings suitable for something like `let*'.
 
@@ -495,7 +491,6 @@ Returns BODY without the `%s' argument."
   ;;
   ;; 1. Flags in `loopy-default-flags'.
   ;; 2. Flags in the `flag' macro argument, which can undo the first group.
-  ;; (mapc #'loopy--apply-flag loopy-default-flags)
   (mapc #'loopy--apply-flag arg-value)
   (seq-remove (lambda (x) (eq (car x) arg-name)) body))
 
@@ -519,6 +514,7 @@ Returns BODY without the `%s' argument."
   (pcase-dolist ((or `(,var ,pos) var) arg-value)
     (push var loopy--optimized-accum-vars)
     (when pos
+      (setq pos (loopy--normalize-position-name pos))
       (loopy--update-accum-place-count loopy--loop-name var pos 1.0e+INF)))
   (seq-remove (lambda (x) (eq (car x) arg-name)) body))
 
@@ -672,9 +668,10 @@ macro `loopy' itself."
          (map-let ((t external)
                    (nil internal))
              (seq-group-by (lambda (x)
-                             (if (loopy--valid-external-at-target-p (cl-first 
x))
-                                 t
-                               nil))
+                             (if (memq (cl-first x)
+                                       '(loopy--main-body loopy--latter-body))
+                                 nil
+                               t))
                            at-instructions)
            (setf (alist-get target-loop loopy--at-instructions)
                  (append (alist-get target-loop
@@ -923,37 +920,35 @@ see the Info node `(loopy)' distributed with this 
package."
    ;; Body forms have the most variety.
    ;; An instruction is (PLACE-TO-ADD . THING-TO-ADD).
    ;; Things added are expanded in place.
-   (unwind-protect
-       (progn
-         (loopy--process-instructions (loopy--parse-loop-commands body))
-
-         ;; (cl-callf2 mapcar #'loopy--accum-code-expansion loopy--main-body)
-         ;; Expand any uses of `loopy--optimized-accum' as if it were a macro,
-         ;; using the function `loopy--expand-optimized-accum'.
-         ;;
-         ;; Prevent the expansion of, at the very least, `cl-block',
-         ;; `cl-return-from', and `cl-return' shouldn't be expanded.
-         ;;
-         ;; TODO: Is there a way to more precisely only expand
-         ;;       `loopy--optimized-accum'?
-         ;; Another option is this, but it massively slows down expansion:
-         ;;     (cl-loop for i being the symbols
-         ;;              when (eq (car-safe (symbol-function i)) 'macro)
-         ;;              collect (cons i nil))
-         (setq loopy--main-body
-               (cl-loop
-                with macro-funcs = `(,@(cl-loop for i in 
loopy--suppressed-macros
-                                                collect (cons i nil))
-                                     (loopy--optimized-accum
-                                      . loopy--expand-optimized-accum)
-                                     ,@macroexpand-all-environment)
-                for i in loopy--main-body
-                collect (macroexpand-all i macro-funcs)))
-
-         ;; Process any `at' instructions from loops lower in the call list.
-         (loopy--process-instructions (map-elt loopy--at-instructions
-                                               loopy--loop-name)))
-     (loopy--clean-up-stack-vars))
+   (loopy--with-protected-stack
+    (loopy--process-instructions (loopy--parse-loop-commands body))
+
+    ;; (cl-callf2 mapcar #'loopy--accum-code-expansion loopy--main-body)
+    ;; Expand any uses of `loopy--optimized-accum' as if it were a macro,
+    ;; using the function `loopy--expand-optimized-accum'.
+    ;;
+    ;; Prevent the expansion of, at the very least, `cl-block',
+    ;; `cl-return-from', and `cl-return' shouldn't be expanded.
+    ;;
+    ;; TODO: Is there a way to more precisely only expand
+    ;;       `loopy--optimized-accum'?
+    ;; Another option is this, but it massively slows down expansion:
+    ;;     (cl-loop for i being the symbols
+    ;;              when (eq (car-safe (symbol-function i)) 'macro)
+    ;;              collect (cons i nil))
+    (setq loopy--main-body
+          (cl-loop
+           with macro-funcs = `(,@(cl-loop for i in loopy--suppressed-macros
+                                           collect (cons i nil))
+                                (loopy--optimized-accum
+                                 . loopy--expand-optimized-accum)
+                                ,@macroexpand-all-environment)
+           for i in loopy--main-body
+           collect (macroexpand-all i macro-funcs)))
+
+    ;; Process any `at' instructions from loops lower in the call list.
+    (loopy--process-instructions (map-elt loopy--at-instructions
+                                          loopy--loop-name)))
 
    ;; Now that instructions processed, make sure the order-dependent lists are
    ;; in the correct order.
diff --git a/tests/misc-tests.el b/tests/misc-tests.el
index 8f28b998b7..c37c759996 100644
--- a/tests/misc-tests.el
+++ b/tests/misc-tests.el
@@ -1915,8 +1915,6 @@ The valid keys are:
   :list nil
   :convert nil)
 
-;; TODO: HERE!!!!!!!!!!!!! start with `pcase-tests-loopy-&map-full-form'
-
 (loopy-def-pcase-test pcase-tests-loopy-&map-full-form-1
   :result (list 1 2)
   :val (list 'a 1 'b 2)
diff --git a/tests/tests.el b/tests/tests.el
index 77b057954a..1c0336a9e5 100644
--- a/tests/tests.el
+++ b/tests/tests.el
@@ -738,6 +738,30 @@ writing a `seq-do' method for the custom seq."
   ;; "for loopy"" should work, but is redundant and unneeded.
   :iter-keyword (array loopy))
 
+(loopy-deftest loopy-at-set
+  :doc "Ensure `loopy--other-vars' are handled by `at' correctly."
+  :result 25
+  :multi-body t
+  :body [((named outer)
+          (cycle 1)
+          ;; Don't turn this into (for cycle 1) inside `loopy',
+          ;; which would break.
+          (loopy (loopy-test-escape (cycle 1))
+                 (loopy-test-escape (at outer (set cat 25))))
+          (finally-return cat))
+         (outer
+          (cycle 1)
+          ;; Don't turn this into (for cycle 1) inside `loopy',
+          ;; which would break.
+          (loopy (loopy-test-escape (cycle 1))
+                 (loopy-test-escape (at outer (set cat 25))))
+          (finally-return cat))]
+  :loopy t
+  ;; `loopy' should work barely.
+  :iter-bare ((cycle . cycling))
+  ;; "for loopy"" should work, but is redundant and unneeded.
+  :iter-keyword (cycle loopy))
+
 (loopy-deftest loopy-at-leave
   :result '(1 2 3)
   :multi-body t
@@ -2057,8 +2081,6 @@ Using numbers directly will use less variables and more 
efficient code."
               (do . ignore)))
 
 ;;;;; Nums
-;; TODO: Names `num' and `number' aren't listed in the Org doc.
-;;       They should be removed.
 (loopy-deftest numbers
   :result '(1 2 3 4 5)
   :repeat _cmd
@@ -5313,7 +5335,6 @@ Using `start' and `end' in either order should give the 
same result."
   :iter-bare ((array . arraying)
               (nunion . nunioning)))
 
-;; TODO: Fail.  Fix in optimized constructor, same as others.
 (loopy-deftest nunion-end-tracking-accum-opt-end-:at-start
   :result '(10 8 9 7 5 6 4 1 2 3)
   :body ((accum-opt (coll end))


Reply via email to