Skip to content

Commit 49b6358

Browse files
committed
iterate: fix scope bug and declarations
Previously initforms were in the scope of the local function: now they are not. Declaration handling is better.
1 parent f3a56a7 commit 49b6358

File tree

3 files changed

+54
-44
lines changed

3 files changed

+54
-44
lines changed

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.2.0
1+
8.2.1

iterate.lisp

+29-43
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
;; Description - Applicative iteration
44
;; Author - Tim Bradshaw (tfb at lostwithiel)
55
;; Created On - Sat Oct 7 00:23:24 2000
6-
;; Last Modified On - Sat Mar 11 16:51:22 2023
6+
;; Last Modified On - Wed Mar 20 08:41:14 2024
77
;; Last Modified By - Tim Bradshaw (tfb at pendeen.fritz.box)
8-
;; Update Count - 18
8+
;; Update Count - 19
99
;; Status - Unknown
1010
;;
1111
;; $Id$
@@ -14,14 +14,14 @@
1414
;;;; * Applicative iteration (don't need this in CMUCL)
1515
;;;
1616

17-
;;; iterate.lisp is copyright 1997-2000, 2021, 2023 by me, Tim
17+
;;; iterate.lisp is copyright 1997-2000, 2021, 2023, 2024 by me, Tim
1818
;;; Bradshaw, and may be used for any purpose whatsoever by anyone. It
1919
;;; has no warranty whatsoever. I would appreciate acknowledgement if
2020
;;; you use it in anger, and I would also very much appreciate any
2121
;;; feedback or bug fixes.
2222
;;;
23-
;;; The improvements to all this code in 2023, as well as the new
24-
;;; ITERATE*, ITERATING & ITERATING* are due to Zyni: thank you.
23+
;;; The improvements to all this code in 2023 & 2024, as well as the
24+
;;; new ITERATE*, ITERATING & ITERATING* are due to Zyni: thank you.
2525
;;;
2626

2727
(defpackage :org.tfeb.hax.iterate
@@ -33,19 +33,21 @@
3333
(provide :org.tfeb.hax.iterate)
3434

3535
(defun extract-ignore/other-decls (decls/forms)
36-
;; See utilities. Bit this is not the same: it returns all ignores and others as two values
37-
;; others as two values.
38-
(do* ((ignores '())
39-
(others '())
40-
(tail decls/forms (rest tail))
41-
(this (first tail) (first tail)))
42-
((or (null tail) (not (consp this))
43-
(not (eql (first this) 'declare)))
44-
(values (nreverse ignores) (nreverse others)))
45-
(if (and (consp (second this))
46-
(eql (first (second this)) 'ignore))
47-
(push this ignores)
48-
(push this others))))
36+
;; See utilities. But this is not the same: it returns all ignores
37+
;; and others as two values What's returned is the bodies of two
38+
;; DECLARE forms.
39+
(let ((ignores '())
40+
(others '()))
41+
(dolist (d/f decls/forms)
42+
(unless (and (consp d/f)
43+
(eql (car d/f) 'declare))
44+
(return))
45+
(dolist (d (rest d/f))
46+
(if (and (consp d) (eql (car d) 'ignore))
47+
(push d ignores)
48+
(push d others))))
49+
(values (nreverse ignores)
50+
(nreverse others))))
4951

5052
(defun expand-iterate (name bindings body starred)
5153
(unless (every (lambda (binding)
@@ -67,30 +69,14 @@
6769
binding)
6870
(list
6971
(first binding))))
70-
bindings))
71-
(argvals
72-
(mapcar (lambda (binding)
73-
(typecase binding
74-
(symbol
75-
nil)
76-
(list
77-
(case (length binding)
78-
((1)
79-
nil)
80-
((2)
81-
(second binding))))))
8272
bindings)))
83-
(if (not starred)
84-
`(labels ((,name ,argnames
85-
,@body))
86-
(,name ,@argvals))
87-
(multiple-value-bind (ignores others) (extract-ignore/other-decls body)
88-
(declare (ignore others))
89-
`(labels ((,name ,argnames
73+
(multiple-value-bind (ignores others) (extract-ignore/other-decls body)
74+
(declare (ignore ignores))
75+
`(,(if starred 'let* 'let) ,bindings
76+
(declare ,@others)
77+
(labels ((,name ,argnames
9078
,@body))
91-
(let* ,(mapcar #'list argnames argvals)
92-
,@ignores
93-
(,name ,@argnames)))))))
79+
(,name ,@argnames))))))
9480

9581
(defmacro iterate (name bindings &body body)
9682
"Scheme-style named-LET: parallel binding
@@ -181,13 +167,13 @@ This is like LET*: initial values can depend on preceeding variables."
181167
(let ((secret-name (make-symbol (symbol-name name))))
182168
(multiple-value-bind (ignores others) (extract-ignore/other-decls body)
183169
`(labels ((,secret-name ,argnames
184-
,@ignores ,@others
170+
(declare ,@ignores ,@others)
185171
(flet ((,name (&key ,@(mapcar #'list argnames argsteps))
186172
(,secret-name ,@argnames)))
187173
(declare (inline ,name))
188174
,@body)))
189175
(let* ,(mapcar #'list argnames argvals)
190-
,@others
176+
(declare ,@others)
191177
(,secret-name ,@argnames))))))))
192178

193179
(defmacro iterating (name bindings &body body)
@@ -203,7 +189,7 @@ preceeding variables and step forms see the old values of variables."
203189
(expand-iterating name bindings body nil))
204190

205191
(defmacro iterating* (name bindings &body body)
206-
"Applicative iteration macro with optional step forms: sequentisl binding
192+
"Applicative iteration macro with optional step forms: sequential binding
207193
208194
This is like ITERATE but each binding can be (var init/step) or (var
209195
init step). The local function has approproate keyword arguments

test/test-iterate.lisp

+24
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,30 @@
4141
(declare (ignorable x))
4242
y))))
4343

44+
;;; Test that the scope of local functions is correct: they were wrong for a
45+
;;; very long time
46+
;;;
47+
48+
(define-test ("org.tfeb.hax.iterate" "iterate local function not in scope of initforms")
49+
(is eql 1
50+
(let ((v 0))
51+
(flet ((foo (x)
52+
(setf v x)))
53+
(iterate foo ((x (foo 1)))
54+
(declare (ignore x))
55+
v)))))
56+
57+
(define-test ("org.tfeb.hax.iterate" "iterate* local function not in scope of initforms")
58+
(is-values
59+
(let ((v 0))
60+
(flet ((foo (x)
61+
(setf v x)))
62+
(iterate* foo ((x (foo 1)) (y (1+ x)))
63+
(declare (ignore x))
64+
(values v y))))
65+
(= 1)
66+
(= 2)))
67+
4468
(define-test ("org.tfeb.hax.iterate" "iterating basic")
4569
(is = 11 (iterating n ((i 0 (1+ i)))
4670
(if (> i 10) i (n))))

0 commit comments

Comments
 (0)