Skip to content

Commit 045cfbe

Browse files
dickmaolarsmagne
authored andcommitted
Refix conditional step clauses in cl-loop
* lisp/emacs-lisp/cl-macs.el (cl--loop-bindings, cl--loop-symbol-macs, cl-loop): Add cl--loop-conditions, remove cl--loop-guard-cond. (cl--push-clause-loop-body): Apply clause to both cl--loop-conditions and cl--loop-body (cl--parse-loop-clause): Use cl--push-clause-loop-body. * test/lisp/emacs-lisp/cl-macs-tests.el (cl-macs-loop-and-assignment): Use docstring. (cl-macs-loop-for-as-arith): Removed expected failure. (cl-macs-loop-conditional-step-clauses): Add some tests (bug#29799).
1 parent f373cec commit 045cfbe

File tree

2 files changed

+101
-63
lines changed

2 files changed

+101
-63
lines changed

lisp/emacs-lisp/cl-macs.el

Lines changed: 37 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -889,15 +889,15 @@ This is compatible with Common Lisp, but note that `defun' and
889889
;;; The "cl-loop" macro.
890890

891891
(defvar cl--loop-args) (defvar cl--loop-accum-var) (defvar cl--loop-accum-vars)
892-
(defvar cl--loop-bindings) (defvar cl--loop-body)
892+
(defvar cl--loop-bindings) (defvar cl--loop-body) (defvar cl--loop-conditions)
893893
(defvar cl--loop-finally)
894894
(defvar cl--loop-finish-flag) ;Symbol set to nil to exit the loop?
895895
(defvar cl--loop-first-flag)
896896
(defvar cl--loop-initially) (defvar cl--loop-iterator-function)
897897
(defvar cl--loop-name)
898898
(defvar cl--loop-result) (defvar cl--loop-result-explicit)
899899
(defvar cl--loop-result-var) (defvar cl--loop-steps)
900-
(defvar cl--loop-symbol-macs) (defvar cl--loop-guard-cond)
900+
(defvar cl--loop-symbol-macs)
901901

902902
(defun cl--loop-set-iterator-function (kind iterator)
903903
(if cl--loop-iterator-function
@@ -966,7 +966,8 @@ For more details, see Info node `(cl)Loop Facility'.
966966
(cl--loop-accum-var nil) (cl--loop-accum-vars nil)
967967
(cl--loop-initially nil) (cl--loop-finally nil)
968968
(cl--loop-iterator-function nil) (cl--loop-first-flag nil)
969-
(cl--loop-symbol-macs nil) (cl--loop-guard-cond nil))
969+
(cl--loop-symbol-macs nil)
970+
(cl--loop-conditions nil))
970971
;; Here is more or less how those dynbind vars are used after looping
971972
;; over cl--parse-loop-clause:
972973
;;
@@ -1001,24 +1002,7 @@ For more details, see Info node `(cl)Loop Facility'.
10011002
(list (or cl--loop-result-explicit
10021003
cl--loop-result))))
10031004
(ands (cl--loop-build-ands (nreverse cl--loop-body)))
1004-
(while-body
1005-
(nconc
1006-
(cadr ands)
1007-
(if (or (not cl--loop-guard-cond) (not cl--loop-first-flag))
1008-
(nreverse cl--loop-steps)
1009-
;; Right after update the loop variable ensure that the loop
1010-
;; condition, i.e. (car ands), is still satisfied; otherwise,
1011-
;; set `cl--loop-first-flag' nil and skip the remaining
1012-
;; body forms (#Bug#29799).
1013-
;;
1014-
;; (last cl--loop-steps) updates the loop var
1015-
;; (car (butlast cl--loop-steps)) sets `cl--loop-first-flag' nil
1016-
;; (nreverse (cdr (butlast cl--loop-steps))) are the
1017-
;; remaining body forms.
1018-
(append (last cl--loop-steps)
1019-
`((and ,(car ands)
1020-
,@(nreverse (cdr (butlast cl--loop-steps)))))
1021-
`(,(car (butlast cl--loop-steps)))))))
1005+
(while-body (nconc (cadr ands) (nreverse cl--loop-steps)))
10221006
(body (append
10231007
(nreverse cl--loop-initially)
10241008
(list (if cl--loop-iterator-function
@@ -1051,6 +1035,12 @@ For more details, see Info node `(cl)Loop Facility'.
10511035
(list `(cl-symbol-macrolet ,cl--loop-symbol-macs ,@body))))
10521036
`(cl-block ,cl--loop-name ,@body)))))
10531037

