Implement keyword argument passing... (foo-func 1 2 :kw1 "bar") works!

This code is heavily, *heavily* based off of Guillermo Vaya
(willyfrog)'s work... instead of defining its own keyword arg though, it
uses the "standard" :kwarg type, which is the main difference from
willyfrog's original branch.

Included tests and some documentation in the tutorial.

Also documented "apply" separately as an example of reproducing
*args and **kwargs.
This commit is contained in:
Christopher Allan Webber 2014-12-22 15:52:34 -06:00
parent 96c591ff9d
commit d98e4fd733
3 changed files with 88 additions and 25 deletions

View File

@ -396,15 +396,26 @@ The same thing in Hy::
[1 2 None 42]
=> (optional_arg 1 2 3 4)
[1 2 3 4]
=> (apply optional_arg []
... {"keyword1" 1
... "pos2" 2
... "pos1" 3
... "keyword2" 4})
...
=> (optional_arg :keyword1 1
... :pos2 2
... :pos1 3
... :keyword2 4)
[3, 2, 1, 4]
See how we use apply to handle the fancy passing? :)
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 kwargs {"keyword2" 3
... "keyword1" 4})
=> (apply optional_arg args kwargs)
[1, 2, 4, 3]
There's also a dictionary-style keyword arguments construction that
looks like:
@ -479,7 +490,7 @@ In Hy:
.. code-block:: clj
(defclass Customer [models.Model]
[[name (apply models.CharField [] {"max_length" 255})]
[[name (models.CharField :max_length 255})]
[address (models.TextField)]
[notes (models.TextField)]])

View File

