From 2074e4bd2f9b66b96087fc5e71433e4bff157ba0 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 26 Dec 2019 09:14:02 -0500 Subject: [PATCH 1/3] Change the indentation of a test --- tests/native_tests/operators.hy | 68 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy index 4445138..e8c858d 100644 --- a/tests/native_tests/operators.hy +++ b/tests/native_tests/operators.hy @@ -306,40 +306,40 @@ (defn test-augassign [] - (setv b 2 c 3 d 4) - (defmacro same-as [expr1 expr2 expected-value] - `(do - (setv a 4) - ~expr1 - (setv expr1-value a) - (setv a 4) - ~expr2 - (assert (= expr1-value a ~expected-value)))) - (same-as (+= a b c d) (+= a (+ b c d)) 13) - (same-as (-= a b c d) (-= a (+ b c d)) -5) - (same-as (*= a b c d) (*= a (* b c d)) 96) - (same-as (**= a b c) (**= a (** b c)) 65,536) - (same-as (/= a b c d) (/= a (* b c d)) (/ 1 6)) - (same-as (//= a b c d) (//= a (* b c d)) 0) - (same-as (<<= a b c d) (<<= a (+ b c d)) 0b10_00000_00000) - (same-as (>>= a b c d) (>>= a (+ b c d)) 0) - (same-as (&= a b c d) (&= a (& b c d)) 0) - (same-as (|= a b c d) (|= a (| b c d)) 0b111) + (setv b 2 c 3 d 4) + (defmacro same-as [expr1 expr2 expected-value] + `(do + (setv a 4) + ~expr1 + (setv expr1-value a) + (setv a 4) + ~expr2 + (assert (= expr1-value a ~expected-value)))) + (same-as (+= a b c d) (+= a (+ b c d)) 13) + (same-as (-= a b c d) (-= a (+ b c d)) -5) + (same-as (*= a b c d) (*= a (* b c d)) 96) + (same-as (**= a b c) (**= a (** b c)) 65,536) + (same-as (/= a b c d) (/= a (* b c d)) (/ 1 6)) + (same-as (//= a b c d) (//= a (* b c d)) 0) + (same-as (<<= a b c d) (<<= a (+ b c d)) 0b10_00000_00000) + (same-as (>>= a b c d) (>>= a (+ b c d)) 0) + (same-as (&= a b c d) (&= a (& b c d)) 0) + (same-as (|= a b c d) (|= a (| b c d)) 0b111) - (defclass C [object] - (defn __init__ [self content] (setv self.content content)) - (defn __matmul__ [self other] (C (+ self.content other.content)))) - (setv a (C "a") b (C "b") c (C "c") d (C "d")) - (@= a b c d) - (assert (= a.content "abcd")) - (setv a (C "a")) - (@= a (@ b c d)) - (assert (= a.content "abcd")) + (defclass C [object] + (defn __init__ [self content] (setv self.content content)) + (defn __matmul__ [self other] (C (+ self.content other.content)))) + (setv a (C "a") b (C "b") c (C "c") d (C "d")) + (@= a b c d) + (assert (= a.content "abcd")) + (setv a (C "a")) + (@= a (@ b c d)) + (assert (= a.content "abcd")) - (setv a 15) - (%= a 9) - (assert (= a 6)) + (setv a 15) + (%= a 9) + (assert (= a 6)) - (setv a 0b1100) - (^= a 0b1010) - (assert (= a 0b0110))) + (setv a 0b1100) + (^= a 0b1010) + (assert (= a 0b0110))) From 170febb2e875dc98e98ff30fa3cefa2e8441ece1 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 26 Dec 2019 12:25:25 -0500 Subject: [PATCH 2/3] Implement chained comparisons --- NEWS.rst | 1 + docs/language/api.rst | 32 ++++++++++++++++++++++++++++++++ hy/compiler.py | 24 +++++++++++++++++++++--- tests/native_tests/operators.hy | 15 +++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 71da3c9..be8eb70 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,7 @@ New Features ------------------------------ * Added special forms ``py`` to ``pys`` that allow Hy programs to include inline Python code. +* Added a special form ``cmp`` for chained comparisons. * All augmented assignment operators (except `%=` and `^=`) now allow more than two arguments. * PEP 3107 and PEP 526 function and variable annotations are now supported. diff --git a/docs/language/api.rst b/docs/language/api.rst index e675126..cb1d70d 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -299,6 +299,38 @@ as the user enters *k*. (print "Try again"))) +cmp +--- + +``cmp`` creates a :ref:`comparison expression `. It isn't +required for unchained comparisons, which have only one comparison operator, +nor for chains of the same operator. For those cases, you can use the +comparison operators directly with Hy's usual prefix syntax, as in ``(= x 1)`` +or ``(< 1 2 3)``. The use of ``cmp`` is to construct chains of heterogeneous +operators, such as ``x <= y < z``. It uses an infix syntax with the general +form + +:: + + (cmp ARG OP ARG OP ARG…) + +Hence, ``(cmp x <= y < z)`` is equivalent to ``(and (<= x y) (< y z))``, +including short-circuiting, except that ``y`` is only evaluated once. + +Each ``ARG`` is an arbitrary form, which does not itself use infix syntax. Use +:ref:`py-specialform` if you want fully Python-style operator syntax. You can +also nest ``cmp`` forms, although this is rarely useful. Each ``OP`` is a +literal comparison operator; other forms that resolve to a comparison operator +are not allowed. + +At least two ``ARG``\ s and one ``OP`` are required, and every ``OP`` must be +followed by an ``ARG``. + +As elsewhere in Hy, the equality operator is spelled ``=``, not ``==`` as in +Python. + + + comment ------- diff --git a/hy/compiler.py b/hy/compiler.py index 2b8bb6e..840f9f7 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1256,12 +1256,18 @@ class HyASTCompiler(object): values=[value.force_expr for value in values]) return ret - c_ops = {"=": ast.Eq, "!=": ast.NotEq, + _c_ops = {"=": ast.Eq, "!=": ast.NotEq, "<": ast.Lt, "<=": ast.LtE, ">": ast.Gt, ">=": ast.GtE, "is": ast.Is, "is-not": ast.IsNot, "in": ast.In, "not-in": ast.NotIn} - c_ops = {ast_str(k): v for k, v in c_ops.items()} + _c_ops = {ast_str(k): v for k, v in _c_ops.items()} + def _get_c_op(self, sym): + k = ast_str(sym) + if k not in self._c_ops: + raise self._syntax_error(sym, + "Illegal comparison operator: " + str(sym)) + return self._c_ops[k]() @special(["=", "is", "<", "<=", ">", ">="], [oneplus(FORM)]) @special(["!=", "is-not"], [times(2, Inf, FORM)]) @@ -1271,11 +1277,23 @@ class HyASTCompiler(object): return (self.compile(args[0]) + asty.Name(expr, id="True", ctx=ast.Load())) - ops = [self.c_ops[ast_str(root)]() for _ in args[1:]] + ops = [self._get_c_op(root) for _ in args[1:]] exprs, ret, _ = self._compile_collect(args) return ret + asty.Compare( expr, left=exprs[0], ops=ops, comparators=exprs[1:]) + @special("cmp", [FORM, many(SYM + FORM)]) + def compile_chained_comparison(self, expr, root, arg1, args): + ret = self.compile(arg1) + arg1 = ret.force_expr + + ops = [self._get_c_op(op) for op, _ in args] + args, ret2, _ = self._compile_collect( + [x for _, x in args]) + + return ret + ret2 + asty.Compare(expr, + left=arg1, ops=ops, comparators=args) + # The second element of each tuple below is an aggregation operator # that's used for augmented assignment with three or more arguments. m_ops = {"+": (ast.Add, "+"), diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy index e8c858d..e62fa57 100644 --- a/tests/native_tests/operators.hy +++ b/tests/native_tests/operators.hy @@ -305,6 +305,21 @@ (assert (= (f {"x" {"y" {"z" 12}}} "x" "y" "z") 12))) +(defn test-chained-comparison [] + (assert (cmp 2 = (+ 1 1) = (- 3 1))) + (assert (not (cmp 2 = (+ 1 1) = (+ 3 1)))) + + (assert (cmp 2 = 2 > 1)) + (assert (cmp 2 = (+ 1 1) > 1)) + (setv x 2) + (assert (cmp 2 = x > 1)) + (assert (cmp 2 = x > (> 4 3))) + (assert (not (cmp (> 4 3) = x > 1))) + + (assert (cmp 1 in [1] in [[1] [2 3]] not-in [5])) + (assert (not (cmp 1 in [1] not-in [[1] [2 3]] not-in [5])))) + + (defn test-augassign [] (setv b 2 c 3 d 4) (defmacro same-as [expr1 expr2 expected-value] From 0e55d7d9552765975ca85cbb5e21e59130345871 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 27 Dec 2019 09:32:14 -0500 Subject: [PATCH 3/3] Allow more than two arguments to `in` or `not-in` --- NEWS.rst | 1 + hy/compiler.py | 3 +-- hy/core/shadow.hy | 14 ++++++-------- tests/native_tests/operators.hy | 3 ++- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index be8eb70..8909d72 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -33,6 +33,7 @@ Bug Fixes ------------------------------ * Statements in the second argument of `assert` are now executed. * Fixed the expression of a while loop that contains statements being compiled twice. +* `in` and `not-in` now allow more than two arguments, as in Python. * `hy2py` can now handle format strings. * Fixed crashes from inaccessible history files. * The unit tests no longer unintentionally import the internal Python module "test". diff --git a/hy/compiler.py b/hy/compiler.py index 840f9f7..7bfcb53 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1270,8 +1270,7 @@ class HyASTCompiler(object): return self._c_ops[k]() @special(["=", "is", "<", "<=", ">", ">="], [oneplus(FORM)]) - @special(["!=", "is-not"], [times(2, Inf, FORM)]) - @special(["in", "not-in"], [times(2, 2, FORM)]) + @special(["!=", "is-not", "in", "not-in"], [times(2, Inf, FORM)]) def compile_compare_op_expression(self, expr, root, args): if len(args) == 1: return (self.compile(args[0]) + diff --git a/hy/core/shadow.hy b/hy/core/shadow.hy index 74ab08f..85e3f56 100644 --- a/hy/core/shadow.hy +++ b/hy/core/shadow.hy @@ -117,6 +117,12 @@ (defn is-not [a1 a2 &rest a-rest] "Shadowed `is-not` keyword perform is-not on `a1` by `a2`, ..., `a-rest`." (comp-op operator.is-not a1 (+ (, a2) a-rest))) +(defn in [a1 a2 &rest a-rest] + "Shadowed `in` keyword perform `a1` in `a2` in …." + (comp-op (fn [x y] (in x y)) a1 (+ (, a2) a-rest))) +(defn not-in [a1 a2 &rest a-rest] + "Shadowed `not in` keyword perform `a1` not in `a2` not in…." + (comp-op (fn [x y] (not-in x y)) a1 (+ (, a2) a-rest))) (defn >= [a1 &rest a-rest] "Shadowed `>=` operator perform ge comparison on `a1` by each `a-rest`." (comp-op operator.ge a1 a-rest)) @@ -148,14 +154,6 @@ "Shadowed `not` keyword perform not on `x`." (not x)) -(defn in [x y] - "Shadowed `in` keyword perform `x` in `y`." - (in x y)) - -(defn not-in [x y] - "Shadowed `not in` keyword perform `x` not in `y`." - (not-in x y)) - (defn get [coll key1 &rest keys] "Access item in `coll` indexed by `key1`, with optional `keys` nested-access." (setv coll (get coll key1)) diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy index e62fa57..831c292 100644 --- a/tests/native_tests/operators.hy +++ b/tests/native_tests/operators.hy @@ -294,7 +294,8 @@ (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]))) + (assert (is (f 2 [1 2] [[1 2] 3]) (= f-name "in"))) + (assert (is (f 3 [1 2] [[2 2] 3]) (!= f-name "in")))) (op-and-shadow-test [get]