sonolus.py 0.3.3__py3-none-any.whl → 0.4.0__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 (66) hide show
  1. sonolus/backend/excepthook.py +30 -0
  2. sonolus/backend/finalize.py +15 -1
  3. sonolus/backend/ops.py +4 -0
  4. sonolus/backend/optimize/allocate.py +5 -5
  5. sonolus/backend/optimize/constant_evaluation.py +124 -19
  6. sonolus/backend/optimize/copy_coalesce.py +15 -12
  7. sonolus/backend/optimize/dead_code.py +7 -6
  8. sonolus/backend/optimize/dominance.py +2 -2
  9. sonolus/backend/optimize/flow.py +54 -8
  10. sonolus/backend/optimize/inlining.py +137 -30
  11. sonolus/backend/optimize/liveness.py +2 -2
  12. sonolus/backend/optimize/optimize.py +15 -1
  13. sonolus/backend/optimize/passes.py +11 -3
  14. sonolus/backend/optimize/simplify.py +137 -8
  15. sonolus/backend/optimize/ssa.py +47 -13
  16. sonolus/backend/place.py +5 -4
  17. sonolus/backend/utils.py +24 -0
  18. sonolus/backend/visitor.py +260 -17
  19. sonolus/build/cli.py +47 -19
  20. sonolus/build/compile.py +12 -5
  21. sonolus/build/engine.py +70 -1
  22. sonolus/build/level.py +3 -3
  23. sonolus/build/project.py +2 -2
  24. sonolus/script/archetype.py +27 -24
  25. sonolus/script/array.py +25 -19
  26. sonolus/script/array_like.py +46 -49
  27. sonolus/script/bucket.py +1 -1
  28. sonolus/script/containers.py +22 -26
  29. sonolus/script/debug.py +24 -47
  30. sonolus/script/effect.py +1 -1
  31. sonolus/script/engine.py +2 -2
  32. sonolus/script/globals.py +3 -3
  33. sonolus/script/instruction.py +3 -3
  34. sonolus/script/internal/builtin_impls.py +155 -28
  35. sonolus/script/internal/constant.py +13 -3
  36. sonolus/script/internal/context.py +46 -15
  37. sonolus/script/internal/impl.py +9 -3
  38. sonolus/script/internal/introspection.py +8 -1
  39. sonolus/script/internal/math_impls.py +17 -0
  40. sonolus/script/internal/native.py +5 -5
  41. sonolus/script/internal/range.py +14 -17
  42. sonolus/script/internal/simulation_context.py +1 -1
  43. sonolus/script/internal/transient.py +2 -2
  44. sonolus/script/internal/value.py +42 -4
  45. sonolus/script/interval.py +15 -15
  46. sonolus/script/iterator.py +38 -107
  47. sonolus/script/maybe.py +139 -0
  48. sonolus/script/num.py +30 -15
  49. sonolus/script/options.py +1 -1
  50. sonolus/script/particle.py +1 -1
  51. sonolus/script/pointer.py +1 -1
  52. sonolus/script/project.py +24 -5
  53. sonolus/script/quad.py +15 -15
  54. sonolus/script/record.py +21 -12
  55. sonolus/script/runtime.py +22 -18
  56. sonolus/script/sprite.py +1 -1
  57. sonolus/script/stream.py +69 -85
  58. sonolus/script/transform.py +35 -34
  59. sonolus/script/values.py +10 -10
  60. sonolus/script/vec.py +23 -20
  61. {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/METADATA +1 -1
  62. sonolus_py-0.4.0.dist-info/RECORD +93 -0
  63. sonolus_py-0.3.3.dist-info/RECORD +0 -92
  64. {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/WHEEL +0 -0
  65. {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/entry_points.txt +0 -0
  66. {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/licenses/LICENSE +0 -0
sonolus/build/level.py CHANGED
@@ -1,16 +1,16 @@
1
- from sonolus.build.engine import package_output
1
+ from sonolus.build.engine import JsonValue, package_output
2
2
  from sonolus.script.level import LevelData
3
3
 
4
4
 
5
5
  def package_level_data(
6
6
  level_data: LevelData,
7
- ):
7
+ ) -> bytes:
8
8
  return package_output(build_level_data(level_data))
9
9
 
10
10
 
11
11
  def build_level_data(
12
12
  level_data: LevelData,
13
- ):
13
+ ) -> JsonValue:
14
14
  level_refs = {entity: f"{i}_{entity.name}" for i, entity in enumerate(level_data.entities)}
15
15
  return {
16
16
  "bgmOffset": level_data.bgm_offset,
sonolus/build/project.py CHANGED
@@ -17,7 +17,7 @@ BLANK_AUDIO = (
17
17
  )
18
18
 
19
19
 
20
- def build_project_to_collection(project: Project, config: BuildConfig):
20
+ def build_project_to_collection(project: Project, config: BuildConfig | None):
21
21
  collection = load_resources_files_to_collection(project.resources)
22
22
  add_engine_to_collection(collection, project, project.engine, config)
23
23
  for level in project.levels:
@@ -26,7 +26,7 @@ def build_project_to_collection(project: Project, config: BuildConfig):
26
26
  return collection
27
27
 
28
28
 
29
- def add_engine_to_collection(collection: Collection, project: Project, engine: Engine, config: BuildConfig):
29
+ def add_engine_to_collection(collection: Collection, project: Project, engine: Engine, config: BuildConfig | None):
30
30
  packaged_engine = package_engine(engine.data, config)
31
31
  item = {
32
32
  "name": engine.name,
@@ -1,3 +1,4 @@
1
+ # type: ignore
1
2
  from __future__ import annotations
2
3
 
3
4
  import inspect
@@ -117,7 +118,7 @@ class _ArchetypeField(SonolusDescriptor):
117
118
  if result is None:
118
119
  raise RuntimeError("Invalid storage type")
119
120
  if ctx():
120
- return result._get_()
121
+ return result._get_readonly_()
121
122
  else:
122
123
  return result._as_py_()
123
124
 
@@ -312,7 +313,7 @@ class StandardImport:
312
313
  def callback[T: Callable](*, order: int = 0) -> Callable[[T], T]:
313
314
  """Annotate a callback with its order.
314
315
 
315
- Callbacks are execute from lowest to highest order. By default, callbacks have an order of 0.
316
+ Callbacks are executed from lowest to highest order. By default, callbacks have an order of 0.
316
317
 
317
318
  Usage:
318
319
  ```python
@@ -327,7 +328,7 @@ def callback[T: Callable](*, order: int = 0) -> Callable[[T], T]:
327
328
  """
328
329
 
329
330
  def decorator(func: T) -> T:
330
- func._callback_order_ = order
331
+ func._callback_order_ = order # type: ignore
331
332
  return func
332
333
 
333
334
  return decorator
@@ -338,9 +339,9 @@ class _ArchetypeSelfData:
338
339
 
339
340
 
340
341
  class _ArchetypeReferenceData:
341
- index: Num
342
+ index: int
342
343
 
343
- def __init__(self, index: Num):
344
+ def __init__(self, index: int):
344
345
  self.index = index
345
346
 
346
347
 
@@ -416,21 +417,21 @@ class _BaseArchetype:
416
417
 
417
418
  @classmethod
418
419
  @meta_fn
419
- def at(cls, index: Num) -> Self:
420
+ def at(cls, index: int) -> Self:
420
421
  result = cls._new()
421
422
  result._data_ = _ArchetypeReferenceData(index=Num._accept_(index))
422
423
  return result
423
424
 
424
425
  @classmethod
425
426
  @meta_fn
426
- def is_at(cls, index: Num) -> bool:
427
+ def is_at(cls, index: int) -> bool:
427
428
  if not ctx():
428
429
  raise RuntimeError("is_at is only available during compilation")
429
430
  return entity_info_at(index).archetype_id == cls.id()
430
431
 
431
432
  @classmethod
432
433
  @meta_fn
433
- def id(cls):
434
+ def id(cls) -> int:
434
435
  if not ctx():
435
436
  raise RuntimeError("Archetype id is only available during compilation")
436
437
  result = ctx().global_state.archetypes.get(cls)
@@ -635,11 +636,11 @@ class _BaseArchetype:
635
636
  """
636
637
  match self._data_:
637
638
  case _ArchetypeSelfData():
638
- return EntityRef[type(self)](index=self.index)
639
+ return EntityRef[type(self)](index=self.index) # type: ignore
639
640
  case _ArchetypeReferenceData(index=index):
640
- return EntityRef[type(self)](index=index)
641
+ return EntityRef[type(self)](index=index) # type: ignore
641
642
  case _ArchetypeLevelData():
642
- result = EntityRef[type(self)](index=-1)
643
+ result = EntityRef[type(self)](index=-1) # type: ignore
643
644
  result._ref_ = self
644
645
  return result
645
646
  case _:
@@ -992,7 +993,7 @@ class PreviewArchetype(_BaseArchetype):
992
993
 
993
994
 
994
995
  @meta_fn
995
- def entity_info_at(index: Num) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
996
+ def entity_info_at(index: int) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
996
997
  """Retrieve entity info of the entity at the given index.
997
998
 
998
999
  Available in play, watch, and preview mode.
@@ -1016,8 +1017,8 @@ def archetype_life_of(archetype: type[_BaseArchetype] | _BaseArchetype) -> Arche
1016
1017
 
1017
1018
  Available in play and watch mode.
1018
1019
  """
1019
- archetype = validate_value(archetype)
1020
- archetype = archetype._as_py_()
1020
+ archetype = validate_value(archetype) # type: ignore
1021
+ archetype = archetype._as_py_() # type: ignore
1021
1022
  if not ctx():
1022
1023
  raise RuntimeError("Calling archetype_life_of is only allowed within a callback")
1023
1024
  match ctx().global_state.mode:
@@ -1047,24 +1048,24 @@ class PreviewEntityInfo(Record):
1047
1048
  class ArchetypeLife(Record):
1048
1049
  """How an entity contributes to life."""
1049
1050
 
1050
- perfect_increment: Num
1051
+ perfect_increment: int
1051
1052
  """Life increment for a perfect judgment."""
1052
1053
 
1053
- great_increment: Num
1054
+ great_increment: int
1054
1055
  """Life increment for a great judgment."""
1055
1056
 
1056
- good_increment: Num
1057
+ good_increment: int
1057
1058
  """Life increment for a good judgment."""
1058
1059
 
1059
- miss_increment: Num
1060
+ miss_increment: int
1060
1061
  """Life increment for a miss judgment."""
1061
1062
 
1062
1063
  def update(
1063
1064
  self,
1064
- perfect_increment: Num | None = None,
1065
- great_increment: Num | None = None,
1066
- good_increment: Num | None = None,
1067
- miss_increment: Num | None = None,
1065
+ perfect_increment: int | None = None,
1066
+ great_increment: int | None = None,
1067
+ good_increment: int | None = None,
1068
+ miss_increment: int | None = None,
1068
1069
  ):
1069
1070
  """Update the life increments."""
1070
1071
  if perfect_increment is not None:
@@ -1110,7 +1111,7 @@ class EntityRef[A: _BaseArchetype](Record):
1110
1111
  """Get the archetype type."""
1111
1112
  return cls.type_var_value(A)
1112
1113
 
1113
- def with_archetype(self, archetype: type[A]) -> EntityRef[A]:
1114
+ def with_archetype[T: _BaseArchetype](self, archetype: type[T]) -> EntityRef[T]:
1114
1115
  """Return a new reference with the given archetype type."""
1115
1116
  return EntityRef[archetype](index=self.index)
1116
1117
 
@@ -1137,11 +1138,13 @@ class EntityRef[A: _BaseArchetype](Record):
1137
1138
  if ref is None:
1138
1139
  return Num._accept_(self.index)._to_list_()
1139
1140
  else:
1141
+ if level_refs is None:
1142
+ raise RuntimeError("Unexpected missing level_refs")
1140
1143
  if ref not in level_refs:
1141
1144
  raise KeyError("Reference to entity not in level data")
1142
1145
  return [level_refs[ref]]
1143
1146
 
1144
- def _copy_from_(self, value: Self):
1147
+ def _copy_from_(self, value: Any):
1145
1148
  super()._copy_from_(value)
1146
1149
  if hasattr(value, "_ref_"):
1147
1150
  self._ref_ = value._ref_
sonolus/script/array.py CHANGED
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from abc import ABCMeta
3
4
  from collections.abc import Iterable
4
- from typing import Any, Self, final
5
+ from typing import Any, Literal, Self, final
5
6
 
6
7
  from sonolus.backend.ir import IRConst, IRSet
7
8
  from sonolus.backend.place import BlockPlace
@@ -14,8 +15,11 @@ from sonolus.script.internal.impl import meta_fn, validate_value
14
15
  from sonolus.script.internal.value import BackingSource, DataValue, Value
15
16
  from sonolus.script.num import Num
16
17
 
18
+ Dim = Literal
19
+ """Shorthand for `Literal` intended for use in array dimensions for type checker compatibility."""
17
20
 
18
- class ArrayMeta(type):
21
+
22
+ class ArrayMeta(ABCMeta):
19
23
  @meta_fn
20
24
  def __pos__[T](cls: type[T]) -> T:
21
25
  """Create a zero-initialized array instance."""
@@ -31,6 +35,8 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
31
35
  array_1 = Array(1, 2, 3)
32
36
  array_2 = Array[int, 0]()
33
37
  array_3 = +Array[int, 3] # Create a zero-initialized array
38
+ array_4 = +Array[int, Dim[3]] # Alternative syntax for compliance with type checkers
39
+ array_5 = +array_1 # Create a copy of array_1
34
40
  ```
35
41
  """
36
42
 
@@ -77,7 +83,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
77
83
  super().__init__()
78
84
 
79
85
  @classmethod
80
- def _with_value(cls, value) -> Self:
86
+ def _with_value(cls, value) -> Array[T, Size]:
81
87
  result = object.__new__(cls)
82
88
  result._value = value
83
89
  return result
@@ -91,11 +97,11 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
91
97
  return False
92
98
 
93
99
  @classmethod
94
- def _from_backing_source_(cls, source: BackingSource) -> Self:
100
+ def _from_backing_source_(cls, source: BackingSource) -> Array[T, Size]:
95
101
  return cls._with_value(source)
96
102
 
97
103
  @classmethod
98
- def _from_place_(cls, place: BlockPlace) -> Self:
104
+ def _from_place_(cls, place: BlockPlace) -> Array[T, Size]:
99
105
  return cls._with_value(place)
100
106
 
101
107
  @classmethod
@@ -103,7 +109,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
103
109
  return isinstance(value, cls)
104
110
 
105
111
  @classmethod
106
- def _accept_(cls, value: Any) -> Self:
112
+ def _accept_(cls, value: Any) -> Array[T, Size]:
107
113
  if not cls._accepts_(value):
108
114
  raise TypeError(f"Cannot accept value {value} as {cls.__name__}")
109
115
  return value
@@ -117,7 +123,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
117
123
  return self
118
124
 
119
125
  @classmethod
120
- def _from_list_(cls, values: Iterable[DataValue]) -> Self:
126
+ def _from_list_(cls, values: Iterable[DataValue]) -> Array[T, Size]:
121
127
  iterator = iter(values)
122
128
  return cls(*(cls.element_type()._from_list_(iterator) for _ in range(cls.size())))
123
129
 
@@ -142,19 +148,19 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
142
148
  def _flat_keys_(cls, prefix: str) -> list[str]:
143
149
  return [entry for i in range(cls.size()) for entry in cls.element_type()._flat_keys_(f"{prefix}[{i}]")]
144
150
 
145
- def _get_(self) -> Self:
151
+ def _get_(self) -> Array[T, Size]:
146
152
  return self
147
153
 
148
- def _set_(self, value: Self):
154
+ def _set_(self, value: Any):
149
155
  raise TypeError("Array does not support _set_")
150
156
 
151
- def _copy_from_(self, value: Self):
157
+ def _copy_from_(self, value: Any):
152
158
  if not isinstance(value, type(self)):
153
159
  raise TypeError("Cannot copy from different type")
154
160
  for i in range(self.size()):
155
161
  self[i] = value[i]
156
162
 
157
- def _copy_(self) -> Self:
163
+ def _copy_(self) -> Array[T, Size]:
158
164
  if ctx():
159
165
  place = ctx().alloc(size=self._size_())
160
166
  result: Self = self._from_place_(place)
@@ -165,7 +171,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
165
171
  return self._with_value([value._copy_() for value in self._value])
166
172
 
167
173
  @classmethod
168
- def _alloc_(cls) -> Self:
174
+ def _alloc_(cls) -> Array[T, Size]:
169
175
  if ctx():
170
176
  place = ctx().alloc(size=cls._size_())
171
177
  return cls._from_place_(place)
@@ -173,7 +179,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
173
179
  return cls._with_value([cls.element_type()._alloc_() for _ in range(cls.size())])
174
180
 
175
181
  @classmethod
176
- def _zero_(cls) -> Self:
182
+ def _zero_(cls) -> Array[T, Size]:
177
183
  if ctx():
178
184
  place = ctx().alloc(size=cls._size_())
179
185
  result: Self = cls._from_place_(place)
@@ -186,7 +192,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
186
192
  return self.size()
187
193
 
188
194
  @meta_fn
189
- def __getitem__(self, index: Num) -> T:
195
+ def __getitem__(self, index: int) -> T:
190
196
  index: Num = Num._accept_(get_positive_index(index, self.size()))
191
197
  if index._is_py_() and 0 <= index._as_py_() < self.size():
192
198
  const_index = index._as_py_()
@@ -206,7 +212,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
206
212
  )
207
213
  elif callable(self._value):
208
214
  return self.element_type()._from_backing_source_(
209
- lambda offset: self._value((Num(offset) + Num(const_index * self.element_type()._size_())).ir())
215
+ lambda offset: self._value((Num(offset) + Num(const_index * self.element_type()._size_())).ir()) # type: ignore
210
216
  )
211
217
  else:
212
218
  raise InternalError("Unexpected array value")
@@ -224,13 +230,13 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
224
230
  elif callable(self._value):
225
231
  base_offset = index * Num(self.element_type()._size_())
226
232
  return self.element_type()._from_backing_source_(
227
- lambda offset: self._value((Num(offset) + base_offset).ir())
233
+ lambda offset: self._value((Num(offset) + base_offset).ir()) # type: ignore
228
234
  )
229
235
  else:
230
236
  raise InternalError("Unexpected array value")
231
237
 
232
238
  @meta_fn
233
- def __setitem__(self, index: Num, value: T):
239
+ def __setitem__(self, index: int, value: T):
234
240
  index: Num = Num._accept_(get_positive_index(index, self.size()))
235
241
  value = self.element_type()._accept_(value)
236
242
  if ctx():
@@ -251,7 +257,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
251
257
  elif callable(self._value):
252
258
  base_offset = index * Num(self.element_type()._size_())
253
259
  dst = self.element_type()._from_backing_source_(
254
- lambda offset: self._value((Num(offset) + base_offset).ir())
260
+ lambda offset: self._value((Num(offset) + base_offset).ir()) # type: ignore
255
261
  )
256
262
  else:
257
263
  raise InternalError("Unexpected array value")
@@ -305,6 +311,6 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
305
311
  return f"{type(self).__name__}({', '.join(repr(self[i]) for i in range(self.size()))})"
306
312
 
307
313
  @meta_fn
308
- def __pos__(self) -> Self:
314
+ def __pos__(self) -> Array[T, Size]:
309
315
  """Return a copy of the array."""
310
316
  return self._copy_()
@@ -2,12 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import random
4
4
  from abc import abstractmethod
5
- from collections.abc import Callable
5
+ from collections.abc import Callable, Sequence
6
6
  from typing import Any
7
7
 
8
8
  from sonolus.script.internal.context import ctx
9
9
  from sonolus.script.internal.impl import meta_fn
10
10
  from sonolus.script.iterator import SonolusIterator
11
+ from sonolus.script.maybe import Maybe, Nothing, Some
11
12
  from sonolus.script.num import Num
12
13
  from sonolus.script.record import Record
13
14
  from sonolus.script.values import copy
@@ -15,7 +16,7 @@ from sonolus.script.values import copy
15
16
  # Note: we don't use Range in this file because Range itself inherits from ArrayLike
16
17
 
17
18
 
18
- class ArrayLike[T]:
19
+ class ArrayLike[T](Sequence):
19
20
  """Mixin for array-like objects.
20
21
 
21
22
  Inheritors must implement `__len__`, `__getitem__`, and `__setitem__`.
@@ -26,10 +27,10 @@ class ArrayLike[T]:
26
27
  def __len__(self) -> int:
27
28
  ...
28
29
 
29
- def __getitem__(self, index: Num) -> T:
30
+ def __getitem__(self, index: int) -> T:
30
31
  ...
31
32
 
32
- def __setitem__(self, index: Num, value: T):
33
+ def __setitem__(self, index: int, value: T):
33
34
  ...
34
35
  ```
35
36
  """
@@ -41,7 +42,7 @@ class ArrayLike[T]:
41
42
  """Return the length of the array."""
42
43
 
43
44
  @abstractmethod
44
- def __getitem__(self, index: Num) -> T:
45
+ def __getitem__(self, index: int) -> T:
45
46
  """Return the item at the given index.
46
47
 
47
48
  Args:
@@ -49,7 +50,7 @@ class ArrayLike[T]:
49
50
  """
50
51
 
51
52
  @abstractmethod
52
- def __setitem__(self, index: Num, value: T):
53
+ def __setitem__(self, index: int, value: T):
53
54
  """Set the value of the item at the given index.
54
55
 
55
56
  Args:
@@ -78,10 +79,10 @@ class ArrayLike[T]:
78
79
  """Return a reversed view of the array."""
79
80
  return _ArrayReverser(self)
80
81
 
81
- def _enumerate_(self, start: Num = 0) -> SonolusIterator[T]:
82
+ def _enumerate_(self, start: int = 0) -> SonolusIterator[T]:
82
83
  return _ArrayEnumerator(0, start, self)
83
84
 
84
- def index(self, value: T, start: Num = 0, stop: Num | None = None) -> Num:
85
+ def index(self, value: T, start: int = 0, stop: int | None = None) -> int:
85
86
  """Return the index of the value in the array equal to the given value.
86
87
 
87
88
  Args:
@@ -98,7 +99,7 @@ class ArrayLike[T]:
98
99
  i += 1
99
100
  return -1
100
101
 
101
- def count(self, value: T) -> Num:
102
+ def count(self, value: T) -> int:
102
103
  """Return the number of elements in the array equal to the given value.
103
104
 
104
105
  Args:
@@ -112,7 +113,7 @@ class ArrayLike[T]:
112
113
  i += 1
113
114
  return count
114
115
 
115
- def last_index(self, value: T) -> Num:
116
+ def last_index(self, value: T) -> int:
116
117
  """Return the last index of the value in the array equal to the given value.
117
118
 
118
119
  Args:
@@ -125,7 +126,7 @@ class ArrayLike[T]:
125
126
  i -= 1
126
127
  return -1
127
128
 
128
- def index_of_max(self, *, key: Callable[T, Any] | None = None) -> Num:
129
+ def index_of_max(self, *, key: Callable[[T], Any] | None = None) -> int:
129
130
  """Return the index of the maximum value in the array.
130
131
 
131
132
  Args:
@@ -134,16 +135,16 @@ class ArrayLike[T]:
134
135
  if len(self) == 0:
135
136
  return -1
136
137
  if key is None:
137
- key = _identity
138
+ key = _identity # type: ignore
138
139
  max_index = 0
139
140
  i = 1
140
141
  while i < len(self):
141
- if key(self[i]) > key(self[max_index]):
142
+ if key(self[i]) > key(self[max_index]): # type: ignore
142
143
  max_index = i
143
144
  i += 1
144
145
  return max_index
145
146
 
146
- def index_of_min(self, *, key: Callable[T, Any] | None = None) -> Num:
147
+ def index_of_min(self, *, key: Callable[[T], Any] | None = None) -> int:
147
148
  """Return the index of the minimum value in the array.
148
149
 
149
150
  Args:
@@ -152,26 +153,26 @@ class ArrayLike[T]:
152
153
  if len(self) == 0:
153
154
  return -1
154
155
  if key is None:
155
- key = _identity
156
+ key = _identity # type: ignore
156
157
  min_index = 0
157
158
  i = 1
158
159
  while i < len(self):
159
- if key(self[i]) < key(self[min_index]):
160
+ if key(self[i]) < key(self[min_index]): # type: ignore
160
161
  min_index = i
161
162
  i += 1
162
163
  return min_index
163
164
 
164
- def _max_(self, key: Callable[T, Any] | None = None) -> T:
165
+ def _max_(self, key: Callable[[T], Any] | None = None) -> T:
165
166
  index = self.index_of_max(key=key)
166
167
  assert index != -1
167
168
  return self[index]
168
169
 
169
- def _min_(self, key: Callable[T, Any] | None = None) -> T:
170
+ def _min_(self, key: Callable[[T], Any] | None = None) -> T:
170
171
  index = self.index_of_min(key=key)
171
172
  assert index != -1
172
173
  return self[index]
173
174
 
174
- def swap(self, i: Num, j: Num, /):
175
+ def swap(self, i: int, j: int, /):
175
176
  """Swap the values at the given indices.
176
177
 
177
178
  Args:
@@ -182,7 +183,7 @@ class ArrayLike[T]:
182
183
  self[i] = self[j]
183
184
  self[j] = temp
184
185
 
185
- def sort(self, *, key: Callable[T, Any] | None = None, reverse: bool = False):
186
+ def sort(self, *, key: Callable[[T], Any] | None = None, reverse: bool = False):
186
187
  """Sort the values in the array in place.
187
188
 
188
189
  Args:
@@ -191,12 +192,12 @@ class ArrayLike[T]:
191
192
  """
192
193
  if len(self) < 15 or key is not None:
193
194
  if key is None:
194
- key = _identity
195
+ key = _identity # type: ignore
195
196
  # May be worth adding a block sort variant for better performance on large arrays in the future
196
- _insertion_sort(self, 0, len(self), key, reverse)
197
+ _insertion_sort(self, 0, len(self), key, reverse) # type: ignore
197
198
  else:
198
199
  # Heap sort is unstable, so if there's a key, we can't rely on it
199
- _heap_sort(self, 0, len(self), reverse)
200
+ _heap_sort(self, 0, len(self), reverse) # type: ignore
200
201
 
201
202
  def shuffle(self):
202
203
  """Shuffle the values in the array in place."""
@@ -216,32 +217,32 @@ def _identity[T](value: T) -> T:
216
217
  return value
217
218
 
218
219
 
219
- def _insertion_sort[T](array: ArrayLike[T], start: Num, end: Num, key: Callable[T, Any], reverse: bool):
220
+ def _insertion_sort[T](array: ArrayLike[T], start: int, end: int, key: Callable[[T], Any], reverse: bool):
220
221
  i = start + 1
221
222
  if reverse:
222
223
  while i < end:
223
224
  j = i
224
- while j > start and key(array[j - 1]) < key(array[j]):
225
+ while j > start and key(array[j - 1]) < key(array[j]): # type: ignore
225
226
  array.swap(j - 1, j)
226
227
  j -= 1
227
228
  i += 1
228
229
  else:
229
230
  while i < end:
230
231
  j = i
231
- while j > start and key(array[j - 1]) > key(array[j]):
232
+ while j > start and key(array[j - 1]) > key(array[j]): # type: ignore
232
233
  array.swap(j - 1, j)
233
234
  j -= 1
234
235
  i += 1
235
236
 
236
237
 
237
- def _heapify[T](array: ArrayLike[T], end: Num, index: Num, reverse: bool):
238
+ def _heapify[T](array: ArrayLike[T], end: int, index: int, reverse: bool):
238
239
  while True:
239
240
  left = index * 2 + 1
240
241
  right = left + 1
241
242
  largest = index
242
- if left < end and (array[left] > array[largest]) != reverse:
243
+ if left < end and (array[left] > array[largest]) != reverse: # type: ignore
243
244
  largest = left
244
- if right < end and (array[right] > array[largest]) != reverse:
245
+ if right < end and (array[right] > array[largest]) != reverse: # type: ignore
245
246
  largest = right
246
247
  if largest == index:
247
248
  break
@@ -249,7 +250,7 @@ def _heapify[T](array: ArrayLike[T], end: Num, index: Num, reverse: bool):
249
250
  index = largest
250
251
 
251
252
 
252
- def _heap_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
253
+ def _heap_sort[T](array: ArrayLike[T], start: int, end: int, reverse: bool):
253
254
  i = end // 2 - 1
254
255
  while i >= start:
255
256
  _heapify(array, end, i, reverse)
@@ -265,14 +266,12 @@ class _ArrayIterator[V: ArrayLike](Record, SonolusIterator):
265
266
  i: int
266
267
  array: V
267
268
 
268
- def has_next(self) -> bool:
269
- return self.i < len(self.array)
270
-
271
- def get(self) -> V:
272
- return self.array[self.i]
273
-
274
- def advance(self):
275
- self.i += 1
269
+ def next(self) -> Maybe[V]:
270
+ if self.i < len(self.array):
271
+ value = self.array[self.i]
272
+ self.i += 1
273
+ return Some(value)
274
+ return Nothing
276
275
 
277
276
 
278
277
  class _ArrayReverser[V: ArrayLike](Record, ArrayLike):
@@ -281,10 +280,10 @@ class _ArrayReverser[V: ArrayLike](Record, ArrayLike):
281
280
  def __len__(self) -> int:
282
281
  return len(self.array)
283
282
 
284
- def __getitem__(self, index: Num) -> V:
283
+ def __getitem__(self, index: int) -> V:
285
284
  return self.array[len(self) - 1 - index]
286
285
 
287
- def __setitem__(self, index: Num, value: V):
286
+ def __setitem__(self, index: int, value: V):
288
287
  self.array[len(self) - 1 - index] = value
289
288
 
290
289
  def reversed(self) -> ArrayLike[V]:
@@ -296,18 +295,16 @@ class _ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
296
295
  offset: int
297
296
  array: V
298
297
 
299
- def has_next(self) -> bool:
300
- return self.i < len(self.array)
301
-
302
- def get(self) -> tuple[int, Any]:
303
- return self.i + self.offset, self.array[self.i]
304
-
305
- def advance(self):
306
- self.i += 1
298
+ def next(self) -> Maybe[tuple[int, Any]]:
299
+ if self.i < len(self.array):
300
+ result = (self.i + self.offset, self.array[self.i])
301
+ self.i += 1
302
+ return Some(result)
303
+ return Nothing
307
304
 
308
305
 
309
306
  @meta_fn
310
- def get_positive_index(index: Num, length: Num) -> Num:
307
+ def get_positive_index(index: int, length: int) -> int:
311
308
  """Get the positive index for the given index in the array of the given length.
312
309
 
313
310
  This is used to convert negative indixes relative to the end of the array to positive indices.
sonolus/script/bucket.py CHANGED
@@ -220,7 +220,7 @@ def bucket(*, sprites: list[_BucketSprite], unit: str | None = None) -> Any:
220
220
  return _BucketInfo(sprites, unit)
221
221
 
222
222
 
223
- type Buckets = NewType("Buckets", Any)
223
+ type Buckets = NewType("Buckets", Any) # type: ignore
224
224
 
225
225
 
226
226
  @dataclass_transform()