sonolus.py 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.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.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/finalize.py +18 -10
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +24 -0
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
- sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
- sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
- sonolus/backend/{flow.py → optimize/flow.py} +6 -5
- sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
- sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/{passes.py → optimize/passes.py} +1 -1
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +10 -0
- sonolus/backend/visitor.py +360 -101
- sonolus/build/cli.py +14 -3
- sonolus/build/compile.py +8 -8
- sonolus/build/engine.py +10 -5
- sonolus/build/project.py +30 -1
- sonolus/script/archetype.py +429 -138
- sonolus/script/array.py +25 -8
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +73 -11
- sonolus/script/containers.py +234 -51
- sonolus/script/debug.py +8 -8
- sonolus/script/easing.py +147 -105
- sonolus/script/effect.py +60 -0
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +66 -32
- sonolus/script/instruction.py +79 -25
- sonolus/script/internal/builtin_impls.py +138 -27
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +14 -5
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +5 -2
- sonolus/script/{math.py → internal/math_impls.py} +28 -28
- sonolus/script/internal/native.py +3 -3
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/interval.py +234 -16
- sonolus/script/iterator.py +120 -167
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +79 -47
- sonolus/script/options.py +78 -12
- sonolus/script/particle.py +37 -4
- sonolus/script/pointer.py +4 -4
- sonolus/script/print.py +22 -1
- sonolus/script/project.py +59 -0
- sonolus/script/{graphics.py → quad.py} +75 -12
- sonolus/script/record.py +44 -13
- sonolus/script/runtime.py +50 -1
- sonolus/script/sprite.py +198 -115
- sonolus/script/text.py +2 -0
- sonolus/script/timing.py +72 -0
- sonolus/script/transform.py +296 -66
- sonolus/script/ui.py +134 -78
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +118 -3
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/METADATA +1 -1
- sonolus_py-0.1.6.dist-info/RECORD +89 -0
- sonolus/backend/dead_code.py +0 -80
- sonolus/backend/optimize.py +0 -37
- sonolus/backend/simplify.py +0 -47
- sonolus/script/comptime.py +0 -160
- sonolus/script/random.py +0 -14
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.4.dist-info/RECORD +0 -84
- /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/WHEEL +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/licenses/LICENSE +0 -0
sonolus/script/interval.py
CHANGED
|
@@ -3,118 +3,336 @@ from typing import Self
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
4
|
from sonolus.script.debug import error
|
|
5
5
|
from sonolus.script.internal.native import native_function
|
|
6
|
+
from sonolus.script.num import Num
|
|
6
7
|
from sonolus.script.record import Record
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Interval(Record):
|
|
10
|
-
"""A closed interval.
|
|
11
|
+
"""A closed interval.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
```python
|
|
15
|
+
Interval(start: float, end: float)
|
|
16
|
+
```
|
|
17
|
+
"""
|
|
11
18
|
|
|
12
19
|
start: float
|
|
13
20
|
end: float
|
|
14
21
|
|
|
15
22
|
@property
|
|
16
23
|
def length(self) -> float:
|
|
24
|
+
"""The length of the interval.
|
|
25
|
+
|
|
26
|
+
May be negative if the end is less than the start.
|
|
27
|
+
"""
|
|
17
28
|
return self.end - self.start
|
|
18
29
|
|
|
19
30
|
@property
|
|
20
31
|
def is_empty(self) -> bool:
|
|
32
|
+
"""Whether the interval has length of zero or less."""
|
|
21
33
|
return self.start > self.end
|
|
22
34
|
|
|
23
35
|
@property
|
|
24
36
|
def mid(self) -> float:
|
|
37
|
+
"""The midpoint of the interval."""
|
|
25
38
|
return (self.start + self.end) / 2
|
|
26
39
|
|
|
27
40
|
@property
|
|
28
41
|
def tuple(self):
|
|
42
|
+
"""The interval as a tuple."""
|
|
29
43
|
return self.start, self.end
|
|
30
44
|
|
|
31
45
|
def __contains__(self, item: Self | float | int) -> bool:
|
|
46
|
+
"""Check if an item is within the interval.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
item: The item to check. If it is an interval, it must be fully contained within this interval.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
True if the item is within the interval, False otherwise.
|
|
53
|
+
"""
|
|
32
54
|
match item:
|
|
33
55
|
case Interval(start, end):
|
|
34
56
|
return self.start <= start and end <= self.end
|
|
35
|
-
case
|
|
57
|
+
case Num(value):
|
|
36
58
|
return self.start <= value <= self.end
|
|
37
59
|
case _:
|
|
38
60
|
error("Invalid type for interval check")
|
|
39
61
|
|
|
40
62
|
def __add__(self, other: float | int) -> Self:
|
|
63
|
+
"""Add a value to both ends of the interval.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
other: The value to add.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
A new interval with the value added to both ends.
|
|
70
|
+
"""
|
|
41
71
|
return Interval(self.start + other, self.end + other)
|
|
42
72
|
|
|
43
73
|
def __sub__(self, other: float | int) -> Self:
|
|
74
|
+
"""Subtract a value from both ends of the interval.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
other: The value to subtract.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A new interval with the value subtracted from both ends.
|
|
81
|
+
"""
|
|
44
82
|
return Interval(self.start - other, self.end - other)
|
|
45
83
|
|
|
46
84
|
def __mul__(self, other: float | int) -> Self:
|
|
85
|
+
"""Multiply both ends of the interval by a value.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
other: The value to multiply by.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
A new interval with both ends multiplied by the value.
|
|
92
|
+
"""
|
|
47
93
|
return Interval(self.start * other, self.end * other)
|
|
48
94
|
|
|
49
95
|
def __truediv__(self, other: float | int) -> Self:
|
|
96
|
+
"""Divide both ends of the interval by a value.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
other: The value to divide by.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
A new interval with both ends divided by the value.
|
|
103
|
+
"""
|
|
50
104
|
return Interval(self.start / other, self.end / other)
|
|
51
105
|
|
|
52
106
|
def __floordiv__(self, other: float | int) -> Self:
|
|
107
|
+
"""Divide both ends of the interval by a value and floor the result.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
other: The value to divide by.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
A new interval with both ends divided by the value and floored.
|
|
114
|
+
"""
|
|
53
115
|
return Interval(self.start // other, self.end // other)
|
|
54
116
|
|
|
55
117
|
def __and__(self, other: Self) -> Self:
|
|
118
|
+
"""Get the intersection of two intervals.
|
|
119
|
+
|
|
120
|
+
The resulting interval will be empty and may have a negative length if the two intervals do not overlap.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
other: The other interval.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
A new interval representing the intersection of the two intervals.
|
|
127
|
+
"""
|
|
56
128
|
return Interval(max(self.start, other.start), min(self.end, other.end))
|
|
57
129
|
|
|
58
130
|
def shrink(self, value: float | int) -> Self:
|
|
131
|
+
"""Shrink the interval by a value on both ends.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
value: The value to shrink by.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
A new interval with the value subtracted from the start and added to the end.
|
|
138
|
+
"""
|
|
59
139
|
return Interval(self.start + value, self.end - value)
|
|
60
140
|
|
|
61
141
|
def expand(self, value: float | int) -> Self:
|
|
142
|
+
"""Expand the interval by a value on both ends.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
value: The value to expand by.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
A new interval with the value subtracted from the start and added to the end.
|
|
149
|
+
"""
|
|
62
150
|
return Interval(self.start - value, self.end + value)
|
|
63
151
|
|
|
64
152
|
def lerp(self, x: float, /) -> float:
|
|
153
|
+
"""Linearly interpolate a value within the interval.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
x: The interpolation factor.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
The interpolated value.
|
|
160
|
+
"""
|
|
65
161
|
return lerp(self.start, self.end, x)
|
|
66
162
|
|
|
67
163
|
def lerp_clamped(self, x: float, /) -> float:
|
|
164
|
+
"""Linearly interpolate a value within the interval, clamped to the interval.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
x: The interpolation factor.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
The interpolated value.
|
|
171
|
+
"""
|
|
68
172
|
return lerp_clamped(self.start, self.end, x)
|
|
69
173
|
|
|
70
174
|
def unlerp(self, x: float, /) -> float:
|
|
175
|
+
"""Inverse linear interpolation of a value within the interval.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
x: The value to unlerp.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
The unlerped value.
|
|
182
|
+
"""
|
|
71
183
|
return unlerp(self.start, self.end, x)
|
|
72
184
|
|
|
73
185
|
def unlerp_clamped(self, x: float, /) -> float:
|
|
186
|
+
"""Inverse linear interpolation of a value within the interval, clamped to the interval.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
x: The value to unlerp.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
The unlerped value.
|
|
193
|
+
"""
|
|
74
194
|
return unlerp_clamped(self.start, self.end, x)
|
|
75
195
|
|
|
76
196
|
def clamp(self, x: float, /) -> float:
|
|
197
|
+
"""Clamp a value to the interval.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
x: The value to clamp.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The clamped value.
|
|
204
|
+
"""
|
|
77
205
|
return clamp(x, self.start, self.end)
|
|
78
206
|
|
|
79
207
|
|
|
80
208
|
@native_function(Op.Lerp)
|
|
81
|
-
def
|
|
209
|
+
def _num_lerp(a, b, x, /):
|
|
82
210
|
return a + (b - a) * x
|
|
83
211
|
|
|
84
212
|
|
|
85
213
|
@native_function(Op.LerpClamped)
|
|
86
|
-
def
|
|
214
|
+
def _num_lerp_clamped(a, b, x, /):
|
|
215
|
+
return a + (b - a) * max(0, min(1, x))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _generic_lerp[T](a: T, b: T, x: float, /) -> T:
|
|
219
|
+
return a + (b - a) * x
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _generic_lerp_clamped[T](a: T, b: T, x: float, /) -> T:
|
|
87
223
|
return a + (b - a) * max(0, min(1, x))
|
|
88
224
|
|
|
89
225
|
|
|
226
|
+
def lerp[T](a: T, b: T, x: float, /) -> T:
|
|
227
|
+
"""Linearly interpolate between two values.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
a: The start value.
|
|
231
|
+
b: The end value.
|
|
232
|
+
x: The interpolation factor.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The interpolated value.
|
|
236
|
+
"""
|
|
237
|
+
match a, b:
|
|
238
|
+
case (Num(a), Num(b)):
|
|
239
|
+
return _num_lerp(a, b, x)
|
|
240
|
+
case _:
|
|
241
|
+
return _generic_lerp(a, b, x)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def lerp_clamped[T](a: T, b: T, x: float, /) -> T:
|
|
245
|
+
"""Linearly interpolate between two values, clamped to the interval.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
a: The start value.
|
|
249
|
+
b: The end value.
|
|
250
|
+
x: The interpolation factor.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The interpolated value.
|
|
254
|
+
"""
|
|
255
|
+
match a, b:
|
|
256
|
+
case (Num(a), Num(b)):
|
|
257
|
+
return _num_lerp_clamped(a, b, x)
|
|
258
|
+
case _:
|
|
259
|
+
return _generic_lerp_clamped(a, b, x)
|
|
260
|
+
|
|
261
|
+
|
|
90
262
|
@native_function(Op.Unlerp)
|
|
91
|
-
def unlerp(a, b, x, /):
|
|
263
|
+
def unlerp(a: float, b: float, x: float, /) -> float:
|
|
264
|
+
"""Inverse linear interpolation.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
a: The start value.
|
|
268
|
+
b: The end value.
|
|
269
|
+
x: The value to unlerp.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
The unlerped value.
|
|
273
|
+
"""
|
|
92
274
|
return (x - a) / (b - a)
|
|
93
275
|
|
|
94
276
|
|
|
95
277
|
@native_function(Op.UnlerpClamped)
|
|
96
|
-
def unlerp_clamped(a, b, x, /):
|
|
278
|
+
def unlerp_clamped(a: float, b: float, x: float, /) -> float:
|
|
279
|
+
"""Inverse linear interpolation, clamped to the interval.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
a: The start value.
|
|
283
|
+
b: The end value.
|
|
284
|
+
x: The value to unlerp.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
The unlerped value.
|
|
288
|
+
"""
|
|
97
289
|
return max(0, min(1, (x - a) / (b - a)))
|
|
98
290
|
|
|
99
291
|
|
|
100
292
|
@native_function(Op.Remap)
|
|
101
|
-
def remap(a, b, c, d, x, /):
|
|
293
|
+
def remap(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
294
|
+
"""Remap a value from one interval to another.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
a: The start of the input interval.
|
|
298
|
+
b: The end of the input interval.
|
|
299
|
+
c: The start of the output interval.
|
|
300
|
+
d: The end of the output interval.
|
|
301
|
+
x: The value to remap.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
The remapped value.
|
|
305
|
+
"""
|
|
102
306
|
return c + (d - c) * (x - a) / (b - a)
|
|
103
307
|
|
|
104
308
|
|
|
105
309
|
@native_function(Op.RemapClamped)
|
|
106
|
-
def remap_clamped(a, b, c, d, x, /):
|
|
310
|
+
def remap_clamped(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
311
|
+
"""Remap a value from one interval to another, clamped to the output interval.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
a: The start of the input interval.
|
|
315
|
+
b: The end of the input interval.
|
|
316
|
+
c: The start of the output interval.
|
|
317
|
+
d: The end of the output interval.
|
|
318
|
+
x: The value to remap.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
The remapped value.
|
|
322
|
+
"""
|
|
107
323
|
return c + (d - c) * max(0, min(1, (x - a) / (b - a)))
|
|
108
324
|
|
|
109
325
|
|
|
110
326
|
@native_function(Op.Clamp)
|
|
111
|
-
def clamp(x, a, b, /):
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def generic_lerp[T](a: T, b: T, x: float, /) -> T:
|
|
116
|
-
return a + (b - a) * x
|
|
327
|
+
def clamp(x: float, a: float, b: float, /) -> float:
|
|
328
|
+
"""Clamp a value to an interval.
|
|
117
329
|
|
|
330
|
+
Args:
|
|
331
|
+
x: The value to clamp.
|
|
332
|
+
a: The start of the interval.
|
|
333
|
+
b: The end of the interval.
|
|
118
334
|
|
|
119
|
-
|
|
120
|
-
|
|
335
|
+
Returns:
|
|
336
|
+
The clamped value.
|
|
337
|
+
"""
|
|
338
|
+
return max(a, min(b, x))
|
sonolus/script/iterator.py
CHANGED
|
@@ -1,20 +1,61 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import abstractmethod
|
|
4
|
-
from collections.abc import
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
|
-
from sonolus.script.
|
|
7
|
+
from sonolus.script.internal.impl import meta_fn
|
|
7
8
|
from sonolus.script.record import Record
|
|
8
|
-
from sonolus.script.values import copy
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class SonolusIterator[T](Iterator[T]):
|
|
12
|
+
"""Base class for Sonolus iterators.
|
|
13
|
+
|
|
14
|
+
This class is used to define custom iterators that can be used in Sonolus.py.
|
|
15
|
+
|
|
16
|
+
Inheritors must implement the `has_next`, `get`, and `advance` methods.
|
|
17
|
+
The `__next__` and `__iter__` methods are implemented by default.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
```python
|
|
21
|
+
class MyIterator(Record, SonolusIterator):
|
|
22
|
+
def has_next(self) -> bool:
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def get(self) -> Any:
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def advance(self):
|
|
29
|
+
...
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def next(self) -> T:
|
|
34
|
+
result = self.get()
|
|
35
|
+
self.advance()
|
|
36
|
+
return result
|
|
37
|
+
|
|
12
38
|
@abstractmethod
|
|
13
39
|
def has_next(self) -> bool:
|
|
40
|
+
"""Return whether the iterator has more elements."""
|
|
14
41
|
raise NotImplementedError
|
|
15
42
|
|
|
16
43
|
@abstractmethod
|
|
17
|
-
def
|
|
44
|
+
def get(self) -> T:
|
|
45
|
+
"""Return the next element of the iterator.
|
|
46
|
+
|
|
47
|
+
May be called multiple times before calling `advance`.
|
|
48
|
+
|
|
49
|
+
Must not be called if `has_next` returns `False`.
|
|
50
|
+
"""
|
|
51
|
+
raise NotImplementedError
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def advance(self):
|
|
55
|
+
"""Advance the iterator to the next element.
|
|
56
|
+
|
|
57
|
+
Must not be called if `has_next` returns `False`.
|
|
58
|
+
"""
|
|
18
59
|
raise NotImplementedError
|
|
19
60
|
|
|
20
61
|
def __next__(self) -> T:
|
|
@@ -22,193 +63,105 @@ class SonolusIterator[T](Iterator[T]):
|
|
|
22
63
|
raise StopIteration
|
|
23
64
|
return self.next()
|
|
24
65
|
|
|
66
|
+
def __iter__(self) -> SonolusIterator[T]:
|
|
67
|
+
return self
|
|
25
68
|
|
|
26
|
-
class ArrayLike[T](Collection):
|
|
27
|
-
@abstractmethod
|
|
28
|
-
def size(self) -> int:
|
|
29
|
-
pass
|
|
30
69
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
70
|
+
class _Enumerator[V: SonolusIterator](Record, SonolusIterator):
|
|
71
|
+
i: int
|
|
72
|
+
offset: int
|
|
73
|
+
iterator: V
|
|
34
74
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
pass
|
|
75
|
+
def has_next(self) -> bool:
|
|
76
|
+
return self.iterator.has_next()
|
|
38
77
|
|
|
39
|
-
def
|
|
40
|
-
return self.
|
|
78
|
+
def get(self) -> tuple[int, Any]:
|
|
79
|
+
return self.i + self.offset, self.iterator.get()
|
|
41
80
|
|
|
42
|
-
def
|
|
43
|
-
|
|
81
|
+
def advance(self):
|
|
82
|
+
self.i += 1
|
|
83
|
+
self.iterator.advance()
|
|
44
84
|
|
|
45
|
-
def __contains__(self, value: T) -> bool:
|
|
46
|
-
i = 0
|
|
47
|
-
while i < self.size():
|
|
48
|
-
if self[i] == value:
|
|
49
|
-
return True
|
|
50
|
-
i += 1
|
|
51
|
-
return False
|
|
52
85
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def iter(self) -> SonolusIterator[T]:
|
|
57
|
-
return self.__iter__() # noqa: PLC2801
|
|
58
|
-
|
|
59
|
-
def enumerate(self, start: Num = 0) -> SonolusIterator[T]:
|
|
60
|
-
return ArrayEnumerator(0, start, self)
|
|
61
|
-
|
|
62
|
-
def index_of(self, value: T, start: Num = 0) -> Num:
|
|
63
|
-
i = start
|
|
64
|
-
while i < self.size():
|
|
65
|
-
if self[i] == value:
|
|
66
|
-
return i
|
|
67
|
-
i += 1
|
|
68
|
-
return -1
|
|
69
|
-
|
|
70
|
-
def last_index_of(self, value: T) -> Num:
|
|
71
|
-
i = self.size() - 1
|
|
72
|
-
while i >= 0:
|
|
73
|
-
if self[i] == value:
|
|
74
|
-
return i
|
|
75
|
-
i -= 1
|
|
76
|
-
return -1
|
|
77
|
-
|
|
78
|
-
def index_of_max(self) -> Num:
|
|
79
|
-
if self.size() == 0:
|
|
80
|
-
return -1
|
|
81
|
-
max_index = 0
|
|
82
|
-
i = 1
|
|
83
|
-
while i < self.size():
|
|
84
|
-
if self[i] > self[max_index]:
|
|
85
|
-
max_index = i
|
|
86
|
-
i += 1
|
|
87
|
-
return max_index
|
|
88
|
-
|
|
89
|
-
def index_of_min(self) -> Num:
|
|
90
|
-
if self.size() == 0:
|
|
91
|
-
return -1
|
|
92
|
-
min_index = 0
|
|
93
|
-
i = 1
|
|
94
|
-
while i < self.size():
|
|
95
|
-
if self[i] < self[min_index]:
|
|
96
|
-
min_index = i
|
|
97
|
-
i += 1
|
|
98
|
-
return min_index
|
|
99
|
-
|
|
100
|
-
def max(self) -> T:
|
|
101
|
-
return self[self.index_of_max()]
|
|
102
|
-
|
|
103
|
-
def min(self) -> T:
|
|
104
|
-
return self[self.index_of_min()]
|
|
105
|
-
|
|
106
|
-
def swap(self, i: Num, j: Num):
|
|
107
|
-
temp = copy(self[i])
|
|
108
|
-
self[i] = self[j]
|
|
109
|
-
self[j] = temp
|
|
110
|
-
|
|
111
|
-
def sort(self, *, reverse: bool = False):
|
|
112
|
-
if self.size() < 15:
|
|
113
|
-
_insertion_sort(self, 0, self.size(), reverse)
|
|
114
|
-
else:
|
|
115
|
-
_heap_sort(self, 0, self.size(), reverse)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def _insertion_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
|
|
119
|
-
i = start + 1
|
|
120
|
-
while i < end:
|
|
121
|
-
value = copy(array[i])
|
|
122
|
-
j = i - 1
|
|
123
|
-
while j >= start and (array[j] > value) != reverse:
|
|
124
|
-
array[j + 1] = array[j]
|
|
125
|
-
j -= 1
|
|
126
|
-
array[j + 1] = value
|
|
127
|
-
i += 1
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def _heapify[T](array: ArrayLike[T], end: Num, index: Num, reverse: bool):
|
|
131
|
-
while True:
|
|
132
|
-
left = index * 2 + 1
|
|
133
|
-
right = left + 1
|
|
134
|
-
largest = index
|
|
135
|
-
if left < end and (array[left] > array[largest]) != reverse:
|
|
136
|
-
largest = left
|
|
137
|
-
if right < end and (array[right] > array[largest]) != reverse:
|
|
138
|
-
largest = right
|
|
139
|
-
if largest == index:
|
|
140
|
-
break
|
|
141
|
-
array.swap(index, largest)
|
|
142
|
-
index = largest
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
# Heap sort is simple to implement iteratively without dynamic memory allocation
|
|
146
|
-
def _heap_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
|
|
147
|
-
i = end // 2 - 1
|
|
148
|
-
while i >= start:
|
|
149
|
-
_heapify(array, end, i, reverse)
|
|
150
|
-
i -= 1
|
|
151
|
-
i = end - 1
|
|
152
|
-
while i > start:
|
|
153
|
-
array.swap(start, i)
|
|
154
|
-
_heapify(array, i, start, reverse)
|
|
155
|
-
i -= 1
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
class ArrayIterator[V: ArrayLike](Record, SonolusIterator):
|
|
159
|
-
i: int
|
|
160
|
-
array: V
|
|
86
|
+
class _Zipper[T](Record, SonolusIterator):
|
|
87
|
+
# Can be a, Pair[a, b], Pair[a, Pair[b, c]], etc.
|
|
88
|
+
iterators: T
|
|
161
89
|
|
|
90
|
+
@meta_fn
|
|
162
91
|
def has_next(self) -> bool:
|
|
163
|
-
|
|
92
|
+
from sonolus.backend.visitor import compile_and_call
|
|
164
93
|
|
|
165
|
-
|
|
166
|
-
value = self.array[self.i]
|
|
167
|
-
self.i += 1
|
|
168
|
-
return value
|
|
94
|
+
return compile_and_call(self._has_next, self._get_iterators())
|
|
169
95
|
|
|
96
|
+
def _get_iterators(self) -> tuple[SonolusIterator, ...]:
|
|
97
|
+
from sonolus.script.containers import Pair
|
|
170
98
|
|
|
171
|
-
|
|
172
|
-
|
|
99
|
+
iterators = []
|
|
100
|
+
v = self.iterators
|
|
101
|
+
while isinstance(v, Pair):
|
|
102
|
+
iterators.append(v.first)
|
|
103
|
+
v = v.second
|
|
104
|
+
iterators.append(v)
|
|
105
|
+
return tuple(iterators)
|
|
173
106
|
|
|
174
|
-
def
|
|
175
|
-
|
|
107
|
+
def _has_next(self, iterators: tuple[SonolusIterator, ...]) -> bool:
|
|
108
|
+
for iterator in iterators: # noqa: SIM110
|
|
109
|
+
if not iterator.has_next():
|
|
110
|
+
return False
|
|
111
|
+
return True
|
|
176
112
|
|
|
177
|
-
|
|
178
|
-
|
|
113
|
+
@meta_fn
|
|
114
|
+
def get(self) -> tuple[Any, ...]:
|
|
115
|
+
from sonolus.backend.visitor import compile_and_call
|
|
179
116
|
|
|
180
|
-
|
|
181
|
-
self.array[self.size() - 1 - index] = value
|
|
117
|
+
return tuple(compile_and_call(iterator.get) for iterator in self._get_iterators())
|
|
182
118
|
|
|
183
|
-
|
|
184
|
-
|
|
119
|
+
@meta_fn
|
|
120
|
+
def advance(self):
|
|
121
|
+
from sonolus.backend.visitor import compile_and_call
|
|
185
122
|
|
|
123
|
+
for iterator in self._get_iterators():
|
|
124
|
+
compile_and_call(iterator.advance)
|
|
186
125
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
126
|
+
|
|
127
|
+
class _EmptyIterator(Record, SonolusIterator):
|
|
128
|
+
def has_next(self) -> bool:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def get(self) -> Any:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
def advance(self):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class _MappingIterator[T, Fn](Record, SonolusIterator):
|
|
139
|
+
fn: Fn
|
|
140
|
+
iterator: T
|
|
191
141
|
|
|
192
142
|
def has_next(self) -> bool:
|
|
193
143
|
return self.iterator.has_next()
|
|
194
144
|
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
index = self.i + self.offset
|
|
198
|
-
self.i += 1
|
|
199
|
-
return index, value
|
|
145
|
+
def get(self) -> Any:
|
|
146
|
+
return self.fn(self.iterator.get())
|
|
200
147
|
|
|
148
|
+
def advance(self):
|
|
149
|
+
self.iterator.advance()
|
|
201
150
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
151
|
+
|
|
152
|
+
class _FilteringIterator[T, Fn](Record, SonolusIterator):
|
|
153
|
+
fn: Fn
|
|
154
|
+
iterator: T
|
|
206
155
|
|
|
207
156
|
def has_next(self) -> bool:
|
|
208
|
-
|
|
157
|
+
while self.iterator.has_next():
|
|
158
|
+
if self.fn(self.iterator.get()):
|
|
159
|
+
return True
|
|
160
|
+
self.iterator.advance()
|
|
161
|
+
return False
|
|
209
162
|
|
|
210
|
-
def
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
163
|
+
def get(self) -> Any:
|
|
164
|
+
return self.iterator.get()
|
|
165
|
+
|
|
166
|
+
def advance(self):
|
|
167
|
+
self.iterator.advance()
|