Skip to content

Commit

Permalink
Add keyword support to more builtins
Browse files Browse the repository at this point in the history
Resolves   #846, #845.
  • Loading branch information
evhub committed Aug 31, 2024
1 parent 16f7e58 commit 373e38b
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 15 deletions.
14 changes: 7 additions & 7 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1737,7 +1737,7 @@ The syntax for a statement lambda is
```
[async|match|copyclosure] def (arguments) => statement; statement; ...
```
where `arguments` can be standard function arguments or [pattern-matching function definition](#pattern-matching-functions) arguments and `statement` can be an assignment statement or a keyword statement. Note that the `async`, `match`, and [`copyclosure`](#copyclosure-functions) keywords can be combined and can be in any order.
where `arguments` can be standard function arguments or [pattern-matching function definition](#pattern-matching-functions) arguments and `statement` can be any non-compound statement—that is, any statement that doesn't open a code block below it (so `def x => assert x` is fine but `def x => if x: True` is not). Note that the `async`, `match`, and [`copyclosure`](#copyclosure-functions) keywords can be combined and can be in any order.

If the last `statement` (not followed by a semicolon) in a statement lambda is an `expression`, it will automatically be returned.

Expand Down Expand Up @@ -3805,9 +3805,9 @@ _Can’t be done quickly without Coconut’s iterable indexing, which requires m

#### `reduce`

**reduce**(_function_, _iterable_[, _initial_], /)
**reduce**(_function_, _iterable_[, _initial_])

Coconut re-introduces Python 2's `reduce` built-in, using the `functools.reduce` version.
Coconut re-introduces Python 2's `reduce` built-in, using the `functools.reduce` version. Additionally, unlike `functools.reduce`, Coconut's `reduce` always supports keyword arguments.

##### Python Docs

Expand Down Expand Up @@ -3937,9 +3937,9 @@ result = itertools.zip_longest(range(5), range(10))

#### `takewhile`

**takewhile**(_predicate_, _iterable_, /)
**takewhile**(_predicate_, _iterable_)

Coconut provides `itertools.takewhile` as a built-in under the name `takewhile`.
Coconut provides `itertools.takewhile` as a built-in under the name `takewhile`. Additionally, unlike `itertools.takewhile`, Coconut's `takewhile` always supports keyword arguments.

##### Python Docs

Expand Down Expand Up @@ -3971,9 +3971,9 @@ negatives = itertools.takewhile(lambda x: x < 0, numiter)

#### `dropwhile`

**dropwhile**(_predicate_, _iterable_, /)
**dropwhile**(_predicate_, _iterable_)

Coconut provides `itertools.dropwhile` as a built-in under the name `dropwhile`.
Coconut provides `itertools.dropwhile` as a built-in under the name `dropwhile`. Additionally, unlike `itertools.dropwhile`, Coconut's `dropwhile` always supports keyword arguments.

##### Python Docs

Expand Down
23 changes: 19 additions & 4 deletions coconut/compiler/templates/header.py_template
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class _coconut{object}:{COMMENT.EVERYTHING_HERE_MUST_BE_COPIED_TO_STUB_FILE}
fmappables = list, tuple, dict, set, frozenset, bytes, bytearray
abc.Sequence.register(collections.deque)
Ellipsis, NotImplemented, NotImplementedError, Exception, AttributeError, ImportError, IndexError, KeyError, NameError, TypeError, ValueError, StopIteration, RuntimeError, all, any, bool, bytes, callable, chr, classmethod, complex, dict, enumerate, filter, float, frozenset, getattr, hasattr, hash, id, int, isinstance, issubclass, iter, len, list, locals, globals, map, min, max, next, object, ord, property, range, reversed, set, setattr, slice, str, sum, super, tuple, type, vars, zip, repr, print{comma_bytearray} = Ellipsis, NotImplemented, NotImplementedError, Exception, AttributeError, ImportError, IndexError, KeyError, NameError, TypeError, ValueError, StopIteration, RuntimeError, all, any, bool, bytes, callable, chr, classmethod, complex, dict, enumerate, filter, float, frozenset, getattr, hasattr, hash, id, int, isinstance, issubclass, iter, len, list, locals, globals, map, {lstatic}min{rstatic}, {lstatic}max{rstatic}, next, object, ord, property, range, reversed, set, setattr, slice, str, sum, {lstatic}super{rstatic}, tuple, type, vars, zip, {lstatic}repr{rstatic}, {lstatic}print{rstatic}{comma_bytearray}
@_coconut.functools.wraps(_coconut.functools.partial)
@_coconut_wraps(_coconut.functools.partial)
def _coconut_partial(_coconut_func, *args, **kwargs):
partial_func = _coconut.functools.partial(_coconut_func, *args, **kwargs)
partial_func.__name__ = _coconut.getattr(_coconut_func, "__name__", None)
Expand Down Expand Up @@ -182,7 +182,7 @@ class _coconut_tail_call(_coconut_baseclass):
return (self.__class__, (self.func, self.args, self.kwargs))
_coconut_tco_func_dict = _coconut.weakref.WeakValueDictionary()
def _coconut_tco(func):
@_coconut.functools.wraps(func)
@_coconut_wraps(func)
def tail_call_optimized_func(*args, **kwargs):
call_func = func
while True:{COMMENT.weakrefs_necessary_for_ignoring_functools_wraps_decorators}
Expand All @@ -209,7 +209,7 @@ def _coconut_tco(func):
tail_call_optimized_func.__qualname__ = _coconut.getattr(func, "__qualname__", None)
_coconut_tco_func_dict[_coconut.id(tail_call_optimized_func)] = tail_call_optimized_func
return tail_call_optimized_func
@_coconut.functools.wraps(_coconut.itertools.tee)
@_coconut_wraps(_coconut.itertools.tee)
def tee(iterable, n=2):
if n < 0:
raise _coconut.ValueError("tee: n cannot be negative")
Expand Down Expand Up @@ -2220,7 +2220,22 @@ class _coconut_SupportsInv(_coconut.typing.Protocol):
"""
def __invert__(self):
raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((~) in a typing context is a Protocol)")
@_coconut_wraps(_coconut.functools.reduce)
def reduce(function, iterable, initial=_coconut_sentinel):
if initial is _coconut_sentinel:
return _coconut.functools.reduce(function, iterable)
return _coconut.functools.reduce(function, iterable, initial)
class takewhile(_coconut.itertools.takewhile{comma_object}):
__slots__ = ()
__doc__ = _coconut.itertools.takewhile.__doc__
def __new__(cls, predicate, iterable):
return _coconut.itertools.takewhile.__new__(cls, predicate, iterable)
class dropwhile(_coconut.itertools.dropwhile{comma_object}):
__slots__ = ()
__doc__ = _coconut.itertools.dropwhile.__doc__
def __new__(cls, predicate, iterable):
return _coconut.itertools.dropwhile.__new__(cls, predicate, iterable)
{def_async_map}
{def_aliases}
_coconut_self_match_types = {self_match_types}
_coconut_Expected, _coconut_MatchError, _coconut_cartesian_product, _coconut_count, _coconut_cycle, _coconut_enumerate, _coconut_flatten, _coconut_fmap, _coconut_filter, _coconut_groupsof, _coconut_ident, _coconut_lift, _coconut_map, _coconut_mapreduce, _coconut_multiset, _coconut_range, _coconut_reiterable, _coconut_reversed, _coconut_scan, _coconut_starmap, _coconut_tee, _coconut_windowsof, _coconut_zip, _coconut_zip_longest, TYPE_CHECKING, reduce, takewhile, dropwhile = Expected, MatchError, cartesian_product, count, cycle, enumerate, flatten, fmap, filter, groupsof, ident, lift, map, mapreduce, multiset, range, reiterable, reversed, scan, starmap, tee, windowsof, zip, zip_longest, False, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile{COMMENT.anything_added_here_should_be_copied_to_stub_file}
TYPE_CHECKING, _coconut_Expected, _coconut_MatchError, _coconut_cartesian_product, _coconut_count, _coconut_cycle, _coconut_enumerate, _coconut_flatten, _coconut_fmap, _coconut_filter, _coconut_groupsof, _coconut_ident, _coconut_lift, _coconut_map, _coconut_mapreduce, _coconut_multiset, _coconut_range, _coconut_reiterable, _coconut_reversed, _coconut_scan, _coconut_starmap, _coconut_tee, _coconut_windowsof, _coconut_zip, _coconut_zip_longest = False, Expected, MatchError, cartesian_product, count, cycle, enumerate, flatten, fmap, filter, groupsof, ident, lift, map, mapreduce, multiset, range, reiterable, reversed, scan, starmap, tee, windowsof, zip, zip_longest{COMMENT.anything_added_here_should_be_copied_to_stub_file}
19 changes: 15 additions & 4 deletions coconut/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,30 @@ def _get_target_info(target):
# HEADER:
# -----------------------------------------------------------------------------------------------------------------------

_base_header = r'''
import functools as _coconut_functools
_coconut_getattr = getattr
def _coconut_wraps(base_func):
def wrap(new_func):
new_func_module = _coconut_getattr(new_func, "__module__")
_coconut_functools.update_wrapper(new_func, base_func)
if new_func_module is not None:
new_func.__module__ = new_func_module
return new_func
return wrap
'''

# if a new assignment is added below, a new builtins import should be added alongside it
_base_py3_header = r'''from builtins import chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, repr
py_bytes, py_chr, py_dict, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_super, py_zip, py_filter, py_reversed, py_enumerate, py_repr, py_min, py_max = bytes, chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, repr, min, max
_coconut_py_str, _coconut_py_super, _coconut_py_dict, _coconut_py_min, _coconut_py_max = str, super, dict, min, max
from functools import wraps as _coconut_wraps
exec("_coconut_exec = exec")
'''

# if a new assignment is added below, a new builtins import should be added alongside it
_base_py2_header = r'''from __builtin__ import chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, raw_input, xrange, repr, long
py_bytes, py_chr, py_dict, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_super, py_zip, py_filter, py_reversed, py_enumerate, py_raw_input, py_xrange, py_repr, py_min, py_max = bytes, chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, raw_input, xrange, repr, min, max
_coconut_py_raw_input, _coconut_py_xrange, _coconut_py_int, _coconut_py_long, _coconut_py_print, _coconut_py_str, _coconut_py_super, _coconut_py_unicode, _coconut_py_repr, _coconut_py_dict, _coconut_py_bytes, _coconut_py_min, _coconut_py_max = raw_input, xrange, int, long, print, str, super, unicode, repr, dict, bytes, min, max
from functools import wraps as _coconut_wraps
from collections import Sequence as _coconut_Sequence
from future_builtins import *
chr, str = unichr, unicode
Expand Down Expand Up @@ -353,7 +364,7 @@ def __repr__(self):
__doc__ = getattr(_coconut_py_dict, "__doc__", "<see help(py_dict)>")''' + _finish_dict_def

_py26_extras = '''if _coconut_sys.version_info < (2, 7):
import functools as _coconut_functools, copy_reg as _coconut_copy_reg
import copy_reg as _coconut_copy_reg
def _coconut_new_partial(func, args, keywords):
return _coconut_functools.partial(func, *(args if args is not None else ()), **(keywords if keywords is not None else {}))
_coconut_copy_reg.constructor(_coconut_new_partial)
Expand Down Expand Up @@ -392,7 +403,7 @@ def _get_root_header(version="universal"):
''' + _indent(_get_root_header("3"))

version_info = _get_target_info(version)
header = ""
header = _base_header

if version.startswith("3"):
header += _base_py3_header
Expand Down
1 change: 1 addition & 0 deletions coconut/tests/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ def pexpect(p, out):
"tutorial.py",
"unused 'type: ignore' comment",
"site-packages/numpy",
".py: error:"
)

ignore_atexit_errors_with = (
Expand Down
3 changes: 3 additions & 0 deletions coconut/tests/src/cocotest/agnostic/primary_2.coco
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@ def primary_test_2() -> bool:
assert False
assert x == 12
assert (x=1).__match_args__ == ('x',) # type: ignore
assert reduce(function=(+), iterable=range(5), initial=-1) == 9 # type: ignore
assert takewhile(predicate=ident, iterable=[1, 2, 1, 0, 1]) |> list == [1, 2, 1] # type: ignore
assert dropwhile(predicate=(not), iterable=range(5)) |> list == [1, 2, 3, 4] # type: ignore

with process_map.multiple_sequential_calls(): # type: ignore
assert map((+), range(3), range(4)$[:-1], strict=True) |> list == [0, 2, 4] == process_map((+), range(3), range(4)$[:-1], strict=True) |> list # type: ignore
Expand Down

0 comments on commit 373e38b

Please sign in to comment.