Fennel wiki: Range-iterator

In order to operate over a numeric range, we have the for form. However, sometimes you want an iterator; for instance when using icollect or accumulate, you can't use for directly; you need something like this:

;; stateless iterator function, used as first return value from range fn bellow
(fn range-next* [[start end step] x]
  (if (= step 0) (when (not= start end) x)
      (let [x (+ x step)]
        (when (or (and (< 0 step) (< x end))
                  (and (> 0 step) (> x end)))
          x))))

(fn range [...]
  "Create a Lua iterator from ?start to ?end (non-inclusive); increment by ?step
When ?step is omitted, defaults to 1
When ?end is omitted, (range x) is identical to (range 0 x 1)
When (= ?step 0), infinitely repeats ?start
When (= ?start ?end), returns an empty iterator"
  {:fnl/arglist [?start ?end ?step]}
  (let [(start end step) (case (values (select :# ...) ...)
                           (0) (values 0 (/ 1 0) 1)
                           (1 ?end) (values 0 ?end 1)
                           (2 ?start ?end) (values ?start ?end 1)
                           _ ...)]
    (values range-next* [start end step] (- start step))))

This iterator supports infinite ranges (limited by Lua number precision), negative and stepped ranges:

(icollect [v (range) :until (= v 10)] v) ; => [0 1 2 3 4 5 6 7 8 9]
(icollect [v (range 10)] v)              ; => [0 1 2 3 4 5 6 7 8 9]
(icollect [v (range 1 5)] v)             ; => [1 2 3 4]
(icollect [v (range 1 -5)] v)            ; => []
(icollect [v (range -1 5)] v)            ; => [-1 0 1 2 3 4]
(icollect [v (range -1 -5)] v)           ; => []
(icollect [v (range -5 -1)] v)           ; => [-5 -4 -3 -2]
(icollect [v (range -5 -1 -1)] v)        ; => []
(icollect [v (range -1 -5 -1)] v)        ; => [-1 -2 -3 -4]

(do (var n 0)
    (icollect [v (range -1 -5 0)
               :until (= n 10)]
      (do (set n (+ n 1))
          v)))                   ; => [-1 -1 -1 -1 -1 -1 -1 -1 -1 -1]

(do (var n 0)
    (icollect [v (range -5 -1 0)
               :until (= n 10)]
      (do (set n (+ n 1))
          v)))                   ; => [-5 -5 -5 -5 -5 -5 -5 -5 -5 -5]

(do (var n 0)
    (icollect [v (range -5 0 0)
               :until (= n 10)]
      (do (set n (+ n 1))
          v)))                   ; => [-5 -5 -5 -5 -5 -5 -5 -5 -5 -5]

(icollect [v (range 0)] v)       ; => []
(icollect [v (range 0 0)] v)     ; => []
(icollect [v (range 0 0 0)] v)   ; => []