1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
(import os sys warnings
pytest
hy.errors [HySyntaxError HyTypeError HyMacroExpansionError])
(defmacro rev [#* body]
"Execute the `body` statements in reverse"
(quasiquote (do (unquote-splice (list (reversed body))))))
(defmacro mac [x expr]
`(~@expr ~x))
(defn test-macro-call-in-called-lambda []
(assert (= ((fn [] (mac 2 (- 10 1)))) 7)))
(defn test-stararged-native-macro []
(setv x [])
(rev (.append x 1) (.append x 2) (.append x 3))
(assert (= x [3 2 1])))
(defn test-macros-returning-constants []
(defmacro an-int [] 42)
(assert (= (an-int) 42))
(defmacro a-true [] True)
(assert (= (a-true) True))
(defmacro a-false [] False)
(assert (= (a-false) False))
(defmacro a-float [] 42.)
(assert (= (a-float) 42.))
(defmacro a-complex [] 42j)
(assert (= (a-complex) 42j))
(defmacro a-string [] "foo")
(assert (= (a-string) "foo"))
(defmacro a-bytes [] b"foo")
(assert (= (a-bytes) b"foo"))
(defmacro a-list [] [1 2])
(assert (= (a-list) [1 2]))
(defmacro a-tuple [#* b] b)
(assert (= (a-tuple 1 2) #(1 2)))
(defmacro a-dict [] {1 2})
(assert (= (a-dict) {1 2}))
(defmacro a-set [] #{1 2})
(assert (= (a-set) #{1 2}))
(defmacro a-none [])
(assert (= (a-none) None)))
; A macro calling a previously defined function
(eval-when-compile
(defn foo [x y]
(quasiquote (+ (unquote x) (unquote y)))))
(defmacro bar [x y]
(foo x y))
(defn test-macro-kw []
"An error is raised when * or #** is used in a macro"
(with [(pytest.raises HySyntaxError)]
(hy.eval '(defmacro f [* a b])))
(with [(pytest.raises HySyntaxError)]
(hy.eval '(defmacro f [#** kw])))
(with [(pytest.raises HySyntaxError)]
(hy.eval '(defmacro f [a b #* body c]))))
(defn test-macro-bad-name []
(with [e (pytest.raises HySyntaxError)]
(hy.eval '(defmacro :kw [])))
(assert (in "got unexpected token: :kw" e.value.msg))
(with [(pytest.raises HySyntaxError)]
(hy.eval '(defmacro foo.bar []))))
(defn test-macro-calling-fn []
(assert (= 3 (bar 1 2))))
(defn test-optional-and-unpacking-in-macro []
; https://github.com/hylang/hy/issues/1154
(defn f [#* args]
(+ "f:" (repr args)))
(defmacro mac [[x None]]
`(f #* [~x]))
(assert (= (mac) "f:(None,)")))
(defn test-macro-autoboxing-docstring []
(defmacro m []
(setv mystring "hello world")
`(fn [] ~mystring (+ 1 2)))
(setv f (m))
(assert (= (f) 3))
(assert (= f.__doc__ "hello world")))
; Macro that checks a variable defined at compile or load time
(setv phase "load")
(eval-when-compile
(setv phase "compile"))
(defmacro phase-when-compiling [] phase)
(assert (= phase "load"))
(assert (= (phase-when-compiling) "compile"))
(setv initialized False)
(eval-and-compile
(setv initialized True))
(defmacro test-initialized [] initialized)
(assert initialized)
(assert (test-initialized))
(defmacro gensym-example []
`(setv ~(hy.gensym) 1))
(defn test-gensym-in-macros []
; Call `gensym-example` twice, getting a distinct gensym each time.
(defclass C []
(gensym-example)
(gensym-example))
(assert (=
(len (sfor a (dir C) :if (not (.startswith a "__")) a))
2)))
(defn test-macro-errors []
(import traceback
hy.importer [read-many])
(setv test-expr (read-many "(defmacro blah [x] `(print ~@z)) (blah y)"))
(with [excinfo (pytest.raises HyMacroExpansionError)]
(hy.eval test-expr))
(setv output (traceback.format_exception_only
excinfo.type excinfo.value))
(setv output (cut (.splitlines (.strip (get output 0))) 1 None))
(setv expected [" File \"<string>\", line 1"
" (defmacro blah [x] `(print ~@z)) (blah y)"
(hy.compat.reu " ^------^")
"expanding macro blah"
" NameError: global name 'z' is not defined"])
(assert (= (cut expected 0 -1) (cut output 0 -1)))
(assert (or (= (get expected -1) (get output -1))
;; Handle PyPy's peculiarities
(= (.replace (get expected -1) "global " "") (get output -1))))
;; This should throw a `HyWrapperError` that gets turned into a
;; `HyMacroExpansionError`.
(with [excinfo (pytest.raises HyMacroExpansionError)]
(hy.eval '(do (defmacro wrap-error-test []
(fn []))
(wrap-error-test))))
(assert (in "HyWrapperError" (str excinfo.value))))
(defn macro-redefinition-warning-tester [local]
(for [should-warn? [True False] head ["defmacro" "require"]]
(with [(if should-warn?
(pytest.warns RuntimeWarning :match "will shadow the core macro")
(warnings.catch-warnings))]
(when (not should-warn?)
; Elevate any warning to an error.
(warnings.simplefilter "error"))
(hy.eval `(
~@(if local '[defn f []] '[do])
~(if should-warn? None '(pragma :warn-on-core-shadow False))
~(if (= head "defmacro")
'(defmacro when [] 1)
'(require tests.resources.tlib [qplah :as when])))))))
(defn test-macro-redefinition-warning []
(macro-redefinition-warning-tester :local False))
|