Merge pull request #1857 from Kodiologist/chain-cmp
Implement chained comparisons
This commit is contained in:
commit
9cace11d83
2
NEWS.rst
2
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.
|
||||
@ -32,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".
|
||||
|
@ -299,6 +299,38 @@ as the user enters *k*.
|
||||
(print "Try again")))
|
||||
|
||||
|
||||
cmp
|
||||
---
|
||||
|
||||
``cmp`` creates a :ref:`comparison expression <py:comparisons>`. 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
|
||||
-------
|
||||
|
||||
|
@ -1256,26 +1256,43 @@ 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)])
|
||||
@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]) +
|
||||
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, "+"),
|
||||
|
@ -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))
|
||||
|
@ -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]
|
||||
@ -305,41 +306,56 @@
|
||||
(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]
|
||||
`(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)))
|
||||
|
Loading…
Reference in New Issue
Block a user