Merge pull request #1325 from Kodiologist/unpacking
Replace `apply` with unpacking operators
This commit is contained in:
commit
35f7dd36bb
2
NEWS
2
NEWS
@ -2,6 +2,8 @@ Changes from 0.13.0
|
||||
|
||||
[ Language Changes ]
|
||||
* `yield-from` is no longer supported under Python 2
|
||||
* `apply` has been replaced with Python-style unpacking operators `#*` and
|
||||
`#**` (e.g., `(f #* args #** kwargs)`)
|
||||
* Single-character "sharp macros" changed to "tag macros", which can have
|
||||
longer names
|
||||
* Periods are no longer allowed in keywords
|
||||
|
@ -1,12 +1,13 @@
|
||||
import _pytest
|
||||
import hy
|
||||
from hy._compat import PY3
|
||||
from hy._compat import PY3, PY35
|
||||
|
||||
def pytest_collect_file(parent, path):
|
||||
if (path.ext == ".hy"
|
||||
and "/tests/native_tests/" in path.dirname + "/"
|
||||
and path.basename != "__init__.hy"
|
||||
and not ("py3_only" in path.basename and not PY3)):
|
||||
and not ("py3_only" in path.basename and not PY3)
|
||||
and not ("py35_only" in path.basename and not PY35)):
|
||||
m = _pytest.python.pytest_pycollect_makemodule(path, parent)
|
||||
# Spoof the module name to avoid hitting an assertion in pytest.
|
||||
m.name = m.name[:-len(".hy")] + ".py"
|
||||
|
@ -154,41 +154,6 @@ it appends it as the last argument. The following code demonstrates this:
|
||||
5 10
|
||||
|
||||
|
||||
apply
|
||||
-----
|
||||
|
||||
``apply`` is used to apply an optional list of arguments and an
|
||||
optional dictionary of kwargs to a function. The symbol mangling
|
||||
transformations will be applied to all keys in the dictionary of
|
||||
kwargs, provided the dictionary and its keys are defined in-place.
|
||||
|
||||
Usage: ``(apply fn-name [args] [kwargs])``
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(defn thunk []
|
||||
"hy there")
|
||||
|
||||
(apply thunk)
|
||||
;=> "hy there"
|
||||
|
||||
(defn total-purchase [price amount &optional [fees 1.05] [vat 1.1]]
|
||||
(* price amount fees vat))
|
||||
|
||||
(apply total-purchase [10 15])
|
||||
;=> 173.25
|
||||
|
||||
(apply total-purchase [10 15] {"vat" 1.05})
|
||||
;=> 165.375
|
||||
|
||||
(apply total-purchase [] {"price" 10 "amount" 15 "vat" 1.05})
|
||||
;=> 165.375
|
||||
|
||||
(apply total-purchase [] {:price 10 :amount 15 :vat 1.05})
|
||||
;=> 165.375
|
||||
|
||||
and
|
||||
---
|
||||
|
||||
@ -596,8 +561,8 @@ Parameters may have the following keywords in front of them:
|
||||
parameter_1 1
|
||||
parameter_2 2
|
||||
|
||||
; to avoid the mangling of '-' to '_', use apply:
|
||||
=> (apply print-parameters [] {"parameter-1" 1 "parameter-2" 2})
|
||||
; to avoid the mangling of '-' to '_', use unpacking:
|
||||
=> (print-parameters #** {"parameter-1" 1 "parameter-2" 2})
|
||||
parameter-1 1
|
||||
parameter-2 2
|
||||
|
||||
@ -634,19 +599,19 @@ Parameters may have the following keywords in front of them:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> (defn compare [a b &kwonly keyfn [reverse false]]
|
||||
=> (defn compare [a b &kwonly keyfn [reverse False]]
|
||||
... (setv result (keyfn a b))
|
||||
... (if (not reverse)
|
||||
... result
|
||||
... (- result)))
|
||||
=> (apply compare ["lisp" "python"]
|
||||
... {"keyfn" (fn [x y]
|
||||
... (reduce - (map (fn [s] (ord (first s))) [x y])))})
|
||||
=> (compare "lisp" "python"
|
||||
... :keyfn (fn [x y]
|
||||
... (reduce - (map (fn [s] (ord (first s))) [x y]))))
|
||||
-4
|
||||
=> (apply compare ["lisp" "python"]
|
||||
... {"keyfn" (fn [x y]
|
||||
=> (compare "lisp" "python"
|
||||
... :keyfn (fn [x y]
|
||||
... (reduce - (map (fn [s] (ord (first s))) [x y])))
|
||||
... "reverse" True})
|
||||
... :reverse True)
|
||||
4
|
||||
|
||||
.. code-block:: python
|
||||
@ -1576,6 +1541,49 @@ the given conditional is ``False``. The following shows the expansion of this ma
|
||||
(do statement))
|
||||
|
||||
|
||||
unpack-iterable, unpack-mapping
|
||||
-------------------------------
|
||||
|
||||
``unpack-iterable`` and ``unpack-mapping`` allow an iterable or mapping
|
||||
object (respectively) to provide positional or keywords arguments
|
||||
(respectively) to a function.
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> (defn f [a b c d] [a b c d])
|
||||
=> (f (unpack-iterable [1 2]) (unpack-mapping {"c" 3 "d" 4}))
|
||||
[1, 2, 3, 4]
|
||||
|
||||
``unpack-iterable`` is usually written with the shorthand ``#*``, and
|
||||
``unpack-mapping`` with ``#**``.
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> (f #* [1 2] #** {"c" 3 "d" 4})
|
||||
[1, 2, 3, 4]
|
||||
|
||||
With Python 3, you can unpack in an assignment list (:pep:`3132`).
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> (setv [a #* b c] [1 2 3 4 5])
|
||||
=> [a b c]
|
||||
[1, [2, 3, 4], 5]
|
||||
|
||||
With Python 3.5 or greater, unpacking is allowed in more contexts than just
|
||||
function calls, and you can unpack more than once in the same expression
|
||||
(:pep:`448`).
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> [#* [1 2] #* [3 4]]
|
||||
[1, 2, 3, 4]
|
||||
=> {#** {1 2} #** {3 4}}
|
||||
{1: 2, 3: 4}
|
||||
=> (f #* [1] #* [2] #** {"c" 3} #** {"d" 4})
|
||||
[1, 2, 3, 4]
|
||||
|
||||
|
||||
unquote
|
||||
-------
|
||||
|
||||
|
@ -1216,9 +1216,9 @@ if *from-file* ends before a complete expression can be parsed.
|
||||
|
||||
=> (import io)
|
||||
=> (def buffer (io.StringIO "(+ 2 2)\n(- 2 1)"))
|
||||
=> (eval (apply read [] {"from_file" buffer}))
|
||||
=> (eval (read :from_file buffer))
|
||||
4
|
||||
=> (eval (apply read [] {"from_file" buffer}))
|
||||
=> (eval (read :from_file buffer))
|
||||
1
|
||||
|
||||
=> ; assuming "example.hy" contains:
|
||||
|
@ -423,8 +423,7 @@ The same thing in Hy::
|
||||
=> (optional-arg 1 2 3 4)
|
||||
[1 2 3 4]
|
||||
|
||||
If you're running a version of Hy past 0.10.1 (eg, git master),
|
||||
there's also a nice new keyword argument syntax::
|
||||
You can call keyword arguments like this::
|
||||
|
||||
=> (optional-arg :keyword1 1
|
||||
... :pos2 2
|
||||
@ -432,21 +431,13 @@ there's also a nice new keyword argument syntax::
|
||||
... :keyword2 4)
|
||||
[3, 2, 1, 4]
|
||||
|
||||
Otherwise, you can always use `apply`. But what's `apply`?
|
||||
|
||||
Are you familiar with passing in `*args` and `**kwargs` in Python?::
|
||||
|
||||
>>> args = [1 2]
|
||||
>>> kwargs = {"keyword2": 3
|
||||
... "keyword1": 4}
|
||||
>>> optional_arg(*args, **kwargs)
|
||||
|
||||
We can reproduce this with `apply`::
|
||||
You can unpack arguments with the syntax ``#* args`` and ``#** kwargs``,
|
||||
similar to `*args` and `**kwargs` in Python::
|
||||
|
||||
=> (setv args [1 2])
|
||||
=> (setv kwargs {"keyword2" 3
|
||||
... "keyword1" 4})
|
||||
=> (apply optional-arg args kwargs)
|
||||
=> (optional-arg #* args #** kwargs)
|
||||
[1, 2, 4, 3]
|
||||
|
||||
There's also a dictionary-style keyword arguments construction that
|
||||
@ -460,7 +451,7 @@ looks like:
|
||||
The difference here is that since it's a dictionary, you can't rely on
|
||||
any specific ordering to the arguments.
|
||||
|
||||
Hy also supports ``*args`` and ``**kwargs``. In Python::
|
||||
Hy also supports ``*args`` and ``**kwargs`` in parameter lists. In Python::
|
||||
|
||||
def some_func(foo, bar, *args, **kwargs):
|
||||
import pprint
|
||||
|
200
hy/compiler.py
200
hy/compiler.py
@ -359,6 +359,13 @@ def checkargs(exact=None, min=None, max=None, even=None, multiple=None):
|
||||
return _dec
|
||||
|
||||
|
||||
def is_unpack(kind, x):
|
||||
return (isinstance(x, HyExpression)
|
||||
and len(x) > 0
|
||||
and isinstance(x[0], HySymbol)
|
||||
and x[0] == "unpack_" + kind)
|
||||
|
||||
|
||||
class HyASTCompiler(object):
|
||||
|
||||
def __init__(self, module_name):
|
||||
@ -441,7 +448,8 @@ class HyASTCompiler(object):
|
||||
|
||||
raise HyCompileError(Exception("Unknown type: `%s'" % _type))
|
||||
|
||||
def _compile_collect(self, exprs, with_kwargs=False):
|
||||
def _compile_collect(self, exprs, with_kwargs=False, dict_display=False,
|
||||
oldpy_unpack=False):
|
||||
"""Collect the expression contexts from a list of compiled expression.
|
||||
|
||||
This returns a list of the expression contexts, and the sum of the
|
||||
@ -451,10 +459,39 @@ class HyASTCompiler(object):
|
||||
compiled_exprs = []
|
||||
ret = Result()
|
||||
keywords = []
|
||||
oldpy_starargs = None
|
||||
oldpy_kwargs = None
|
||||
|
||||
exprs_iter = iter(exprs)
|
||||
for expr in exprs_iter:
|
||||
if with_kwargs and isinstance(expr, HyKeyword):
|
||||
|
||||
if not PY35 and oldpy_unpack and is_unpack("iterable", expr):
|
||||
if oldpy_starargs:
|
||||
raise HyTypeError(expr, "Pythons < 3.5 allow only one "
|
||||
"`unpack-iterable` per call")
|
||||
oldpy_starargs = self.compile(expr[1])
|
||||
ret += oldpy_starargs
|
||||
oldpy_starargs = oldpy_starargs.force_expr
|
||||
|
||||
elif is_unpack("mapping", expr):
|
||||
ret += self.compile(expr[1])
|
||||
if PY35:
|
||||
if dict_display:
|
||||
compiled_exprs.append(None)
|
||||
compiled_exprs.append(ret.force_expr)
|
||||
elif with_kwargs:
|
||||
keywords.append(ast.keyword(
|
||||
arg=None,
|
||||
value=ret.force_expr,
|
||||
lineno=expr.start_line,
|
||||
col_offset=expr.start_column))
|
||||
elif oldpy_unpack:
|
||||
if oldpy_kwargs:
|
||||
raise HyTypeError(expr, "Pythons < 3.5 allow only one "
|
||||
"`unpack-mapping` per call")
|
||||
oldpy_kwargs = ret.force_expr
|
||||
|
||||
elif with_kwargs and isinstance(expr, HyKeyword):
|
||||
try:
|
||||
value = next(exprs_iter)
|
||||
except StopIteration:
|
||||
@ -474,11 +511,15 @@ class HyASTCompiler(object):
|
||||
value=compiled_value.force_expr,
|
||||
lineno=expr.start_line,
|
||||
col_offset=expr.start_column))
|
||||
|
||||
else:
|
||||
ret += self.compile(expr)
|
||||
compiled_exprs.append(ret.force_expr)
|
||||
|
||||
return compiled_exprs, ret, keywords
|
||||
if oldpy_unpack:
|
||||
return compiled_exprs, ret, keywords, oldpy_starargs, oldpy_kwargs
|
||||
else:
|
||||
return compiled_exprs, ret, keywords
|
||||
|
||||
def _compile_branch(self, exprs):
|
||||
return _branch(self.compile(expr) for expr in exprs)
|
||||
@ -610,6 +651,9 @@ class HyASTCompiler(object):
|
||||
new_name = ast.Subscript(value=name.value, slice=name.slice)
|
||||
elif isinstance(name, ast.Attribute):
|
||||
new_name = ast.Attribute(value=name.value, attr=name.attr)
|
||||
elif PY3 and isinstance(name, ast.Starred):
|
||||
new_name = ast.Starred(
|
||||
value=self._storeize(expr, name.value, func))
|
||||
else:
|
||||
raise HyTypeError(expr,
|
||||
"Can't assign or delete a %s" %
|
||||
@ -717,6 +761,23 @@ class HyASTCompiler(object):
|
||||
raise HyTypeError(expr,
|
||||
"`%s' can't be used at the top-level" % expr[0])
|
||||
|
||||
@builds("unpack_iterable")
|
||||
@checkargs(exact=1)
|
||||
def compile_unpack_iterable(self, expr):
|
||||
if not PY3:
|
||||
raise HyTypeError(expr, "`unpack-iterable` isn't allowed here")
|
||||
ret = self.compile(expr[1])
|
||||
ret += ast.Starred(value=ret.force_expr,
|
||||
lineno=expr.start_line,
|
||||
col_offset=expr.start_column,
|
||||
ctx=ast.Load())
|
||||
return ret
|
||||
|
||||
@builds("unpack_mapping")
|
||||
@checkargs(exact=1)
|
||||
def compile_unpack_mapping(self, expr):
|
||||
raise HyTypeError(expr, "`unpack-mapping` isn't allowed here")
|
||||
|
||||
@builds("do")
|
||||
def compile_do(self, expression):
|
||||
expression.pop(0)
|
||||
@ -1526,115 +1587,6 @@ class HyASTCompiler(object):
|
||||
generators=expr.generators)
|
||||
return ret
|
||||
|
||||
@builds("apply")
|
||||
@checkargs(min=1, max=3)
|
||||
def compile_apply_expression(self, expr):
|
||||
expr.pop(0) # apply
|
||||
|
||||
ret = Result()
|
||||
|
||||
fun = expr.pop(0)
|
||||
|
||||
# We actually defer the compilation of the function call to
|
||||
# @builds(HyExpression), allowing us to work on method calls
|
||||
call = HyExpression([fun]).replace(fun)
|
||||
|
||||
if isinstance(fun, HySymbol) and fun.startswith("."):
|
||||
# (apply .foo lst) needs to work as lst[0].foo(*lst[1:])
|
||||
if not expr:
|
||||
raise HyTypeError(
|
||||
expr, "apply of a method needs to have an argument"
|
||||
)
|
||||
|
||||
# We need to grab the arguments, and split them.
|
||||
|
||||
# Assign them to a variable if they're not one already
|
||||
if type(expr[0]) == HyList:
|
||||
if len(expr[0]) == 0:
|
||||
raise HyTypeError(
|
||||
expr, "apply of a method needs to have an argument"
|
||||
)
|
||||
call.append(expr[0].pop(0))
|
||||
else:
|
||||
if isinstance(expr[0], HySymbol):
|
||||
tempvar = expr[0]
|
||||
else:
|
||||
tempvar = HySymbol(self.get_anon_var()).replace(expr[0])
|
||||
assignment = HyExpression(
|
||||
[HySymbol("setv"), tempvar, expr[0]]
|
||||
).replace(expr[0])
|
||||
|
||||
# and add the assignment to our result
|
||||
ret += self.compile(assignment)
|
||||
|
||||
# The first argument is the object on which to call the method
|
||||
# So we translate (apply .foo args) to (.foo (get args 0))
|
||||
call.append(HyExpression(
|
||||
[HySymbol("get"), tempvar, HyInteger(0)]
|
||||
).replace(tempvar))
|
||||
|
||||
# We then pass the other arguments to the function
|
||||
expr[0] = HyExpression(
|
||||
[HySymbol("cut"), tempvar, HyInteger(1)]
|
||||
).replace(expr[0])
|
||||
|
||||
ret += self.compile(call)
|
||||
|
||||
if not isinstance(ret.expr, ast.Call):
|
||||
raise HyTypeError(
|
||||
fun, "compiling the application of `{}' didn't return a "
|
||||
"function call, but `{}'".format(fun, type(ret.expr).__name__)
|
||||
)
|
||||
if ret.expr.starargs or ret.expr.kwargs:
|
||||
raise HyTypeError(
|
||||
expr, "compiling the function application returned a function "
|
||||
"call with arguments"
|
||||
)
|
||||
|
||||
if expr:
|
||||
stargs = expr.pop(0)
|
||||
if stargs is not None:
|
||||
stargs = self.compile(stargs)
|
||||
if PY35:
|
||||
stargs_expr = stargs.force_expr
|
||||
ret.expr.args.append(
|
||||
ast.Starred(stargs_expr, ast.Load(),
|
||||
lineno=stargs_expr.lineno,
|
||||
col_offset=stargs_expr.col_offset)
|
||||
)
|
||||
else:
|
||||
ret.expr.starargs = stargs.force_expr
|
||||
ret = stargs + ret
|
||||
|
||||
if expr:
|
||||
kwargs = expr.pop(0)
|
||||
if isinstance(kwargs, HyDict):
|
||||
new_kwargs = []
|
||||
for k, v in kwargs.items():
|
||||
if isinstance(k, HySymbol):
|
||||
pass
|
||||
elif isinstance(k, HyString):
|
||||
k = HyString(hy_symbol_mangle(str_type(k))).replace(k)
|
||||
elif isinstance(k, HyKeyword):
|
||||
sym = hy_symbol_mangle(str_type(k)[2:])
|
||||
k = HyString(sym).replace(k)
|
||||
new_kwargs += [k, v]
|
||||
kwargs = HyDict(new_kwargs).replace(kwargs)
|
||||
|
||||
kwargs = self.compile(kwargs)
|
||||
if PY35:
|
||||
kwargs_expr = kwargs.force_expr
|
||||
ret.expr.keywords.append(
|
||||
ast.keyword(None, kwargs_expr,
|
||||
lineno=kwargs_expr.lineno,
|
||||
col_offset=kwargs_expr.col_offset)
|
||||
)
|
||||
else:
|
||||
ret.expr.kwargs = kwargs.force_expr
|
||||
ret = kwargs + ret
|
||||
|
||||
return ret
|
||||
|
||||
@builds("not")
|
||||
@builds("~")
|
||||
@checkargs(1)
|
||||
@ -2001,9 +1953,15 @@ class HyASTCompiler(object):
|
||||
return self._compile_keyword_call(expression)
|
||||
|
||||
if isinstance(fn, HySymbol):
|
||||
ret = self.compile_atom(fn, expression)
|
||||
if ret:
|
||||
return ret
|
||||
# First check if `fn` is a special form, unless it has an
|
||||
# `unpack_iterable` in it, since Python's operators (`+`,
|
||||
# etc.) can't unpack. An exception to this exception is that
|
||||
# tuple literals (`,`) can unpack.
|
||||
if fn == "," or not (
|
||||
any(is_unpack("iterable", x) for x in expression[1:])):
|
||||
ret = self.compile_atom(fn, expression)
|
||||
if ret:
|
||||
return ret
|
||||
|
||||
if fn.startswith("."):
|
||||
# (.split "test test") -> "test test".split()
|
||||
@ -2054,14 +2012,14 @@ class HyASTCompiler(object):
|
||||
else:
|
||||
with_kwargs = True
|
||||
|
||||
args, ret, kwargs = self._compile_collect(expression[1:],
|
||||
with_kwargs)
|
||||
args, ret, keywords, oldpy_starargs, oldpy_kwargs = self._compile_collect(
|
||||
expression[1:], with_kwargs, oldpy_unpack=True)
|
||||
|
||||
ret += ast.Call(func=func.expr,
|
||||
args=args,
|
||||
keywords=kwargs,
|
||||
starargs=None,
|
||||
kwargs=None,
|
||||
keywords=keywords,
|
||||
starargs=oldpy_starargs,
|
||||
kwargs=oldpy_kwargs,
|
||||
lineno=expression.start_line,
|
||||
col_offset=expression.start_column)
|
||||
|
||||
@ -2583,7 +2541,7 @@ class HyASTCompiler(object):
|
||||
|
||||
@builds(HyDict)
|
||||
def compile_dict(self, m):
|
||||
keyvalues, ret, _ = self._compile_collect(m)
|
||||
keyvalues, ret, _ = self._compile_collect(m, dict_display=True)
|
||||
|
||||
ret += ast.Dict(lineno=m.start_line,
|
||||
col_offset=m.start_column,
|
||||
|
@ -37,6 +37,10 @@
|
||||
(+ "~" (f (second x) q))
|
||||
(= (first x) 'unquote_splice)
|
||||
(+ "~@" (f (second x) q))
|
||||
(= (first x) 'unpack_iterable)
|
||||
(+ "#* " (f (second x) q))
|
||||
(= (first x) 'unpack_mapping)
|
||||
(+ "#** " (f (second x) q))
|
||||
; else
|
||||
(+ "(" (catted) ")"))
|
||||
(+ "(" (catted) ")"))
|
||||
|
@ -27,7 +27,7 @@
|
||||
(when (not (first active))
|
||||
(assoc active 0 True)
|
||||
(while (> (len accumulated) 0)
|
||||
(setv result (apply f (.pop accumulated))))
|
||||
(setv result (f #* (.pop accumulated))))
|
||||
(assoc active 0 False)
|
||||
result)))
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
(setv output None)
|
||||
(for [[i f] (.items (get self._fns self.f.__module__ self.f.__name__))]
|
||||
(when (.fn? self i args kwargs)
|
||||
(setv output (apply f args kwargs))
|
||||
(setv output (f #* args #** kwargs))
|
||||
(break)))
|
||||
(if output
|
||||
output
|
||||
@ -37,10 +37,10 @@
|
||||
|
||||
(defn multi-decorator [dispatch-fn]
|
||||
(setv inner (fn [&rest args &kwargs kwargs]
|
||||
(setv dispatch-key (apply dispatch-fn args kwargs))
|
||||
(setv dispatch-key (dispatch-fn #* args #** kwargs))
|
||||
(if (in dispatch-key inner.--multi--)
|
||||
(apply (get inner.--multi-- dispatch-key) args kwargs)
|
||||
(apply inner.--multi-default-- args kwargs))))
|
||||
((get inner.--multi-- dispatch-key) #* args #** kwargs)
|
||||
(inner.--multi-default-- #* args #** kwargs))))
|
||||
(setv inner.--multi-- {})
|
||||
(setv inner.--doc-- dispatch-fn.--doc--)
|
||||
(setv inner.--multi-default-- (fn [&rest args &kwargs kwargs] None))
|
||||
|
@ -10,7 +10,7 @@
|
||||
`(do
|
||||
(import [pycallgraph [PyCallGraph]]
|
||||
[pycallgraph.output [GraphvizOutput]])
|
||||
(with* [(apply PyCallGraph [] {"output" (GraphvizOutput)})]
|
||||
(with* [(PyCallGraph :output (GraphvizOutput)))]
|
||||
~@body)))
|
||||
|
||||
|
||||
@ -29,6 +29,6 @@
|
||||
(.disable ~g!hy-pr)
|
||||
(setv ~g!hy-s (StringIO))
|
||||
(setv ~g!hy-ps
|
||||
(.sort-stats (apply pstats.Stats [~g!hy-pr] {"stream" ~g!hy-s})))
|
||||
(.sort-stats (pstats.Stats ~g!hy-pr :stream ~g!hy-s)))
|
||||
(.print-stats ~g!hy-ps)
|
||||
(print (.getvalue ~g!hy-s))))
|
||||
|
@ -37,7 +37,7 @@
|
||||
first-f (next rfs)
|
||||
fs (tuple rfs))
|
||||
(fn [&rest args &kwargs kwargs]
|
||||
(setv res (apply first-f args kwargs))
|
||||
(setv res (first-f #* args #** kwargs))
|
||||
(for* [f fs]
|
||||
(setv res (f res)))
|
||||
res))))
|
||||
@ -45,7 +45,7 @@
|
||||
(defn complement [f]
|
||||
"Create a function that reverses truth value of another function"
|
||||
(fn [&rest args &kwargs kwargs]
|
||||
(not (apply f args kwargs))))
|
||||
(not (f #* args #** kwargs))))
|
||||
|
||||
(defn cons [a b]
|
||||
"Return a fresh cons cell with car = a and cdr = b"
|
||||
@ -160,8 +160,8 @@
|
||||
(defn drop-last [n coll]
|
||||
"Return a sequence of all but the last n elements in coll."
|
||||
(setv iters (tee coll))
|
||||
(map first (apply zip [(get iters 0)
|
||||
(drop n (get iters 1))])))
|
||||
(map first (zip #* [(get iters 0)
|
||||
(drop n (get iters 1))])))
|
||||
|
||||
(defn empty? [coll]
|
||||
"Return True if `coll` is empty"
|
||||
@ -250,7 +250,7 @@
|
||||
|
||||
(defn interleave [&rest seqs]
|
||||
"Return an iterable of the first item in each of seqs, then the second etc."
|
||||
(chain.from-iterable (apply zip seqs)))
|
||||
(chain.from-iterable (zip #* seqs)))
|
||||
|
||||
(defn interpose [item seq]
|
||||
"Return an iterable of the elements of seq separated by item"
|
||||
@ -275,7 +275,7 @@
|
||||
set of arguments and collects the results into a list."
|
||||
(setv fs (cons f fs))
|
||||
(fn [&rest args &kwargs kwargs]
|
||||
(list-comp (apply f args kwargs) [f fs])))
|
||||
(list-comp (f #* args #** kwargs) [f fs])))
|
||||
|
||||
(defn last [coll]
|
||||
"Return last item from `coll`"
|
||||
@ -285,7 +285,7 @@
|
||||
"Return a dotted list construed from the elements of the argument"
|
||||
(if (not tl)
|
||||
hd
|
||||
(cons hd (apply list* tl))))
|
||||
(cons hd (list* #* tl))))
|
||||
|
||||
(defn macroexpand [form]
|
||||
"Return the full macro expansion of form"
|
||||
@ -350,8 +350,8 @@
|
||||
slices (genexpr (islice (get coll-clones start) start None step)
|
||||
[start (range n)]))
|
||||
(if (is fillvalue -sentinel)
|
||||
(apply zip slices)
|
||||
(apply zip-longest slices {"fillvalue" fillvalue})))
|
||||
(zip #* slices)
|
||||
(zip-longest #* slices :fillvalue fillvalue)))
|
||||
|
||||
(defn pos? [n]
|
||||
"Return true if n is > 0"
|
||||
|
@ -207,7 +207,7 @@
|
||||
(setv retval (gensym))
|
||||
`(when (= --name-- "__main__")
|
||||
(import sys)
|
||||
(setv ~retval (apply (fn [~@args] ~@body) sys.argv))
|
||||
(setv ~retval ((fn [~@args] ~@body) #* sys.argv))
|
||||
(if (integer? ~retval)
|
||||
(sys.exit ~retval))))
|
||||
|
||||
|
@ -26,6 +26,7 @@ lg.add('QUASIQUOTE', r'`%s' % end_quote)
|
||||
lg.add('UNQUOTESPLICE', r'~@%s' % end_quote)
|
||||
lg.add('UNQUOTE', r'~%s' % end_quote)
|
||||
lg.add('HASHBANG', r'#!.*[^\r\n]')
|
||||
lg.add('HASHSTARS', r'#\*+')
|
||||
lg.add('HASHOTHER', r'#%s' % identifier)
|
||||
|
||||
# A regexp which matches incomplete strings, used to support
|
||||
|
@ -197,6 +197,22 @@ def term_unquote_splice(p):
|
||||
return HyExpression([HySymbol("unquote_splice"), p[1]])
|
||||
|
||||
|
||||
@pg.production("term : HASHSTARS term")
|
||||
@set_quote_boundaries
|
||||
def term_hashstars(p):
|
||||
n_stars = len(p[0].getstr()[1:])
|
||||
if n_stars == 1:
|
||||
sym = "unpack_iterable"
|
||||
elif n_stars == 2:
|
||||
sym = "unpack_mapping"
|
||||
else:
|
||||
raise LexException(
|
||||
"Too many stars in `#*` construct (if you want to unpack a symbol "
|
||||
"beginning with a star, separate it with whitespace)",
|
||||
p[0].source_pos.lineno, p[0].source_pos.colno)
|
||||
return HyExpression([HySymbol(sym), p[1]])
|
||||
|
||||
|
||||
@pg.production("term : HASHOTHER term")
|
||||
@set_quote_boundaries
|
||||
def hash_other(p):
|
||||
|
@ -28,8 +28,8 @@
|
||||
[1 2 3] (, 1 2 3) #{1 2 3} (frozenset #{1 2 3})
|
||||
'[1 2 3] '(, 1 2 3) '#{1 2 3} '(frozenset #{1 2 3})
|
||||
{"a" 1 "b" 2 "a" 3} '{"a" 1 "b" 2 "a" 3}
|
||||
[1 [2 3] (, 4 (, 'mysymbol :mykeyword)) {"a" b"hello"}]
|
||||
'[1 [2 3] (, 4 (, mysymbol :mykeyword)) {"a" b"hello"}]])
|
||||
[1 [2 3] (, 4 (, 'mysymbol :mykeyword)) {"a" b"hello"} '(f #* a #** b)]
|
||||
'[1 [2 3] (, 4 (, mysymbol :mykeyword)) {"a" b"hello"} (f #* a #** b)]])
|
||||
(for [original-val values]
|
||||
(setv evaled (eval (read-str (hy-repr original-val))))
|
||||
(assert (= evaled original-val))
|
||||
@ -59,7 +59,8 @@
|
||||
"{1 20}"
|
||||
"'{1 10 1 20}"
|
||||
"'asymbol"
|
||||
":akeyword"])
|
||||
":akeyword"
|
||||
"'(f #* args #** kwargs)"])
|
||||
(for [original-str strs]
|
||||
(setv rep (hy-repr (eval (read-str original-str))))
|
||||
(assert (= rep original-str))))
|
||||
|
@ -95,9 +95,9 @@
|
||||
([&optional [a "nop"] [b "p"]] (+ a b)))
|
||||
|
||||
(assert (= (fun 1) 1))
|
||||
(assert (= (apply fun [] {"a" "t"}) "t"))
|
||||
(assert (= (apply fun ["hello "] {"b" "world"}) "hello world"))
|
||||
(assert (= (apply fun [] {"a" "hello " "b" "world"}) "hello world")))
|
||||
(assert (= (fun :a "t") "t"))
|
||||
(assert (= (fun "hello " :b "world") "hello world"))
|
||||
(assert (= (fun :a "hello " :b "world") "hello world")))
|
||||
|
||||
|
||||
(defn test-docs []
|
||||
|
@ -369,38 +369,26 @@
|
||||
(assert (is (isfile ".") False)))
|
||||
|
||||
|
||||
(defn test-star-unpacking []
|
||||
; Python 3-only forms of unpacking are in py3_only_tests.hy
|
||||
(setv l [1 2 3])
|
||||
(setv d {"a" "x" "b" "y"})
|
||||
(defn fun [&optional x1 x2 x3 x4 a b c] [x1 x2 x3 x4 a b c])
|
||||
(assert (= (fun 5 #* l) [5 1 2 3 None None None]))
|
||||
(assert (= (+ #* l) 6))
|
||||
(assert (= (fun 5 #** d) [5 None None None "x" "y" None]))
|
||||
(assert (= (fun 5 #* l #** d) [5 1 2 3 "x" "y" None])))
|
||||
|
||||
|
||||
|
||||
(defn test-kwargs []
|
||||
"NATIVE: test kwargs things."
|
||||
(assert (= (apply kwtest [] {"one" "two"}) {"one" "two"}))
|
||||
(assert (= (kwtest :one "two") {"one" "two"}))
|
||||
(setv mydict {"one" "three"})
|
||||
(assert (= (apply kwtest [] mydict) mydict))
|
||||
(assert (= (apply kwtest [] ((fn [] {"one" "two"}))) {"one" "two"})))
|
||||
(assert (= (kwtest #** mydict) mydict))
|
||||
(assert (= (kwtest #** ((fn [] {"one" "two"}))) {"one" "two"})))
|
||||
|
||||
|
||||
(defn test-apply []
|
||||
"NATIVE: test working with args and functions"
|
||||
(defn sumit [a b c] (+ a b c))
|
||||
(assert (= (apply sumit [1] {"b" 2 "c" 3}) 6))
|
||||
(assert (= (apply sumit [1 2 2]) 5))
|
||||
(assert (= (apply sumit [] {"a" 1 "b" 1 "c" 2}) 4))
|
||||
(assert (= (apply sumit ((fn [] [1 1])) {"c" 1}) 3))
|
||||
(defn noargs [] [1 2 3])
|
||||
(assert (= (apply noargs) [1 2 3]))
|
||||
(defn sumit-mangle [an-a a-b a-c a-d] (+ an-a a-b a-c a-d))
|
||||
(def Z "a_d")
|
||||
(assert (= (apply sumit-mangle [] {"an-a" 1 :a-b 2 'a-c 3 Z 4}) 10)))
|
||||
|
||||
|
||||
(defn test-apply-with-methods []
|
||||
"NATIVE: test apply to call a method"
|
||||
(setv str "foo {bar}")
|
||||
(assert (= (apply .format [str] {"bar" "baz"})
|
||||
(apply .format ["foo {0}" "baz"])
|
||||
"foo baz"))
|
||||
(setv lst ["a {0} {1} {foo} {bar}" "b" "c"])
|
||||
(assert (= (apply .format lst {"foo" "d" "bar" "e"})
|
||||
"a b c d e")))
|
||||
|
||||
|
||||
(defn test-dotted []
|
||||
"NATIVE: test dotted invocation"
|
||||
@ -418,20 +406,20 @@
|
||||
(assert (= (.meth m) "meth"))
|
||||
(assert (= (.meth m "foo" "bar") "meth foo bar"))
|
||||
(assert (= (.meth :b "1" :a "2" m "foo" "bar") "meth foo bar 2 1"))
|
||||
(assert (= (apply .meth [m "foo" "bar"]) "meth foo bar"))
|
||||
(assert (= (.meth m #* ["foo" "bar"]) "meth foo bar"))
|
||||
|
||||
(setv x.p m)
|
||||
(assert (= (.p.meth x) "meth"))
|
||||
(assert (= (.p.meth x "foo" "bar") "meth foo bar"))
|
||||
(assert (= (.p.meth :b "1" :a "2" x "foo" "bar") "meth foo bar 2 1"))
|
||||
(assert (= (apply .p.meth [x "foo" "bar"]) "meth foo bar"))
|
||||
(assert (= (.p.meth x #* ["foo" "bar"]) "meth foo bar"))
|
||||
|
||||
(setv x.a (X))
|
||||
(setv x.a.b m)
|
||||
(assert (= (.a.b.meth x) "meth"))
|
||||
(assert (= (.a.b.meth x "foo" "bar") "meth foo bar"))
|
||||
(assert (= (.a.b.meth :b "1" :a "2" x "foo" "bar") "meth foo bar 2 1"))
|
||||
(assert (= (apply .a.b.meth [x "foo" "bar"]) "meth foo bar"))
|
||||
(assert (= (.a.b.meth x #* ["foo" "bar"]) "meth foo bar"))
|
||||
|
||||
(assert (is (.isdigit :foo) False)))
|
||||
|
||||
@ -1173,8 +1161,8 @@
|
||||
"NATIVE: test &key function arguments"
|
||||
(defn foo [&key {"a" None "b" 1}] [a b])
|
||||
(assert (= (foo) [None 1]))
|
||||
(assert (= (apply foo [] {"a" 2}) [2 1]))
|
||||
(assert (= (apply foo [] {"b" 42}) [None 42])))
|
||||
(assert (= (foo :a 2) [2 1]))
|
||||
(assert (= (foo :b 42) [None 42])))
|
||||
|
||||
|
||||
(defn test-optional-arguments []
|
||||
|
@ -84,12 +84,12 @@
|
||||
"NATIVE: test macro calling a plain function"
|
||||
(assert (= 3 (bar 1 2))))
|
||||
|
||||
(defn test-optional-and-apply-in-macro []
|
||||
(defn test-optional-and-unpacking-in-macro []
|
||||
; https://github.com/hylang/hy/issues/1154
|
||||
(defn f [&rest args]
|
||||
(+ "f:" (repr args)))
|
||||
(defmacro mac [&optional x]
|
||||
`(apply f [~x]))
|
||||
`(f #* [~x]))
|
||||
(assert (= (mac) "f:(None,)")))
|
||||
|
||||
(defn test-midtree-yield []
|
||||
|
@ -288,8 +288,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])))
|
||||
|
||||
#@(pytest.mark.xfail
|
||||
(defn test-apply-op []
|
||||
; https://github.com/hylang/hy/issues/647
|
||||
(assert (= (eval '(apply + ["a" "b" "c"])) "abc"))))
|
||||
|
26
tests/native_tests/py35_only_tests.hy
Normal file
26
tests/native_tests/py35_only_tests.hy
Normal file
@ -0,0 +1,26 @@
|
||||
;; Copyright 2017 the authors.
|
||||
;; This file is part of Hy, which is free software licensed under the Expat
|
||||
;; license. See the LICENSE.
|
||||
|
||||
;; Tests where the emitted code relies on Python ≥3.5.
|
||||
;; conftest.py skips this file when running on Python <3.5.
|
||||
|
||||
|
||||
(defn test-unpacking-pep448-1star []
|
||||
(setv l [1 2 3])
|
||||
(setv p [4 5])
|
||||
(assert (= ["a" #*l "b" #*p #*l] ["a" 1 2 3 "b" 4 5 1 2 3]))
|
||||
(assert (= (, "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
|
||||
(assert (= #{"a" #*l "b" #*p #*l} #{"a" "b" 1 2 3 4 5}))
|
||||
(defn f [&rest args] args)
|
||||
(assert (= (f "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
|
||||
(assert (= (+ #*l #*p) 15))
|
||||
(assert (= (and #*l) 3)))
|
||||
|
||||
|
||||
(defn test-unpacking-pep448-2star []
|
||||
(setv d1 {"a" 1 "b" 2})
|
||||
(setv d2 {"c" 3 "d" 4})
|
||||
(assert (= {1 "x" #**d1 #**d2 2 "y"} {"a" 1 "b" 2 "c" 3 "d" 4 1 "x" 2 "y"}))
|
||||
(defn fun [&optional a b c d e f] [a b c d e f])
|
||||
(assert (= (fun #**d1 :e "eee" #**d2) [1 2 3 4 "eee" None])))
|
@ -16,15 +16,15 @@
|
||||
"NATIVE: test keyword-only arguments"
|
||||
;; keyword-only with default works
|
||||
(defn kwonly-foo-default-false [&kwonly [foo False]] foo)
|
||||
(assert (= (apply kwonly-foo-default-false) False))
|
||||
(assert (= (apply kwonly-foo-default-false [] {"foo" True}) True))
|
||||
(assert (= (kwonly-foo-default-false) False))
|
||||
(assert (= (kwonly-foo-default-false :foo True) True))
|
||||
;; keyword-only without default ...
|
||||
(defn kwonly-foo-no-default [&kwonly foo] foo)
|
||||
(setv attempt-to-omit-default (try
|
||||
(kwonly-foo-no-default)
|
||||
(except [e [Exception]] e)))
|
||||
;; works
|
||||
(assert (= (apply kwonly-foo-no-default [] {"foo" "quux"}) "quux"))
|
||||
(assert (= (kwonly-foo-no-default :foo "quux") "quux"))
|
||||
;; raises TypeError with appropriate message if not supplied
|
||||
(assert (isinstance attempt-to-omit-default TypeError))
|
||||
(assert (in "missing 1 required keyword-only argument: 'foo'"
|
||||
@ -32,11 +32,20 @@
|
||||
;; keyword-only with other arg types works
|
||||
(defn function-of-various-args [a b &rest args &kwonly foo &kwargs kwargs]
|
||||
(, a b args foo kwargs))
|
||||
(assert (= (apply function-of-various-args
|
||||
[1 2 3 4] {"foo" 5 "bar" 6 "quux" 7})
|
||||
(assert (= (function-of-various-args 1 2 3 4 :foo 5 :bar 6 :quux 7)
|
||||
(, 1 2 (, 3 4) 5 {"bar" 6 "quux" 7}))))
|
||||
|
||||
|
||||
(defn test-extended-unpacking-1star-lvalues []
|
||||
(setv [x #*y] [1 2 3 4])
|
||||
(assert (= x 1))
|
||||
(assert (= y [2 3 4]))
|
||||
(setv [a #*b c] "ghijklmno")
|
||||
(assert (= a "g"))
|
||||
(assert (= b (list "hijklmn")))
|
||||
(assert (= c "o")))
|
||||
|
||||
|
||||
(defn test-yield-from []
|
||||
"NATIVE: testing yield from"
|
||||
(defn yield-from-test []
|
||||
|
@ -100,9 +100,8 @@
|
||||
"Increments each argument passed to the decorated function."
|
||||
((wraps func)
|
||||
(fn [&rest args &kwargs kwargs]
|
||||
(apply func
|
||||
(map inc args)
|
||||
(dict-comp k (inc v) [[k v] (.items kwargs)])))))
|
||||
(func #* (map inc args)
|
||||
#** (dict-comp k (inc v) [[k v] (.items kwargs)])))))
|
||||
|
||||
#@(increment-arguments
|
||||
(defn foo [&rest args &kwargs kwargs]
|
||||
|
@ -7,7 +7,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
from hy._compat import PY3
|
||||
from hy._compat import PY3, PY35
|
||||
from hy.importer import get_bytecode_path
|
||||
import pytest
|
||||
|
||||
@ -210,12 +210,13 @@ def test_hy2py():
|
||||
if f.endswith(".hy"):
|
||||
if f == "py3_only_tests.hy" and not PY3:
|
||||
continue
|
||||
else:
|
||||
i += 1
|
||||
output, err = run_cmd("hy2py -s -a " +
|
||||
os.path.join(dirpath, f))
|
||||
assert len(output) > 1, f
|
||||
assert len(err) == 0, f
|
||||
if f == "py35_only_tests.hy" and not PY35:
|
||||
continue
|
||||
i += 1
|
||||
output, err = run_cmd("hy2py -s -a " +
|
||||
os.path.join(dirpath, f))
|
||||
assert len(output) > 1, f
|
||||
assert len(err) == 0, f
|
||||
assert i
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user