crosshair-tool 0.0.99__cp312-cp312-macosx_10_13_x86_64.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.
- _crosshair_tracers.cpython-312-darwin.so +0 -0
- crosshair/__init__.py +42 -0
- crosshair/__main__.py +8 -0
- crosshair/_mark_stacks.h +790 -0
- crosshair/_preliminaries_test.py +18 -0
- crosshair/_tracers.h +94 -0
- crosshair/_tracers_pycompat.h +522 -0
- crosshair/_tracers_test.py +138 -0
- crosshair/abcstring.py +245 -0
- crosshair/auditwall.py +190 -0
- crosshair/auditwall_test.py +77 -0
- crosshair/codeconfig.py +113 -0
- crosshair/codeconfig_test.py +117 -0
- crosshair/condition_parser.py +1237 -0
- crosshair/condition_parser_test.py +497 -0
- crosshair/conftest.py +30 -0
- crosshair/copyext.py +155 -0
- crosshair/copyext_test.py +84 -0
- crosshair/core.py +1763 -0
- crosshair/core_and_libs.py +149 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +1316 -0
- crosshair/diff_behavior.py +314 -0
- crosshair/diff_behavior_test.py +261 -0
- crosshair/dynamic_typing.py +346 -0
- crosshair/dynamic_typing_test.py +210 -0
- crosshair/enforce.py +282 -0
- crosshair/enforce_test.py +182 -0
- crosshair/examples/PEP316/__init__.py +1 -0
- crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
- crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
- crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
- crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
- crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- crosshair/examples/PEP316/correct_code/arith.py +60 -0
- crosshair/examples/PEP316/correct_code/chess.py +77 -0
- crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
- crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
- crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
- crosshair/examples/PEP316/correct_code/showcase.py +104 -0
- crosshair/examples/__init__.py +0 -0
- crosshair/examples/check_examples_test.py +146 -0
- crosshair/examples/deal/__init__.py +1 -0
- crosshair/examples/icontract/__init__.py +1 -0
- crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
- crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
- crosshair/examples/icontract/correct_code/__init__.py +0 -0
- crosshair/examples/icontract/correct_code/arith.py +51 -0
- crosshair/examples/icontract/correct_code/showcase.py +94 -0
- crosshair/fnutil.py +391 -0
- crosshair/fnutil_test.py +75 -0
- crosshair/fuzz_core_test.py +516 -0
- crosshair/libimpl/__init__.py +0 -0
- crosshair/libimpl/arraylib.py +161 -0
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +23 -0
- crosshair/libimpl/builtinslib.py +5228 -0
- crosshair/libimpl/builtinslib_ch_test.py +1191 -0
- crosshair/libimpl/builtinslib_test.py +3735 -0
- crosshair/libimpl/codecslib.py +86 -0
- crosshair/libimpl/codecslib_test.py +86 -0
- crosshair/libimpl/collectionslib.py +264 -0
- crosshair/libimpl/collectionslib_ch_test.py +252 -0
- crosshair/libimpl/collectionslib_test.py +332 -0
- crosshair/libimpl/copylib.py +23 -0
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +2559 -0
- crosshair/libimpl/datetimelib_ch_test.py +354 -0
- crosshair/libimpl/datetimelib_test.py +112 -0
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/__init__.py +23 -0
- crosshair/libimpl/encodings/_encutil.py +187 -0
- crosshair/libimpl/encodings/ascii.py +44 -0
- crosshair/libimpl/encodings/latin_1.py +40 -0
- crosshair/libimpl/encodings/utf_8.py +93 -0
- crosshair/libimpl/encodings_ch_test.py +83 -0
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +34 -0
- crosshair/libimpl/functoolslib_test.py +56 -0
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +47 -0
- crosshair/libimpl/heapqlib_test.py +21 -0
- crosshair/libimpl/importliblib.py +18 -0
- crosshair/libimpl/importliblib_test.py +38 -0
- crosshair/libimpl/iolib.py +216 -0
- crosshair/libimpl/iolib_ch_test.py +128 -0
- crosshair/libimpl/iolib_test.py +19 -0
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib.py +44 -0
- crosshair/libimpl/itertoolslib_test.py +44 -0
- crosshair/libimpl/jsonlib.py +984 -0
- crosshair/libimpl/jsonlib_ch_test.py +42 -0
- crosshair/libimpl/jsonlib_test.py +51 -0
- crosshair/libimpl/mathlib.py +179 -0
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +67 -0
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +178 -0
- crosshair/libimpl/randomlib_test.py +120 -0
- crosshair/libimpl/relib.py +846 -0
- crosshair/libimpl/relib_ch_test.py +169 -0
- crosshair/libimpl/relib_test.py +493 -0
- crosshair/libimpl/timelib.py +72 -0
- crosshair/libimpl/timelib_test.py +82 -0
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib.py +75 -0
- crosshair/libimpl/unicodedatalib_test.py +42 -0
- crosshair/libimpl/urlliblib.py +23 -0
- crosshair/libimpl/urlliblib_test.py +19 -0
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +261 -0
- crosshair/lsp_server_test.py +30 -0
- crosshair/main.py +973 -0
- crosshair/main_test.py +543 -0
- crosshair/objectproxy.py +376 -0
- crosshair/objectproxy_test.py +41 -0
- crosshair/opcode_intercept.py +601 -0
- crosshair/opcode_intercept_test.py +304 -0
- crosshair/options.py +218 -0
- crosshair/options_test.py +10 -0
- crosshair/patch_equivalence_test.py +75 -0
- crosshair/path_cover.py +209 -0
- crosshair/path_cover_test.py +138 -0
- crosshair/path_search.py +161 -0
- crosshair/path_search_test.py +52 -0
- crosshair/pathing_oracle.py +271 -0
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer.py +27 -0
- crosshair/pure_importer_test.py +16 -0
- crosshair/py.typed +0 -0
- crosshair/register_contract.py +273 -0
- crosshair/register_contract_test.py +190 -0
- crosshair/simplestructs.py +1165 -0
- crosshair/simplestructs_test.py +283 -0
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +1199 -0
- crosshair/statespace_test.py +108 -0
- crosshair/stubs_parser.py +352 -0
- crosshair/stubs_parser_test.py +43 -0
- crosshair/test_util.py +329 -0
- crosshair/test_util_test.py +26 -0
- crosshair/tools/__init__.py +0 -0
- crosshair/tools/check_help_in_doc.py +264 -0
- crosshair/tools/check_init_and_setup_coincide.py +119 -0
- crosshair/tools/generate_demo_table.py +127 -0
- crosshair/tracers.py +544 -0
- crosshair/tracers_test.py +154 -0
- crosshair/type_repo.py +151 -0
- crosshair/unicode_categories.py +589 -0
- crosshair/unicode_categories_test.py +27 -0
- crosshair/util.py +741 -0
- crosshair/util_test.py +173 -0
- crosshair/watcher.py +307 -0
- crosshair/watcher_test.py +107 -0
- crosshair/z3util.py +76 -0
- crosshair/z3util_test.py +11 -0
- crosshair_tool-0.0.99.dist-info/METADATA +144 -0
- crosshair_tool-0.0.99.dist-info/RECORD +176 -0
- crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
- crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
- crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
- crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
import copy
|
|
3
|
+
import dataclasses
|
|
4
|
+
import functools
|
|
5
|
+
import itertools
|
|
6
|
+
import numbers
|
|
7
|
+
import operator
|
|
8
|
+
import sys
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Callable,
|
|
12
|
+
Iterable,
|
|
13
|
+
Mapping,
|
|
14
|
+
MutableMapping,
|
|
15
|
+
MutableSequence,
|
|
16
|
+
Optional,
|
|
17
|
+
Sequence,
|
|
18
|
+
Set,
|
|
19
|
+
Tuple,
|
|
20
|
+
Union,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from crosshair.core import deep_realize
|
|
24
|
+
from crosshair.tracers import NoTracing, ResumedTracing, tracing_iter
|
|
25
|
+
from crosshair.util import (
|
|
26
|
+
CrossHairValue,
|
|
27
|
+
assert_tracing,
|
|
28
|
+
is_hashable,
|
|
29
|
+
is_iterable,
|
|
30
|
+
name_of_type,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MapBase(collections.abc.MutableMapping):
|
|
35
|
+
def __eq__(self, other):
|
|
36
|
+
# Make our own __eq__ because the one in abc will hash all of our keys.
|
|
37
|
+
if not isinstance(other, collections.abc.Mapping):
|
|
38
|
+
return NotImplemented
|
|
39
|
+
if len(self) != len(other):
|
|
40
|
+
return False
|
|
41
|
+
for k, self_value in self.items():
|
|
42
|
+
found = False
|
|
43
|
+
# We do a slow nested loop search because we don't want to hash the key.
|
|
44
|
+
for other_key, other_value in other.items():
|
|
45
|
+
if other_key != k:
|
|
46
|
+
continue
|
|
47
|
+
if self_value == other_value:
|
|
48
|
+
found = True
|
|
49
|
+
break
|
|
50
|
+
else:
|
|
51
|
+
return False
|
|
52
|
+
if not found:
|
|
53
|
+
return False
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
def copy(self):
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
|
|
59
|
+
def __ch_pytype__(self):
|
|
60
|
+
return dict
|
|
61
|
+
|
|
62
|
+
def __ch_realize__(self):
|
|
63
|
+
memo = {}
|
|
64
|
+
return {deep_realize(k, memo): v for k, v in tracing_iter(self.items())}
|
|
65
|
+
|
|
66
|
+
def __ch_deep_realize__(self, memo):
|
|
67
|
+
return {
|
|
68
|
+
deep_realize(k, memo): deep_realize(v, memo)
|
|
69
|
+
for k, v in tracing_iter(self.items())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def __repr__(self):
|
|
73
|
+
contents = ", ".join(f"{repr(k)}: {repr(v)}" for (k, v) in self.items())
|
|
74
|
+
return "{" + contents + "}"
|
|
75
|
+
|
|
76
|
+
if sys.version_info >= (3, 9):
|
|
77
|
+
|
|
78
|
+
def __or__(self, other: Mapping) -> Mapping:
|
|
79
|
+
if not isinstance(other, Mapping):
|
|
80
|
+
raise TypeError
|
|
81
|
+
union_map = self.copy()
|
|
82
|
+
union_map.update(other)
|
|
83
|
+
return union_map
|
|
84
|
+
|
|
85
|
+
__ror__ = __or__
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_MISSING = object()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SimpleDict(MapBase):
|
|
92
|
+
"""
|
|
93
|
+
A pure Python implementation of a dictionary.
|
|
94
|
+
Intentionally does no hashing (linear searches).
|
|
95
|
+
|
|
96
|
+
#inv: set(self.keys()) == set(dict(self.items()).keys())
|
|
97
|
+
|
|
98
|
+
>>> d = SimpleDict([(1, 'one'), (2, 'two')])
|
|
99
|
+
>>> d
|
|
100
|
+
{1: 'one', 2: 'two'}
|
|
101
|
+
>>> d[3] = 'three'
|
|
102
|
+
>>> len(d)
|
|
103
|
+
3
|
|
104
|
+
>>> d[2] = 'cat'
|
|
105
|
+
>>> d[2]
|
|
106
|
+
'cat'
|
|
107
|
+
>>> del d[1]
|
|
108
|
+
>>> list(d.keys())
|
|
109
|
+
[2, 3]
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
contents_: MutableSequence
|
|
113
|
+
|
|
114
|
+
def __init__(self, contents: MutableSequence):
|
|
115
|
+
"""
|
|
116
|
+
Initialize with (key, value) pairs.
|
|
117
|
+
|
|
118
|
+
``contents`` is assumed to not have duplicate keys.
|
|
119
|
+
"""
|
|
120
|
+
self.contents_ = contents
|
|
121
|
+
|
|
122
|
+
def __getitem__(self, key, default=_MISSING):
|
|
123
|
+
if not is_hashable(key):
|
|
124
|
+
raise TypeError("unhashable type")
|
|
125
|
+
for k, v in self.contents_:
|
|
126
|
+
# Note that the identity check below is not just an optimization;
|
|
127
|
+
# it is required to implement the semantics of NaN dict keys
|
|
128
|
+
if k is key or k == key:
|
|
129
|
+
return v
|
|
130
|
+
if default is _MISSING:
|
|
131
|
+
raise KeyError
|
|
132
|
+
return default
|
|
133
|
+
|
|
134
|
+
def __setitem__(self, key, value):
|
|
135
|
+
if not is_hashable(key):
|
|
136
|
+
raise TypeError("unhashable type")
|
|
137
|
+
for i, (k, v) in enumerate(self.contents_):
|
|
138
|
+
if k == key:
|
|
139
|
+
self.contents_[i] = (k, value)
|
|
140
|
+
return
|
|
141
|
+
self.contents_.append((key, value))
|
|
142
|
+
|
|
143
|
+
def __delitem__(self, key):
|
|
144
|
+
if not is_hashable(key):
|
|
145
|
+
raise TypeError("unhashable type")
|
|
146
|
+
for i, (k, v) in enumerate(self.contents_):
|
|
147
|
+
if k == key:
|
|
148
|
+
del self.contents_[i]
|
|
149
|
+
return
|
|
150
|
+
raise KeyError
|
|
151
|
+
|
|
152
|
+
def __iter__(self):
|
|
153
|
+
return (k for (k, v) in self.contents_)
|
|
154
|
+
|
|
155
|
+
def __reversed__(self):
|
|
156
|
+
return (k for (k, v) in reversed(self.contents_))
|
|
157
|
+
|
|
158
|
+
def __bool__(self):
|
|
159
|
+
return (len(self.contents_) > 0).__bool__()
|
|
160
|
+
|
|
161
|
+
def __len__(self):
|
|
162
|
+
return self.contents_.__len__()
|
|
163
|
+
|
|
164
|
+
def popitem(self):
|
|
165
|
+
if not self.contents_:
|
|
166
|
+
raise KeyError
|
|
167
|
+
(k, v) = self.contents_.pop()
|
|
168
|
+
return (k, v)
|
|
169
|
+
|
|
170
|
+
def copy(self):
|
|
171
|
+
return SimpleDict(self.contents_[:])
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
_DELETED = object()
|
|
175
|
+
_NOT_FOUND = object()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ShellMutableMap(MapBase, collections.abc.MutableMapping):
|
|
179
|
+
def __init__(self, inner: Mapping):
|
|
180
|
+
self._mutations: MutableMapping = SimpleDict([])
|
|
181
|
+
self._inner = inner
|
|
182
|
+
self._len = inner.__len__()
|
|
183
|
+
|
|
184
|
+
def __getitem__(self, key):
|
|
185
|
+
ret = self._mutations.get(key, _NOT_FOUND)
|
|
186
|
+
if ret is _DELETED:
|
|
187
|
+
raise KeyError
|
|
188
|
+
elif ret is _NOT_FOUND:
|
|
189
|
+
return self._inner.__getitem__(key)
|
|
190
|
+
else:
|
|
191
|
+
return ret
|
|
192
|
+
|
|
193
|
+
if sys.version_info >= (3, 8):
|
|
194
|
+
|
|
195
|
+
def __reversed__(self):
|
|
196
|
+
return self._reversed()
|
|
197
|
+
|
|
198
|
+
def _reversed(self):
|
|
199
|
+
deleted = []
|
|
200
|
+
mutations = self._mutations
|
|
201
|
+
for k in reversed(mutations):
|
|
202
|
+
if mutations[k] is _DELETED:
|
|
203
|
+
deleted.append(k)
|
|
204
|
+
continue
|
|
205
|
+
else:
|
|
206
|
+
yield k
|
|
207
|
+
inner = self._inner
|
|
208
|
+
for k in reversed(inner):
|
|
209
|
+
if k in deleted:
|
|
210
|
+
continue
|
|
211
|
+
else:
|
|
212
|
+
yield k
|
|
213
|
+
|
|
214
|
+
def __iter__(self):
|
|
215
|
+
mutations = self._mutations
|
|
216
|
+
suppress = list(mutations.keys()) # check against list to avoid hash
|
|
217
|
+
for k in self._inner:
|
|
218
|
+
if k not in suppress:
|
|
219
|
+
yield k
|
|
220
|
+
for k, v in self._mutations.items():
|
|
221
|
+
if v is not _DELETED:
|
|
222
|
+
yield k
|
|
223
|
+
|
|
224
|
+
def __eq__(self, other):
|
|
225
|
+
if not self._mutations:
|
|
226
|
+
return self._inner.__eq__(other)
|
|
227
|
+
if not isinstance(other, collections.abc.Mapping):
|
|
228
|
+
return False
|
|
229
|
+
if len(self) != len(other):
|
|
230
|
+
return False
|
|
231
|
+
for k, v in other.items():
|
|
232
|
+
if k not in self or self[k] != v:
|
|
233
|
+
return False
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
def __bool__(self):
|
|
237
|
+
return bool(self._len > 0)
|
|
238
|
+
|
|
239
|
+
def __len__(self):
|
|
240
|
+
return self._len
|
|
241
|
+
|
|
242
|
+
def __setitem__(self, key, val):
|
|
243
|
+
if key not in self:
|
|
244
|
+
self._len += 1
|
|
245
|
+
self._mutations[key] = val
|
|
246
|
+
|
|
247
|
+
def __delitem__(self, key):
|
|
248
|
+
first_hit = self._mutations.get(key, _NOT_FOUND)
|
|
249
|
+
if first_hit is _DELETED:
|
|
250
|
+
raise KeyError
|
|
251
|
+
if first_hit is _NOT_FOUND:
|
|
252
|
+
if key not in self._inner:
|
|
253
|
+
raise KeyError
|
|
254
|
+
self._mutations[key] = _DELETED
|
|
255
|
+
self._len -= 1
|
|
256
|
+
|
|
257
|
+
def _lastitem(self):
|
|
258
|
+
raise KeyError
|
|
259
|
+
|
|
260
|
+
def pop(self, key, default=_MISSING):
|
|
261
|
+
# CPython checks the empty case before attempting to hash the key.
|
|
262
|
+
# So this must happen before the hash-ability check:
|
|
263
|
+
if self._len > 0:
|
|
264
|
+
try:
|
|
265
|
+
value = self[key]
|
|
266
|
+
except KeyError:
|
|
267
|
+
pass
|
|
268
|
+
else:
|
|
269
|
+
del self[key]
|
|
270
|
+
return value
|
|
271
|
+
# Not found:
|
|
272
|
+
if default is _MISSING:
|
|
273
|
+
raise KeyError(key)
|
|
274
|
+
return default
|
|
275
|
+
|
|
276
|
+
def popitem(self):
|
|
277
|
+
for key in self._reversed():
|
|
278
|
+
val = self.__getitem__(key)
|
|
279
|
+
self.__delitem__(key)
|
|
280
|
+
return (key, val)
|
|
281
|
+
raise KeyError
|
|
282
|
+
|
|
283
|
+
def copy(self):
|
|
284
|
+
m = ShellMutableMap(self._inner)
|
|
285
|
+
m._mutations = self._mutations.copy()
|
|
286
|
+
return m
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def normalize_idx(idx: Any, container_len: int) -> int:
|
|
290
|
+
if (idx is not None) and (not hasattr(idx, "__index__")):
|
|
291
|
+
raise TypeError("indices must be integers or slices")
|
|
292
|
+
if idx < 0:
|
|
293
|
+
return idx + container_len
|
|
294
|
+
return idx
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def check_idx(idx: Any, container_len: int) -> int:
|
|
298
|
+
if not hasattr(idx, "__index__"):
|
|
299
|
+
raise TypeError("indices must be integers or slices, not str")
|
|
300
|
+
normalized_idx = normalize_idx(idx, container_len)
|
|
301
|
+
if 0 <= normalized_idx < container_len:
|
|
302
|
+
return normalized_idx
|
|
303
|
+
raise IndexError
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def clamp_slice(s: slice, container_len: int) -> slice:
|
|
307
|
+
if s.step < 0:
|
|
308
|
+
if s.start < 0 or s.stop >= container_len - 1:
|
|
309
|
+
return slice(0, 0, s.step)
|
|
310
|
+
|
|
311
|
+
def clamper(i):
|
|
312
|
+
if i < 0:
|
|
313
|
+
return None
|
|
314
|
+
if i >= container_len:
|
|
315
|
+
return container_len - 1
|
|
316
|
+
return i
|
|
317
|
+
|
|
318
|
+
else:
|
|
319
|
+
|
|
320
|
+
def clamper(i):
|
|
321
|
+
if i < 0:
|
|
322
|
+
return 0
|
|
323
|
+
if i > container_len:
|
|
324
|
+
return container_len
|
|
325
|
+
return i
|
|
326
|
+
|
|
327
|
+
return slice(clamper(s.start), clamper(s.stop), s.step)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def offset_slice(s: slice, offset: int) -> slice:
|
|
331
|
+
return slice(s.start + offset, s.stop + offset, s.step)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def compose_slices(prelen: int, postlen: int, s: slice):
|
|
335
|
+
"""Transform a slice to apply to a larger sequence."""
|
|
336
|
+
start, stop = s.start, s.stop
|
|
337
|
+
if start >= 0:
|
|
338
|
+
start += prelen
|
|
339
|
+
else:
|
|
340
|
+
start -= prelen
|
|
341
|
+
if stop >= 0:
|
|
342
|
+
stop += prelen
|
|
343
|
+
else:
|
|
344
|
+
stop -= postlen
|
|
345
|
+
return slice(start, stop, s.step)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def cut_slice(start: int, stop: int, step: int, cut: int) -> Tuple[slice, slice]:
|
|
349
|
+
backwards = step < 0
|
|
350
|
+
if backwards:
|
|
351
|
+
start, stop, step, cut = -start, -stop, -step, -cut
|
|
352
|
+
# Modulous with negatives is super hard to reason about, shift everything >= 0:
|
|
353
|
+
delta = -min(start, stop, cut)
|
|
354
|
+
start, stop, cut = start + delta, stop + delta, cut + delta
|
|
355
|
+
if cut < start:
|
|
356
|
+
lstart, lstop = cut, cut
|
|
357
|
+
rstart, rstop = start, stop
|
|
358
|
+
elif cut > stop:
|
|
359
|
+
lstart, lstop = start, stop
|
|
360
|
+
rstart, rstop = cut, cut
|
|
361
|
+
else:
|
|
362
|
+
mid = min(cut, stop)
|
|
363
|
+
lstart, lstop = start, mid
|
|
364
|
+
empties_at_tail = mid % step
|
|
365
|
+
if empties_at_tail > 0:
|
|
366
|
+
mid += step - empties_at_tail
|
|
367
|
+
rstart = mid
|
|
368
|
+
rstop = stop
|
|
369
|
+
lstart, lstop = lstart - delta, lstop - delta
|
|
370
|
+
rstart, rstop = rstart - delta, rstop - delta
|
|
371
|
+
if backwards:
|
|
372
|
+
lstart, lstop = -lstart, -lstop
|
|
373
|
+
rstart, rstop = -rstart, -rstop
|
|
374
|
+
step = -step
|
|
375
|
+
return (slice(lstart, lstop, step), slice(rstart, rstop, step))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def indices(s: slice, container_len: int) -> Tuple[int, int, int]:
|
|
379
|
+
"""
|
|
380
|
+
(Mostly) mimic ``slice.indices``.
|
|
381
|
+
|
|
382
|
+
This is a pure Python version of ``slice.indices()`` that doesn't force integers
|
|
383
|
+
into existence.
|
|
384
|
+
Note that, unlike `slice.indices`, this function does not "clamp" the index to the
|
|
385
|
+
range [0, container_len).
|
|
386
|
+
"""
|
|
387
|
+
start, stop, step = s.start, s.stop, s.step
|
|
388
|
+
if (step is not None) and (not hasattr(step, "__index__")):
|
|
389
|
+
raise TypeError(
|
|
390
|
+
"slice indices must be integers or None or have an __index__ method"
|
|
391
|
+
)
|
|
392
|
+
if step is None:
|
|
393
|
+
step = 1
|
|
394
|
+
elif step <= 0:
|
|
395
|
+
# fallback to python implementation (this will realize values)
|
|
396
|
+
return s.indices(container_len)
|
|
397
|
+
return (
|
|
398
|
+
0 if start is None else normalize_idx(start, container_len),
|
|
399
|
+
container_len if stop is None else normalize_idx(stop, container_len),
|
|
400
|
+
step,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@functools.total_ordering
|
|
405
|
+
class SeqBase(CrossHairValue):
|
|
406
|
+
def __hash__(self):
|
|
407
|
+
# TODO: test
|
|
408
|
+
return hash(tuple(self))
|
|
409
|
+
|
|
410
|
+
def __eq__(self, other):
|
|
411
|
+
if self is other:
|
|
412
|
+
return True
|
|
413
|
+
if not is_iterable(other):
|
|
414
|
+
return False
|
|
415
|
+
if len(self) != len(other):
|
|
416
|
+
return False
|
|
417
|
+
for myval, otherval in zip(self, other):
|
|
418
|
+
if myval is otherval:
|
|
419
|
+
continue
|
|
420
|
+
if myval != otherval:
|
|
421
|
+
return False
|
|
422
|
+
return True
|
|
423
|
+
|
|
424
|
+
def __lt__(self, other):
|
|
425
|
+
# NOTE: subclasses will need further type restrictions.
|
|
426
|
+
# For example, `[1,2] <= (1,2)` raises a TypeError.
|
|
427
|
+
if not is_iterable(other):
|
|
428
|
+
return NotImplemented
|
|
429
|
+
for v1, v2 in zip(self, other):
|
|
430
|
+
if v1 == v2:
|
|
431
|
+
continue
|
|
432
|
+
return v1 < v2
|
|
433
|
+
return len(self) < len(other)
|
|
434
|
+
|
|
435
|
+
def __bool__(self):
|
|
436
|
+
return bool(self.__len__() > 0)
|
|
437
|
+
|
|
438
|
+
def __add__(self, other):
|
|
439
|
+
if isinstance(other, collections.abc.Sequence):
|
|
440
|
+
return concatenate_sequences(self, other)
|
|
441
|
+
raise TypeError(f"unsupported operand type(s) for +")
|
|
442
|
+
|
|
443
|
+
def __radd__(self, other):
|
|
444
|
+
if isinstance(other, collections.abc.Sequence):
|
|
445
|
+
return concatenate_sequences(other, self)
|
|
446
|
+
raise TypeError(f"unsupported operand type(s) for +")
|
|
447
|
+
|
|
448
|
+
def __mul__(self, other):
|
|
449
|
+
if not isinstance(other, int):
|
|
450
|
+
raise TypeError("can't multiply by non-int xx")
|
|
451
|
+
if other <= 0:
|
|
452
|
+
# A trick to get an empty thing of the same type!:
|
|
453
|
+
return self[0:0]
|
|
454
|
+
ret = self
|
|
455
|
+
for idx in range(1, other):
|
|
456
|
+
ret = self.__add__(ret)
|
|
457
|
+
return ret
|
|
458
|
+
|
|
459
|
+
def __rmul__(self, other):
|
|
460
|
+
return self.__mul__(other)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@dataclasses.dataclass(eq=False)
|
|
464
|
+
class SequenceConcatenation(collections.abc.Sequence, SeqBase):
|
|
465
|
+
_first: Sequence
|
|
466
|
+
_second: Sequence
|
|
467
|
+
_len: Optional[int] = None
|
|
468
|
+
|
|
469
|
+
def __ch_pytype__(self):
|
|
470
|
+
return tuple
|
|
471
|
+
|
|
472
|
+
def __getitem__(self, i: Union[int, slice]):
|
|
473
|
+
"""Get the item from the concatenation."""
|
|
474
|
+
first, second = self._first, self._second
|
|
475
|
+
firstlen, secondlen = len(first), len(second)
|
|
476
|
+
totallen = firstlen + secondlen
|
|
477
|
+
if isinstance(i, int):
|
|
478
|
+
i = check_idx(i, totallen)
|
|
479
|
+
return first[i] if i < firstlen else second[i - firstlen]
|
|
480
|
+
else:
|
|
481
|
+
if i.step is None or i.step > 0:
|
|
482
|
+
# This block is functionally redundant with the more general
|
|
483
|
+
# logic afterwards. It exists for additional efficency when
|
|
484
|
+
# we can easily slice using one side or the other.
|
|
485
|
+
if i.stop is not None and 0 <= i.stop <= firstlen:
|
|
486
|
+
if i.start is None or i.start >= 0:
|
|
487
|
+
return first.__getitem__(i)
|
|
488
|
+
if i.start < -secondlen:
|
|
489
|
+
return first.__getitem__(
|
|
490
|
+
slice(i.start + secondlen, i.stop, i.step)
|
|
491
|
+
)
|
|
492
|
+
if i.start is not None and i.start >= firstlen:
|
|
493
|
+
return second.__getitem__(
|
|
494
|
+
slice(
|
|
495
|
+
i.start - firstlen,
|
|
496
|
+
(
|
|
497
|
+
i.stop
|
|
498
|
+
if i.stop is None or i.stop < 0
|
|
499
|
+
else i.stop - firstlen
|
|
500
|
+
),
|
|
501
|
+
i.step,
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
start, stop, step = i.indices(totallen)
|
|
505
|
+
cutpoint = firstlen if step > 0 else firstlen - 1
|
|
506
|
+
slice1, slice2 = cut_slice(start, stop, step, cutpoint)
|
|
507
|
+
if step > 0:
|
|
508
|
+
slice1 = clamp_slice(slice1, firstlen)
|
|
509
|
+
slice2 = clamp_slice(offset_slice(slice2, -firstlen), secondlen)
|
|
510
|
+
return concatenate_sequences(first[slice1], second[slice2])
|
|
511
|
+
else:
|
|
512
|
+
slice1 = clamp_slice(offset_slice(slice1, -firstlen), secondlen)
|
|
513
|
+
slice2 = clamp_slice(slice2, firstlen)
|
|
514
|
+
return concatenate_sequences(second[slice1], first[slice2])
|
|
515
|
+
|
|
516
|
+
def __eq__(self, other):
|
|
517
|
+
with NoTracing():
|
|
518
|
+
if not hasattr(other, "__len__"):
|
|
519
|
+
return False
|
|
520
|
+
first, second = self._first, self._second
|
|
521
|
+
if self.__len__() != other.__len__():
|
|
522
|
+
return False
|
|
523
|
+
firstlen = first.__len__()
|
|
524
|
+
return first == other[:firstlen] and second == other[firstlen:]
|
|
525
|
+
|
|
526
|
+
def __contains__(self, item):
|
|
527
|
+
return self._first.__contains__(item) or self._second.__contains__(item)
|
|
528
|
+
|
|
529
|
+
def __iter__(self):
|
|
530
|
+
return itertools.chain(self._first, self._second)
|
|
531
|
+
|
|
532
|
+
def __len__(self):
|
|
533
|
+
if self._len is None:
|
|
534
|
+
self._len = len(self._first) + len(self._second)
|
|
535
|
+
return self._len
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@dataclasses.dataclass(eq=False) # type: ignore # (https://github.com/python/mypy/issues/5374)
|
|
539
|
+
class SliceView(collections.abc.Sequence, SeqBase):
|
|
540
|
+
seq: Sequence
|
|
541
|
+
start: int
|
|
542
|
+
stop: int
|
|
543
|
+
|
|
544
|
+
def __ch_pytype__(self):
|
|
545
|
+
return tuple
|
|
546
|
+
|
|
547
|
+
@staticmethod
|
|
548
|
+
def slice(seq: Sequence, start: int, stop: int) -> Sequence:
|
|
549
|
+
seqlen = seq.__len__()
|
|
550
|
+
left_at_end = start <= 0
|
|
551
|
+
right_at_end = stop >= seqlen
|
|
552
|
+
if left_at_end:
|
|
553
|
+
if right_at_end:
|
|
554
|
+
return seq
|
|
555
|
+
start = 0
|
|
556
|
+
if right_at_end:
|
|
557
|
+
stop = seqlen
|
|
558
|
+
if stop <= start:
|
|
559
|
+
stop = start
|
|
560
|
+
return SliceView(seq, start, stop)
|
|
561
|
+
|
|
562
|
+
def __getitem__(self, key):
|
|
563
|
+
mystart = self.start
|
|
564
|
+
mylen = self.stop - mystart
|
|
565
|
+
if type(key) is slice:
|
|
566
|
+
start, stop, step = indices(key, mylen)
|
|
567
|
+
if step == 1:
|
|
568
|
+
clamped = clamp_slice(slice(start, stop, step), mylen)
|
|
569
|
+
slice_start = mystart + clamped.start
|
|
570
|
+
slice_stop = mystart + clamped.stop
|
|
571
|
+
if slice_stop <= slice_start:
|
|
572
|
+
return SliceView((), 0, 0)
|
|
573
|
+
return SliceView(self.seq, slice_start, slice_stop)
|
|
574
|
+
else:
|
|
575
|
+
return list(self)[key]
|
|
576
|
+
else:
|
|
577
|
+
key = self.start + check_idx(key, mylen)
|
|
578
|
+
return self.seq[key]
|
|
579
|
+
|
|
580
|
+
def __len__(self) -> int:
|
|
581
|
+
return self.stop - self.start
|
|
582
|
+
|
|
583
|
+
def __iter__(self):
|
|
584
|
+
for i in range(self.start, self.stop):
|
|
585
|
+
yield self.seq[i]
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def concatenate_sequences(a: Sequence, b: Sequence) -> Sequence:
|
|
589
|
+
with NoTracing():
|
|
590
|
+
if isinstance(a, list):
|
|
591
|
+
if isinstance(b, list):
|
|
592
|
+
return a + b
|
|
593
|
+
elif isinstance(b, SequenceConcatenation) and isinstance(b._first, list):
|
|
594
|
+
return SequenceConcatenation(a + b._first, b._second)
|
|
595
|
+
elif (
|
|
596
|
+
isinstance(a, SequenceConcatenation)
|
|
597
|
+
and isinstance(b, list)
|
|
598
|
+
and isinstance(a._second, list)
|
|
599
|
+
):
|
|
600
|
+
return SequenceConcatenation(a._first, a._second + b)
|
|
601
|
+
return SequenceConcatenation(a, b)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def sequence_evaluation(seq: Sequence):
|
|
605
|
+
with NoTracing():
|
|
606
|
+
if is_hashable(seq):
|
|
607
|
+
return seq # immutable datastructures are fine
|
|
608
|
+
elif isinstance(seq, ShellMutableSequence):
|
|
609
|
+
return seq.inner
|
|
610
|
+
else:
|
|
611
|
+
return list(seq) # TODO: use tracing_iter() here?
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
@dataclasses.dataclass(eq=False)
|
|
615
|
+
class ShellMutableSequence(collections.abc.MutableSequence, SeqBase):
|
|
616
|
+
"""
|
|
617
|
+
Wrap a sequence and provide mutating operations without modifying the original.
|
|
618
|
+
|
|
619
|
+
It reuses portions of the original list as best it can.
|
|
620
|
+
"""
|
|
621
|
+
|
|
622
|
+
inner: Sequence
|
|
623
|
+
|
|
624
|
+
__hash__ = None # type: ignore
|
|
625
|
+
|
|
626
|
+
def _spawn(self, items: Sequence) -> "ShellMutableSequence":
|
|
627
|
+
# For overriding in subclasses.
|
|
628
|
+
return ShellMutableSequence(items)
|
|
629
|
+
|
|
630
|
+
def __eq__(self, other):
|
|
631
|
+
with NoTracing():
|
|
632
|
+
if isinstance(other, ShellMutableSequence):
|
|
633
|
+
other = other.inner
|
|
634
|
+
return self.inner.__eq__(other)
|
|
635
|
+
|
|
636
|
+
def __setitem__(self, k, v):
|
|
637
|
+
inner = self.inner
|
|
638
|
+
old_len = len(inner)
|
|
639
|
+
if isinstance(k, slice):
|
|
640
|
+
if not isinstance(v, collections.abc.Iterable):
|
|
641
|
+
raise TypeError("can only assign an iterable")
|
|
642
|
+
# Make a copy if the argument is a mutable container:
|
|
643
|
+
v = sequence_evaluation(v)
|
|
644
|
+
start, stop, step = indices(k, old_len)
|
|
645
|
+
if step != 1:
|
|
646
|
+
# abort cleverness:
|
|
647
|
+
newinner = list(inner)
|
|
648
|
+
newinner[k] = v
|
|
649
|
+
self.inner = newinner
|
|
650
|
+
return
|
|
651
|
+
else:
|
|
652
|
+
newinner = v
|
|
653
|
+
elif isinstance(k, numbers.Integral):
|
|
654
|
+
k = check_idx(k, old_len)
|
|
655
|
+
start, stop = k, k + 1
|
|
656
|
+
newinner = [v]
|
|
657
|
+
else:
|
|
658
|
+
raise TypeError(
|
|
659
|
+
f'indices must be integers or slices, not "{name_of_type(k)}"'
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
if stop < start:
|
|
663
|
+
stop = start
|
|
664
|
+
# At this point, `stop` >= `start`
|
|
665
|
+
if start > 0:
|
|
666
|
+
newinner = concatenate_sequences(inner[:start], newinner)
|
|
667
|
+
elif stop <= 0:
|
|
668
|
+
stop = 0
|
|
669
|
+
# At this point, `stop` must be >= 0
|
|
670
|
+
if stop < old_len:
|
|
671
|
+
newinner = concatenate_sequences(newinner, inner[stop:])
|
|
672
|
+
self.inner = newinner
|
|
673
|
+
|
|
674
|
+
def __delitem__(self, k):
|
|
675
|
+
if isinstance(k, slice):
|
|
676
|
+
if k.step in (None, 1):
|
|
677
|
+
self.__setitem__(k, [])
|
|
678
|
+
else:
|
|
679
|
+
self.inner = list(self.inner)
|
|
680
|
+
self.inner.__delitem__(k)
|
|
681
|
+
else:
|
|
682
|
+
mylen = self.inner.__len__()
|
|
683
|
+
idx = check_idx(k, mylen)
|
|
684
|
+
self.__setitem__(slice(idx, idx + 1, 1), [])
|
|
685
|
+
|
|
686
|
+
def __add__(self, other):
|
|
687
|
+
if isinstance(other, collections.abc.Sequence):
|
|
688
|
+
return self._spawn(
|
|
689
|
+
concatenate_sequences(self.inner, sequence_evaluation(other))
|
|
690
|
+
)
|
|
691
|
+
raise TypeError(f"unsupported operand type(s) for +")
|
|
692
|
+
|
|
693
|
+
def __radd__(self, other):
|
|
694
|
+
if isinstance(other, collections.abc.Sequence):
|
|
695
|
+
return self._spawn(
|
|
696
|
+
concatenate_sequences(sequence_evaluation(other), self.inner)
|
|
697
|
+
)
|
|
698
|
+
raise TypeError(f"unsupported operand type(s) for +")
|
|
699
|
+
|
|
700
|
+
def __imul__(self, other):
|
|
701
|
+
return self._spawn(self * other)
|
|
702
|
+
|
|
703
|
+
def append(self, item):
|
|
704
|
+
inner = self.inner
|
|
705
|
+
self.inner = concatenate_sequences(inner, [item])
|
|
706
|
+
|
|
707
|
+
def extend(self, other):
|
|
708
|
+
if not isinstance(other, collections.abc.Iterable):
|
|
709
|
+
raise TypeError("object is not iterable")
|
|
710
|
+
self.inner = concatenate_sequences(self.inner, sequence_evaluation(other))
|
|
711
|
+
|
|
712
|
+
def index(self, *a) -> int:
|
|
713
|
+
return self.inner.index(*a)
|
|
714
|
+
|
|
715
|
+
def sort(self, key=None, reverse=False):
|
|
716
|
+
self.inner = sorted(self.inner, key=key, reverse=reverse)
|
|
717
|
+
|
|
718
|
+
def copy(self):
|
|
719
|
+
return self[:]
|
|
720
|
+
|
|
721
|
+
def __len__(self):
|
|
722
|
+
return self.inner.__len__()
|
|
723
|
+
|
|
724
|
+
def insert(self, index, item):
|
|
725
|
+
self.__setitem__(slice(index, index, 1), [item])
|
|
726
|
+
|
|
727
|
+
def __getitem__(self, key):
|
|
728
|
+
if isinstance(key, slice):
|
|
729
|
+
return self._spawn(self.inner.__getitem__(key))
|
|
730
|
+
else:
|
|
731
|
+
return self.inner.__getitem__(key)
|
|
732
|
+
|
|
733
|
+
def __repr__(self):
|
|
734
|
+
contents = ", ".join(map(repr, self))
|
|
735
|
+
return f"[{contents}]"
|
|
736
|
+
|
|
737
|
+
def __contains__(self, other):
|
|
738
|
+
return self.inner.__contains__(other)
|
|
739
|
+
|
|
740
|
+
def __iter__(self):
|
|
741
|
+
return self.inner.__iter__()
|
|
742
|
+
|
|
743
|
+
def reverse(self):
|
|
744
|
+
self.inner = list(reversed(self.inner))
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
AbcSet = collections.abc.Set
|
|
748
|
+
AbcMutableSet = collections.abc.MutableSet
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def _force_arg_to_set(x: object) -> AbcSet:
|
|
752
|
+
with NoTracing():
|
|
753
|
+
if isinstance(x, AbcSet):
|
|
754
|
+
while isinstance(x, ShellMutableSet):
|
|
755
|
+
x = x._inner
|
|
756
|
+
if isinstance(x, AbcMutableSet):
|
|
757
|
+
# Already known to have unique elements:
|
|
758
|
+
return LinearSet(list(tracing_iter(x)))
|
|
759
|
+
elif isinstance(x, (frozenset, FrozenSetBase)):
|
|
760
|
+
return x # Immutable set
|
|
761
|
+
if is_iterable(x):
|
|
762
|
+
with ResumedTracing():
|
|
763
|
+
return LinearSet.check_unique_and_create(x)
|
|
764
|
+
raise TypeError
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
class SetBase(CrossHairValue):
|
|
768
|
+
def __bool__(self):
|
|
769
|
+
itr = iter(self)
|
|
770
|
+
try:
|
|
771
|
+
next(itr)
|
|
772
|
+
return True
|
|
773
|
+
except StopIteration:
|
|
774
|
+
return False
|
|
775
|
+
|
|
776
|
+
def __repr__(self):
|
|
777
|
+
return deep_realize(self).__repr__()
|
|
778
|
+
|
|
779
|
+
def __and__(self, x):
|
|
780
|
+
if not isinstance(x, AbcSet):
|
|
781
|
+
return NotImplemented
|
|
782
|
+
return AbcSet.__and__(self, x)
|
|
783
|
+
|
|
784
|
+
def __or__(self, x):
|
|
785
|
+
if not isinstance(x, AbcSet):
|
|
786
|
+
return NotImplemented
|
|
787
|
+
return AbcSet.__or__(self, x)
|
|
788
|
+
|
|
789
|
+
def __xor__(self, x):
|
|
790
|
+
if not isinstance(x, AbcSet):
|
|
791
|
+
return NotImplemented
|
|
792
|
+
return AbcSet.__xor__(self, x)
|
|
793
|
+
|
|
794
|
+
__rxor__ = __xor__
|
|
795
|
+
__ror__ = __or__
|
|
796
|
+
__rand__ = __and__
|
|
797
|
+
|
|
798
|
+
def __sub__(self, x):
|
|
799
|
+
if not isinstance(x, AbcSet):
|
|
800
|
+
return NotImplemented
|
|
801
|
+
return AbcSet.__sub__(self, x)
|
|
802
|
+
|
|
803
|
+
def __rsub__(self, x):
|
|
804
|
+
if not isinstance(x, AbcSet):
|
|
805
|
+
return NotImplemented
|
|
806
|
+
return AbcSet.__rsub__(self, x)
|
|
807
|
+
|
|
808
|
+
def copy(self):
|
|
809
|
+
return copy.copy(self)
|
|
810
|
+
|
|
811
|
+
def difference(self, *itrs):
|
|
812
|
+
for itr in itrs:
|
|
813
|
+
self = self.__sub__(_force_arg_to_set(itr))
|
|
814
|
+
return self
|
|
815
|
+
|
|
816
|
+
def intersection(self, *itrs):
|
|
817
|
+
for itr in itrs:
|
|
818
|
+
self = self.__and__(_force_arg_to_set(itr))
|
|
819
|
+
return self
|
|
820
|
+
|
|
821
|
+
def isdisjoint(self, x):
|
|
822
|
+
return not (self.intersection(x))
|
|
823
|
+
|
|
824
|
+
def issubset(self, x):
|
|
825
|
+
return self <= _force_arg_to_set(x)
|
|
826
|
+
|
|
827
|
+
def issuperset(self, x):
|
|
828
|
+
return self >= _force_arg_to_set(x)
|
|
829
|
+
|
|
830
|
+
def symmetric_difference(self, x):
|
|
831
|
+
return self.__xor__(_force_arg_to_set(x))
|
|
832
|
+
|
|
833
|
+
def union(self, *itrs):
|
|
834
|
+
for itr in itrs:
|
|
835
|
+
self = self.__or__(_force_arg_to_set(itr))
|
|
836
|
+
return self
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
class FrozenSetBase(SetBase, AbcSet):
|
|
840
|
+
def __ch_realize__(self):
|
|
841
|
+
# We are going to have to hash all of our contents,
|
|
842
|
+
# so just realize everything.
|
|
843
|
+
return self.__ch_deep_realize__({})
|
|
844
|
+
|
|
845
|
+
@assert_tracing(False)
|
|
846
|
+
def __ch_deep_realize__(self, memo):
|
|
847
|
+
contents = []
|
|
848
|
+
for item in tracing_iter(self):
|
|
849
|
+
contents.append(deep_realize(item, memo))
|
|
850
|
+
# TODO: This fails to preserve the iteration order;
|
|
851
|
+
# should we do something about that?:
|
|
852
|
+
return frozenset(contents)
|
|
853
|
+
|
|
854
|
+
def __ch_pytype__(self):
|
|
855
|
+
return frozenset
|
|
856
|
+
|
|
857
|
+
@classmethod
|
|
858
|
+
def _from_iterable(cls, it):
|
|
859
|
+
# overrides collections.abc.Set's version
|
|
860
|
+
return LinearSet.check_unique_and_create(it)
|
|
861
|
+
|
|
862
|
+
def __hash__(self):
|
|
863
|
+
return hash(deep_realize(self))
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
class SingletonSet(FrozenSetBase):
|
|
867
|
+
# Primarily this exists to avoid hashing values.
|
|
868
|
+
# TODO: should we fold uses of this into LinearSet, below?
|
|
869
|
+
|
|
870
|
+
def __init__(self, item):
|
|
871
|
+
self._item = item
|
|
872
|
+
|
|
873
|
+
def __contains__(self, x):
|
|
874
|
+
return x == self._item
|
|
875
|
+
|
|
876
|
+
def __iter__(self):
|
|
877
|
+
yield self._item
|
|
878
|
+
|
|
879
|
+
def __len__(self):
|
|
880
|
+
return 1
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
class EmptySet(FrozenSetBase):
|
|
884
|
+
def __contains__(self, x):
|
|
885
|
+
if not is_hashable(x):
|
|
886
|
+
raise TypeError
|
|
887
|
+
return False
|
|
888
|
+
|
|
889
|
+
def __iter__(self):
|
|
890
|
+
return
|
|
891
|
+
yield
|
|
892
|
+
|
|
893
|
+
def __len__(self):
|
|
894
|
+
return 0
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
class LinearSet(FrozenSetBase):
|
|
898
|
+
# Primarily this exists to avoid hashing values.
|
|
899
|
+
# Presumes that its arguments are already unique.
|
|
900
|
+
|
|
901
|
+
def __init__(self, items: Iterable):
|
|
902
|
+
self._items = items
|
|
903
|
+
|
|
904
|
+
@staticmethod
|
|
905
|
+
def check_unique_and_create(seq):
|
|
906
|
+
accepted = []
|
|
907
|
+
# duplicate detection:
|
|
908
|
+
# (alternatively, we could defer using LazySetCombination)
|
|
909
|
+
for item in seq:
|
|
910
|
+
if not is_hashable(item):
|
|
911
|
+
raise TypeError
|
|
912
|
+
if item not in accepted:
|
|
913
|
+
accepted.append(item)
|
|
914
|
+
return LinearSet(accepted)
|
|
915
|
+
|
|
916
|
+
def __contains__(self, x):
|
|
917
|
+
if not is_hashable(x):
|
|
918
|
+
raise TypeError
|
|
919
|
+
for item in self._items:
|
|
920
|
+
if x == item:
|
|
921
|
+
return True
|
|
922
|
+
return False
|
|
923
|
+
|
|
924
|
+
def __iter__(self):
|
|
925
|
+
for item in self._items:
|
|
926
|
+
yield item
|
|
927
|
+
|
|
928
|
+
def __len__(self):
|
|
929
|
+
return len(self._items)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
class LazySetCombination(FrozenSetBase):
|
|
933
|
+
"""
|
|
934
|
+
Provide a view over two sets and a logical operation in-between.
|
|
935
|
+
|
|
936
|
+
The view itself is an immutable set.
|
|
937
|
+
|
|
938
|
+
>>> a = {2, 4, 6 }
|
|
939
|
+
>>> b = { 4, 5, 6, 7}
|
|
940
|
+
>>> s = LazySetCombination(lambda a,b: (a and b), a, b)
|
|
941
|
+
>>> sorted(s)
|
|
942
|
+
[4, 6]
|
|
943
|
+
>>> a.add(5)
|
|
944
|
+
>>> sorted(s)
|
|
945
|
+
[4, 5, 6]
|
|
946
|
+
"""
|
|
947
|
+
|
|
948
|
+
def __init__(self, op: Callable[[bool, bool], bool], a: Set, b: Set):
|
|
949
|
+
self._op = op
|
|
950
|
+
self._a = a
|
|
951
|
+
self._b = b
|
|
952
|
+
|
|
953
|
+
def __contains__(self, x):
|
|
954
|
+
ina = self._a.__contains__(x)
|
|
955
|
+
inb = self._b.__contains__(x)
|
|
956
|
+
return self._op(ina, inb)
|
|
957
|
+
|
|
958
|
+
def __iter__(self):
|
|
959
|
+
op, a, b = self._op, self._a, self._b
|
|
960
|
+
|
|
961
|
+
def afilter(a_item):
|
|
962
|
+
return op(True, a_item in b)
|
|
963
|
+
|
|
964
|
+
def bfilter(b_item):
|
|
965
|
+
ina = b_item in a
|
|
966
|
+
if ina:
|
|
967
|
+
# We've already seen this item and would have returned it
|
|
968
|
+
# while traversing a, if we were supposed to.
|
|
969
|
+
return False
|
|
970
|
+
return op(ina, True)
|
|
971
|
+
|
|
972
|
+
return itertools.chain(filter(afilter, a), filter(bfilter, b))
|
|
973
|
+
|
|
974
|
+
def __len__(self):
|
|
975
|
+
return sum(1 for i in self.__iter__())
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
class ShellMutableSet(SetBase, AbcMutableSet):
|
|
979
|
+
"""
|
|
980
|
+
Provide a mutable view over an immutable set.
|
|
981
|
+
|
|
982
|
+
The mutating operations simply replace the underlying
|
|
983
|
+
data structure entirely.
|
|
984
|
+
This set also attempts to preserve insertion order of the set,
|
|
985
|
+
assuming the underlying set(s) do so as well.
|
|
986
|
+
"""
|
|
987
|
+
|
|
988
|
+
_inner: Union[frozenset, FrozenSetBase]
|
|
989
|
+
|
|
990
|
+
@assert_tracing(False)
|
|
991
|
+
def __init__(self, inner: Iterable = EmptySet()):
|
|
992
|
+
if isinstance(inner, FrozenSetBase):
|
|
993
|
+
self._inner = inner
|
|
994
|
+
elif isinstance(inner, frozenset):
|
|
995
|
+
self._inner = LinearSet(inner)
|
|
996
|
+
elif isinstance(inner, set):
|
|
997
|
+
self._inner = LinearSet(frozenset(inner))
|
|
998
|
+
elif is_iterable(inner):
|
|
999
|
+
with ResumedTracing():
|
|
1000
|
+
self._inner = LinearSet.check_unique_and_create(inner)
|
|
1001
|
+
else:
|
|
1002
|
+
raise TypeError
|
|
1003
|
+
|
|
1004
|
+
def __ch_realize__(self):
|
|
1005
|
+
# Deep realize contents because the real set will want to hash them:
|
|
1006
|
+
return set(map(deep_realize, tracing_iter(self)))
|
|
1007
|
+
|
|
1008
|
+
def __ch_pytype__(self):
|
|
1009
|
+
return set
|
|
1010
|
+
|
|
1011
|
+
# methods that just defer to _inner
|
|
1012
|
+
def __contains__(self, x):
|
|
1013
|
+
return self._inner.__contains__(x)
|
|
1014
|
+
|
|
1015
|
+
def __iter__(self):
|
|
1016
|
+
# TODO: replace _inner after iteration (to avoid recalculation of lazy sub-sets)
|
|
1017
|
+
return self._inner.__iter__()
|
|
1018
|
+
|
|
1019
|
+
def __len__(self):
|
|
1020
|
+
return self._inner.__len__()
|
|
1021
|
+
|
|
1022
|
+
def __le__(self, x):
|
|
1023
|
+
return self._inner.__le__(x)
|
|
1024
|
+
|
|
1025
|
+
def __lt__(self, x):
|
|
1026
|
+
return self._inner.__lt__(x)
|
|
1027
|
+
|
|
1028
|
+
def __eq__(self, x):
|
|
1029
|
+
return self._inner.__eq__(x)
|
|
1030
|
+
|
|
1031
|
+
def __ne__(self, x):
|
|
1032
|
+
return self._inner.__ne__(x)
|
|
1033
|
+
|
|
1034
|
+
def __gt__(self, x):
|
|
1035
|
+
return self._inner.__gt__(x)
|
|
1036
|
+
|
|
1037
|
+
def __ge__(self, x):
|
|
1038
|
+
return self._inner.__ge__(x)
|
|
1039
|
+
|
|
1040
|
+
def isdisjoint(self, x):
|
|
1041
|
+
return self._inner.isdisjoint(x)
|
|
1042
|
+
|
|
1043
|
+
# mutation operations
|
|
1044
|
+
def add(self, x):
|
|
1045
|
+
if not is_hashable(x):
|
|
1046
|
+
raise TypeError(f"unhashable type: '{name_of_type(type(x))}'")
|
|
1047
|
+
self.__ior__(SingletonSet(x))
|
|
1048
|
+
|
|
1049
|
+
def clear(self):
|
|
1050
|
+
self._inner = frozenset()
|
|
1051
|
+
|
|
1052
|
+
def difference_update(self, x):
|
|
1053
|
+
self._inner = self._inner.difference(x)
|
|
1054
|
+
|
|
1055
|
+
def discard(self, x):
|
|
1056
|
+
if not is_hashable(x):
|
|
1057
|
+
raise TypeError(f"unhashable type: '{name_of_type(type(x))}'")
|
|
1058
|
+
self.__isub__(SingletonSet(x))
|
|
1059
|
+
|
|
1060
|
+
def intersection_update(self, x):
|
|
1061
|
+
self._inner = self._inner.intersection(x)
|
|
1062
|
+
|
|
1063
|
+
def pop(self):
|
|
1064
|
+
if self:
|
|
1065
|
+
x = next(iter(self))
|
|
1066
|
+
self.remove(x)
|
|
1067
|
+
return x
|
|
1068
|
+
else:
|
|
1069
|
+
raise KeyError
|
|
1070
|
+
|
|
1071
|
+
def remove(self, x):
|
|
1072
|
+
if x not in self:
|
|
1073
|
+
raise KeyError
|
|
1074
|
+
self.discard(x)
|
|
1075
|
+
|
|
1076
|
+
def symmetric_difference_update(self, x):
|
|
1077
|
+
self._inner = self._inner.symmetric_difference(x)
|
|
1078
|
+
|
|
1079
|
+
def update(self, *iterables):
|
|
1080
|
+
for itr in iterables:
|
|
1081
|
+
additions = _force_arg_to_set(itr)
|
|
1082
|
+
self._inner = LazySetCombination(operator.or_, self._inner, additions)
|
|
1083
|
+
|
|
1084
|
+
def __or__(self, x):
|
|
1085
|
+
with NoTracing():
|
|
1086
|
+
if not isinstance(x, AbcSet):
|
|
1087
|
+
return NotImplemented
|
|
1088
|
+
return ShellMutableSet(
|
|
1089
|
+
LazySetCombination(operator.or_, self._inner, _force_arg_to_set(x))
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
__ror__ = __or__
|
|
1093
|
+
|
|
1094
|
+
def __and__(self, x):
|
|
1095
|
+
with NoTracing():
|
|
1096
|
+
if not isinstance(x, AbcSet):
|
|
1097
|
+
return NotImplemented
|
|
1098
|
+
return ShellMutableSet(
|
|
1099
|
+
LazySetCombination(operator.and_, self._inner, _force_arg_to_set(x))
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
__rand__ = __and__
|
|
1103
|
+
|
|
1104
|
+
def __xor__(self, x):
|
|
1105
|
+
with NoTracing():
|
|
1106
|
+
if not isinstance(x, AbcSet):
|
|
1107
|
+
return NotImplemented
|
|
1108
|
+
return ShellMutableSet(
|
|
1109
|
+
LazySetCombination(operator.xor, self._inner, _force_arg_to_set(x))
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
__rxor__ = __xor__
|
|
1113
|
+
|
|
1114
|
+
def __sub__(self, x):
|
|
1115
|
+
with NoTracing():
|
|
1116
|
+
if not isinstance(x, AbcSet):
|
|
1117
|
+
return NotImplemented
|
|
1118
|
+
# TODO: why not lazy set combination here?
|
|
1119
|
+
return ShellMutableSet(
|
|
1120
|
+
LazySetCombination(lambda x, y: (x and not y), self._inner, x)
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
def __rsub__(self, x):
|
|
1124
|
+
with NoTracing():
|
|
1125
|
+
if not isinstance(x, AbcSet):
|
|
1126
|
+
return NotImplemented
|
|
1127
|
+
return ShellMutableSet(
|
|
1128
|
+
LazySetCombination(lambda x, y: (y and not x), self._inner, x)
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
def __ior__(self, x):
|
|
1132
|
+
with NoTracing():
|
|
1133
|
+
if not isinstance(x, AbcSet):
|
|
1134
|
+
return NotImplemented
|
|
1135
|
+
self._inner = LazySetCombination(
|
|
1136
|
+
operator.or_, self._inner, _force_arg_to_set(x)
|
|
1137
|
+
)
|
|
1138
|
+
return self
|
|
1139
|
+
|
|
1140
|
+
def __iand__(self, x):
|
|
1141
|
+
with NoTracing():
|
|
1142
|
+
if not isinstance(x, AbcSet):
|
|
1143
|
+
return NotImplemented
|
|
1144
|
+
self._inner = LazySetCombination(
|
|
1145
|
+
operator.and_, self._inner, _force_arg_to_set(x)
|
|
1146
|
+
)
|
|
1147
|
+
return self
|
|
1148
|
+
|
|
1149
|
+
def __ixor__(self, x):
|
|
1150
|
+
with NoTracing():
|
|
1151
|
+
if not isinstance(x, AbcSet):
|
|
1152
|
+
return NotImplemented
|
|
1153
|
+
self._inner = LazySetCombination(
|
|
1154
|
+
operator.xor, self._inner, _force_arg_to_set(x)
|
|
1155
|
+
)
|
|
1156
|
+
return self
|
|
1157
|
+
|
|
1158
|
+
def __isub__(self, x):
|
|
1159
|
+
with NoTracing():
|
|
1160
|
+
if not isinstance(x, AbcSet):
|
|
1161
|
+
return NotImplemented
|
|
1162
|
+
self._inner = LazySetCombination(
|
|
1163
|
+
lambda x, y: (x and not y), self._inner, _force_arg_to_set(x)
|
|
1164
|
+
)
|
|
1165
|
+
return self
|