"Is there a way to do dotimes or a loop in reverse?" — the answer was a hand-written let plus set. Now it is (dotimes [i 9 -1 -1]). Three arities: [i n], [i start stop], [i start stop step]. The stop is exclusive in all of them, so [i 0 n] is [i n] — one rule, not two — and a negative step counts down, testing with > instead of <. A literal step of 0 is refused where it is written. One that is only a value cannot be, so the condition asks the sign first and 0 falls out of it as a loop that runs no times: terminating and deterministic, and free, because a literal step still emits the single comparison it always did. Each bound is evaluated once, left to right, before the counter exists: the start into the counter, the stop into the hidden slot it always had, the step into one of its own unless it is a literal. Still a special form, still a Let and a While with the step in the latch, so neither backend learned anything — the new program prints the same thing under --x86 and at -O0. load.ml's Form-level walk had to learn more than one bound for the same reason parse.ml did; it is part of this feature and not a bug that was sitting there, because before this a three-bound dotimes was a parse error long before that walk could reach it.
127 lines
5.8 KiB
Plaintext
127 lines
5.8 KiB
Plaintext
;;;; dotimes with start, stop and step.
|
|
;;;;
|
|
;;;; stop is exclusive in every arity, so (dotimes [i 0 n]) is (dotimes [i n]),
|
|
;;;; and a negative step counts down and tests with > instead of <. What is
|
|
;;;; asserted here rather than in a unit test is what comes out:
|
|
;;;;
|
|
;;;; 1. Each bound is evaluated exactly once, before the loop, and left to
|
|
;;;; right. The counter function prints a marker per call, so a bound read
|
|
;;;; twice shows up as an extra marker and a bound read per iteration shows
|
|
;;;; up as many.
|
|
;;;;
|
|
;;;; 2. A body that assigns to what a bound was computed from cannot change the
|
|
;;;; trip count — the bounds are in hidden slots by then.
|
|
;;;;
|
|
;;;; 3. (continue) advances a *down*-counting loop too. The step is the latch
|
|
;;;; whichever way it goes, and folded onto the body this hangs rather than
|
|
;;;; printing the wrong thing, which is what the watchdog is for.
|
|
;;;;
|
|
;;;; 4. A step whose sign is only known at run time picks its direction at the
|
|
;;;; test, and a run-time step of 0 runs the loop no times at all.
|
|
|
|
;; Prints its tag and answers with its value, so the output says how many times
|
|
;; and in what order each bound was evaluated.
|
|
(defn bump [tag i32 v i32] i32
|
|
(print tag)
|
|
v)
|
|
|
|
(defn main [] i32
|
|
;; ── the three arities, counting up ────────────────────────────────
|
|
(dotimes [i 3] (print i)) (println "") ; 012
|
|
(dotimes [i 2 5] (print i)) (println "") ; 234
|
|
(dotimes [i 0 10 3] (print i)) (println "") ; 0369 — uneven, stops short
|
|
|
|
;; (dotimes [i 0 n]) is (dotimes [i n]). One rule, not two.
|
|
(dotimes [i 0 4] (print i)) (println "") ; 0123
|
|
|
|
;; ── counting down ─────────────────────────────────────────────────
|
|
(dotimes [i 9 -1 -1] (print i)) (println "") ; 9876543210
|
|
(dotimes [i 10 0 -3] (print i)) (println "") ; 10 7 4 1
|
|
|
|
;; The last representable i32 is reachable as a stop: it is exclusive, so
|
|
;; the counter reaches -2147483647 and the test ends it there.
|
|
(dotimes [i -2147483645 -2147483648 -1] (print i) (println "")) ; three lines
|
|
|
|
;; ── zero-trip loops ───────────────────────────────────────────────
|
|
(dotimes [i 5 5] (print i)) ; nothing
|
|
(dotimes [i 0 10 -1] (print i)) ; nothing: already past
|
|
(dotimes [i 10 0] (print i)) ; nothing: already past
|
|
(println "none")
|
|
|
|
;; ── each bound once, in the order written ─────────────────────────
|
|
;; 7 8 9 for the three bounds, then the two iterations. A bound evaluated
|
|
;; per iteration would interleave; one evaluated twice would repeat.
|
|
(dotimes [i (bump 7 0) (bump 8 2) (bump 9 1)] (print i))
|
|
(println "")
|
|
|
|
;; The same for the one-bound form, which is the one that always worked.
|
|
(dotimes [i (bump 7 2)] (print i))
|
|
(println "")
|
|
|
|
;; ── a body cannot move the bounds ─────────────────────────────────
|
|
(let [n 3]
|
|
(dotimes [i 0 n] (set n 0) (print i))) ; 012, not 0
|
|
(println "")
|
|
(let [s 1]
|
|
(dotimes [i 0 3 s] (set s 5) (print i))) ; 012, not 0
|
|
(println "")
|
|
|
|
;; ── break and continue, in every arity ────────────────────────────
|
|
(dotimes [i 5] (when (= i 3) (break)) (print i))
|
|
(println "") ; 012
|
|
(dotimes [i 5] (when (= i 2) (continue)) (print i))
|
|
(println "") ; 0134
|
|
|
|
(dotimes [i 2 8] (when (= i 5) (break)) (print i))
|
|
(println "") ; 234
|
|
(dotimes [i 2 6] (when (= i 4) (continue)) (print i))
|
|
(println "") ; 235
|
|
|
|
(dotimes [i 0 12 3] (when (= i 9) (break)) (print i))
|
|
(println "") ; 036
|
|
(dotimes [i 0 12 3] (when (= i 6) (continue)) (print i))
|
|
(println "") ; 039
|
|
|
|
;; Counting down, which is the new path for the latch: the skipped
|
|
;; iteration still subtracts, or this never finishes.
|
|
(dotimes [i 5 0 -1] (when (= i 3) (break)) (print i))
|
|
(println "") ; 54
|
|
(dotimes [i 5 0 -1] (when (= i 3) (continue)) (print i))
|
|
(println "") ; 5421
|
|
|
|
;; Every iteration continues and the trip count is still the trip count.
|
|
(let [c 0]
|
|
(dotimes [i 4 0 -1] (set c (+ c 1)) (continue))
|
|
(print c))
|
|
(println "") ; 4
|
|
|
|
;; Labels, on a down-counting loop.
|
|
(dotimes :outer [a 2 -1 -1]
|
|
(dotimes [b 2 -1 -1]
|
|
(when (= b 0) (break :outer))
|
|
(print b)))
|
|
(println "") ; 21
|
|
|
|
(dotimes :rows [r 2 -1 -1]
|
|
(dotimes [c 2 -1 -1]
|
|
(when (= c 1) (continue :rows))
|
|
(print c))
|
|
(println "tail"))
|
|
(println "") ; 222, no tail
|
|
|
|
;; ── a step the compiler cannot see the sign of ────────────────────
|
|
(let [up 2 down -2 flat 0]
|
|
(dotimes [i 0 7 up] (print i)) ; 0246
|
|
(println "")
|
|
(dotimes [i 6 -1 down] (print i)) ; 6420
|
|
(println "")
|
|
;; A step of 0 cannot be refused here — it is a value, not a literal — so
|
|
;; the sign test leaves it with no direction and the loop runs no times.
|
|
(dotimes [i 0 7 flat] (print i))
|
|
(println "zero")
|
|
;; And it is evaluated once like the others, so a body that changes it
|
|
;; changes nothing.
|
|
(dotimes [i (bump 4 0) (bump 5 6) (bump 6 up)] (print i))
|
|
(println ""))
|
|
0)
|