diff --git a/hy/core/macros.hy b/hy/core/macros.hy index cdc5d17..b5e5ee0 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -45,10 +45,18 @@ (if (empty? body) (macro-error None "`for' requires a body to evaluate")) - (if args - `(for* [~(.pop args 0) ~(.pop args 0)] - (for ~args ~@body)) - `(do ~@body))) + + (if (empty? args) + `(do ~@body) + (if (= (len args) 2) + ; basecase, let's just slip right in. + `(for* [~@args] ~@body) + ; otherwise, let's do some legit handling. + (let [[alist (slice args 0 nil 2)] + [ilist (slice args 1 nil 2)]] + `(do + (import itertools) + (for* [(, ~@alist) (itertools.product ~@ilist)] ~@body)))))) (defmacro with [args &rest body] diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index f116233..11bf01a 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -41,6 +41,46 @@ (assert (= count 150))) +(defn test-nasty-for-nesting [] + "NATIVE: test nesting for loops harder" + ;; This test and feature is dedicated to @nedbat. + + ;; let's ensure empty iterating is an implicit do + (setv t 0) + (for [] (setv t 1)) + (assert (= t 1)) + + ;; OK. This first test will ensure that the else is hooked up to the + ;; for when we break out of it. + (for [x (range 2) + y (range 2)] + (break) + (else (throw Exception))) + + ;; OK. This next test will ensure that the else is hooked up to the + ;; "inner" iteration + (for [x (range 2) + y (range 2)] + (if (= y 1) (break)) + (else (throw Exception))) + + ;; OK. This next test will ensure that the else is hooked up to the + ;; "outer" iteration + (for [x (range 2) + y (range 2)] + (if (= x 1) (break)) + (else (throw Exception))) + + ;; OK. This next test will ensure that we call the else branch exactly + ;; once. + (setv flag 0) + (for [x (range 2) + y (range 2)] + (+ 1 1) + (else (setv flag (+ flag 2)))) + (assert (= flag 2))) + + (defn test-while-loop [] "NATIVE: test while loops?" (setv count 5)