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
|
* Added special forms ``py`` to ``pys`` that allow Hy programs to include
|
||||||
inline Python code.
|
inline Python code.
|
||||||
|
* Added a special form ``cmp`` for chained comparisons.
|
||||||
* All augmented assignment operators (except `%=` and `^=`) now allow
|
* All augmented assignment operators (except `%=` and `^=`) now allow
|
||||||
more than two arguments.
|
more than two arguments.
|
||||||
* PEP 3107 and PEP 526 function and variable annotations are now supported.
|
* 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.
|
* Statements in the second argument of `assert` are now executed.
|
||||||
* Fixed the expression of a while loop that contains statements being compiled twice.
|
* 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.
|
* `hy2py` can now handle format strings.
|
||||||
* Fixed crashes from inaccessible history files.
|
* Fixed crashes from inaccessible history files.
|
||||||
* The unit tests no longer unintentionally import the internal Python module "test".
|
* The unit tests no longer unintentionally import the internal Python module "test".
|
||||||
|
@ -299,6 +299,38 @@ as the user enters *k*.
|
|||||||
(print "Try again")))
|
(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
|
comment
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -1256,26 +1256,43 @@ class HyASTCompiler(object):
|
|||||||
values=[value.force_expr for value in values])
|
values=[value.force_expr for value in values])
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
c_ops = {"=": ast.Eq, "!=": ast.NotEq,
|
_c_ops = {"=": ast.Eq, "!=": ast.NotEq,
|
||||||
"<": ast.Lt, "<=": ast.LtE,
|
"<": ast.Lt, "<=": ast.LtE,
|
||||||
">": ast.Gt, ">=": ast.GtE,
|
">": ast.Gt, ">=": ast.GtE,
|
||||||
"is": ast.Is, "is-not": ast.IsNot,
|
"is": ast.Is, "is-not": ast.IsNot,
|
||||||
"in": ast.In, "not-in": ast.NotIn}
|
"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", "<", "<=", ">", ">="], [oneplus(FORM)])
|
||||||
@special(["!=", "is-not"], [times(2, Inf, FORM)])
|
@special(["!=", "is-not", "in", "not-in"], [times(2, Inf, FORM)])
|
||||||
@special(["in", "not-in"], [times(2, 2, FORM)])
|
|
||||||
def compile_compare_op_expression(self, expr, root, args):
|
def compile_compare_op_expression(self, expr, root, args):
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
return (self.compile(args[0]) +
|
return (self.compile(args[0]) +
|
||||||
asty.Name(expr, id="True", ctx=ast.Load()))
|
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)
|
exprs, ret, _ = self._compile_collect(args)
|
||||||
return ret + asty.Compare(
|
return ret + asty.Compare(
|
||||||
expr, left=exprs[0], ops=ops, comparators=exprs[1:])
|
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
|
# The second element of each tuple below is an aggregation operator
|
||||||
# that's used for augmented assignment with three or more arguments.
|
# that's used for augmented assignment with three or more arguments.
|
||||||
m_ops = {"+": (ast.Add, "+"),
|
m_ops = {"+": (ast.Add, "+"),
|
||||||
|
@ -117,6 +117,12 @@
|
|||||||
(defn is-not [a1 a2 &rest a-rest]
|
(defn is-not [a1 a2 &rest a-rest]
|
||||||
"Shadowed `is-not` keyword perform is-not on `a1` by `a2`, ..., `a-rest`."
|
"Shadowed `is-not` keyword perform is-not on `a1` by `a2`, ..., `a-rest`."
|
||||||
(comp-op operator.is-not a1 (+ (, 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]
|
(defn >= [a1 &rest a-rest]
|
||||||
"Shadowed `>=` operator perform ge comparison on `a1` by each `a-rest`."
|
"Shadowed `>=` operator perform ge comparison on `a1` by each `a-rest`."
|
||||||
(comp-op operator.ge a1 a-rest))
|
(comp-op operator.ge a1 a-rest))
|
||||||
@ -148,14 +154,6 @@
|
|||||||
"Shadowed `not` keyword perform not on `x`."
|
"Shadowed `not` keyword perform not on `x`."
|
||||||
(not 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]
|
(defn get [coll key1 &rest keys]
|
||||||
"Access item in `coll` indexed by `key1`, with optional `keys` nested-access."
|
"Access item in `coll` indexed by `key1`, with optional `keys` nested-access."
|
||||||
(setv coll (get coll key1))
|
(setv coll (get coll key1))
|
||||||
|
@ -294,7 +294,8 @@
|
|||||||
(forbid (f 3))
|
(forbid (f 3))
|
||||||
(assert (is (f 3 [1 2]) (!= f-name "in")))
|
(assert (is (f 3 [1 2]) (!= f-name "in")))
|
||||||
(assert (is (f 2 [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]
|
(op-and-shadow-test [get]
|
||||||
@ -305,6 +306,21 @@
|
|||||||
(assert (= (f {"x" {"y" {"z" 12}}} "x" "y" "z") 12)))
|
(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 []
|
(defn test-augassign []
|
||||||
(setv b 2 c 3 d 4)
|
(setv b 2 c 3 d 4)
|
||||||
(defmacro same-as [expr1 expr2 expected-value]
|
(defmacro same-as [expr1 expr2 expected-value]
|
||||||
|
Loading…
Reference in New Issue
Block a user