Skip to content

Commit

Permalink
Review evaluation of keyword arguments during run time and expansion …
Browse files Browse the repository at this point in the history
…time.

- Make `close` in `iter` evaluable.  Make it evaluated at the beginning.

- Make `on-failure` in `find` evaluated at the beginning.

Closes issue #210.
  • Loading branch information
okamsn committed Sep 14, 2024
1 parent a374774 commit 88195fa
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 45 deletions.
43 changes: 37 additions & 6 deletions doc/loopy-doc.org
Original file line number Diff line number Diff line change
Expand Up @@ -1507,13 +1507,17 @@ loop. This restriction allows for producing more efficient code.
holds the value yielded by the iterator. The loop ends when the iterator
finishes.

=close= is whether the generator should be closed via ~iter-close~ after the
loop ends. The default is ~t~. Note that Emacs will eventually close
un-closed, un-reachable generators during garbage collection. To be
consistent with other commands, =close= is evaluated at the start of the loop,
even though it's value is only used after the loop finishes.

=yield-result= is the optional second argument to the function ~iter-next~,
which is the value of ~iter-yield~ in the iterator (not to be confused with
the value yielded by calling ~iter-next~).

=close= is whether the generator should be closed via ~iter-close~ after the
loop ends. The default is ~t~. Note that Emacs will eventually close
un-closed, un-reachable generators during garbage collection.
the value yielded by calling ~iter-next~). Unlike =close=, which is evaluated
once, =yield-result= is an expression which is substituted into the loop body.
Therefore, =yield-result= can be used to repeatedly call functions.

For efficiency, when possible, =VAR= is bound to the yielded value before each
step of the loop, which is used to detect whether the iterator signals that it
Expand All @@ -1526,6 +1530,7 @@ loop. This restriction allows for producing more efficient code.

