Skip to content

Commit ce14e0d

Browse files
committed
Improve set-prev.
- Allow `back` to no be known at compile time. Use a simple implementation of a queue, adapted from https://irreal.org/blog/?p=40. - Add a test of a `with`-bound `back`. - State clearly in the documentation that `set-prev` stores the value of `VAL` found at the end of the loop cycle and that it does not modify `VAR` until the specified loop cycle.
1 parent 5b40c0d commit ce14e0d

File tree

4 files changed

+108
-48
lines changed

4 files changed

+108
-48
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,20 @@ This document describes the user-facing changes to Loopy.
1818
([#197], [#145]). These commands no longer take multiple conditions in the
1919
same command.
2020

21+
### Bugs Fixed
22+
23+
- Allow `back` in `set-prev` to not be known at compile time ([#202]).
24+
25+
### Documentation Improvements
26+
27+
- State explicitly that `set-prev` records values from the end
28+
of each loop cycle and that it does not modify its variable
29+
until the specified cycle ([#202]).
30+
2131
[#195]: https://github.com/okamsn/loopy/pull/195
2232
[#196]: https://github.com/okamsn/loopy/pull/196
2333
[#197]: https://github.com/okamsn/loopy/pull/197
34+
[#202]: https://github.com/okamsn/loopy/pull/202
2435

2536
## 0.12.2
2637

doc/loopy-doc.org

+20-5
Original file line numberDiff line numberDiff line change
@@ -1370,9 +1370,11 @@ value and do no affect how the loop iterates.
13701370
#+findex: prev
13711371
- =(set-prev|prev-expr VAR VAL &key back)= :: Bind =VAR= to a value =VAL= from a
13721372
previous cycle in the loop. With =BACK= (default: 1), use the value from that
1373-
many cycles previous. This command /does not/ work like a queue; it always
1374-
uses the value from the =BACK=-th previous cycle, regardless of when the
1375-
command is run.
1373+
many cycles previous. _If not enough cycles have passed yet, then the value
1374+
of =VAR= is not modified._ This command /does not/ work like a queue for
1375+
recording =VAL=; it always uses the value from the =BACK=-th previous cycle,
1376+
regardless of when the command is run. The value used is always the value at
1377+
the end of the cycle.
13761378

13771379
This command also has the aliases =setting-prev=, =prev-set=, and =prev=.
13781380

@@ -1383,10 +1385,13 @@ value and do no affect how the loop iterates.
13831385
(collect j))
13841386

13851387
;; => (nil nil nil 1 2)
1386-
(loopy (list i '(1 2 3 4 5))
1387-
(set-prev j i :back 3)
1388+
(loopy (with (n 3))
1389+
(list i '(1 2 3 4 5))
1390+
(set-prev j i :back n)
13881391
(collect j))
13891392

1393+
;; NOTE: `j' isn't overwritten until the correct cycle:
1394+
;;
13901395
;; => ((first-val nil) (first-val nil) (1 2) (3 4))
13911396
(loopy (with (j 'first-val))
13921397
(list i '((1 . 2) (3 . 4) (5 . 6) (7 . 8)))
@@ -1402,6 +1407,16 @@ value and do no affect how the loop iterates.
14021407
(when (cl-oddp i)
14031408
(set-prev j i))
14041409
(collect j))
1410+
1411+
;; NOTE: `j' is always bound to the previous value of `i'
1412+
;; from the end of the specified cycle.
1413+
;;
1414+
;; => (nil 101 102 103)
1415+
(loopy (numbers i :from 1 :to 4)
1416+
(set i2 i)
1417+
(set-prev j i2)
1418+
(set i2 (+ i 100))
1419+
(collect j))
14051420
#+end_src
14061421

14071422
** Iteration

loopy-commands.el

+63-40
Original file line numberDiff line numberDiff line change
@@ -190,52 +190,75 @@ handled by `loopy-iter'."
190190
;; being known at compile time (but still only being evaluated once.)
191191
;; (#194)
192192
(cl-defun loopy--parse-set-prev-command
193-
((_ var val &key back))
193+
((_ var val &key (back 1)))
194194
"Parse the `set-prev' command as (set-prev VAR VAL &key back).
195195
196196
VAR is set to a version of VAL in a past loop cycle. With BACK,
197197
wait that many cycle before beginning to update VAR.
198198
199+
This command records the value of VAL at the end of the cycle,
200+
not when the command is run.
201+
199202
This command does not wait for VAL to change before updating VAR."
200-
(let* ((holding-vars (cl-loop for i from 1 to (or back 1)
201-
collect (gensym "set-prev-hold")))
202-
(using-destructuring (seqp var))
203-
(with-bound (if using-destructuring
204-
(cl-some #'loopy--with-bound-p
205-
(cl-second (loopy--destructure-for-iteration var val)))
206-
(loopy--with-bound-p var)))
207-
;; We don't use `cl-shiftf' in the main body because we want the
208-
;; holding variables to update regardless of whether we update
209-
;; VAR.
210-
(holding-vars-setq `(loopy--latter-body
211-
(cl-shiftf ,@holding-vars ,val))))
212-
(if with-bound
213-
(if using-destructuring
214-
(let ((cnt-holder (gensym "count"))
215-
(back-holder (gensym "back")))
216-
`((loopy--other-vars (,cnt-holder 0))
217-
(loopy--latter-body (setq ,cnt-holder (1+ ,cnt-holder)))
218-
(loopy--other-vars (,back-holder ,back))
219-
,@(mapcar (lambda (x) `(loopy--other-vars (,x nil)))
220-
holding-vars)
221-
,@(loopy--bind-main-body (main-exprs rest-instr)
222-
(loopy--destructure-for-other-command
223-
var (car holding-vars))
224-
`((loopy--main-body (when (>= ,cnt-holder ,back-holder)
225-
,@main-exprs))
226-
,@rest-instr))
227-
,holding-vars-setq))
228-
(let ((val-holder (gensym "set-prev-val")))
229-
`((loopy--other-vars (,val-holder ,var))
230-
,@(mapcar (lambda (x) `(loopy--other-vars (,x ,val-holder)))
231-
holding-vars)
232-
(loopy--main-body (setq ,var ,(car holding-vars)))
233-
,holding-vars-setq)))
234-
`(,@(mapcar (lambda (x) `(loopy--other-vars (,x nil)))
235-
holding-vars)
236-
,@(loopy--destructure-for-other-command
237-
var (car holding-vars))
238-
,holding-vars-setq))))
203+
(if (not (numberp back))
204+
;; When we don't know ahead of time how far back we need to go, we have to
205+
;; use a queue. This code is adapted from Irreal's blog
206+
;; (https://irreal.org/blog/?p=40) where they give an example of a simple
207+
;; FIFO queue in Scheme. It uses two lists. The "front" lists contains
208+
;; values for popping off. The "back" list contains values for pushing
209+
;; on. When the "front" list is exhausted, values are moved from the
210+
;; "back" list in reverse.
211+
(loopy--instr-let-const* ((prev back))
212+
loopy--other-vars
213+
(loopy--instr-let-var* ((cnt 0)
214+
(queue-front nil)
215+
(queue-end nil))
216+
loopy--other-vars
217+
;; We generate a main-body expression for binding the variables to the
218+
;; desired values and for setting them to nil. There is overlap in the
219+
;; remaining expressions, which initialize the variables.
220+
(loopy--bind-main-body (main-exprs init-instr)
221+
(loopy--destructure-for-other-command
222+
var `(or (pop ,queue-front)
223+
(progn
224+
(setq ,queue-front (reverse ,queue-end)
225+
,queue-end nil)
226+
(pop ,queue-front))))
227+
`((loopy--main-body (when (>= ,cnt ,prev)
228+
,(macroexp-progn main-exprs)))
229+
,@init-instr
230+
(loopy--latter-body (push ,val ,queue-end))
231+
(loopy--latter-body (setq ,cnt (1+ ,cnt)))))))
232+
233+
;; When we know ahead of time how far we need to go back, we can use a chain
234+
;; of `setq's for storing values instead of queue. However, except when
235+
;; BACK is 1, we still need to use a count, in case one of the variables is
236+
;; initialized in `with'.
237+
238+
(if (= back 1)
239+
(loopy--instr-let-var* ((hold-var nil)
240+
(run nil))
241+
loopy--other-vars
242+
`(,@(loopy--bind-main-body (main-exprs init-instrs)
243+
(loopy--destructure-for-other-command var hold-var)
244+
`((loopy--main-body (when ,run
245+
,@main-exprs))
246+
,@init-instrs))
247+
(loopy--latter-body (setq ,hold-var ,val))
248+
(loopy--latter-body (setq ,run t))))
249+
(let ((hold-vars (cl-loop for i from 1 to back
250+
collect (gensym "hold-var"))))
251+
(loopy--instr-let-var* ((cnt 0))
252+
loopy--other-vars
253+
`(,@(mapcar (lambda (x) `(loopy--other-vars (,x nil)))
254+
hold-vars)
255+
,@(loopy--bind-main-body (main-exprs init-instrs)
256+
(loopy--destructure-for-other-command var (car hold-vars))
257+
`((loopy--main-body (when (>= ,cnt ,back)
258+
,@main-exprs))
259+
,@init-instrs))
260+
(loopy--latter-body (cl-shiftf ,@hold-vars ,val))
261+
(loopy--latter-body (setq ,cnt (1+ ,cnt)))))))))
239262

240263
;;;;;; Group
241264
(cl-defun loopy--parse-group-command ((_ &rest body))

tests/tests.el

+14-3
Original file line numberDiff line numberDiff line change
@@ -876,9 +876,20 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'."
876876

877877
(loopy-deftest set-prev-keyword-back
878878
:result '(nil nil nil 1 2)
879-
:body ((list i '(1 2 3 4 5))
880-
(set-prev j i :back 3)
881-
(collect j))
879+
:multi-body t
880+
:body [((list i '(1 2 3 4 5))
881+
(set-prev j i :back 3)
882+
(collect j))
883+
884+
((with (n 3)
885+
(first-time t))
886+
(list i '(1 2 3 4 5))
887+
(set-prev j i :back (if first-time
888+
(progn
889+
(setq first-time nil)
890+
n)
891+
(error "Evaluated more than once.")))
892+
(collect j))]
882893
:loopy t
883894
:iter-bare ((list . listing)
884895
(collect . collecting)

0 commit comments

Comments
 (0)