1038+
(defmacro cl--push-clause-loop-body (clause)
1039+
"Apply CLAUSE to both `cl--loop-conditions' and `cl--loop-body'."
1040+
`(progn
1041+
(push ,clause cl--loop-conditions)
1042+
(push ,clause cl--loop-body)))
1043+
10541044
;; Below is a complete spec for cl-loop, in several parts that correspond
10551045
;; to the syntax given in CLtL2. The specs do more than specify where
10561046
;; the forms are; it also specifies, as much as Edebug allows, all the
@@ -1201,8 +1191,6 @@ For more details, see Info node `(cl)Loop Facility'.
12011191
;; (def-edebug-spec loop-d-type-spec
12021192
;; (&or (loop-d-type-spec . [&or nil loop-d-type-spec]) cl-type-spec))
12031193

1204-
1205-
12061194
(defun cl--parse-loop-clause () ; uses loop-*
12071195
(let ((word (pop cl--loop-args))
12081196
(hash-types '(hash-key hash-keys hash-value hash-values))
@@ -1281,11 +1269,11 @@ For more details, see Info node `(cl)Loop Facility'.
12811269
(if end-var (push (list end-var end) loop-for-bindings))
12821270
(if step-var (push (list step-var step)
12831271
loop-for-bindings))
1284-
(if end
1285-
(push (list
1286-
(if down (if excl '> '>=) (if excl '< '<=))
1287-
var (or end-var end))
1288-
cl--loop-body))
1272+
(when end
1273+
(cl--push-clause-loop-body
1274+
(list
1275+
(if down (if excl '> '>=) (if excl '< '<=))
1276+
var (or end-var end))))
12891277
(push (list var (list (if down '- '+) var
12901278
(or step-var step 1)))
12911279
loop-for-steps)))
@@ -1295,7 +1283,7 @@ For more details, see Info node `(cl)Loop Facility'.
12951283
(temp (if (and on (symbolp var))
12961284
var (make-symbol "--cl-var--"))))
12971285
(push (list temp (pop cl--loop-args)) loop-for-bindings)
1298-
(push `(consp ,temp) cl--loop-body)
1286+
(cl--push-clause-loop-body `(consp ,temp))
12991287
(if (eq word 'in-ref)
13001288
(push (list var `(car ,temp)) cl--loop-symbol-macs)
13011289
(or (eq temp var)
@@ -1318,24 +1306,19 @@ For more details, see Info node `(cl)Loop Facility'.
13181306
((eq word '=)
13191307
(let* ((start (pop cl--loop-args))
13201308
(then (if (eq (car cl--loop-args) 'then)
1321-
(cl--pop2 cl--loop-args) start)))
1309+
(cl--pop2 cl--loop-args) start))
1310+
(first-assign (or cl--loop-first-flag
1311+
(setq cl--loop-first-flag
1312+
(make-symbol "--cl-var--")))))
13221313
(push (list var nil) loop-for-bindings)
13231314
(if (or ands (eq (car cl--loop-args) 'and))
13241315
(progn
1325-
(push `(,var
1326-
(if ,(or cl--loop-first-flag
1327-
(setq cl--loop-first-flag
1328-
(make-symbol "--cl-var--")))
1329-
,start ,var))
1330-
loop-for-sets)
1331-
(push (list var then) loop-for-steps))
1332-
(push (list var
1333-
(if (eq start then) start
1334-
`(if ,(or cl--loop-first-flag
1335-
(setq cl--loop-first-flag
1336-
(make-symbol "--cl-var--")))
1337-
,start ,then)))
1338-
loop-for-sets))))
1316+
(push `(,var (if ,first-assign ,start ,var)) loop-for-sets)
1317+
(push `(,var (if ,(car (cl--loop-build-ands
1318+
(nreverse cl--loop-conditions)))
1319+
,then ,var))
1320+
loop-for-steps))
1321+
(push `(,var (if ,first-assign ,start ,then)) loop-for-sets))))
13391322

13401323
((memq word '(across across-ref))
13411324
(let ((temp-vec (make-symbol "--cl-vec--"))
@@ -1344,9 +1327,8 @@ For more details, see Info node `(cl)Loop Facility'.
13441327
(push (list temp-vec (pop cl--loop-args)) loop-for-bindings)
13451328
(push (list temp-len `(length ,temp-vec)) loop-for-bindings)
13461329
(push (list temp-idx -1) loop-for-bindings)
1347-
(push `(< (setq ,temp-idx (1+ ,temp-idx))
1348-
,temp-len)
1349-
cl--loop-body)
1330+
(cl--push-clause-loop-body
1331+
`(< (setq ,temp-idx (1+ ,temp-idx)) ,temp-len))
13501332
(if (eq word 'across-ref)
13511333
(push (list var `(aref ,temp-vec ,temp-idx))
13521334
cl--loop-symbol-macs)
@@ -1376,15 +1358,14 @@ For more details, see Info node `(cl)Loop Facility'.
13761358
loop-for-bindings)
13771359
(push (list var `(elt ,temp-seq ,temp-idx))
13781360
cl--loop-symbol-macs)
1379-
(push `(< ,temp-idx ,temp-len) cl--loop-body))
1361+
(cl--push-clause-loop-body `(< ,temp-idx ,temp-len)))
13801362
;; Evaluate seq length just if needed, that is, when seq is not a cons.
13811363
(push (list temp-len (or (consp seq) `(length ,temp-seq)))
13821364
loop-for-bindings)
13831365
(push (list var nil) loop-for-bindings)
1384-
(push `(and ,temp-seq
1385-
(or (consp ,temp-seq)
1386-
(< ,temp-idx ,temp-len)))
1387-
cl--loop-body)
1366+
(cl--push-clause-loop-body `(and ,temp-seq
1367+
(or (consp ,temp-seq)
1368+
(< ,temp-idx ,temp-len))))
13881369
(push (list var `(if (consp ,temp-seq)
13891370
(pop ,temp-seq)
13901371
(aref ,temp-seq ,temp-idx)))
@@ -1480,9 +1461,8 @@ For more details, see Info node `(cl)Loop Facility'.
14801461
(push (list var '(selected-frame))
14811462
loop-for-bindings)
14821463
(push (list temp nil) loop-for-bindings)
1483-
(push `(prog1 (not (eq ,var ,temp))
1484-
(or ,temp (setq ,temp ,var)))
1485-
cl--loop-body)
1464+
(cl--push-clause-loop-body `(prog1 (not (eq ,var ,temp))
1465+
(or ,temp (setq ,temp ,var))))
14861466
(push (list var `(next-frame ,var))
14871467
loop-for-steps)))
14881468

@@ -1503,9 +1483,8 @@ For more details, see Info node `(cl)Loop Facility'.
15031483
(push (list minip `(minibufferp (window-buffer ,var)))
15041484
loop-for-bindings)
15051485
(push (list temp nil) loop-for-bindings)
1506-
(push `(prog1 (not (eq ,var ,temp))
1507-
(or ,temp (setq ,temp ,var)))
1508-
cl--loop-body)
1486+
(cl--push-clause-loop-body `(prog1 (not (eq ,var ,temp))
1487+
(or ,temp (setq ,temp ,var))))
15091488
(push (list var `(next-window ,var ,minip))
15101489
loop-for-steps)))
15111490

@@ -1529,7 +1508,6 @@ For more details, see Info node `(cl)Loop Facility'.
15291508
t)
15301509
cl--loop-body))
15311510
(when loop-for-steps
1532-
(setq cl--loop-guard-cond t)
15331511
(push (cons (if ands 'cl-psetq 'setq)
15341512
(apply 'append (nreverse loop-for-steps)))
15351513
cl--loop-steps))))

