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.
- sonolus/backend/excepthook.py +30 -0
- sonolus/backend/finalize.py +15 -1
- sonolus/backend/ops.py +4 -0
- sonolus/backend/optimize/allocate.py +5 -5
- sonolus/backend/optimize/constant_evaluation.py +124 -19
- sonolus/backend/optimize/copy_coalesce.py +15 -12
- sonolus/backend/optimize/dead_code.py +7 -6
- sonolus/backend/optimize/dominance.py +2 -2
- sonolus/backend/optimize/flow.py +54 -8
- sonolus/backend/optimize/inlining.py +137 -30
- sonolus/backend/optimize/liveness.py +2 -2
- sonolus/backend/optimize/optimize.py +15 -1
- sonolus/backend/optimize/passes.py +11 -3
- sonolus/backend/optimize/simplify.py +137 -8
- sonolus/backend/optimize/ssa.py +47 -13
- sonolus/backend/place.py +5 -4
- sonolus/backend/utils.py +24 -0
- sonolus/backend/visitor.py +260 -17
- sonolus/build/cli.py +47 -19
- sonolus/build/compile.py +12 -5
- sonolus/build/engine.py +70 -1
- sonolus/build/level.py +3 -3
- sonolus/build/project.py +2 -2
- sonolus/script/archetype.py +27 -24
- sonolus/script/array.py +25 -19
- sonolus/script/array_like.py +46 -49
- sonolus/script/bucket.py +1 -1
- sonolus/script/containers.py +22 -26
- sonolus/script/debug.py +24 -47
- sonolus/script/effect.py +1 -1
- sonolus/script/engine.py +2 -2
- sonolus/script/globals.py +3 -3
- sonolus/script/instruction.py +3 -3
- sonolus/script/internal/builtin_impls.py +155 -28
- sonolus/script/internal/constant.py +13 -3
- sonolus/script/internal/context.py +46 -15
- sonolus/script/internal/impl.py +9 -3
- sonolus/script/internal/introspection.py +8 -1
- sonolus/script/internal/math_impls.py +17 -0
- sonolus/script/internal/native.py +5 -5
- sonolus/script/internal/range.py +14 -17
- sonolus/script/internal/simulation_context.py +1 -1
- sonolus/script/internal/transient.py +2 -2
- sonolus/script/internal/value.py +42 -4
- sonolus/script/interval.py +15 -15
- sonolus/script/iterator.py +38 -107
- sonolus/script/maybe.py +139 -0
- sonolus/script/num.py +30 -15
- sonolus/script/options.py +1 -1
- sonolus/script/particle.py +1 -1
- sonolus/script/pointer.py +1 -1
- sonolus/script/project.py +24 -5
- sonolus/script/quad.py +15 -15
- sonolus/script/record.py +21 -12
- sonolus/script/runtime.py +22 -18
- sonolus/script/sprite.py +1 -1
- sonolus/script/stream.py +69 -85
- sonolus/script/transform.py +35 -34
- sonolus/script/values.py +10 -10
- sonolus/script/vec.py +23 -20
- {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/METADATA +1 -1
- sonolus_py-0.4.0.dist-info/RECORD +93 -0
- sonolus_py-0.3.3.dist-info/RECORD +0 -92
- {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/entry_points.txt +0 -0
- {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,
|
sonolus/script/archetype.py
CHANGED
|
@@ -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.
|
|
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
|
|
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:
|
|
342
|
+
index: int
|
|
342
343
|
|
|
343
|
-
def __init__(self, index:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1051
|
+
perfect_increment: int
|
|
1051
1052
|
"""Life increment for a perfect judgment."""
|
|
1052
1053
|
|
|
1053
|
-
great_increment:
|
|
1054
|
+
great_increment: int
|
|
1054
1055
|
"""Life increment for a great judgment."""
|
|
1055
1056
|
|
|
1056
|
-
good_increment:
|
|
1057
|
+
good_increment: int
|
|
1057
1058
|
"""Life increment for a good judgment."""
|
|
1058
1059
|
|
|
1059
|
-
miss_increment:
|
|
1060
|
+
miss_increment: int
|
|
1060
1061
|
"""Life increment for a miss judgment."""
|
|
1061
1062
|
|
|
1062
1063
|
def update(
|
|
1063
1064
|
self,
|
|
1064
|
-
perfect_increment:
|
|
1065
|
-
great_increment:
|
|
1066
|
-
good_increment:
|
|
1067
|
-
miss_increment:
|
|
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[
|
|
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:
|
|
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
|
-
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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]) ->
|
|
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) ->
|
|
151
|
+
def _get_(self) -> Array[T, Size]:
|
|
146
152
|
return self
|
|
147
153
|
|
|
148
|
-
def _set_(self, value:
|
|
154
|
+
def _set_(self, value: Any):
|
|
149
155
|
raise TypeError("Array does not support _set_")
|
|
150
156
|
|
|
151
|
-
def _copy_from_(self, value:
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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:
|
|
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:
|
|
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) ->
|
|
314
|
+
def __pos__(self) -> Array[T, Size]:
|
|
309
315
|
"""Return a copy of the array."""
|
|
310
316
|
return self._copy_()
|
sonolus/script/array_like.py
CHANGED
|
@@ -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:
|
|
30
|
+
def __getitem__(self, index: int) -> T:
|
|
30
31
|
...
|
|
31
32
|
|
|
32
|
-
def __setitem__(self, index:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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:
|
|
283
|
+
def __getitem__(self, index: int) -> V:
|
|
285
284
|
return self.array[len(self) - 1 - index]
|
|
286
285
|
|
|
287
|
-
def __setitem__(self, index:
|
|
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
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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:
|
|
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()
|