Merge pull request #1325 from Kodiologist/unpacking

Replace `apply` with unpacking operators
This commit is contained in:
Ryan Gonzalez 2017-07-20 09:49:17 -05:00 committed by GitHub
commit 35f7dd36bb
23 changed files with 260 additions and 260 deletions

2
NEWS
View File

@ -2,6 +2,8 @@ Changes from 0.13.0
[ Language Changes ] [ Language Changes ]
* `yield-from` is no longer supported under Python 2 * `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 * Single-character "sharp macros" changed to "tag macros", which can have
longer names longer names
* Periods are no longer allowed in keywords * Periods are no longer allowed in keywords

View File

@ -1,12 +1,13 @@
import _pytest import _pytest
import hy import hy
from hy._compat import PY3 from hy._compat import PY3, PY35
def pytest_collect_file(parent, path): def pytest_collect_file(parent, path):
if (path.ext == ".hy" if (path.ext == ".hy"
and "/tests/native_tests/" in path.dirname + "/" and "/tests/native_tests/" in path.dirname + "/"
and path.basename != "__init__.hy" 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) m = _pytest.python.pytest_pycollect_makemodule(path, parent)
# Spoof the module name to avoid hitting an assertion in pytest. # Spoof the module name to avoid hitting an assertion in pytest.
m.name = m.name[:-len(".hy")] + ".py" m.name = m.name[:-len(".hy")] + ".py"

View File

@ -154,41 +154,6 @@ it appends it as the last argument. The following code demonstrates this:
5 10 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 and
--- ---
@ -596,8 +561,8 @@ Parameters may have the following keywords in front of them:
parameter_1 1 parameter_1 1
parameter_2 2 parameter_2 2
; to avoid the mangling of '-' to '_', use apply: ; to avoid the mangling of '-' to '_', use unpacking:
=> (apply print-parameters [] {"parameter-1" 1 "parameter-2" 2}) => (print-parameters #** {"parameter-1" 1 "parameter-2" 2})
parameter-1 1 parameter-1 1
parameter-2 2 parameter-2 2
@ -634,19 +599,19 @@ Parameters may have the following keywords in front of them:
.. code-block:: clj .. code-block:: clj
=> (defn compare [a b &kwonly keyfn [reverse false]] => (defn compare [a b &kwonly keyfn [reverse False]]
... (setv result (keyfn a b)) ... (setv result (keyfn a b))
... (if (not reverse) ... (if (not reverse)
... result ... result
... (- result))) ... (- result)))
=> (apply compare ["lisp" "python"] => (compare "lisp" "python"
... {"keyfn" (fn [x y] ... :keyfn (fn [x y]
... (reduce - (map (fn [s] (ord (first s))) [x y])))}) ... (reduce - (map (fn [s] (ord (first s))) [x y]))))
-4 -4
=> (apply compare ["lisp" "python"] => (compare "lisp" "python"
... {"keyfn" (fn [x y] ... :keyfn (fn [x y]
... (reduce - (map (fn [s] (ord (first s))) [x y]))) ... (reduce - (map (fn [s] (ord (first s))) [x y])))
... "reverse" True}) ... :reverse True)
4 4
.. code-block:: python .. code-block:: python
@ -1576,6 +1541,49 @@ the given conditional is ``False``. The following shows the expansion of this ma
(do statement)) (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 unquote
------- -------

View File

@ -1216,9 +1216,9 @@ if *from-file* ends before a complete expression can be parsed.
=> (import io) => (import io)
=> (def buffer (io.StringIO "(+ 2 2)\n(- 2 1)")) => (def buffer (io.StringIO "(+ 2 2)\n(- 2 1)"))
=> (eval (apply read [] {"from_file" buffer})) => (eval (read :from_file buffer))
4 4
=> (eval (apply read [] {"from_file" buffer})) => (eval (read :from_file buffer))
1 1
=> ; assuming "example.hy" contains: => ; assuming "example.hy" contains:

View File

@ -423,8 +423,7 @@ The same thing in Hy::
=> (optional-arg 1 2 3 4) => (optional-arg 1 2 3 4)
[1 2 3 4] [1 2 3 4]
If you're running a version of Hy past 0.10.1 (eg, git master), You can call keyword arguments like this::
there's also a nice new keyword argument syntax::
=> (optional-arg :keyword1 1 => (optional-arg :keyword1 1
... :pos2 2 ... :pos2 2
@ -432,21 +431,13 @@ there's also a nice new keyword argument syntax::
... :keyword2 4) ... :keyword2 4)
[3, 2, 1, 4] [3, 2, 1, 4]
Otherwise, you can always use `apply`. But what's `apply`? You can unpack arguments with the syntax ``#* args`` and ``#** kwargs``,
similar to `*args` and `**kwargs` in Python::
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`::
=> (setv args [1 2]) => (setv args [1 2])
=> (setv kwargs {"keyword2" 3 => (setv kwargs {"keyword2" 3
... "keyword1" 4}) ... "keyword1" 4})
=> (apply optional-arg args kwargs) => (optional-arg #* args #** kwargs)
[1, 2, 4, 3] [1, 2, 4, 3]
There's also a dictionary-style keyword arguments construction that 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 The difference here is that since it's a dictionary, you can't rely on
any specific ordering to the arguments. 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): def some_func(foo, bar, *args, **kwargs):
import pprint import pprint