test/lisp/emacs-lisp/cl-macs-tests.el

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
;;; ANSI 6.1.1.7 Destructuring
3232
(ert-deftest cl-macs-loop-and-assignment ()
33-
;; Bug#6583
33+
"Bug#6583"
3434
:expected-result :failed
3535
(should (equal (cl-loop for numlist in '((1 2 4.0) (5 6 8.3) (8 9 10.4))
3636
for a = (cl-first numlist)
@@ -61,7 +61,6 @@
6161
;;; 6.1.2.1.1 The for-as-arithmetic subclause
6262
(ert-deftest cl-macs-loop-for-as-arith ()
6363
"Test various for-as-arithmetic subclauses."
64-
:expected-result :failed
6564
(should (equal (cl-loop for i to 10 by 3 collect i)
6665
'(0 3 6 9)))
6766
(should (equal (cl-loop for i upto 3 collect i)
@@ -74,9 +73,9 @@
7473
'(10 8 6)))
7574
(should (equal (cl-loop for i from 10 downto 1 by 3 collect i)
7675
'(10 7 4 1)))
77-
(should (equal (cl-loop for i above 0 by 2 downfrom 10 collect i)
76+
(should (equal (cl-loop for i downfrom 10 above 0 by 2 collect i)
7877
'(10 8 6 4 2)))
79-
(should (equal (cl-loop for i downto 10 from 15 collect i)
78+
(should (equal (cl-loop for i from 15 downto 10 collect i)
8079
'(15 14 13 12 11 10))))
8180

8281
(ert-deftest cl-macs-loop-for-as-arith-order-side-effects ()
@@ -530,4 +529,65 @@ collection clause."
530529
l)
531530
'(1))))
532531

532+
(ert-deftest cl-macs-loop-conditional-step-clauses ()
533+
"These tests failed under the initial fixes in #bug#29799."
534+
(should (cl-loop for i from 1 upto 100 and j = 1 then (1+ j)
535+
if (not (= i j))
536+
return nil
537+
end
538+
until (> j 10)
539+
finally return t))
540+
541+
(should (equal (let* ((size 7)
542+
(arr (make-vector size 0)))
543+
(cl-loop for k below size
544+
for x = (* 2 k) and y = (1+ (elt arr k))
545+
collect (list k x y)))
546+
'((0 0 1) (1 2 1) (2 4 1) (3 6 1) (4 8 1) (5 10 1) (6 12 1))))
547+
548+
(should (equal (cl-loop for x below 3
549+
for y below 2 and z = 1
550+
collect x)
551+
'(0 1)))
552+
553+
(should (equal (cl-loop for x below 3
554+
and y below 2
555+
collect x)
556+
'(0 1)))
557+
558+
;; this is actually disallowed in clisp, but is semantically consistent
559+
(should (equal (cl-loop with result
560+
for x below 3
561+
for y = (progn (push x result) x) and z = 1
562+
append (list x y) into result1
563+
finally return (append result result1))
564+
'(2 1 0 0 0 1 1 2 2)))
565+
566+
(should (equal (cl-loop with result
567+
for x below 3
568+
for _y = (progn (push x result))
569+
finally return result)
570+
'(2 1 0)))
571+
572+
;; this nonintuitive result is replicated by clisp
573+
(should (equal (cl-loop with result
574+
for x below 3
575+
and y = (progn (push x result))
576+
finally return result)
577+
'(2 1 0 0)))
578+
579+
;; this nonintuitive result is replicated by clisp
580+
(should (equal (cl-loop with result
581+
for x below 3
582+
and y = (progn (push x result)) then (progn (push (1+ x) result))
583+
finally return result)
584+
'(3 2 1 0)))
585+
586+
(should (cl-loop with result
587+
for x below 3
588+
for y = (progn (push x result) x) then (progn (push (1+ x) result) (1+ x))
589+
and z = 1
590+
collect y into result1
591+
finally return (equal (nreverse result) result1))))
592+
533593
;;; cl-macs-tests.el ends here

0 commit comments

Comments
 (0)