Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve set-prev. #202

Merged
merged 1 commit into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,20 @@ This document describes the user-facing changes to Loopy.
([#197], [#145]). These commands no longer take multiple conditions in the
same command.

### Bugs Fixed

- Allow `back` in `set-prev` to not be known at compile time ([#202]).

### Documentation Improvements

- State explicitly that `set-prev` records values from the end
of each loop cycle and that it does not modify its variable
until the specified cycle ([#202]).

[#195]: https://github.com/okamsn/loopy/pull/195
[#196]: https://github.com/okamsn/loopy/pull/196
[#197]: https://github.com/okamsn/loopy/pull/197
[#202]: https://github.com/okamsn/loopy/pull/202

## 0.12.2

Expand Down
25 changes: 20 additions & 5 deletions doc/loopy-doc.org
Original file line number Diff line number Diff line change
Expand Up @@ -1370,9 +1370,11 @@ value and do no affect how the loop iterates.
#+findex: prev
- =(set-prev|prev-expr VAR VAL &key back)= :: Bind =VAR= to a value =VAL= from a
previous cycle in the loop. With =BACK= (default: 1), use the value from that
many cycles previous. This command /does not/ work like a queue; it always
uses the value from the =BACK=-th previous cycle, regardless of when the
command is run.
many cycles previous. _If not enough cycles have passed yet, then the value
of =VAR= is not modified._ This command /does not/ work like a queue for
recording =VAL=; it always uses the value from the =BACK=-th previous cycle,
regardless of when the command is run. The value used is always the value at
the end of the cycle.

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

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

;; => (nil nil nil 1 2)
(loopy (list i '(1 2 3 4 5))
(set-prev j i :back 3)
(loopy (with (n 3))
(list i '(1 2 3 4 5))
(set-prev j i :back n)
(collect j))

;; NOTE: `j' isn't overwritten until the correct cycle:
;;
;; => ((first-val nil) (first-val nil) (1 2) (3 4))
(loopy (with (j 'first-val))
(list i '((1 . 2) (3 . 4) (5 . 6) (7 . 8)))
Expand All @@ -1402,6 +1407,16 @@ value and do no affect how the loop iterates.
(when (cl-oddp i)
(set-prev j i))
(collect j))

;; NOTE: `j' is always bound to the previous value of `i'
;; from the end of the specified cycle.
;;
;; => (nil 101 102 103)
(loopy (numbers i :from 1 :to 4)
(set i2 i)
(set-prev j i2)
(set i2 (+ i 100))
(collect j))
#+end_src

** Iteration
Expand Down
103 changes: 63 additions & 40 deletions loopy-commands.el
Original file line number Diff line number Diff line change
Expand Up @@ -190,52 +190,75 @@ handled by `loopy-iter'."
;; being known at compile time (but still only being evaluated once.)
;; (#194)
(cl-defun loopy--parse-set-prev-command
((_ var val &key back))
((_ var val &key (back 1)))
"Parse the `set-prev' command as (set-prev VAR VAL &key back).

VAR is set to a version of VAL in a past loop cycle. With BACK,
wait that many cycle before beginning to update VAR.

This command records the value of VAL at the end of the cycle,
not when the command is run.

This command does not wait for VAL to change before updating VAR."
(let* ((holding-vars (cl-loop for i from 1 to (or back 1)
collect (gensym "set-prev-hold")))
(using-destructuring (seqp var))
(with-bound (if using-destructuring
(cl-some #'loopy--with-bound-p
(cl-second (loopy--destructure-for-iteration var val)))
(loopy--with-bound-p var)))
;; We don't use `cl-shiftf' in the main body because we want the
;; holding variables to update regardless of whether we update
;; VAR.
(holding-vars-setq `(loopy--latter-body
(cl-shiftf ,@holding-vars ,val))))
(if with-bound
(if using-destructuring
(let ((cnt-holder (gensym "count"))
(back-holder (gensym "back")))
`((loopy--other-vars (,cnt-holder 0))
(loopy--latter-body (setq ,cnt-holder (1+ ,cnt-holder)))
(loopy--other-vars (,back-holder ,back))
,@(mapcar (lambda (x) `(loopy--other-vars (,x nil)))
holding-vars)
,@(loopy--bind-main-body (main-exprs rest-instr)
(loopy--destructure-for-other-command
var (car holding-vars))
`((loopy--main-body (when (>= ,cnt-holder ,back-holder)
,@main-exprs))
,@rest-instr))
,holding-vars-setq))
(let ((val-holder (gensym "set-prev-val")))
`((loopy--other-vars (,val-holder ,var))
,@(mapcar (lambda (x) `(loopy--other-vars (,x ,val-holder)))
holding-vars)
(loopy--main-body (setq ,var ,(car holding-vars)))
,holding-vars-setq)))
`(,@(mapcar (lambda (x) `(loopy--other-vars (,x nil)))
holding-vars)
,@(loopy--destructure-for-other-command
var (car holding-vars))
,holding-vars-setq))))
(if (not (numberp back))
;; When we don't know ahead of time how far back we need to go, we have to
;; use a queue. This code is adapted from Irreal's blog
;; (https://irreal.org/blog/?p=40) where they give an example of a simple
;; FIFO queue in Scheme. It uses two lists. The "front" lists contains
;; values for popping off. The "back" list contains values for pushing
;; on. When the "front" list is exhausted, values are moved from the
;; "back" list in reverse.
(loopy--instr-let-const* ((prev back))
loopy--other-vars
(loopy--instr-let-var* ((cnt 0)
(queue-front nil)
(queue-end nil))
loopy--other-vars
;; We generate a main-body expression for binding the variables to the
;; desired values and for setting them to nil. There is overlap in the
;; remaining expressions, which initialize the variables.
(loopy--bind-main-body (main-exprs init-instr)
(loopy--destructure-for-other-command
var `(or (pop ,queue-front)
(progn
(setq ,queue-front (reverse ,queue-end)
,queue-end nil)
(pop ,queue-front))))
`((loopy--main-body (when (>= ,cnt ,prev)
,(macroexp-progn main-exprs)))
,@init-instr
(loopy--latter-body (push ,val ,queue-end))
(loopy--latter-body (setq ,cnt (1+ ,cnt)))))))

;; When we know ahead of time how far we need to go back, we can use a chain
;; of `setq's for storing values instead of queue. However, except when
;; BACK is 1, we still need to use a count, in case one of the variables is
;; initialized in `with'.

(if (= back 1)
(loopy--instr-let-var* ((hold-var nil)
(run nil))
loopy--other-vars
`(,@(loopy--bind-main-body (main-exprs init-instrs)
(loopy--destructure-for-other-command var hold-var)
`((loopy--main-body (when ,run
,@main-exprs))
,@init-instrs))
(loopy--latter-body (setq ,hold-var ,val))
(loopy--latter-body (setq ,run t))))
(let ((hold-vars (cl-loop for i from 1 to back
collect (gensym "hold-var"))))
(loopy--instr-let-var* ((cnt 0))
loopy--other-vars
`(,@(mapcar (lambda (x) `(loopy--other-vars (,x nil)))
hold-vars)
,@(loopy--bind-main-body (main-exprs init-instrs)
(loopy--destructure-for-other-command var (car hold-vars))
`((loopy--main-body (when (>= ,cnt ,back)
,@main-exprs))
,@init-instrs))
(loopy--latter-body (cl-shiftf ,@hold-vars ,val))
(loopy--latter-body (setq ,cnt (1+ ,cnt)))))))))

;;;;;; Group
(cl-defun loopy--parse-group-command ((_ &rest body))
Expand Down
17 changes: 14 additions & 3 deletions tests/tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -876,9 +876,20 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'."

(loopy-deftest set-prev-keyword-back
:result '(nil nil nil 1 2)
:body ((list i '(1 2 3 4 5))
(set-prev j i :back 3)
(collect j))
:multi-body t
:body [((list i '(1 2 3 4 5))
(set-prev j i :back 3)
(collect j))

((with (n 3)
(first-time t))
(list i '(1 2 3 4 5))
(set-prev j i :back (if first-time
(progn
(setq first-time nil)
n)
(error "Evaluated more than once.")))
(collect j))]
:loopy t
:iter-bare ((list . listing)
(collect . collecting)
Expand Down
Loading