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.
This commit is contained in:
parent
18acfe6495
commit
5eb928356a
7
NEWS
7
NEWS
@ -7,11 +7,18 @@ Changes from 0.12.1
|
|||||||
* Added bytestring literals, which create `bytes` objects under Python 3
|
* Added bytestring literals, which create `bytes` objects under Python 3
|
||||||
and `str` objects under Python 2
|
and `str` objects under Python 2
|
||||||
* Commas and underscores are allowed in numeric literals
|
* 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
|
* `setv` always returns None
|
||||||
* xor: If exactly one argument is true, return it
|
* xor: If exactly one argument is true, return it
|
||||||
* hy.core.reserved is now hy.extra.reserved
|
* hy.core.reserved is now hy.extra.reserved
|
||||||
|
|
||||||
[ Bug Fixes ]
|
[ Bug Fixes ]
|
||||||
|
* All shadowed operators have the same arities as real operators
|
||||||
* Shadowed comparison operators now use `and` instead of `&`
|
* Shadowed comparison operators now use `and` instead of `&`
|
||||||
for chained comparisons
|
for chained comparisons
|
||||||
* partition no longer prematurely exhausts input iterators
|
* partition no longer prematurely exhausts input iterators
|
||||||
|
102
hy/compiler.py
102
hy/compiler.py
@ -371,7 +371,7 @@ def checkargs(exact=None, min=None, max=None, even=None, multiple=None):
|
|||||||
class HyASTCompiler(object):
|
class HyASTCompiler(object):
|
||||||
|
|
||||||
def __init__(self, module_name):
|
def __init__(self, module_name):
|
||||||
self.allow_builtins = False
|
self.allow_builtins = module_name.startswith("hy.core")
|
||||||
self.anon_fn_count = 0
|
self.anon_fn_count = 0
|
||||||
self.anon_var_count = 0
|
self.anon_var_count = 0
|
||||||
self.imports = defaultdict(set)
|
self.imports = defaultdict(set)
|
||||||
@ -1876,7 +1876,7 @@ class HyASTCompiler(object):
|
|||||||
col_offset=e.start_column)
|
col_offset=e.start_column)
|
||||||
|
|
||||||
@builds("=")
|
@builds("=")
|
||||||
@builds("!=")
|
@builds("is")
|
||||||
@builds("<")
|
@builds("<")
|
||||||
@builds("<=")
|
@builds("<=")
|
||||||
@builds(">")
|
@builds(">")
|
||||||
@ -1884,33 +1884,25 @@ class HyASTCompiler(object):
|
|||||||
@checkargs(min=1)
|
@checkargs(min=1)
|
||||||
def compile_compare_op_expression(self, expression):
|
def compile_compare_op_expression(self, expression):
|
||||||
if len(expression) == 2:
|
if len(expression) == 2:
|
||||||
rval = "True"
|
return ast.Name(id="True",
|
||||||
if expression[0] == "!=":
|
|
||||||
rval = "False"
|
|
||||||
return ast.Name(id=rval,
|
|
||||||
ctx=ast.Load(),
|
ctx=ast.Load(),
|
||||||
lineno=expression.start_line,
|
lineno=expression.start_line,
|
||||||
col_offset=expression.start_column)
|
col_offset=expression.start_column)
|
||||||
return self._compile_compare_op_expression(expression)
|
return self._compile_compare_op_expression(expression)
|
||||||
|
|
||||||
@builds("is")
|
@builds("!=")
|
||||||
@builds("in")
|
|
||||||
@builds("is_not")
|
@builds("is_not")
|
||||||
@builds("not_in")
|
|
||||||
@checkargs(min=2)
|
@checkargs(min=2)
|
||||||
def compile_compare_op_expression_coll(self, expression):
|
def compile_compare_op_expression_coll(self, expression):
|
||||||
return self._compile_compare_op_expression(expression)
|
return self._compile_compare_op_expression(expression)
|
||||||
|
|
||||||
@builds("%")
|
@builds("in")
|
||||||
@builds("**")
|
@builds("not_in")
|
||||||
@builds("<<")
|
@checkargs(2)
|
||||||
@builds(">>")
|
def compile_compare_op_expression_binary(self, expression):
|
||||||
@builds("|")
|
return self._compile_compare_op_expression(expression)
|
||||||
@builds("^")
|
|
||||||
@builds("&")
|
def _compile_maths_expression(self, expression):
|
||||||
@builds_if("@", PY35)
|
|
||||||
@checkargs(min=2)
|
|
||||||
def compile_maths_expression(self, expression):
|
|
||||||
ops = {"+": ast.Add,
|
ops = {"+": ast.Add,
|
||||||
"/": ast.Div,
|
"/": ast.Div,
|
||||||
"//": ast.FloorDiv,
|
"//": ast.FloorDiv,
|
||||||
@ -1926,14 +1918,18 @@ class HyASTCompiler(object):
|
|||||||
if PY35:
|
if PY35:
|
||||||
ops.update({"@": ast.MatMult})
|
ops.update({"@": ast.MatMult})
|
||||||
|
|
||||||
inv = expression.pop(0)
|
op = ops[expression.pop(0)]
|
||||||
op = ops[inv]
|
right_associative = op == ast.Pow
|
||||||
|
|
||||||
|
if right_associative:
|
||||||
|
expression = expression[::-1]
|
||||||
ret = self.compile(expression.pop(0))
|
ret = self.compile(expression.pop(0))
|
||||||
for child in expression:
|
for child in expression:
|
||||||
left_expr = ret.force_expr
|
left_expr = ret.force_expr
|
||||||
ret += self.compile(child)
|
ret += self.compile(child)
|
||||||
right_expr = ret.force_expr
|
right_expr = ret.force_expr
|
||||||
|
if right_associative:
|
||||||
|
left_expr, right_expr = right_expr, left_expr
|
||||||
ret += ast.BinOp(left=left_expr,
|
ret += ast.BinOp(left=left_expr,
|
||||||
op=op(),
|
op=op(),
|
||||||
right=right_expr,
|
right=right_expr,
|
||||||
@ -1941,27 +1937,46 @@ class HyASTCompiler(object):
|
|||||||
col_offset=child.start_column)
|
col_offset=child.start_column)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@builds("*")
|
@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):
|
def compile_maths_expression_mul(self, expression):
|
||||||
if len(expression) > 2:
|
id_elem = {"*": 1, "|": 0}[expression[0]]
|
||||||
return self.compile_maths_expression(expression)
|
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:
|
else:
|
||||||
id_op = {"*": HyInteger(1), "/": HyInteger(1), "//": HyInteger(1)}
|
return self._compile_maths_expression(expression)
|
||||||
|
|
||||||
op = expression.pop(0)
|
@builds("/")
|
||||||
arg = expression.pop(0) if expression else id_op[op]
|
@checkargs(min=1)
|
||||||
expr = HyExpression([
|
def compile_maths_expression_div(self, expression):
|
||||||
HySymbol(op),
|
if len(expression) == 2:
|
||||||
id_op[op],
|
expression = HyExpression([HySymbol("/"),
|
||||||
arg
|
HyInteger(1),
|
||||||
]).replace(expression)
|
expression[1]]).replace(expression)
|
||||||
return self.compile_maths_expression(expr)
|
return self._compile_maths_expression(expression)
|
||||||
|
|
||||||
def compile_maths_expression_additive(self, expression):
|
def _compile_maths_expression_additive(self, expression):
|
||||||
if len(expression) > 2:
|
if len(expression) > 2:
|
||||||
return self.compile_maths_expression(expression)
|
return self._compile_maths_expression(expression)
|
||||||
else:
|
else:
|
||||||
op = {"+": ast.UAdd, "-": ast.USub}[expression.pop(0)]()
|
op = {"+": ast.UAdd, "-": ast.USub}[expression.pop(0)]()
|
||||||
arg = expression.pop(0)
|
arg = expression.pop(0)
|
||||||
@ -1972,6 +1987,17 @@ class HyASTCompiler(object):
|
|||||||
col_offset=arg.start_column)
|
col_offset=arg.start_column)
|
||||||
return ret
|
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("+")
|
@builds("+")
|
||||||
def compile_maths_expression_add(self, expression):
|
def compile_maths_expression_add(self, expression):
|
||||||
if len(expression) == 1:
|
if len(expression) == 1:
|
||||||
@ -1980,12 +2006,12 @@ class HyASTCompiler(object):
|
|||||||
lineno=expression.start_line,
|
lineno=expression.start_line,
|
||||||
col_offset=expression.start_column)
|
col_offset=expression.start_column)
|
||||||
else:
|
else:
|
||||||
return self.compile_maths_expression_additive(expression)
|
return self._compile_maths_expression_additive(expression)
|
||||||
|
|
||||||
@builds("-")
|
@builds("-")
|
||||||
@checkargs(min=1)
|
@checkargs(min=1)
|
||||||
def compile_maths_expression_sub(self, expression):
|
def compile_maths_expression_sub(self, expression):
|
||||||
return self.compile_maths_expression_additive(expression)
|
return self._compile_maths_expression_additive(expression)
|
||||||
|
|
||||||
@builds("+=")
|
@builds("+=")
|
||||||
@builds("/=")
|
@builds("/=")
|
||||||
|
@ -22,72 +22,144 @@
|
|||||||
;;;; Hy shadow functions
|
;;;; Hy shadow functions
|
||||||
|
|
||||||
(import operator)
|
(import operator)
|
||||||
|
(import [hy._compat [PY35]])
|
||||||
|
|
||||||
|
|
||||||
(defn + [&rest args]
|
(defn + [&rest args]
|
||||||
"Shadow + operator for when we need to import / map it against something"
|
"Shadow + operator for when we need to import / map it against something"
|
||||||
(if
|
(if
|
||||||
|
(= (len args) 0)
|
||||||
|
0
|
||||||
(= (len args) 1)
|
(= (len args) 1)
|
||||||
(operator.pos (get args 0))
|
(+ (first args))
|
||||||
args
|
; else
|
||||||
(reduce operator.add args)
|
(reduce operator.add args)))
|
||||||
(raise (TypeError "Need at least 1 argument to add/concatenate"))))
|
|
||||||
|
|
||||||
|
(defn - [a1 &rest a-rest]
|
||||||
(defn - [&rest args]
|
|
||||||
"Shadow - operator for when we need to import / map it against something"
|
"Shadow - operator for when we need to import / map it against something"
|
||||||
(if
|
(if a-rest
|
||||||
(= (len args) 1)
|
(reduce operator.sub a-rest a1)
|
||||||
(- (get args 0))
|
(- a1)))
|
||||||
args
|
|
||||||
(reduce operator.sub args)
|
|
||||||
(raise (TypeError "Need at least 1 argument to subtract"))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn * [&rest args]
|
(defn * [&rest args]
|
||||||
"Shadow * operator for when we need to import / map it against something"
|
"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
|
(if
|
||||||
|
(= (len args) 0)
|
||||||
|
1
|
||||||
(= (len args) 1)
|
(= (len args) 1)
|
||||||
(operator.truediv 1 (get args 0))
|
(first args)
|
||||||
args
|
; else
|
||||||
(reduce operator.truediv args)
|
(reduce operator.mul args)))
|
||||||
(raise (TypeError "Need at least 1 argument to divide"))))
|
|
||||||
|
|
||||||
|
(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"
|
"Helper for shadow comparison operators"
|
||||||
(if (< (len args) 2)
|
(if a-rest
|
||||||
(raise (TypeError "Need at least 2 arguments to compare"))
|
|
||||||
(reduce (fn [x y] (and x y))
|
(reduce (fn [x y] (and x y))
|
||||||
(list-comp (op x y)
|
(list-comp (op x y) [(, x y) (zip (+ (, a1) a-rest) a-rest)]))
|
||||||
[(, x y) (zip args (cut args 1))]))))
|
True))
|
||||||
(defn < [&rest args]
|
(defn < [a1 &rest a-rest]
|
||||||
"Shadow < operator for when we need to import / map it against something"
|
"Shadow < operator for when we need to import / map it against something"
|
||||||
(comp-op operator.lt args))
|
(comp-op operator.lt a1 a-rest))
|
||||||
(defn <= [&rest args]
|
(defn <= [a1 &rest a-rest]
|
||||||
"Shadow <= operator for when we need to import / map it against something"
|
"Shadow <= operator for when we need to import / map it against something"
|
||||||
(comp-op operator.le args))
|
(comp-op operator.le a1 a-rest))
|
||||||
(defn = [&rest args]
|
(defn = [a1 &rest a-rest]
|
||||||
"Shadow = operator for when we need to import / map it against something"
|
"Shadow = operator for when we need to import / map it against something"
|
||||||
(comp-op operator.eq args))
|
(comp-op operator.eq a1 a-rest))
|
||||||
(defn != [&rest args]
|
(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"
|
"Shadow != operator for when we need to import / map it against something"
|
||||||
(comp-op operator.ne args))
|
(comp-op operator.ne a1 (+ (, a2) a-rest)))
|
||||||
(defn >= [&rest args]
|
(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"
|
"Shadow >= operator for when we need to import / map it against something"
|
||||||
(comp-op operator.ge args))
|
(comp-op operator.ge a1 a-rest))
|
||||||
(defn > [&rest args]
|
(defn > [a1 &rest a-rest]
|
||||||
"Shadow > operator for when we need to import / map it against something"
|
"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* '@))
|
||||||
|
@ -12,7 +12,7 @@ from .native_tests.when import * # noqa
|
|||||||
from .native_tests.with_decorator import * # noqa
|
from .native_tests.with_decorator import * # noqa
|
||||||
from .native_tests.core import * # noqa
|
from .native_tests.core import * # noqa
|
||||||
from .native_tests.reader_macros 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.with_test import * # noqa
|
||||||
from .native_tests.extra.anaphoric import * # noqa
|
from .native_tests.extra.anaphoric import * # noqa
|
||||||
from .native_tests.contrib.loop import * # noqa
|
from .native_tests.contrib.loop import * # noqa
|
||||||
|
@ -498,7 +498,7 @@ def test_compile_error():
|
|||||||
try:
|
try:
|
||||||
can_compile("(fn [] (in [1 2 3]))")
|
can_compile("(fn [] (in [1 2 3]))")
|
||||||
except HyTypeError as e:
|
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:
|
else:
|
||||||
assert(False)
|
assert(False)
|
||||||
|
|
||||||
|
@ -253,57 +253,6 @@
|
|||||||
(assert (= fact 120)))
|
(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 []
|
(defn test-branching []
|
||||||
"NATIVE: test if branching"
|
"NATIVE: test if branching"
|
||||||
(if True
|
(if True
|
||||||
|
286
tests/native_tests/operators.hy
Normal file
286
tests/native_tests/operators.hy
Normal file
@ -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])))
|
@ -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")))
|
|
Loading…
Reference in New Issue
Block a user