@ -433,7 +433,7 @@ class HyASTCompiler(object):
raise HyCompileError(Exception("Unknown type: `%s'" % _type))
def _compile_collect(self, exprs):
def _compile_collect(self, exprs, with_kwargs=False):
"""Collect the expression contexts from a list of compiled expression.
This returns a list of the expression contexts, and the sum of the
@ -442,10 +442,33 @@ class HyASTCompiler(object):
"""
compiled_exprs = []
ret = Result()
for expr in exprs:
ret += self.compile(expr)
compiled_exprs.append(ret.force_expr)
return compiled_exprs, ret
keywords = []
exprs_iter = exprs.__iter__()
for expr in exprs_iter:
if with_kwargs and isinstance(expr, HyKeyword):
try:
value = next(exprs_iter)
except StopIteration:
msg = "Keyword argument {kw} needs a value"
raise HyCompileError(msg.format(kw=str(expr)))
compiled_value = self.compile(value)
# no unicode for py2 in ast names
keyword = str(expr[2:])
if "-" in keyword and keyword != "-":
keyword = keyword.replace("-", "_")
keywords.append(ast.keyword(arg=keyword,
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
def _compile_branch(self, exprs):
return _branch(self.compile(expr) for expr in exprs)
@ -897,7 +920,7 @@ class HyASTCompiler(object):
if isinstance(exceptions_list, list):
if len(exceptions_list):
# [FooBar BarFoo] → catch Foobar and BarFoo exceptions
elts, _type = self._compile_collect(exceptions_list)
elts, _type, _ = self._compile_collect(exceptions_list)
_type += ast.Tuple(elts=elts,
lineno=expr.start_line,
col_offset=expr.start_column,
@ -1140,7 +1163,7 @@ class HyASTCompiler(object):
expr.pop(0) # index
val = self.compile(expr.pop(0))
slices, ret = self._compile_collect(expr)
slices, ret, _ = self._compile_collect(expr)
if val.stmts:
ret += val
@ -1200,7 +1223,7 @@ class HyASTCompiler(object):
@checkargs(min=1)
def compile_del_expression(self, expr):
expr.pop(0)
ld_targets, ret = self._compile_collect(expr)
ld_targets, ret, _ = self._compile_collect(expr)
del_targets = []
for target in ld_targets:
@ -1271,7 +1294,7 @@ class HyASTCompiler(object):
if not fn.stmts or not (isinstance(fn.stmts[-1], ast.FunctionDef) or
isinstance(fn.stmts[-1], ast.ClassDef)):
raise HyTypeError(expr, "Decorated a non-function")
decorators, ret = self._compile_collect(expr)
decorators, ret, _ = self._compile_collect(expr)
fn.stmts[-1].decorator_list = decorators
return ret + fn
@ -1333,7 +1356,7 @@ class HyASTCompiler(object):
@builds(",")
def compile_tuple(self, expr):
expr.pop(0)
elts, ret = self._compile_collect(expr)
elts, ret, _ = self._compile_collect(expr)
ret += ast.Tuple(elts=elts,
lineno=expr.start_line,
col_offset=expr.start_column,
@ -1562,7 +1585,7 @@ class HyASTCompiler(object):
ops = {"and": ast.And,
"or": ast.Or}
operator = expression.pop(0)
values, ret = self._compile_collect(expression)
values, ret, _ = self._compile_collect(expression)
ret += ast.BoolOp(op=ops[operator](),
lineno=operator.start_line,
@ -1593,7 +1616,7 @@ class HyASTCompiler(object):
ops = [op() for x in range(1, len(expression))]
e = expression[0]
exprs, ret = self._compile_collect(expression)
exprs, ret, _ = self._compile_collect(expression)
return ret + ast.Compare(left=exprs[0],
ops=ops,
@ -1762,11 +1785,20 @@ class HyASTCompiler(object):
if not func:
func = self.compile(fn)
args, ret = self._compile_collect(expression[1:])
# An exception for pulling together keyword args is if we're doing
# a typecheck, eg (type :foo)
if fn in ("type", "HyKeyword", "keyword", "name", "is_keyword"):
with_kwargs = False
else:
with_kwargs = True
args, ret, kwargs = self._compile_collect(expression[1:],
with_kwargs)
ret += ast.Call(func=func.expr,
args=args,
keywords=[],
keywords=kwargs,
starargs=None,
kwargs=None,
lineno=expression.start_line,
@ -1879,7 +1911,7 @@ class HyASTCompiler(object):
@builds(HyList)
def compile_list(self, expression):
elts, ret = self._compile_collect(expression)
elts, ret, _ = self._compile_collect(expression)
ret += ast.List(elts=elts,
ctx=ast.Load(),
lineno=expression.start_line,
@ -1983,7 +2015,7 @@ class HyASTCompiler(object):
if not isinstance(base_list, HyList):
raise HyTypeError(expression,
"Bases class must be a list")
bases_expr, bases = self._compile_collect(base_list)
bases_expr, bases, _ = self._compile_collect(base_list)
else:
bases_expr = []
bases = Result()
@ -2184,7 +2216,7 @@ class HyASTCompiler(object):
@builds(HyDict)
def compile_dict(self, m):
keyvalues, ret = self._compile_collect(m)
keyvalues, ret, _ = self._compile_collect(m)
ret += ast.Dict(lineno=m.start_line,
col_offset=m.start_column,

View File

@ -1133,3 +1133,23 @@
(assert (= (name :foo) "foo"))
(assert (= (name :foo_bar) "foo-bar"))
(assert (= (name test-name-conversion) "test-name-conversion")))
(defn test-keywords []
"Check keyword use in function calls"
(assert (= (kwtest) {}))
(assert (= (kwtest :key "value") {"key" "value"}))
(assert (= (kwtest :key-with-dashes "value") {"key_with_dashes" "value"}))
(assert (= (kwtest :result (+ 1 1)) {"result" 2}))
(assert (= (kwtest :key (kwtest :key2 "value")) {"key" {"key2" "value"}})))
(defmacro identify-keywords [&rest elts]
`(list
(map
(lambda (x) (if (is-keyword x) "keyword" "other"))
~elts)))
(defn test-keywords-and-macros []
"Macros should still be able to handle keywords as they best see fit."
(assert
(= (identify-keywords 1 "bloo" :foo)
["other" "other" "keyword"])))