View File

@ -359,6 +359,13 @@ def checkargs(exact=None, min=None, max=None, even=None, multiple=None):
return _dec 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): class HyASTCompiler(object):
def __init__(self, module_name): def __init__(self, module_name):
@ -441,7 +448,8 @@ class HyASTCompiler(object):
raise HyCompileError(Exception("Unknown type: `%s'" % _type)) 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. """Collect the expression contexts from a list of compiled expression.
This returns a list of the expression contexts, and the sum of the This returns a list of the expression contexts, and the sum of the
@ -451,10 +459,39 @@ class HyASTCompiler(object):
compiled_exprs = [] compiled_exprs = []
ret = Result() ret = Result()
keywords = [] keywords = []
oldpy_starargs = None
oldpy_kwargs = None
exprs_iter = iter(exprs) exprs_iter = iter(exprs)
for expr in exprs_iter: 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: try:
value = next(exprs_iter) value = next(exprs_iter)
except StopIteration: except StopIteration:
@ -474,10 +511,14 @@ class HyASTCompiler(object):
value=compiled_value.force_expr, value=compiled_value.force_expr,
lineno=expr.start_line, lineno=expr.start_line,
col_offset=expr.start_column)) col_offset=expr.start_column))
else: else:
ret += self.compile(expr) ret += self.compile(expr)
compiled_exprs.append(ret.force_expr) compiled_exprs.append(ret.force_expr)
if oldpy_unpack:
return compiled_exprs, ret, keywords, oldpy_starargs, oldpy_kwargs
else:
return compiled_exprs, ret, keywords return compiled_exprs, ret, keywords
def _compile_branch(self, exprs): def _compile_branch(self, exprs):
@ -610,6 +651,9 @@ class HyASTCompiler(object):
new_name = ast.Subscript(value=name.value, slice=name.slice) new_name = ast.Subscript(value=name.value, slice=name.slice)
elif isinstance(name, ast.Attribute): elif isinstance(name, ast.Attribute):
new_name = ast.Attribute(value=name.value, attr=name.attr) 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: else:
raise HyTypeError(expr, raise HyTypeError(expr,
"Can't assign or delete a %s" % "Can't assign or delete a %s" %
@ -717,6 +761,23 @@ class HyASTCompiler(object):
raise HyTypeError(expr, raise HyTypeError(expr,
"`%s' can't be used at the top-level" % expr[0]) "`%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") @builds("do")
def compile_do(self, expression): def compile_do(self, expression):
expression.pop(0) expression.pop(0)
@ -1526,115 +1587,6 @@ class HyASTCompiler(object):
generators=expr.generators) generators=expr.generators)
return ret 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("not")
@builds("~") @builds("~")
@checkargs(1) @checkargs(1)
@ -2001,6 +1953,12 @@ class HyASTCompiler(object):
return self._compile_keyword_call(expression) return self._compile_keyword_call(expression)
if isinstance(fn, HySymbol): if isinstance(fn, HySymbol):
# 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) ret = self.compile_atom(fn, expression)
if ret: if ret:
return ret return ret
@ -2054,14 +2012,14 @@ class HyASTCompiler(object):
else: else:
with_kwargs = True with_kwargs = True
args, ret, kwargs = self._compile_collect(expression[1:], args, ret, keywords, oldpy_starargs, oldpy_kwargs = self._compile_collect(
with_kwargs) expression[1:], with_kwargs, oldpy_unpack=True)
ret += ast.Call(func=func.expr, ret += ast.Call(func=func.expr,
args=args, args=args,
keywords=kwargs, keywords=keywords,
starargs=None, starargs=oldpy_starargs,
kwargs=None, kwargs=oldpy_kwargs,
lineno=expression.start_line, lineno=expression.start_line,
col_offset=expression.start_column) col_offset=expression.start_column)
@ -2583,7 +2541,7 @@ class HyASTCompiler(object):
@builds(HyDict) @builds(HyDict)
def compile_dict(self, m): 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, ret += ast.Dict(lineno=m.start_line,
col_offset=m.start_column, col_offset=m.start_column,

View File

@ -37,6 +37,10 @@
(+ "~" (f (second x) q)) (+ "~" (f (second x) q))
(= (first x) 'unquote_splice) (= (first x) 'unquote_splice)
(+ "~@" (f (second x) q)) (+ "~@" (f (second x) q))
(= (first x) 'unpack_iterable)
(+ "#* " (f (second x) q))
(= (first x) 'unpack_mapping)
(+ "#** " (f (second x) q))
; else ; else
(+ "(" (catted) ")")) (+ "(" (catted) ")"))
(+ "(" (catted) ")")) (+ "(" (catted) ")"))

View File

@ -27,7 +27,7 @@
(when (not (first active)) (when (not (first active))
(assoc active 0 True) (assoc active 0 True)
(while (> (len accumulated) 0) (while (> (len accumulated) 0)
(setv result (apply f (.pop accumulated)))) (setv result (f #* (.pop accumulated))))
(assoc active 0 False) (assoc active 0 False)
result))) result)))

View File

@ -29,7 +29,7 @@
(setv output None) (setv output None)
(for [[i f] (.items (get self._fns self.f.__module__ self.f.__name__))] (for [[i f] (.items (get self._fns self.f.__module__ self.f.__name__))]
(when (.fn? self i args kwargs) (when (.fn? self i args kwargs)
(setv output (apply f args kwargs)) (setv output (f #* args #** kwargs))
(break))) (break)))
(if output (if output
output output
@ -37,10 +37,10 @@
(defn multi-decorator [dispatch-fn] (defn multi-decorator [dispatch-fn]
(setv inner (fn [&rest args &kwargs kwargs] (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--) (if (in dispatch-key inner.--multi--)
(apply (get inner.--multi-- dispatch-key) args kwargs) ((get inner.--multi-- dispatch-key) #* args #** kwargs)
(apply inner.--multi-default-- args kwargs)))) (inner.--multi-default-- #* args #** kwargs))))
(setv inner.--multi-- {}) (setv inner.--multi-- {})
(setv inner.--doc-- dispatch-fn.--doc--) (setv inner.--doc-- dispatch-fn.--doc--)
(setv inner.--multi-default-- (fn [&rest args &kwargs kwargs] None)) (setv inner.--multi-default-- (fn [&rest args &kwargs kwargs] None))

View File

@ -10,7 +10,7 @@
`(do `(do
(import [pycallgraph [PyCallGraph]] (import [pycallgraph [PyCallGraph]]
[pycallgraph.output [GraphvizOutput]]) [pycallgraph.output [GraphvizOutput]])
(with* [(apply PyCallGraph [] {"output" (GraphvizOutput)})] (with* [(PyCallGraph :output (GraphvizOutput)))]
~@body))) ~@body)))
@ -29,6 +29,6 @@
(.disable ~g!hy-pr) (.disable ~g!hy-pr)
(setv ~g!hy-s (StringIO)) (setv ~g!hy-s (StringIO))
(setv ~g!hy-ps (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-stats ~g!hy-ps)
(print (.getvalue ~g!hy-s)))) (print (.getvalue ~g!hy-s))))

View File

@ -37,7 +37,7 @@
first-f (next rfs) first-f (next rfs)
fs (tuple rfs)) fs (tuple rfs))
(fn [&rest args &kwargs kwargs] (fn [&rest args &kwargs kwargs]
(setv res (apply first-f args kwargs)) (setv res (first-f #* args #** kwargs))
(for* [f fs] (for* [f fs]
(setv res (f res))) (setv res (f res)))
res)))) res))))
@ -45,7 +45,7 @@
(defn complement [f] (defn complement [f]
"Create a function that reverses truth value of another function" "Create a function that reverses truth value of another function"
(fn [&rest args &kwargs kwargs] (fn [&rest args &kwargs kwargs]
(not (apply f args kwargs)))) (not (f #* args #** kwargs))))
(defn cons [a b] (defn cons [a b]
"Return a fresh cons cell with car = a and cdr = b" "Return a fresh cons cell with car = a and cdr = b"
@ -160,7 +160,7 @@
(defn drop-last [n coll] (defn drop-last [n coll]
"Return a sequence of all but the last n elements in coll." "Return a sequence of all but the last n elements in coll."
(setv iters (tee coll)) (setv iters (tee coll))
(map first (apply zip [(get iters 0) (map first (zip #* [(get iters 0)
(drop n (get iters 1))]))) (drop n (get iters 1))])))
(defn empty? [coll] (defn empty? [coll]
@ -250,7 +250,7 @@
(defn interleave [&rest seqs] (defn interleave [&rest seqs]
"Return an iterable of the first item in each of seqs, then the second etc." "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] (defn interpose [item seq]
"Return an iterable of the elements of seq separated by item" "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." set of arguments and collects the results into a list."
(setv fs (cons f fs)) (setv fs (cons f fs))
(fn [&rest args &kwargs kwargs] (fn [&rest args &kwargs kwargs]
(list-comp (apply f args kwargs) [f fs]))) (list-comp (f #* args #** kwargs) [f fs])))
(defn last [coll] (defn last [coll]
"Return last item from `coll`" "Return last item from `coll`"
@ -285,7 +285,7 @@
"Return a dotted list construed from the elements of the argument" "Return a dotted list construed from the elements of the argument"
(if (not tl) (if (not tl)
hd hd
(cons hd (apply list* tl)))) (cons hd (list* #* tl))))
(defn macroexpand [form] (defn macroexpand [form]
"Return the full macro expansion of form" "Return the full macro expansion of form"
@ -350,8 +350,8 @@
slices (genexpr (islice (get coll-clones start) start None step) slices (genexpr (islice (get coll-clones start) start None step)
[start (range n)])) [start (range n)]))
(if (is fillvalue -sentinel) (if (is fillvalue -sentinel)
(apply zip slices) (zip #* slices)
(apply zip-longest slices {"fillvalue" fillvalue}))) (zip-longest #* slices :fillvalue fillvalue)))
(defn pos? [n] (defn pos? [n]
"Return true if n is > 0" "Return true if n is > 0"

View File

@ -207,7 +207,7 @@
(setv retval (gensym)) (setv retval (gensym))
`(when (= --name-- "__main__") `(when (= --name-- "__main__")
(import sys) (import sys)
(setv ~retval (apply (fn [~@args] ~@body) sys.argv)) (setv ~retval ((fn [~@args] ~@body) #* sys.argv))
(if (integer? ~retval) (if (integer? ~retval)
(sys.exit ~retval)))) (sys.exit ~retval))))

View File

@ -26,6 +26,7 @@ lg.add('QUASIQUOTE', r'`%s' % end_quote)
lg.add('UNQUOTESPLICE', r'~@%s' % end_quote) lg.add('UNQUOTESPLICE', r'~@%s' % end_quote)
lg.add('UNQUOTE', r'~%s' % end_quote) lg.add('UNQUOTE', r'~%s' % end_quote)
lg.add('HASHBANG', r'#!.*[^\r\n]') lg.add('HASHBANG', r'#!.*[^\r\n]')
lg.add('HASHSTARS', r'#\*+')
lg.add('HASHOTHER', r'#%s' % identifier) lg.add('HASHOTHER', r'#%s' % identifier)
# A regexp which matches incomplete strings, used to support # A regexp which matches incomplete strings, used to support

View File

@ -197,6 +197,22 @@ def term_unquote_splice(p):
return HyExpression([HySymbol("unquote_splice"), p[1]]) 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") @pg.production("term : HASHOTHER term")
@set_quote_boundaries @set_quote_boundaries
def hash_other(p): def hash_other(p):

View File

@ -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})
'[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} {"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"} '(f #* a #** b)]
'[1 [2 3] (, 4 (, mysymbol :mykeyword)) {"a" b"hello"}]]) '[1 [2 3] (, 4 (, mysymbol :mykeyword)) {"a" b"hello"} (f #* a #** b)]])
(for [original-val values] (for [original-val values]
(setv evaled (eval (read-str (hy-repr original-val)))) (setv evaled (eval (read-str (hy-repr original-val))))
(assert (= evaled original-val)) (assert (= evaled original-val))
@ -59,7 +59,8 @@
"{1 20}" "{1 20}"
"'{1 10 1 20}" "'{1 10 1 20}"
"'asymbol" "'asymbol"
":akeyword"]) ":akeyword"
"'(f #* args #** kwargs)"])
(for [original-str strs] (for [original-str strs]
(setv rep (hy-repr (eval (read-str original-str)))) (setv rep (hy-repr (eval (read-str original-str))))
(assert (= rep original-str)))) (assert (= rep original-str))))

View File

@ -95,9 +95,9 @@
([&optional [a "nop"] [b "p"]] (+ a b))) ([&optional [a "nop"] [b "p"]] (+ a b)))
(assert (= (fun 1) 1)) (assert (= (fun 1) 1))
(assert (= (apply fun [] {"a" "t"}) "t")) (assert (= (fun :a "t") "t"))
(assert (= (apply fun ["hello "] {"b" "world"}) "hello world")) (assert (= (fun "hello " :b "world") "hello world"))
(assert (= (apply fun [] {"a" "hello " "b" "world"}) "hello world"))) (assert (= (fun :a "hello " :b "world") "hello world")))
(defn test-docs [] (defn test-docs []

View File

@ -369,38 +369,26 @@
(assert (is (isfile ".") False))) (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 [] (defn test-kwargs []
"NATIVE: test kwargs things." "NATIVE: test kwargs things."
(assert (= (apply kwtest [] {"one" "two"}) {"one" "two"})) (assert (= (kwtest :one "two") {"one" "two"}))
(setv mydict {"one" "three"}) (setv mydict {"one" "three"})
(assert (= (apply kwtest [] mydict) mydict)) (assert (= (kwtest #** mydict) mydict))
(assert (= (apply kwtest [] ((fn [] {"one" "two"}))) {"one" "two"}))) (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 [] (defn test-dotted []
"NATIVE: test dotted invocation" "NATIVE: test dotted invocation"
@ -418,20 +406,20 @@
(assert (= (.meth m) "meth")) (assert (= (.meth m) "meth"))
(assert (= (.meth m "foo" "bar") "meth foo bar")) (assert (= (.meth m "foo" "bar") "meth foo bar"))
(assert (= (.meth :b "1" :a "2" m "foo" "bar") "meth foo bar 2 1")) (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) (setv x.p m)
(assert (= (.p.meth x) "meth")) (assert (= (.p.meth x) "meth"))
(assert (= (.p.meth x "foo" "bar") "meth foo bar")) (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 (= (.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 (X))
(setv x.a.b m) (setv x.a.b m)
(assert (= (.a.b.meth x) "meth")) (assert (= (.a.b.meth x) "meth"))
(assert (= (.a.b.meth x "foo" "bar") "meth foo bar")) (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 (= (.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))) (assert (is (.isdigit :foo) False)))
@ -1173,8 +1161,8 @@
"NATIVE: test &key function arguments" "NATIVE: test &key function arguments"
(defn foo [&key {"a" None "b" 1}] [a b]) (defn foo [&key {"a" None "b" 1}] [a b])
(assert (= (foo) [None 1])) (assert (= (foo) [None 1]))
(assert (= (apply foo [] {"a" 2}) [2 1])) (assert (= (foo :a 2) [2 1]))
(assert (= (apply foo [] {"b" 42}) [None 42]))) (assert (= (foo :b 42) [None 42])))
(defn test-optional-arguments [] (defn test-optional-arguments []

View File

@ -84,12 +84,12 @@
"NATIVE: test macro calling a plain function" "NATIVE: test macro calling a plain function"
(assert (= 3 (bar 1 2)))) (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 ; https://github.com/hylang/hy/issues/1154
(defn f [&rest args] (defn f [&rest args]
(+ "f:" (repr args))) (+ "f:" (repr args)))
(defmacro mac [&optional x] (defmacro mac [&optional x]
`(apply f [~x])) `(f #* [~x]))
(assert (= (mac) "f:(None,)"))) (assert (= (mac) "f:(None,)")))
(defn test-midtree-yield [] (defn test-midtree-yield []

View File

@ -288,8 +288,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]))) (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"))))

View 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])))

View File

@ -16,15 +16,15 @@
"NATIVE: test keyword-only arguments" "NATIVE: test keyword-only arguments"
;; keyword-only with default works ;; keyword-only with default works
(defn kwonly-foo-default-false [&kwonly [foo False]] foo) (defn kwonly-foo-default-false [&kwonly [foo False]] foo)
(assert (= (apply kwonly-foo-default-false) False)) (assert (= (kwonly-foo-default-false) False))
(assert (= (apply kwonly-foo-default-false [] {"foo" True}) True)) (assert (= (kwonly-foo-default-false :foo True) True))
;; keyword-only without default ... ;; keyword-only without default ...
(defn kwonly-foo-no-default [&kwonly foo] foo) (defn kwonly-foo-no-default [&kwonly foo] foo)
(setv attempt-to-omit-default (try (setv attempt-to-omit-default (try
(kwonly-foo-no-default) (kwonly-foo-no-default)
(except [e [Exception]] e))) (except [e [Exception]] e)))
;; works ;; 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 ;; raises TypeError with appropriate message if not supplied
(assert (isinstance attempt-to-omit-default TypeError)) (assert (isinstance attempt-to-omit-default TypeError))
(assert (in "missing 1 required keyword-only argument: 'foo'" (assert (in "missing 1 required keyword-only argument: 'foo'"
@ -32,11 +32,20 @@
;; keyword-only with other arg types works ;; keyword-only with other arg types works
(defn function-of-various-args [a b &rest args &kwonly foo &kwargs kwargs] (defn function-of-various-args [a b &rest args &kwonly foo &kwargs kwargs]
(, a b args foo kwargs)) (, a b args foo kwargs))
(assert (= (apply function-of-various-args (assert (= (function-of-various-args 1 2 3 4 :foo 5 :bar 6 :quux 7)
[1 2 3 4] {"foo" 5 "bar" 6 "quux" 7})
(, 1 2 (, 3 4) 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 [] (defn test-yield-from []
"NATIVE: testing yield from" "NATIVE: testing yield from"
(defn yield-from-test [] (defn yield-from-test []

View File

@ -100,9 +100,8 @@
"Increments each argument passed to the decorated function." "Increments each argument passed to the decorated function."
((wraps func) ((wraps func)
(fn [&rest args &kwargs kwargs] (fn [&rest args &kwargs kwargs]
(apply func (func #* (map inc args)
(map inc args) #** (dict-comp k (inc v) [[k v] (.items kwargs)])))))
(dict-comp k (inc v) [[k v] (.items kwargs)])))))
#@(increment-arguments #@(increment-arguments
(defn foo [&rest args &kwargs kwargs] (defn foo [&rest args &kwargs kwargs]

View File

@ -7,7 +7,7 @@
import os import os
import subprocess import subprocess
import re import re
from hy._compat import PY3 from hy._compat import PY3, PY35
from hy.importer import get_bytecode_path from hy.importer import get_bytecode_path
import pytest import pytest
@ -210,7 +210,8 @@ def test_hy2py():
if f.endswith(".hy"): if f.endswith(".hy"):
if f == "py3_only_tests.hy" and not PY3: if f == "py3_only_tests.hy" and not PY3:
continue continue
else: if f == "py35_only_tests.hy" and not PY35:
continue
i += 1 i += 1
output, err = run_cmd("hy2py -s -a " + output, err = run_cmd("hy2py -s -a " +
os.path.join(dirpath, f)) os.path.join(dirpath, f))