hy/tests/native_tests/operators.hy
Kodi Arfer 5eb928356a Overhaul semantics of binary operators (#1261)
I've added shadow versions of many operators that didn't have one. And, I've changed the behavior of various binary operators with more or fewer than 2 arguments to make the shadow and real versions more consistent and to make the behavior more logical in either case. For details, see the additions to NEWS and the new file tests/native_tests/operators.hy, which simultaneously tests shadow and real operators.

Although there are a lot of changes, I've put them all in one commit because they're interdependent.
2017-04-13 19:42:01 -05:00

287 lines
7.0 KiB
Hy

(import [hy._compat [PY35]])
(defmacro op-and-shadow-test [op &rest body]
; Creates two tests with the given `body`, one where all occurrences
; of the symbol `f` are syntactically replaced with `op` (a test of
; the real operator), and one where the body is preceded by an
; assignment of `op` to `f` (a test of the shadow operator).
;
; `op` can also be a list of operators, in which case two tests are
; created for each operator.
(import [hy [HySymbol HyString]] [hy.contrib.walk [prewalk]])
(setv defns [])
(for [o (if (coll? op) op [op])]
(.append defns `(defn ~(HySymbol (+ "test_operator_" o "_real")) []
(setv f-name ~(HyString o))
~@(prewalk :form body :f (fn [x]
(if (and (symbol? x) (= x "f")) o x)))))
(.append defns `(defn ~(HySymbol (+ "test_operator_" o "_shadow")) []
(setv f-name ~(HyString o))
(setv f ~o)
~@body)))
`(do ~@defns))
(defmacro forbid [expr]
`(assert (try
(eval '~expr)
(except [TypeError] True)
(else (raise AssertionError)))))
(op-and-shadow-test +
(assert (= (f) 0))
(defclass C [object] [__pos__ (fn [self] "called __pos__")])
(assert (= (f (C)) "called __pos__"))
(assert (= (f 1 2) 3))
(assert (= (f 1 2 3 4) 10))
(assert (= (f 1 2 3 4 5) 15))
; with strings
(assert (= (f "a" "b" "c")
"abc"))
; with lists
(assert (= (f ["a"] ["b"] ["c"])
["a" "b" "c"])))
(op-and-shadow-test -
(forbid (f))
(assert (= (f 1) -1))
(assert (= (f 2 1) 1))
(assert (= (f 2 1 1) 0)))
(op-and-shadow-test *
(assert (= (f) 1))
(assert (= (f 3) 3))
(assert (= (f 3 3) 9))
(assert (= (f 2 3 4) 24))
(assert (= (f "ke" 4) "kekekeke"))
(assert (= (f [1 2 3] 2) [1 2 3 1 2 3])))
(op-and-shadow-test **
(forbid (f))
(forbid (f 1))
(assert (= (f 3 2) 9))
(assert (= (f 5 4 3 2) (** 5 (** 4 (** 3 2))))))
; Exponentiation is right-associative.
(op-and-shadow-test /
(forbid (f))
(assert (= (f 2) .5))
(assert (= (f 3 2) 1.5))
(assert (= (f 8 2) 4))
(assert (= (f 8 2 2) 2))
(assert (= (f 8 2 2 2) 1)))
(op-and-shadow-test //
(forbid (f))
(forbid (f 1))
(assert (= (f 16 5) 3))
(assert (= (f 8 2) 4))
(assert (= (f 8 2 2) 2)))
(op-and-shadow-test %
(forbid (f))
(forbid (f 1))
(assert (= (f 16 5) 1))
(assert (= (f 8 2) 0))
(assert (= (f "aa %s bb" 15) "aa 15 bb"))
(assert (= (f "aa %s bb %s cc" (, "X" "Y")) "aa X bb Y cc"))
(forbid (f 1 2 3)))
(when PY35 (op-and-shadow-test @
(defclass C [object] [
__init__ (fn [self content] (setv self.content content))
__matmul__ (fn [self other] (C (+ self.content other.content)))])
(forbid (f))
(assert (do (setv c (C "a")) (is (f c) c)))
(assert (= (. (f (C "b") (C "c")) content) "bc"))
(assert (= (. (f (C "d") (C "e") (C "f")) content) "def"))))
(op-and-shadow-test <<
(forbid (f))
(forbid (f 1))
(assert (= (f 0b101 2) 0b10100))
(assert (= (f 0b101 2 3) 0b10100000)))
(op-and-shadow-test >>
(forbid (f))
(forbid (f 1))
(assert (= (f 0b101 2) 0b1))
(assert (= (f 0b101000010 2 3) 0b1010)))
(op-and-shadow-test &
(forbid (f))
; Binary AND has no identity element for the set of all
; nonnegative integers, because we can't have a 1 in every bit
; when there are infinitely many bits.
(assert (= (f 17) 17))
(assert (= (f 0b0011 0b0101) 0b0001))
(assert (= (f 0b111 0b110 0b100) 0b100)))
(op-and-shadow-test |
(assert (= (f) 0))
(assert (= (f 17) 17))
(assert (= (f 0b0011 0b0101) 0b0111))
(assert (= (f 0b11100 0b11000 0b10010) 0b11110)))
(op-and-shadow-test ^
(forbid (f))
(forbid (f 17))
(assert (= (f 0b0011 0b0101) 0b0110))
(forbid (f 0b111 0b110 0b100)))
; `xor` with 3 arguments is kill (https://github.com/hylang/hy/pull/1102),
; so we don't allow `^` with 3 arguments, either.
(op-and-shadow-test ~
(forbid (f))
(assert (= (f (chr 0b00101111)
(chr 0b11010000))))
(forbid (f (chr 0b00101111) (chr 0b11010000))))
(op-and-shadow-test <
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) True))
(assert (is (f 2 1) False))
(assert (is (f 1 1) False))
(assert (is (f 1 2 3) True))
(assert (is (f 3 2 1) False))
(assert (is (f 1 3 2) False))
(assert (is (f 1 2 2) False))
; Make sure chained comparisons use `and`, not `&`.
; https://github.com/hylang/hy/issues/1191
(defclass C [object] [
__init__ (fn [self x]
(setv self.x x))
__lt__ (fn [self other]
self.x)])
(assert (= (f (C "a") (C "b") (C "c")) "b")))
(op-and-shadow-test >
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) False))
(assert (is (f 2 1) True))
(assert (is (f 1 1) False))
(assert (is (f 1 2 3) False))
(assert (is (f 3 2 1) True))
(assert (is (f 1 3 2) False))
(assert (is (f 2 1 1) False)))
(op-and-shadow-test <=
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) True))
(assert (is (f 2 1) False))
(assert (is (f 1 1) True))
(assert (is (f 1 2 3) True))
(assert (is (f 3 2 1) False))
(assert (is (f 1 3 2) False))
(assert (is (f 1 2 2) True)))
(op-and-shadow-test >=
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) False))
(assert (is (f 2 1) True))
(assert (is (f 1 1) True))
(assert (is (f 1 2 3) False))
(assert (is (f 3 2 1) True))
(assert (is (f 1 3 2) False))
(assert (is (f 2 1 1) True)))
(op-and-shadow-test [= is]
(forbid (f))
(assert (is (f "hello") True))
(defclass C)
(setv x (get {"is" (C) "=" 0} f-name))
(setv y (get {"is" (C) "=" 1} f-name))
(assert (is (f x x) True))
(assert (is (f y y) True))
(assert (is (f x y) False))
(assert (is (f y x) False))
(assert (is (f x x x x x) True))
(assert (is (f x x x y x) False))
(setv n None)
(assert (is (f n None) True))
(assert (is (f n "b") False)))
(op-and-shadow-test [!= is-not]
(forbid (f))
(forbid (f "hello"))
(defclass C)
(setv x (get {"is_not" (C) "!=" 0} f-name))
(setv y (get {"is_not" (C) "!=" 1} f-name))
(setv z (get {"is_not" (C) "!=" 2} f-name))
(assert (is (f x x) False))
(assert (is (f y y) False))
(assert (is (f x y) True))
(assert (is (f y x) True))
(assert (is (f x y z) True))
(assert (is (f x x x) False))
(assert (is (f x y x) True))
(assert (is (f x x y) False)))
(op-and-shadow-test and
(assert (is (f) True))
(assert (= (f 17) 17))
(assert (= (f 1 2) 2))
(assert (= (f 1 0) 0))
(assert (= (f 0 2) 0))
(assert (= (f 0 0) 0))
(assert (= (f 1 2 3) 3))
(assert (= (f 1 0 3) 0))
(assert (= (f "a" 1 True [1]) [1])))
(op-and-shadow-test or
(assert (is (f) None))
(assert (= (f 17) 17))
(assert (= (f 1 2) 1))
(assert (= (f 1 0) 1))
(assert (= (f 0 2) 2))
(assert (= (f 0 0) 0))
(assert (= (f 1 2 3) 1))
(assert (= (f 0 0 3) 3))
(assert (= (f "" None 0 False []) [])))
(op-and-shadow-test not
(forbid (f))
(assert (is (f "hello") False))
(assert (is (f 0) True))
(assert (is (f None) True)))
(op-and-shadow-test [in not-in]
(forbid (f))
(forbid (f 3))
(assert (is (f 3 [1 2]) (!= f-name "in")))
(assert (is (f 2 [1 2]) (= f-name "in")))
(forbid (f 2 [1 2] [3 4])))