sonolus.py 0.1.4__py3-none-any.whl → 0.1.5__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.

Files changed (77) hide show
  1. sonolus/backend/finalize.py +18 -10
  2. sonolus/backend/interpret.py +7 -7
  3. sonolus/backend/ir.py +24 -0
  4. sonolus/backend/optimize/__init__.py +0 -0
  5. sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
  6. sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
  7. sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
  8. sonolus/backend/optimize/dead_code.py +185 -0
  9. sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
  10. sonolus/backend/{flow.py → optimize/flow.py} +6 -5
  11. sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
  12. sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
  13. sonolus/backend/optimize/optimize.py +44 -0
  14. sonolus/backend/{passes.py → optimize/passes.py} +1 -1
  15. sonolus/backend/optimize/simplify.py +191 -0
  16. sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
  17. sonolus/backend/place.py +17 -25
  18. sonolus/backend/utils.py +10 -0
  19. sonolus/backend/visitor.py +360 -101
  20. sonolus/build/compile.py +8 -8
  21. sonolus/build/engine.py +10 -5
  22. sonolus/script/archetype.py +419 -137
  23. sonolus/script/array.py +25 -8
  24. sonolus/script/array_like.py +297 -0
  25. sonolus/script/bucket.py +73 -11
  26. sonolus/script/containers.py +234 -51
  27. sonolus/script/debug.py +8 -8
  28. sonolus/script/easing.py +147 -105
  29. sonolus/script/effect.py +60 -0
  30. sonolus/script/engine.py +71 -4
  31. sonolus/script/globals.py +66 -32
  32. sonolus/script/instruction.py +79 -25
  33. sonolus/script/internal/builtin_impls.py +138 -27
  34. sonolus/script/internal/constant.py +139 -0
  35. sonolus/script/internal/context.py +14 -5
  36. sonolus/script/internal/dict_impl.py +65 -0
  37. sonolus/script/internal/generic.py +6 -9
  38. sonolus/script/internal/impl.py +38 -13
  39. sonolus/script/internal/introspection.py +5 -2
  40. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  41. sonolus/script/internal/native.py +3 -3
  42. sonolus/script/internal/random.py +67 -0
  43. sonolus/script/internal/range.py +81 -0
  44. sonolus/script/internal/transient.py +51 -0
  45. sonolus/script/internal/tuple_impl.py +113 -0
  46. sonolus/script/interval.py +234 -16
  47. sonolus/script/iterator.py +120 -167
  48. sonolus/script/level.py +24 -0
  49. sonolus/script/num.py +79 -47
  50. sonolus/script/options.py +78 -12
  51. sonolus/script/particle.py +37 -4
  52. sonolus/script/pointer.py +4 -4
  53. sonolus/script/print.py +22 -1
  54. sonolus/script/project.py +8 -0
  55. sonolus/script/{graphics.py → quad.py} +75 -12
  56. sonolus/script/record.py +44 -13
  57. sonolus/script/runtime.py +50 -1
  58. sonolus/script/sprite.py +197 -112
  59. sonolus/script/text.py +2 -0
  60. sonolus/script/timing.py +72 -0
  61. sonolus/script/transform.py +296 -66
  62. sonolus/script/ui.py +134 -78
  63. sonolus/script/values.py +6 -13
  64. sonolus/script/vec.py +118 -3
  65. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  66. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  67. sonolus/backend/dead_code.py +0 -80
  68. sonolus/backend/optimize.py +0 -37
  69. sonolus/backend/simplify.py +0 -47
  70. sonolus/script/comptime.py +0 -160
  71. sonolus/script/random.py +0 -14
  72. sonolus/script/range.py +0 -58
  73. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  74. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  75. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +0 -0
  76. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -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 float() | int() as value:
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 lerp(a, b, x, /):
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 lerp_clamped(a, b, x, /):
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
- return max(a, min(b, x))
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
- def generic_lerp_clamped[T](a: T, b: T, x: float, /) -> T:
120
- return a + (b - a) * max(0, min(1, x))
335
+ Returns:
336
+ The clamped value.
337
+ """
338
+ return max(a, min(b, x))
@@ -1,20 +1,61 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import abstractmethod
4
- from collections.abc import Collection, Iterator
4
+ from collections.abc import Iterator
5
+ from typing import Any
5
6
 
6
- from sonolus.script.num import Num
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 next(self) -> T:
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
- @abstractmethod
32
- def __getitem__(self, index: Num) -> T:
33
- pass
70
+ class _Enumerator[V: SonolusIterator](Record, SonolusIterator):
71
+ i: int
72
+ offset: int
73
+ iterator: V
34
74
 
35
- @abstractmethod
36
- def __setitem__(self, index: Num, value: T):
37
- pass
75
+ def has_next(self) -> bool:
76
+ return self.iterator.has_next()
38
77
 
39
- def __len__(self) -> int:
40
- return self.size()
78
+ def get(self) -> tuple[int, Any]:
79
+ return self.i + self.offset, self.iterator.get()
41
80
 
42
- def __iter__(self) -> SonolusIterator[T]:
43
- return ArrayIterator(0, self)
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
- def reversed(self) -> ArrayLike[T]:
54
- return ArrayReverser(self)
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
- return self.i < self.array.size()
92
+ from sonolus.backend.visitor import compile_and_call
164
93
 
165
- def next(self) -> V:
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
- class ArrayReverser[V: ArrayLike](Record, ArrayLike):
172
- array: V
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 size(self) -> int:
175
- return self.array.size()
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
- def __getitem__(self, index: Num) -> V:
178
- return self.array[self.size() - 1 - index]
113
+ @meta_fn
114
+ def get(self) -> tuple[Any, ...]:
115
+ from sonolus.backend.visitor import compile_and_call
179
116
 
180
- def __setitem__(self, index: Num, value: V):
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
- def reversed(self) -> ArrayLike[V]:
184
- return self.array
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
- class Enumerator[V: SonolusIterator](Record, SonolusIterator):
188
- i: int
189
- offset: int
190
- iterator: V
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 next(self):
196
- value = self.iterator.next()
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
- class ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
203
- i: int
204
- offset: int
205
- array: V
151
+
152
+ class _FilteringIterator[T, Fn](Record, SonolusIterator):
153
+ fn: Fn
154
+ iterator: T
206
155
 
207
156
  def has_next(self) -> bool:
208
- return self.i < self.array.size()
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 next(self):
211
- value = self.array[self.i]
212
- index = self.i + self.offset
213
- self.i += 1
214
- return index, value
163
+ def get(self) -> Any:
164
+ return self.iterator.get()
165
+
166
+ def advance(self):
167
+ self.iterator.advance()