From 5eb928356a66d02bf74a06c0ffc9b625626c23a8 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 13 Apr 2017 17:42:01 -0700 Subject: [PATCH] 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. --- NEWS | 7 + hy/compiler.py | 102 +++++++----- hy/core/shadow.hy | 158 +++++++++++++----- tests/__init__.py | 2 +- tests/compilers/test_ast.py | 2 +- tests/native_tests/language.hy | 51 ------ tests/native_tests/operators.hy | 286 ++++++++++++++++++++++++++++++++ tests/native_tests/shadow.hy | 101 ----------- 8 files changed, 474 insertions(+), 235 deletions(-) create mode 100644 tests/native_tests/operators.hy delete mode 100644 tests/native_tests/shadow.hy diff --git a/NEWS b/NEWS index 01d139b..d5d7dc5 100644 --- a/NEWS +++ b/NEWS @@ -7,11 +7,18 @@ Changes from 0.12.1 * Added bytestring literals, which create `bytes` objects under Python 3 and `str` objects under Python 2 * Commas and underscores are allowed in numeric literals + * Many more operators (e.g., `**`, `//`, `not`, `in`) can be used + as first-class functions + * The semantics of binary operators when applied to fewer or more + than two arguments have been made more logical + * `(** a b c d)` is now equivalent to `(** a (** b (** c d)))`, + not `(** (** (** a b) c) d)` * `setv` always returns None * xor: If exactly one argument is true, return it * hy.core.reserved is now hy.extra.reserved [ Bug Fixes ] + * All shadowed operators have the same arities as real operators * Shadowed comparison operators now use `and` instead of `&` for chained comparisons * partition no longer prematurely exhausts input iterators diff --git a/hy/compiler.py b/hy/compiler.py index 1bf3bdf..2db237d 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -371,7 +371,7 @@ def checkargs(exact=None, min=None, max=None, even=None, multiple=None): class HyASTCompiler(object): def __init__(self, module_name): - self.allow_builtins = False + self.allow_builtins = module_name.startswith("hy.core") self.anon_fn_count = 0 self.anon_var_count = 0 self.imports = defaultdict(set) @@ -1876,7 +1876,7 @@ class HyASTCompiler(object): col_offset=e.start_column) @builds("=") - @builds("!=") + @builds("is") @builds("<") @builds("<=") @builds(">") @@ -1884,33 +1884,25 @@ class HyASTCompiler(object): @checkargs(min=1) def compile_compare_op_expression(self, expression): if len(expression) == 2: - rval = "True" - if expression[0] == "!=": - rval = "False" - return ast.Name(id=rval, + return ast.Name(id="True", ctx=ast.Load(), lineno=expression.start_line, col_offset=expression.start_column) return self._compile_compare_op_expression(expression) - @builds("is") - @builds("in") + @builds("!=") @builds("is_not") - @builds("not_in") @checkargs(min=2) def compile_compare_op_expression_coll(self, expression): return self._compile_compare_op_expression(expression) - @builds("%") - @builds("**") - @builds("<<") - @builds(">>") - @builds("|") - @builds("^") - @builds("&") - @builds_if("@", PY35) - @checkargs(min=2) - def compile_maths_expression(self, expression): + @builds("in") + @builds("not_in") + @checkargs(2) + def compile_compare_op_expression_binary(self, expression): + return self._compile_compare_op_expression(expression) + + def _compile_maths_expression(self, expression): ops = {"+": ast.Add, "/": ast.Div, "//": ast.FloorDiv, @@ -1926,14 +1918,18 @@ class HyASTCompiler(object): if PY35: ops.update({"@": ast.MatMult}) - inv = expression.pop(0) - op = ops[inv] + op = ops[expression.pop(0)] + right_associative = op == ast.Pow + if right_associative: + expression = expression[::-1] ret = self.compile(expression.pop(0)) for child in expression: left_expr = ret.force_expr ret += self.compile(child) right_expr = ret.force_expr + if right_associative: + left_expr, right_expr = right_expr, left_expr ret += ast.BinOp(left=left_expr, op=op(), right=right_expr, @@ -1941,27 +1937,46 @@ class HyASTCompiler(object): col_offset=child.start_column) return ret - @builds("*") - @builds("/") + @builds("**") @builds("//") + @builds("<<") + @builds(">>") + @builds("&") + @checkargs(min=2) + def compile_maths_expression_2_or_more(self, expression): + return self._compile_maths_expression(expression) + + @builds("%") + @builds("^") + @checkargs(2) + def compile_maths_expression_exactly_2(self, expression): + return self._compile_maths_expression(expression) + + @builds("*") + @builds("|") def compile_maths_expression_mul(self, expression): - if len(expression) > 2: - return self.compile_maths_expression(expression) + id_elem = {"*": 1, "|": 0}[expression[0]] + if len(expression) == 1: + return ast.Num(n=long_type(id_elem), + lineno=expression.start_line, + col_offset=expression.start_column) + elif len(expression) == 2: + return self.compile(expression[1]) else: - id_op = {"*": HyInteger(1), "/": HyInteger(1), "//": HyInteger(1)} + return self._compile_maths_expression(expression) - op = expression.pop(0) - arg = expression.pop(0) if expression else id_op[op] - expr = HyExpression([ - HySymbol(op), - id_op[op], - arg - ]).replace(expression) - return self.compile_maths_expression(expr) + @builds("/") + @checkargs(min=1) + def compile_maths_expression_div(self, expression): + if len(expression) == 2: + expression = HyExpression([HySymbol("/"), + HyInteger(1), + expression[1]]).replace(expression) + return self._compile_maths_expression(expression) - def compile_maths_expression_additive(self, expression): + def _compile_maths_expression_additive(self, expression): if len(expression) > 2: - return self.compile_maths_expression(expression) + return self._compile_maths_expression(expression) else: op = {"+": ast.UAdd, "-": ast.USub}[expression.pop(0)]() arg = expression.pop(0) @@ -1972,6 +1987,17 @@ class HyASTCompiler(object): col_offset=arg.start_column) return ret + @builds("&") + @builds_if("@", PY35) + @checkargs(min=1) + def compile_maths_expression_unary_idempotent(self, expression): + if len(expression) == 2: + # Used as a unary operator, this operator simply + # returns its argument. + return self.compile(expression[1]) + else: + return self._compile_maths_expression(expression) + @builds("+") def compile_maths_expression_add(self, expression): if len(expression) == 1: @@ -1980,12 +2006,12 @@ class HyASTCompiler(object): lineno=expression.start_line, col_offset=expression.start_column) else: - return self.compile_maths_expression_additive(expression) + return self._compile_maths_expression_additive(expression) @builds("-") @checkargs(min=1) def compile_maths_expression_sub(self, expression): - return self.compile_maths_expression_additive(expression) + return self._compile_maths_expression_additive(expression) @builds("+=") @builds("/=") diff --git a/hy/core/shadow.hy b/hy/core/shadow.hy index f5f9b4c..f7d3466 100644 --- a/hy/core/shadow.hy +++ b/hy/core/shadow.hy @@ -22,72 +22,144 @@ ;;;; Hy shadow functions (import operator) +(import [hy._compat [PY35]]) (defn + [&rest args] "Shadow + operator for when we need to import / map it against something" (if + (= (len args) 0) + 0 (= (len args) 1) - (operator.pos (get args 0)) - args - (reduce operator.add args) - (raise (TypeError "Need at least 1 argument to add/concatenate")))) + (+ (first args)) + ; else + (reduce operator.add args))) - -(defn - [&rest args] +(defn - [a1 &rest a-rest] "Shadow - operator for when we need to import / map it against something" - (if - (= (len args) 1) - (- (get args 0)) - args - (reduce operator.sub args) - (raise (TypeError "Need at least 1 argument to subtract")))) - + (if a-rest + (reduce operator.sub a-rest a1) + (- a1))) (defn * [&rest args] "Shadow * operator for when we need to import / map it against something" - (if (= (len args) 0) - 1 ; identity - (reduce operator.mul args))) - - -(defn / [&rest args] - "Shadow / operator for when we need to import / map it against something" (if + (= (len args) 0) + 1 (= (len args) 1) - (operator.truediv 1 (get args 0)) - args - (reduce operator.truediv args) - (raise (TypeError "Need at least 1 argument to divide")))) + (first args) + ; else + (reduce operator.mul args))) +(defn ** [a1 a2 &rest a-rest] + ; We use `-foldr` instead of `reduce` because exponentiation + ; is right-associative. + (-foldr operator.pow (+ (, a1 a2) a-rest))) +(defn -foldr [f xs] + (reduce (fn [x y] (f y x)) (cut xs None None -1))) -(defn comp-op [op args] +(defn / [a1 &rest a-rest] + "Shadow / operator for when we need to import / map it against something" + (if a-rest + (reduce operator.truediv a-rest a1) + (/ 1 a1))) + +(defn // [a1 a2 &rest a-rest] + (reduce operator.floordiv (+ (, a2) a-rest) a1)) + +(defn % [x y] + (% x y)) + +(if PY35 (defn @ [a1 &rest a-rest] + (reduce operator.matmul a-rest a1))) + +(defn << [a1 a2 &rest a-rest] + (reduce operator.lshift (+ (, a2) a-rest) a1)) + +(defn >> [a1 a2 &rest a-rest] + (reduce operator.rshift (+ (, a2) a-rest) a1)) + +(defn & [a1 &rest a-rest] + (if a-rest + (reduce operator.and_ a-rest a1) + a1)) + +(defn | [&rest args] + (if + (= (len args) 0) + 0 + (= (len args) 1) + (first args) + ; else + (reduce operator.or_ args))) + +(defn ^ [x y] + (^ x y)) + +(defn ~ [x] + (~ x)) + +(defn comp-op [op a1 a-rest] "Helper for shadow comparison operators" - (if (< (len args) 2) - (raise (TypeError "Need at least 2 arguments to compare")) + (if a-rest (reduce (fn [x y] (and x y)) - (list-comp (op x y) - [(, x y) (zip args (cut args 1))])))) -(defn < [&rest args] + (list-comp (op x y) [(, x y) (zip (+ (, a1) a-rest) a-rest)])) + True)) +(defn < [a1 &rest a-rest] "Shadow < operator for when we need to import / map it against something" - (comp-op operator.lt args)) -(defn <= [&rest args] + (comp-op operator.lt a1 a-rest)) +(defn <= [a1 &rest a-rest] "Shadow <= operator for when we need to import / map it against something" - (comp-op operator.le args)) -(defn = [&rest args] + (comp-op operator.le a1 a-rest)) +(defn = [a1 &rest a-rest] "Shadow = operator for when we need to import / map it against something" - (comp-op operator.eq args)) -(defn != [&rest args] + (comp-op operator.eq a1 a-rest)) +(defn is [a1 &rest a-rest] + (comp-op operator.is_ a1 a-rest)) +(defn != [a1 a2 &rest a-rest] "Shadow != operator for when we need to import / map it against something" - (comp-op operator.ne args)) -(defn >= [&rest args] + (comp-op operator.ne a1 (+ (, a2) a-rest))) +(defn is-not [a1 a2 &rest a-rest] + (comp-op operator.is-not a1 (+ (, a2) a-rest))) +(defn >= [a1 &rest a-rest] "Shadow >= operator for when we need to import / map it against something" - (comp-op operator.ge args)) -(defn > [&rest args] + (comp-op operator.ge a1 a-rest)) +(defn > [a1 &rest a-rest] "Shadow > operator for when we need to import / map it against something" - (comp-op operator.gt args)) + (comp-op operator.gt a1 a-rest)) -; TODO figure out a way to shadow "is", "is_not", "and", "or" +(defn and [&rest args] + (if + (= (len args) 0) + True + (= (len args) 1) + (first args) + ; else + (reduce (fn [x y] (and x y)) args))) +(defn or [&rest args] + (if + (= (len args) 0) + None + (= (len args) 1) + (first args) + ; else + (reduce (fn [x y] (or x y)) args))) -(setv *exports* ['+ '- '* '/ '< '<= '= '!= '>= '>]) +(defn not [x] + (not x)) + +(defn in [x y] + (in x y)) + +(defn not-in [x y] + (not-in x y)) + +(setv *exports* [ + '+ '- '* '** '/ '// '% '@ + '<< '>> '& '| '^ '~ + '< '> '<= '>= '= '!= + 'and 'or 'not + 'is 'is-not 'in 'not-in]) +(if (not PY35) + (.remove *exports* '@)) diff --git a/tests/__init__.py b/tests/__init__.py index 74b8390..15a102e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,7 +12,7 @@ from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa from .native_tests.reader_macros import * # noqa -from .native_tests.shadow import * # noqa +from .native_tests.operators import * # noqa from .native_tests.with_test import * # noqa from .native_tests.extra.anaphoric import * # noqa from .native_tests.contrib.loop import * # noqa diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index dfcede1..906b252 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -498,7 +498,7 @@ def test_compile_error(): try: can_compile("(fn [] (in [1 2 3]))") except HyTypeError as e: - assert(e.message == "`in' needs at least 2 arguments, got 1.") + assert(e.message == "`in' needs 2 arguments, got 1") else: assert(False) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 013ef1e..7b11edb 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -253,57 +253,6 @@ (assert (= fact 120))) -(defn test-not [] - "NATIVE: test not" - (assert (not (= 1 2))) - (assert (= True (not False))) - (assert (= False (not 42))) ) - - -(defn test-inv [] - "NATIVE: test inv" - (assert (= (~ 1) -2)) - (assert (= (~ -2) 1))) - - -(defn test-in [] - "NATIVE: test in" - (assert (in "a" ["a" "b" "c" "d"])) - (assert (not-in "f" ["a" "b" "c" "d"]))) - - -(defn test-noteq [] - "NATIVE: not eq" - (assert (!= 2 3)) - (assert (not (!= 1)))) - - -(defn test-eq [] - "NATIVE: eq" - (assert (= 1 1)) - (assert (= 1))) - - -(defn test-numops [] - "NATIVE: test numpos" - (assert (> 5 4 3 2 1)) - (assert (> 1)) - (assert (< 1 2 3 4 5)) - (assert (< 1)) - (assert (<= 5 5 5 5 )) - (assert (<= 1)) - (assert (>= 5 5 5 5 )) - (assert (>= 1))) - - -(defn test-is [] - "NATIVE: test is can deal with None" - (setv a None) - (assert (is a None)) - (assert (is-not a "b")) - (assert (none? a))) - - (defn test-branching [] "NATIVE: test if branching" (if True diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy new file mode 100644 index 0000000..1f60650 --- /dev/null +++ b/tests/native_tests/operators.hy @@ -0,0 +1,286 @@ +(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]))) diff --git a/tests/native_tests/shadow.hy b/tests/native_tests/shadow.hy deleted file mode 100644 index 72ac0b9..0000000 --- a/tests/native_tests/shadow.hy +++ /dev/null @@ -1,101 +0,0 @@ -(defn test-shadow-addition [] - "NATIVE: test shadow addition" - (setv x +) - (assert (try - (x) - (except [TypeError] True) - (else (raise AssertionError)))) - (assert (= (x 1 2 3 4) 10)) - (assert (= (x 1 2 3 4 5) 15)) - ; with strings - (assert (= (x "a" "b" "c") - "abc")) - ; with lists - (assert (= (x ["a"] ["b"] ["c"]) - ["a" "b" "c"]))) - - -(defn test-shadow-subtraction [] - "NATIVE: test shadow subtraction" - (setv x -) - (assert (try - (x) - (except [TypeError] True) - (else (raise AssertionError)))) - (assert (= (x 1) -1)) - (assert (= (x 2 1) 1)) - (assert (= (x 2 1 1) 0))) - - -(defn test-shadow-multiplication [] - "NATIVE: test shadow multiplication" - (setv x *) - (assert (= (x) 1)) - (assert (= (x 3) 3)) - (assert (= (x 3 3) 9))) - - -(defn test-shadow-division [] - "NATIVE: test shadow division" - (setv x /) - (assert (try - (x) - (except [TypeError] True) - (else (raise AssertionError)))) - (assert (= (x 1) 1)) - (assert (= (x 8 2) 4)) - (assert (= (x 8 2 2) 2)) - (assert (= (x 8 2 2 2) 1))) - - -(defn test-shadow-compare [] - "NATIVE: test shadow compare" - - (for [x [< <= = != >= >]] - (assert (try - (x) - (except [TypeError] True) - (else (raise AssertionError)))) - (assert (try - (x 1) - (except [TypeError] True) - (else (raise AssertionError))))) - - (for [(, x y) [[< >=] - [<= >] - [= !=]]] - (for [args [[1 2] - [2 1] - [1 1] - [2 2]]] - (assert (= (apply x args) (not (apply y args)))))) - - (setv s-lt < - s-gt > - s-le <= - s-ge >= - s-eq = - s-ne !=) - (assert (apply s-lt [1 2 3])) - (assert (not (apply s-lt [3 2 1]))) - (assert (apply s-gt [3 2 1])) - (assert (not (apply s-gt [1 2 3]))) - (assert (apply s-le [1 1 2 2 3 3])) - (assert (not (apply s-le [1 1 2 2 1 1]))) - (assert (apply s-ge [3 3 2 2 1 1])) - (assert (not (apply s-ge [3 3 2 2 3 3]))) - (assert (apply s-eq [1 1 1 1 1])) - (assert (not (apply s-eq [1 1 2 1 1]))) - (assert (apply s-ne [1 2 3 4 5])) - (assert (not (apply s-ne [1 1 2 3 4]))) - - ; 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 (= (< (C "a") (C "b") (C "c")) "b")) - (setv f <) - (assert (= (f (C "a") (C "b") (C "c")) "b")))