osiris-lang 0.2.4__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osiris/__init__.py +12 -0
- osiris/prelude/__init__.py +8 -0
- osiris/prelude/_sequence_consumers.py +77 -0
- osiris/prelude/_sequence_core.py +259 -0
- osiris/prelude/_sequence_eager.py +255 -0
- osiris/prelude/_sequence_partitions.py +261 -0
- osiris/prelude/_sequence_transforms.py +255 -0
- osiris/prelude/control.py +420 -0
- osiris/prelude/sequences.py +75 -0
- osiris/py.typed +1 -0
- osiris_build/__init__.py +31 -0
- osiris_build/_api.py +80 -0
- osiris_build/_common.py +22 -0
- osiris_build/_interface.py +150 -0
- osiris_build/_interface_records.py +196 -0
- osiris_build/_model.py +63 -0
- osiris_build/_project.py +339 -0
- osiris_build/_requirements.py +414 -0
- osiris_build/_sexpr.py +233 -0
- osiris_build/_sidecar.py +161 -0
- osiris_build/_wheel.py +518 -0
- osiris_lang-0.2.4.data/scripts/osr.exe +0 -0
- osiris_lang-0.2.4.dist-info/METADATA +285 -0
- osiris_lang-0.2.4.dist-info/RECORD +27 -0
- osiris_lang-0.2.4.dist-info/WHEEL +4 -0
- osiris_lang-0.2.4.dist-info/licenses/LICENSE +21 -0
- osiris_lang-0.2.4.dist-info/sboms/osiris.cyclonedx.json +2211 -0
osiris/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version as distribution_version
|
|
2
|
+
|
|
3
|
+
__all__ = ["version"]
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = distribution_version("osiris-lang")
|
|
7
|
+
except PackageNotFoundError:
|
|
8
|
+
__version__ = "0+unknown"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def version() -> str:
|
|
12
|
+
return __version__
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Sequence realization and predicate consumers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins as _builtins
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
from ._sequence_core import _MISSING, _iter_or_empty
|
|
9
|
+
from .control import truthy
|
|
10
|
+
|
|
11
|
+
def run_bang(function: Callable[[object], object], collection: object) -> None:
|
|
12
|
+
"""Invoke ``function`` for each item and discard all results."""
|
|
13
|
+
|
|
14
|
+
for value in _iter_or_empty(collection):
|
|
15
|
+
function(value)
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _realize_prefix(arguments: tuple[object, ...]) -> object:
|
|
20
|
+
if len(arguments) == 1:
|
|
21
|
+
limit = None
|
|
22
|
+
collection = arguments[0]
|
|
23
|
+
elif len(arguments) == 2:
|
|
24
|
+
limit = arguments[0]
|
|
25
|
+
collection = arguments[1]
|
|
26
|
+
if not isinstance(limit, int) or isinstance(limit, bool):
|
|
27
|
+
raise TypeError("realization count must be an integer")
|
|
28
|
+
else:
|
|
29
|
+
raise TypeError("expected collection or count and collection")
|
|
30
|
+
iterator = _iter_or_empty(collection)
|
|
31
|
+
if limit is None:
|
|
32
|
+
for _ in iterator:
|
|
33
|
+
pass
|
|
34
|
+
else:
|
|
35
|
+
for _ in range(max(0, limit)):
|
|
36
|
+
if _builtins.next(iterator, _MISSING) is _MISSING:
|
|
37
|
+
break
|
|
38
|
+
return collection
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def doall(*arguments: object) -> object:
|
|
42
|
+
"""Realize all, or a bounded prefix of, an iterable and return it."""
|
|
43
|
+
|
|
44
|
+
return _realize_prefix(arguments)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def dorun(*arguments: object) -> None:
|
|
48
|
+
"""Realize all, or a bounded prefix of, an iterable and return nil."""
|
|
49
|
+
|
|
50
|
+
_realize_prefix(arguments)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def some(predicate: Callable[[object], object], collection: object) -> object:
|
|
55
|
+
"""Return the first Clojure-truthy predicate result, or nil."""
|
|
56
|
+
|
|
57
|
+
for value in _iter_or_empty(collection):
|
|
58
|
+
result = predicate(value)
|
|
59
|
+
if truthy(result):
|
|
60
|
+
return result
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def every_q(predicate: Callable[[object], object], collection: object) -> bool:
|
|
65
|
+
"""Clojure-truthy all predicate check."""
|
|
66
|
+
|
|
67
|
+
return all(truthy(predicate(value)) for value in _iter_or_empty(collection))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def not_every_q(predicate: Callable[[object], object], collection: object) -> bool:
|
|
71
|
+
return not every_q(predicate, collection)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def not_any_q(predicate: Callable[[object], object], collection: object) -> bool:
|
|
75
|
+
# ``some`` returns the predicate's value, and Python's ``not`` would
|
|
76
|
+
# incorrectly treat Clojure-truthy values such as ``0``/``""`` as false.
|
|
77
|
+
return not truthy(some(predicate, collection))
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Memoized lazy-sequence representation and core sequence operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins as _builtins
|
|
6
|
+
import threading as _threading
|
|
7
|
+
from collections.abc import Callable, Iterable
|
|
8
|
+
from typing import Iterator, Optional
|
|
9
|
+
|
|
10
|
+
_MISSING = object()
|
|
11
|
+
|
|
12
|
+
class _LazySeq:
|
|
13
|
+
"""Memoized lazy sequence that realizes elements one at a time.
|
|
14
|
+
|
|
15
|
+
The source iterator and already produced values are retained so a lazy
|
|
16
|
+
sequence can be traversed repeatedly without re-running its thunk or
|
|
17
|
+
losing values from a one-shot generator.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
__slots__ = ("_thunk", "_source", "_cache", "_done", "_error", "_lock")
|
|
21
|
+
|
|
22
|
+
def __init__(self, thunk: Callable[[], Iterable[object]]) -> None:
|
|
23
|
+
self._thunk = thunk
|
|
24
|
+
self._source: Optional[Iterator[object]] = None
|
|
25
|
+
self._cache: list[object] = []
|
|
26
|
+
self._done = False
|
|
27
|
+
self._error: Optional[BaseException] = None
|
|
28
|
+
self._lock = _threading.RLock()
|
|
29
|
+
|
|
30
|
+
def _ensure_source_locked(self) -> Iterator[object]:
|
|
31
|
+
"""Initialize the source while ``_lock`` is held."""
|
|
32
|
+
|
|
33
|
+
if self._error is not None:
|
|
34
|
+
raise self._error
|
|
35
|
+
if self._source is None:
|
|
36
|
+
try:
|
|
37
|
+
value = self._thunk()
|
|
38
|
+
self._source = iter(()) if value is None else iter(value)
|
|
39
|
+
except BaseException as error:
|
|
40
|
+
# A lazy source is a memoized computation. Cache failures so
|
|
41
|
+
# a second consumer cannot rerun an effectful or partial thunk.
|
|
42
|
+
self._error = error
|
|
43
|
+
self._done = True
|
|
44
|
+
raise
|
|
45
|
+
return self._source
|
|
46
|
+
|
|
47
|
+
def _iter_from(self, index: int) -> Iterator[object]:
|
|
48
|
+
"""Iterate from a cached position without replaying earlier values."""
|
|
49
|
+
|
|
50
|
+
while True:
|
|
51
|
+
with self._lock:
|
|
52
|
+
if self._error is not None:
|
|
53
|
+
raise self._error
|
|
54
|
+
if index < len(self._cache):
|
|
55
|
+
value = self._cache[index]
|
|
56
|
+
index += 1
|
|
57
|
+
elif self._done:
|
|
58
|
+
return
|
|
59
|
+
else:
|
|
60
|
+
try:
|
|
61
|
+
value = _builtins.next(self._ensure_source_locked())
|
|
62
|
+
except StopIteration:
|
|
63
|
+
self._done = True
|
|
64
|
+
return
|
|
65
|
+
except BaseException as error:
|
|
66
|
+
self._error = error
|
|
67
|
+
self._done = True
|
|
68
|
+
raise
|
|
69
|
+
self._cache.append(value)
|
|
70
|
+
index += 1
|
|
71
|
+
yield value
|
|
72
|
+
|
|
73
|
+
def __iter__(self) -> Iterator[object]:
|
|
74
|
+
return self._iter_from(0)
|
|
75
|
+
|
|
76
|
+
def __bool__(self) -> bool:
|
|
77
|
+
"""Match sequence truthiness without realizing more than one item."""
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
_builtins.next(iter(self))
|
|
81
|
+
except StopIteration:
|
|
82
|
+
return False
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def lazy_seq(thunk: Callable[[], Iterable[object]]) -> _LazySeq:
|
|
87
|
+
"""Construct a memoized, iterable lazy sequence from ``thunk``."""
|
|
88
|
+
|
|
89
|
+
return _LazySeq(thunk)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _iter_or_empty(collection: object) -> Iterator[object]:
|
|
93
|
+
"""Treat Clojure nil as an empty sequence at sequence boundaries."""
|
|
94
|
+
|
|
95
|
+
if collection is None:
|
|
96
|
+
return iter(())
|
|
97
|
+
return iter(collection) # type: ignore[arg-type]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def cons(value: object, collection: object) -> _LazySeq:
|
|
101
|
+
"""Return a lazy sequence with ``value`` before ``collection``."""
|
|
102
|
+
|
|
103
|
+
def produce() -> Iterator[object]:
|
|
104
|
+
yield value
|
|
105
|
+
yield from _iter_or_empty(collection)
|
|
106
|
+
|
|
107
|
+
return lazy_seq(produce)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def concat(*collections: object) -> _LazySeq:
|
|
111
|
+
"""Concatenate sequences lazily, skipping nil collections."""
|
|
112
|
+
|
|
113
|
+
def produce() -> Iterator[object]:
|
|
114
|
+
for collection in collections:
|
|
115
|
+
yield from _iter_or_empty(collection)
|
|
116
|
+
|
|
117
|
+
return lazy_seq(produce)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def count(collection: object) -> int:
|
|
121
|
+
"""Count a collection without applying Python truthiness to it."""
|
|
122
|
+
|
|
123
|
+
if collection is None:
|
|
124
|
+
return 0
|
|
125
|
+
try:
|
|
126
|
+
return len(collection) # type: ignore[arg-type]
|
|
127
|
+
except TypeError:
|
|
128
|
+
return sum(1 for _ in collection) # type: ignore[operator]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
_EMPTY_PROBE = object()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def empty_q(collection: object) -> bool:
|
|
135
|
+
"""Return whether ``collection`` has no values.
|
|
136
|
+
|
|
137
|
+
Sized containers are checked without iteration. For a one-shot iterable
|
|
138
|
+
the probe necessarily advances its iterator by at most one item; Osiris
|
|
139
|
+
lazy sequences memoize that item, so repeated checks remain non-destructive
|
|
140
|
+
at the public sequence boundary. ``None`` follows Clojure's empty-seq
|
|
141
|
+
convention, while scalar non-collections raise ``TypeError``.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
if collection is None:
|
|
145
|
+
return True
|
|
146
|
+
try:
|
|
147
|
+
return len(collection) == 0 # type: ignore[arg-type]
|
|
148
|
+
except TypeError:
|
|
149
|
+
try:
|
|
150
|
+
iterator = iter(collection) # type: ignore[arg-type]
|
|
151
|
+
except TypeError as error:
|
|
152
|
+
raise TypeError("empty? expects a collection") from error
|
|
153
|
+
return _builtins.next(iterator, _EMPTY_PROBE) is _EMPTY_PROBE
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def seq_q(value: object) -> bool:
|
|
157
|
+
"""Return whether ``value`` is an Osiris seq/list value.
|
|
158
|
+
|
|
159
|
+
Raw Python iterators are deliberately not treated as seqs; callers can
|
|
160
|
+
opt into the replayable seq protocol with :func:`sequence`.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
return isinstance(value, (list, _LazySeq))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def coll_q(value: object) -> bool:
|
|
167
|
+
"""Return whether ``value`` is an Osiris collection value."""
|
|
168
|
+
|
|
169
|
+
return isinstance(value, (list, tuple, dict, set, _LazySeq))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def sequential_q(value: object) -> bool:
|
|
173
|
+
"""Return whether ``value`` is an ordered sequential collection."""
|
|
174
|
+
|
|
175
|
+
return isinstance(value, (list, tuple, _LazySeq))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def first(collection: object) -> object:
|
|
179
|
+
"""Return the first item or nil for an empty sequence."""
|
|
180
|
+
|
|
181
|
+
return _builtins.next(_iter_or_empty(collection), None)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def rest(collection: object) -> list[object]:
|
|
185
|
+
"""Return all but the first item as a materialized list."""
|
|
186
|
+
|
|
187
|
+
iterator = _iter_or_empty(collection)
|
|
188
|
+
_builtins.next(iterator, None)
|
|
189
|
+
return list(iterator)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def next(collection: object) -> Optional[_LazySeq]:
|
|
193
|
+
"""Return the lazy tail, or nil when no tail exists."""
|
|
194
|
+
|
|
195
|
+
iterator = _iter_or_empty(collection)
|
|
196
|
+
try:
|
|
197
|
+
_builtins.next(iterator)
|
|
198
|
+
except StopIteration:
|
|
199
|
+
return None
|
|
200
|
+
try:
|
|
201
|
+
first_tail = _builtins.next(iterator)
|
|
202
|
+
except StopIteration:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
def produce() -> Iterator[object]:
|
|
206
|
+
yield first_tail
|
|
207
|
+
yield from iterator
|
|
208
|
+
|
|
209
|
+
return lazy_seq(produce)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def nth(collection: object, index: int, not_found: object = _MISSING) -> object:
|
|
213
|
+
"""Return an indexed item, or use the explicitly supplied fallback.
|
|
214
|
+
|
|
215
|
+
Clojure distinguishes ``(nth coll index)`` from
|
|
216
|
+
``(nth coll index not-found)`` even when ``not-found`` is nil. Keep that
|
|
217
|
+
distinction at the Python boundary instead of silently turning an invalid
|
|
218
|
+
two-argument lookup into ``None``.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
if not isinstance(index, int) or isinstance(index, bool):
|
|
222
|
+
raise TypeError("nth index must be an integer")
|
|
223
|
+
if index < 0:
|
|
224
|
+
if not_found is _MISSING:
|
|
225
|
+
raise IndexError("nth index is out of range")
|
|
226
|
+
return not_found
|
|
227
|
+
for position, value in enumerate(_iter_or_empty(collection)):
|
|
228
|
+
if position == index:
|
|
229
|
+
return value
|
|
230
|
+
if not_found is _MISSING:
|
|
231
|
+
raise IndexError("nth index is out of range")
|
|
232
|
+
return not_found
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def seq(collection: object) -> Optional[object]:
|
|
236
|
+
"""Return a non-empty sequence or nil."""
|
|
237
|
+
|
|
238
|
+
iterator = _iter_or_empty(collection)
|
|
239
|
+
try:
|
|
240
|
+
first_value = _builtins.next(iterator)
|
|
241
|
+
except StopIteration:
|
|
242
|
+
return None
|
|
243
|
+
return cons(first_value, iterator)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def empty(collection: object) -> object:
|
|
247
|
+
"""Return an empty value with the broad shape of ``collection``."""
|
|
248
|
+
|
|
249
|
+
if isinstance(collection, tuple):
|
|
250
|
+
return ()
|
|
251
|
+
if isinstance(collection, set):
|
|
252
|
+
return set()
|
|
253
|
+
if isinstance(collection, dict):
|
|
254
|
+
return {}
|
|
255
|
+
if isinstance(collection, str):
|
|
256
|
+
return ""
|
|
257
|
+
if isinstance(collection, bytes):
|
|
258
|
+
return b""
|
|
259
|
+
return []
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Eager collection, reduction, loop, and trampoline operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins as _builtins
|
|
6
|
+
from collections.abc import Callable, Iterable
|
|
7
|
+
from typing import Generic, Iterator, TypeVar, Union
|
|
8
|
+
|
|
9
|
+
from ._sequence_core import _LazySeq, _iter_or_empty, empty_q, lazy_seq
|
|
10
|
+
from .control import truthy
|
|
11
|
+
|
|
12
|
+
_Result = TypeVar("_Result")
|
|
13
|
+
|
|
14
|
+
def nonempty(collection: object) -> bool:
|
|
15
|
+
"""Test a collection without consuming memoized lazy values."""
|
|
16
|
+
|
|
17
|
+
return not empty_q(collection)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _ForStop:
|
|
21
|
+
"""Private nearest-collection stop token used by ``for :while``."""
|
|
22
|
+
|
|
23
|
+
__slots__ = ()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_FOR_STOP = _ForStop()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def for_stop() -> _ForStop:
|
|
30
|
+
"""Return the private stop token consumed by collection control helpers."""
|
|
31
|
+
|
|
32
|
+
return _FOR_STOP
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def doseq(
|
|
36
|
+
function: Callable[[object], object], collection: Iterable[object]
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Invoke ``function`` in order without materializing callback results."""
|
|
39
|
+
|
|
40
|
+
for value in _iter_or_empty(collection):
|
|
41
|
+
if function(value) is _FOR_STOP:
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _map_values(
|
|
46
|
+
function: Callable[..., _Result], collections: tuple[Iterable[object], ...]
|
|
47
|
+
) -> Iterator[_Result]:
|
|
48
|
+
return _builtins.map(
|
|
49
|
+
function, *(_iter_or_empty(collection) for collection in collections)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def mapv(
|
|
54
|
+
function: Callable[..., _Result], *collections: Iterable[object]
|
|
55
|
+
) -> tuple[_Result, ...]:
|
|
56
|
+
"""Apply ``function`` lazily through ``map`` and materialize a vector."""
|
|
57
|
+
|
|
58
|
+
return tuple(_map_values(function, collections))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def map(
|
|
62
|
+
function: Callable[..., _Result], *collections: Iterable[object]
|
|
63
|
+
) -> list[_Result]:
|
|
64
|
+
"""Apply ``function`` in order and materialize a Lisp list.
|
|
65
|
+
|
|
66
|
+
``mapv`` is the explicit tuple/vector variant. Keeping this helper
|
|
67
|
+
materialized gives the typed core a deterministic ``List`` result while
|
|
68
|
+
``lazy_seq`` remains available when a caller needs deferred production.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
return list(_map_values(function, collections))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def mapcatv(
|
|
75
|
+
function: Callable[..., Iterable[object]], *collections: Iterable[object]
|
|
76
|
+
) -> tuple[object, ...]:
|
|
77
|
+
"""Map one or more collections and concatenate each returned sequence.
|
|
78
|
+
|
|
79
|
+
Like Clojure's ``mapcat``, traversal stops at the shortest input and the
|
|
80
|
+
callback receives one value from each collection.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
result: list[object] = []
|
|
84
|
+
iterators = tuple(_iter_or_empty(collection) for collection in collections)
|
|
85
|
+
for values in zip(*iterators):
|
|
86
|
+
mapped = function(*values)
|
|
87
|
+
if mapped is _FOR_STOP:
|
|
88
|
+
break
|
|
89
|
+
result.extend(_iter_or_empty(mapped))
|
|
90
|
+
return tuple(result)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def mapcat(
|
|
94
|
+
function: Callable[..., Iterable[object]], *collections: Iterable[object]
|
|
95
|
+
) -> list[object]:
|
|
96
|
+
"""List variant of :func:`mapcatv`."""
|
|
97
|
+
|
|
98
|
+
return list(mapcatv(function, *collections))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _filter_values(
|
|
102
|
+
predicate: Callable[[object], object], collection: Iterable[object]
|
|
103
|
+
) -> Iterator[object]:
|
|
104
|
+
return (
|
|
105
|
+
value
|
|
106
|
+
for value in _iter_or_empty(collection)
|
|
107
|
+
if truthy(predicate(value))
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def filterv(
|
|
112
|
+
predicate: Callable[[object], bool], collection: Iterable[object]
|
|
113
|
+
) -> tuple[object, ...]:
|
|
114
|
+
"""Return the values satisfying ``predicate`` as a vector."""
|
|
115
|
+
|
|
116
|
+
return tuple(_filter_values(predicate, collection))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def filter(
|
|
120
|
+
predicate: Callable[[object], bool], collection: Iterable[object]
|
|
121
|
+
) -> list[object]:
|
|
122
|
+
"""Return the values satisfying ``predicate`` as a Lisp list."""
|
|
123
|
+
|
|
124
|
+
return list(_filter_values(predicate, collection))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def removev(
|
|
128
|
+
predicate: Callable[[object], object], collection: object
|
|
129
|
+
) -> tuple[object, ...]:
|
|
130
|
+
"""Vector form of ``remove`` using Clojure truthiness."""
|
|
131
|
+
|
|
132
|
+
return tuple(value for value in _iter_or_empty(collection) if not truthy(predicate(value)))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def remove(
|
|
136
|
+
predicate: Callable[[object], object], collection: object
|
|
137
|
+
) -> _LazySeq:
|
|
138
|
+
"""Lazily retain values for which ``predicate`` is false/nil."""
|
|
139
|
+
|
|
140
|
+
def produce() -> Iterator[object]:
|
|
141
|
+
for value in _iter_or_empty(collection):
|
|
142
|
+
if not truthy(predicate(value)):
|
|
143
|
+
yield value
|
|
144
|
+
|
|
145
|
+
return lazy_seq(produce)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Reduced(Generic[_Result]):
|
|
149
|
+
"""Value marker returned by a reducing callback to stop traversal."""
|
|
150
|
+
|
|
151
|
+
__slots__ = ("value",)
|
|
152
|
+
|
|
153
|
+
def __init__(self, value: _Result) -> None:
|
|
154
|
+
self.value = value
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def reduced(value: _Result) -> Reduced[_Result]:
|
|
158
|
+
"""Wrap ``value`` as an early result for ``reduce`` or ``fold``."""
|
|
159
|
+
|
|
160
|
+
return Reduced(value)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def reduced_p(value: object) -> bool:
|
|
164
|
+
"""Return whether ``value`` carries the reduction stop marker."""
|
|
165
|
+
|
|
166
|
+
return isinstance(value, Reduced)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def unreduced(value: Union[_Result, Reduced[_Result]]) -> _Result:
|
|
170
|
+
"""Remove one reduction marker, leaving ordinary values unchanged."""
|
|
171
|
+
|
|
172
|
+
if isinstance(value, Reduced):
|
|
173
|
+
return value.value
|
|
174
|
+
return value
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _reduce_with_initial(
|
|
178
|
+
function: Callable[[object, object], object],
|
|
179
|
+
initial: object,
|
|
180
|
+
collection: Iterable[object],
|
|
181
|
+
) -> object:
|
|
182
|
+
accumulator = initial
|
|
183
|
+
for item in collection:
|
|
184
|
+
accumulator = function(accumulator, item)
|
|
185
|
+
if isinstance(accumulator, Reduced):
|
|
186
|
+
return accumulator.value
|
|
187
|
+
return accumulator
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def reduce(function: Callable[[object, object], object], *arguments: object) -> object:
|
|
191
|
+
"""Reduce ``collection`` with an optional initial accumulator."""
|
|
192
|
+
|
|
193
|
+
if len(arguments) == 1:
|
|
194
|
+
iterator = _iter_or_empty(arguments[0])
|
|
195
|
+
try:
|
|
196
|
+
initial = _builtins.next(iterator)
|
|
197
|
+
except StopIteration:
|
|
198
|
+
raise TypeError("reduce() of empty iterable with no initial value") from None
|
|
199
|
+
return _reduce_with_initial(function, initial, iterator)
|
|
200
|
+
if len(arguments) == 2:
|
|
201
|
+
return _reduce_with_initial(
|
|
202
|
+
function,
|
|
203
|
+
arguments[0],
|
|
204
|
+
_iter_or_empty(arguments[1]),
|
|
205
|
+
)
|
|
206
|
+
raise TypeError("reduce expects a function, collection, and optional initial value")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def fold(
|
|
210
|
+
function: Callable[[object, object], object],
|
|
211
|
+
initial: object,
|
|
212
|
+
collection: Iterable[object],
|
|
213
|
+
) -> object:
|
|
214
|
+
"""Named initial-value form of ``reduce``."""
|
|
215
|
+
|
|
216
|
+
return _reduce_with_initial(function, initial, collection)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class _RecurSignal:
|
|
220
|
+
"""Private value used by ``loop`` to carry the next state tuple."""
|
|
221
|
+
|
|
222
|
+
__slots__ = ("values",)
|
|
223
|
+
|
|
224
|
+
def __init__(self, values: tuple[object, ...]) -> None:
|
|
225
|
+
self.values = values
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def recur(*values: object) -> _RecurSignal:
|
|
229
|
+
"""Return a control token consumed by :func:`loop`."""
|
|
230
|
+
|
|
231
|
+
return _RecurSignal(tuple(values))
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def loop(function: Callable[..., _Result], *initial: object) -> _Result:
|
|
235
|
+
"""Run a Clojure-style state loop without growing the Python stack."""
|
|
236
|
+
|
|
237
|
+
state = tuple(initial)
|
|
238
|
+
while True:
|
|
239
|
+
result = function(*state)
|
|
240
|
+
if not isinstance(result, _RecurSignal):
|
|
241
|
+
return result
|
|
242
|
+
if len(result.values) != len(state):
|
|
243
|
+
raise TypeError(
|
|
244
|
+
"recur must supply the same number of values as loop bindings"
|
|
245
|
+
)
|
|
246
|
+
state = result.values
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def trampoline(function: Callable[..., object], *arguments: object) -> object:
|
|
250
|
+
"""Invoke a function, then bounce through zero-argument callables."""
|
|
251
|
+
|
|
252
|
+
result = function(*arguments)
|
|
253
|
+
while callable(result):
|
|
254
|
+
result = result()
|
|
255
|
+
return result
|