#+begin_src emacs-lisp
;; With var:
;;
;; => ((1 . 4) (2 . 5) (3 . 6))
(loopy (with (iter-maker (iter-lambda (x)
(while x
Expand All @@ -1535,6 +1540,7 @@ loop. This restriction allows for producing more efficient code.
(collect (cons i j)))

;; Without var:
;;
;; => (1 2 3)
(loopy (iter (funcall (iter-lambda ()
;; These yielded values are all ignored.
Expand All @@ -1543,6 +1549,21 @@ loop. This restriction allows for producing more efficient code.
(iter-yield 'third-yield))))
(set i 1 (1+ i))
(collect i))

;; Using `yield-result':
;;
;; => (3 2 1)
(loopy (with (yield-results nil))
(set i 1 (1+ i))
(iter (funcall (iter-lambda ()
;; The value from the expression specified by
;; `:yield-result' is `push'-ed:
(push (iter-yield 'first-yield) yield-results)
(push (iter-yield 'second-yield) yield-results)
(push (iter-yield 'third-yield) yield-results)))
;; Note that the value of `i' evaluated each time:
:yield-result i)
(finally-return yield-results))
#+end_src

#+ATTR_TEXINFO: :tag Warning
Expand Down Expand Up @@ -2123,7 +2144,7 @@ source sequences.
=EXPR=. If =by= is non-nil (default: 1), then move to the next n-th element
during each iteration. This command is a special case of the =substream=
command (described below), setting =VAR= to the first element of each
substream. For more information, see the command =substream=.
substream. For more information on streams, see the command =substream=.

This command also has the alias =streaming=.

Expand Down Expand Up @@ -3396,6 +3417,9 @@ Sequence accumulation commands are used to join lists (such as =union= and
normal failure and =VAR= will be set to the value of =ON-FAILURE=, if
provided.

To be consistent with other commands, =ON-FAILURE= is evaluated at the
start of the loop, even though that is not necessarily where it is used.

#+BEGIN_SRC emacs-lisp
;; => (13 (1 2))
(loopy (list i '(1 2 3 4 5 6 7 8))
Expand All @@ -3419,6 +3443,13 @@ Sequence accumulation commands are used to join lists (such as =union= and
;; => nil
(loopy (list i '(1 2 3 4 5 6))
(find nil (> i 3) :on-failure 27))

;; Value of `:on-failure' gotten at the start of the loop:
;; => 27
(loopy (with (on-fail 27))
(list i '(1 2 3))
(set on-fail 33)
(find i (> i 4) :on-failure on-fail))
#+END_SRC

*** Optimizing Accumulations
Expand Down
91 changes: 52 additions & 39 deletions loopy-commands.el
Original file line number Diff line number Diff line change
Expand Up @@ -782,8 +782,9 @@ is a function by which to update VAR (default `cdr')."
VAR is the variable. VAL is an generator-producing function, as
from `iter-defun' or `iter-lambda'. YIELD-RESULT is the optional
value of `iter-next'. CLOSE is whether the iterator should be
closed after the loop completes."
value of `iter-next' and is an expression, not a value. CLOSE is
whether the iterator should be closed after the loop completes
and is a value."
:required-vals 0 ;; Require values /after/ `var'.
:other-vals (0 1)
:keywords (:yield-result :close)
Expand Down Expand Up @@ -825,11 +826,16 @@ closed after the loop completes."
(iter-next ,obj-holder ,yield-result)
(iter-end-of-sequence nil)
(:success t))))))
,(when close
`(loopy--vars-final-updates
(,obj-holder . (iter-close ,obj-holder))))))))


,@(pcase close
('nil nil)
('t `((loopy--vars-final-updates
(,obj-holder . (iter-close ,obj-holder)))))
(_
(loopy--instr-let-const* ((close2 close))
loopy--iteration-vars
`((loopy--vars-final-updates
(,obj-holder . (when ,close2
(iter-close ,obj-holder))))))))))))

;;;;;; List
;; TODO: Make this a normal function.
Expand Down Expand Up @@ -2161,57 +2167,64 @@ This function is called by `loopy--expand-optimized-accum'."

;;;;;;; Find
(loopy--defaccumulation find
"Parse a command of the form `(finding VAR EXPR TEST &key ON-FAILURE)'."
"Parse a command of the form `(finding VAR EXPR TEST &key ON-FAILURE)'.
ON-FAILURE is evaluated at the beginning of the loop, even though
it is only meaningful at the end."
:num-args 3
:keywords (on-failure)
:explicit (let* ((test-arg (cl-third args))
(test-form (if (loopy--quoted-form-p test-arg)
`(,(loopy--get-function-symbol test-arg) ,val)
test-arg))
(of-used (plist-member opts :on-failure))
(on-failure-given (plist-member opts :on-failure))
(on-failure (plist-get opts :on-failure))
(tag-name (loopy--produce-non-returning-exit-tag-name
loopy--loop-name))
(found (gensym "found")))

`((loopy--non-returning-exit-used ,tag-name)
(loopy--accumulation-vars (,var nil))
,@(if of-used
;; If TEST always nil, bind to ON-FAILURE.
`((loopy--accumulation-vars (,found nil))
(loopy--main-body (when ,test-form
(setq ,var ,val
,found t)
(throw (quote ,tag-name) t)))
(loopy--vars-final-updates
(,var . (unless ,found (setq ,var ,on-failure)))))
`((loopy--main-body (when ,test-form
(setq ,var ,val)
(throw (quote ,tag-name) t)))))))
(loopy--instr-let-const* ((on-failure2 on-failure))
loopy--accumulation-vars
`((loopy--non-returning-exit-used ,tag-name)
(loopy--accumulation-vars (,var nil))
,@(if on-failure-given
;; If TEST always nil, bind to ON-FAILURE.
`((loopy--accumulation-vars (,found nil))
(loopy--main-body (when ,test-form
(setq ,var ,val
,found t)
(throw (quote ,tag-name) t)))
(loopy--vars-final-updates
(,var . (unless ,found (setq ,var ,on-failure2)))))
`((loopy--main-body (when ,test-form
(setq ,var ,val)
(throw (quote ,tag-name) t))))))))
:implicit (let* ((test-arg (cl-second args))
(test-form (if (loopy--quoted-form-p test-arg)
`(,(loopy--get-function-symbol test-arg) ,val)
test-arg))
(of-used (plist-member opts :on-failure))
(on-failure-given (plist-member opts :on-failure))
(on-failure (plist-get opts :on-failure))
(found (gensym "found"))
(tag-name (loopy--produce-non-returning-exit-tag-name
loopy--loop-name)))
`((loopy--non-returning-exit-used ,tag-name)
(loopy--accumulation-vars (,var nil))
,@(if of-used
;; If TEST always nil, bind to ON-FAILURE.
`((loopy--accumulation-vars (,found nil))
(loopy--main-body (when ,test-form
(setq ,var ,val
,found t)
(throw (quote ,tag-name) t)))
(loopy--vars-final-updates
(,var . (unless ,found (setq ,var ,on-failure)))))
`((loopy--main-body (when ,test-form
(setq ,var ,val)
(throw (quote ,tag-name) t)))))
(loopy--implicit-return ,var))))
(loopy--instr-let-const* ((on-failure2 on-failure))
loopy--accumulation-vars
`((loopy--non-returning-exit-used ,tag-name)
(loopy--accumulation-vars (,var nil))
,@(if on-failure-given
;; If TEST always nil, bind to ON-FAILURE.
`((loopy--accumulation-vars (,found nil))
(loopy--main-body (when ,test-form
(setq ,var ,val
,found t)
(throw (quote ,tag-name) t)))
(loopy--vars-final-updates
(,var . (unless ,found (setq ,var ,on-failure2)))))
`((loopy--main-body (when ,test-form
(setq ,var ,val)
(throw (quote ,tag-name) t)))))
(loopy--implicit-return ,var)))))

;;;;;;; Set Accum
(loopy--defaccumulation set-accum
Expand Down
44 changes: 44 additions & 0 deletions tests/tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,31 @@ Using numbers directly will use less variables and more efficient code."
(collect . collecting)
(leave . leaving)))

(loopy-deftest iter-close-value
:doc "Check that `close' is respected when it's a variable."
:result t
:body (loopy (with (some-val nil)
(iter-maker (iter-lambda ()
(iter-yield 1)
(iter-yield 2)
(iter-yield 3)))
(gen (funcall iter-maker)))
(iter i gen :close some-val)
(leave) ; Should not preventing closing or final updates.
(finally-return
(condition-case err
(iter-next gen)
(iter-end-of-sequence nil)
(:success (progn
(iter-close gen)
t))
(t
(signal (car err) (cdr err))))))
:loopy t
:iter-keyword (iter leave)
:iter-bare ((iter . iterating)
(leave . leaving)))

(loopy-deftest iter-same-gen
:doc "Check that `iter' doesn't reset iterator objects."
:result '((1 . 2) (3 . 4))
Expand Down Expand Up @@ -5607,6 +5632,25 @@ Not multiple of 3: 7"
:iter-bare ((list . listing)
(find . finding)))

(loopy-deftest find-onfail-at-beginning
:doc "Make sure `:on-failure' is evaluated at the beginning."
:result 27
:multi-body t
:body [((with (on-fail 27))
(list i '(1 2 3))
(set on-fail 33)
(find res i (> i 4) :on-failure on-fail)
(finally-return res))

((with (on-fail 27))
(list i '(1 2 3))
(set on-fail 33)
(find i (> i 4) :on-failure on-fail))]
:loopy t
:iter-keyword (list find)
:iter-bare ((list . listing)
(find . finding)))

(loopy-deftest find-fail-onfail
:result 0
:multi-body t
Expand Down

0 comments on commit 88195fa

Please sign in to comment.