diff --git a/hy/core/language.hy b/hy/core/language.hy index 04b2705..e1b8083 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -23,6 +23,7 @@ ;;;; to make functional programming slightly easier. ;;;; + (import [hy._compat [long-type]]) ; long for python2, int for python3 (defn _numeric-check [x] @@ -91,6 +92,20 @@ "Return True if x is float" (isinstance x float)) +(import [threading [Lock]]) +(setv _gensym_counter 1234) +(setv _gensym_lock (Lock)) + +(defn gensym [&optional [g "G"]] + (let [[new_symbol None]] + (global _gensym_counter) + (global _gensym_lock) + (.acquire _gensym_lock) + (try (do (setv _gensym_counter (inc _gensym_counter)) + (setv new_symbol (HySymbol (.format ":{0}_{1}" g _gensym_counter)))) + (finally (.release _gensym_lock))) + new_symbol)) + (defn inc [n] "Increment n by 1" (_numeric-check n) @@ -223,6 +238,7 @@ (= n 0)) (def *exports* '[cycle dec distinct drop drop-while empty? even? filter float? + gensym inc instance? integer integer? iterable? iterate iterator? neg? none? nth numeric? odd? pos? remove repeat repeatedly second string string? take take-nth take-while zero?]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 17792f9..79c7983 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -113,6 +113,6 @@ (defmacro yield-from [iterable] "Yield all the items from iterable" - ;; TODO: this needs some gensym love - `(foreach [_hy_yield_from_x ~iterable] - (yield _hy_yield_from_x))) + (let [[x (gensym)]] + `(foreach [~x ~iterable] + (yield ~x)))) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 2f6b717..39dee93 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -141,6 +141,18 @@ (assert-true (float? -3.2)) (assert-false (float? "foo"))) +(defn test-gensym [] + "NATIVE: testing the gensym function" + (import [hy.models.symbol [HySymbol]]) + (setv s1 (gensym)) + (assert (isinstance s1 HySymbol)) + (assert (= 0 (.find s1 ":G_"))) + (setv s2 (gensym "xx")) + (setv s3 (gensym "xx")) + (assert (= 0 (.find s2 ":xx_"))) + (assert (not (= s2 s3))) + (assert (not (= (str s2) (str s3))))) + (defn test-inc [] "NATIVE: testing the inc function" (assert-equal 3 (inc 2)) @@ -393,3 +405,4 @@ (assert-equal res [None None]) (setv res (list (take-while (fn [x] (not (none? x))) [1 2 3 4 None 5 6 None 7]))) (assert-equal res [1 2 3 4])) + diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index b9f1cf0..ea252e5 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -94,7 +94,6 @@ (assert initialized) (assert (test-initialized)) - (defn test-yield-from [] "NATIVE: testing yield from" (defn yield-from-test [] @@ -107,3 +106,28 @@ (import sys) (assert (= (get sys.version_info 0) (if-python2 2 3)))) + +(defn test-gensym-in-macros [] + (import ast) + (import [astor.codegen [to_source]]) + (import [hy.importer [import_buffer_to_ast]]) + (setv macro1 "(defmacro nif [expr pos zero neg] + (let [[g (gensym)]] + `(let [[~g ~expr]] + (cond [(pos? ~g) ~pos] + [(zero? ~g) ~zero] + [(neg? ~g) ~neg])))) + + (print (nif (inc -1) 1 0 -1)) + ") + ;; expand the macro twice, should use a different + ;; gensym each time + (setv _ast1 (import_buffer_to_ast macro1 "foo")) + (setv _ast2 (import_buffer_to_ast macro1 "foo")) + (setv s1 (to_source _ast1)) + (setv s2 (to_source _ast2)) + ;; and make sure there is something new that starts with :G_ + (assert (in ":G_" s1)) + (assert (in ":G_" s2)) + ;; but make sure the two don't match each other + (assert (not (= s1 s2))))