Skip to content

Commit 9cd2977

Browse files
authored
feat: add Object and sparse Array methods (#17)
* feat: establish JavaScript values and packaging * feat: add Object and sparse Array methods
1 parent 1efe84f commit 9cd2977

5 files changed

Lines changed: 763 additions & 0 deletions

File tree

‎src/JavaScript/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""JavaScript-style objects, methods and functions for Python."""
22

3+
from ._array import Array
4+
from ._object import Object
35
from ._values import (
46
Infinity,
57
NaN,
@@ -13,6 +15,7 @@
1315
)
1416

1517
__all__ = [
18+
"Array", "Object",
1619
"Infinity", "NaN", "isFinite", "isNaN", "null", "strictEqual",
1720
"truthy", "typeof", "undefined",
1821
]

‎src/JavaScript/_array.py‎

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
"""Sparse arrays and selected ECMAScript Array algorithms."""
2+
3+
from __future__ import annotations
4+
5+
from functools import cmp_to_key
6+
from reprlib import recursive_repr
7+
8+
from ._object import array_index, ordered_keys
9+
from ._values import (
10+
callback, integer, is_number, relative_index, same_value_zero, strictEqual,
11+
to_number, to_string, truthy, undefined,
12+
)
13+
14+
15+
class Array:
16+
"""Identity-based sparse indexed values; use Array.of for one numeric item."""
17+
18+
__slots__ = ("_items", "_length", "_properties")
19+
20+
def __init__(self, *values):
21+
self._items = {}
22+
self._properties = {}
23+
self._length = 0
24+
if len(values) == 1 and is_number(values[0]):
25+
self.length = values[0]
26+
else:
27+
self.push(*values)
28+
29+
@property
30+
def length(self):
31+
return self._length
32+
33+
@length.setter
34+
def length(self, value):
35+
number = to_number(value)
36+
if not 0 <= number <= 2**32 - 1 or number != int(number):
37+
raise ValueError("Invalid array length")
38+
length = int(number)
39+
for index in list(self._items):
40+
if index >= length:
41+
del self._items[index]
42+
self._length = length
43+
44+
def __getitem__(self, key):
45+
key = to_string(key)
46+
if key == "length":
47+
return self.length
48+
index = array_index(key)
49+
return self._items.get(index, undefined) if index is not None else self._properties.get(key, undefined)
50+
51+
def __setitem__(self, key, value):
52+
key = to_string(key)
53+
if key == "length":
54+
self.length = value
55+
return
56+
index = array_index(key)
57+
if index is None:
58+
self._properties[key] = value
59+
else:
60+
self._items[index] = value
61+
self._length = max(self._length, index + 1)
62+
63+
def __delitem__(self, key):
64+
key = to_string(key)
65+
if key == "length":
66+
raise TypeError("Cannot delete array length")
67+
index = array_index(key)
68+
(self._items if index is not None else self._properties).pop(index if index is not None else key, None)
69+
70+
def __contains__(self, key):
71+
return self._js_has_own(to_string(key))
72+
73+
def _js_has_own(self, key):
74+
index = array_index(key)
75+
return key == "length" or (index in self._items if index is not None else key in self._properties)
76+
77+
def _js_entries(self):
78+
return [(str(i), self._items[i]) for i in sorted(self._items)] + [(key, self._properties[key]) for key in ordered_keys(self._properties)]
79+
80+
def __iter__(self):
81+
index = 0
82+
while index < self.length:
83+
yield self[index]
84+
index += 1
85+
86+
def __len__(self):
87+
return self.length
88+
89+
def __bool__(self):
90+
return True
91+
92+
@recursive_repr("Array(...)")
93+
def __repr__(self):
94+
return f"Array(length={self.length}, items={self._items!r})"
95+
96+
@recursive_repr("")
97+
def _js_primitive(self):
98+
return self.join()
99+
100+
@classmethod
101+
def of(cls, *values):
102+
result = cls()
103+
result.push(*values)
104+
return result
105+
106+
@classmethod
107+
def from_(cls, source, mapFn=undefined):
108+
transform = callback(mapFn) if mapFn is not undefined else None
109+
result = cls()
110+
for index, value in enumerate(source):
111+
result.push(transform(value, index) if transform else value)
112+
return result
113+
114+
@staticmethod
115+
def isArray(value):
116+
return isinstance(value, Array)
117+
118+
def at(self, index=undefined):
119+
index = integer(index)
120+
if index < 0:
121+
index += self.length
122+
return self[index] if 0 <= index < self.length else undefined
123+
124+
def push(self, *values):
125+
if self.length + len(values) > 2**32 - 1:
126+
raise ValueError("Invalid array length")
127+
for value in values:
128+
self[self.length] = value
129+
return self.length
130+
131+
def pop(self):
132+
if not self.length:
133+
return undefined
134+
index = self.length - 1
135+
value = self[index]
136+
self._items.pop(index, None)
137+
self._length -= 1
138+
return value
139+
140+
def shift(self):
141+
if not self.length:
142+
return undefined
143+
value = self[0]
144+
self._items = {i - 1: item for i, item in self._items.items() if i > 0}
145+
self._length -= 1
146+
return value
147+
148+
def unshift(self, *values):
149+
if self.length + len(values) > 2**32 - 1:
150+
raise ValueError("Invalid array length")
151+
self._items = {i + len(values): item for i, item in self._items.items()}
152+
self._items.update(enumerate(values))
153+
self._length += len(values)
154+
return self.length
155+
156+
def slice(self, start=0, end=undefined):
157+
length = self.length
158+
start = relative_index(start, length)
159+
end = length if end is undefined else relative_index(end, length)
160+
result = Array(max(end - start, 0))
161+
for i in range(start, end):
162+
if i in self._items:
163+
result[i - start] = self[i]
164+
return result
165+
166+
def splice(self, *args):
167+
if not args:
168+
return Array()
169+
start = relative_index(args[0], self.length)
170+
count = self.length - start if len(args) == 1 else max(0, min(integer(args[1]), self.length - start))
171+
values = args[2:]
172+
new_length = self.length - count + len(values)
173+
if new_length > 2**32 - 1:
174+
raise ValueError("Invalid array length")
175+
removed = self.slice(start, start + count)
176+
items = {i: value for i, value in self._items.items() if i < start}
177+
items.update({i - count + len(values): value for i, value in self._items.items() if i >= start + count})
178+
items.update({start + i: value for i, value in enumerate(values)})
179+
self._items, self._length = items, new_length
180+
return removed
181+
182+
def concat(self, *values):
183+
result = self.slice()
184+
for value in values:
185+
if isinstance(value, Array):
186+
start = result.length
187+
result.length += value.length
188+
for i, child in value._items.items():
189+
result[start + i] = child
190+
else:
191+
result.push(value)
192+
return result
193+
194+
def join(self, separator=undefined):
195+
separator = "," if separator is undefined else to_string(separator)
196+
return separator.join("" if value is undefined or value is None else to_string(value) for value in self)
197+
198+
def toString(self):
199+
return self.join()
200+
201+
def includes(self, value=undefined, fromIndex=0):
202+
start = relative_index(fromIndex, self.length)
203+
return any(same_value_zero(self[i], value) for i in range(start, self.length))
204+
205+
def indexOf(self, value=undefined, fromIndex=0):
206+
for i in range(relative_index(fromIndex, self.length), self.length):
207+
if i in self._items and strictEqual(self[i], value):
208+
return i
209+
return -1
210+
211+
def lastIndexOf(self, value=undefined, *args):
212+
start = self.length - 1 if not args else integer(args[0])
213+
if start < 0:
214+
start += self.length
215+
start = min(start, self.length - 1)
216+
if start < 0:
217+
return -1
218+
for i in range(start, -1, -1):
219+
if i in self._items and strictEqual(self[i], value):
220+
return i
221+
return -1
222+
223+
def map(self, function):
224+
transform = callback(function)
225+
result = Array(self.length)
226+
for i in range(self.length):
227+
if i in self._items:
228+
result[i] = transform(self[i], i, self)
229+
return result
230+
231+
def filter(self, function):
232+
predicate = callback(function)
233+
result = Array()
234+
for i in range(self.length):
235+
if i in self._items:
236+
value = self[i]
237+
if truthy(predicate(value, i, self)):
238+
result.push(value)
239+
return result
240+
241+
def forEach(self, function):
242+
visit = callback(function)
243+
for i in range(self.length):
244+
if i in self._items:
245+
visit(self[i], i, self)
246+
return undefined
247+
248+
def some(self, function):
249+
predicate = callback(function)
250+
return any(i in self._items and truthy(predicate(self[i], i, self)) for i in range(self.length))
251+
252+
def every(self, function):
253+
predicate = callback(function)
254+
return all(i not in self._items or truthy(predicate(self[i], i, self)) for i in range(self.length))
255+
256+
def _find(self, function, reverse=False):
257+
predicate = callback(function)
258+
indices = range(self.length - 1, -1, -1) if reverse else range(self.length)
259+
for i in indices:
260+
value = self[i]
261+
if truthy(predicate(value, i, self)):
262+
return i, value
263+
return -1, undefined
264+
265+
def find(self, function):
266+
return self._find(function)[1]
267+
268+
def findIndex(self, function):
269+
return self._find(function)[0]
270+
271+
def findLast(self, function):
272+
return self._find(function, True)[1]
273+
274+
def findLastIndex(self, function):
275+
return self._find(function, True)[0]
276+
277+
def _reduce(self, function, args, reverse=False):
278+
reducer = callback(function)
279+
indices = iter(range(self.length - 1, -1, -1) if reverse else range(self.length))
280+
if args:
281+
accumulator = args[0]
282+
else:
283+
for i in indices:
284+
if i in self._items:
285+
accumulator = self[i]
286+
break
287+
else:
288+
raise TypeError("Reduce of empty array with no initial value")
289+
for i in indices:
290+
if i in self._items:
291+
accumulator = reducer(accumulator, self[i], i, self)
292+
return accumulator
293+
294+
def reduce(self, function, *args):
295+
return self._reduce(function, args)
296+
297+
def reduceRight(self, function, *args):
298+
return self._reduce(function, args, True)
299+
300+
def reverse(self):
301+
self._items = {self.length - i - 1: value for i, value in self._items.items()}
302+
return self
303+
304+
def fill(self, value=undefined, start=0, end=undefined):
305+
end = self.length if end is undefined else relative_index(end, self.length)
306+
for i in range(relative_index(start, self.length), end):
307+
self[i] = value
308+
return self
309+
310+
def sort(self, compareFn=undefined):
311+
compare = callback(compareFn) if compareFn is not undefined else None
312+
values = [self._items[i] for i in sorted(self._items) if self._items[i] is not undefined]
313+
undefined_count = sum(value is undefined for value in self._items.values())
314+
if compare:
315+
def order(a, b):
316+
number = to_number(compare(a, b))
317+
return -1 if number < 0 else 1 if number > 0 else 0
318+
values.sort(key=cmp_to_key(order))
319+
else:
320+
values.sort(key=lambda value: to_string(value).encode("utf-16-be", "surrogatepass"))
321+
self._items = dict(enumerate(values + [undefined] * undefined_count))
322+
return self
323+
324+
def keys(self):
325+
index = 0
326+
while index < self.length:
327+
yield index
328+
index += 1
329+
330+
def values(self):
331+
return iter(self)
332+
333+
def entries(self):
334+
for index in self.keys():
335+
yield Array.of(index, self[index])

0 commit comments

Comments
 (0)