sonolus.py 0.3.1__py3-none-any.whl → 0.3.3__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 +16 -4
- sonolus/backend/node.py +13 -5
- sonolus/backend/optimize/allocate.py +41 -4
- sonolus/backend/optimize/flow.py +24 -7
- sonolus/backend/optimize/optimize.py +2 -9
- sonolus/backend/utils.py +6 -1
- sonolus/backend/visitor.py +72 -23
- sonolus/build/cli.py +6 -1
- sonolus/build/engine.py +1 -1
- sonolus/script/archetype.py +52 -24
- sonolus/script/array.py +20 -8
- sonolus/script/array_like.py +30 -3
- sonolus/script/containers.py +27 -7
- sonolus/script/debug.py +66 -8
- sonolus/script/globals.py +17 -0
- sonolus/script/internal/builtin_impls.py +12 -8
- sonolus/script/internal/context.py +55 -1
- sonolus/script/internal/range.py +25 -2
- sonolus/script/internal/simulation_context.py +131 -0
- sonolus/script/internal/tuple_impl.py +18 -11
- sonolus/script/interval.py +60 -2
- sonolus/script/iterator.py +3 -2
- sonolus/script/num.py +11 -2
- sonolus/script/options.py +24 -1
- sonolus/script/quad.py +41 -3
- sonolus/script/record.py +24 -3
- sonolus/script/runtime.py +411 -0
- sonolus/script/stream.py +133 -16
- sonolus/script/transform.py +291 -2
- sonolus/script/values.py +9 -3
- sonolus/script/vec.py +14 -2
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/METADATA +1 -1
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/RECORD +36 -35
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/licenses/LICENSE +0 -0
sonolus/script/archetype.py
CHANGED
|
@@ -185,14 +185,14 @@ class _ArchetypeField(SonolusDescriptor):
|
|
|
185
185
|
def imported(*, name: str | None = None) -> Any:
|
|
186
186
|
"""Declare a field as imported.
|
|
187
187
|
|
|
188
|
-
Imported fields may be loaded from the level
|
|
188
|
+
Imported fields may be loaded from the level.
|
|
189
189
|
|
|
190
190
|
In watch mode, data may also be loaded from a corresponding exported field in play mode.
|
|
191
191
|
|
|
192
192
|
Imported fields may only be updated in the `preprocess` callback, and are read-only in other callbacks.
|
|
193
193
|
|
|
194
194
|
Usage:
|
|
195
|
-
```
|
|
195
|
+
```python
|
|
196
196
|
class MyArchetype(PlayArchetype):
|
|
197
197
|
field: int = imported()
|
|
198
198
|
field_with_explicit_name: int = imported(name="field_name")
|
|
@@ -207,10 +207,10 @@ def entity_data() -> Any:
|
|
|
207
207
|
Entity data is accessible from other entities, but may only be updated in the `preprocess` callback
|
|
208
208
|
and is read-only in other callbacks.
|
|
209
209
|
|
|
210
|
-
It functions like `imported
|
|
210
|
+
It functions like `imported` and shares the same underlying storage, except that it is not loaded from a level.
|
|
211
211
|
|
|
212
212
|
Usage:
|
|
213
|
-
```
|
|
213
|
+
```python
|
|
214
214
|
class MyArchetype(PlayArchetype):
|
|
215
215
|
field: int = entity_data()
|
|
216
216
|
```
|
|
@@ -226,7 +226,7 @@ def exported(*, name: str | None = None) -> Any:
|
|
|
226
226
|
Exported fields are write-only.
|
|
227
227
|
|
|
228
228
|
Usage:
|
|
229
|
-
```
|
|
229
|
+
```python
|
|
230
230
|
class MyArchetype(PlayArchetype):
|
|
231
231
|
field: int = exported()
|
|
232
232
|
field_with_explicit_name: int = exported(name="#FIELD")
|
|
@@ -238,12 +238,13 @@ def exported(*, name: str | None = None) -> Any:
|
|
|
238
238
|
def entity_memory() -> Any:
|
|
239
239
|
"""Declare a field as entity memory.
|
|
240
240
|
|
|
241
|
-
Entity memory is private to the entity and is not accessible from other entities.
|
|
241
|
+
Entity memory is private to the entity and is not accessible from other entities. It may be read or updated in any
|
|
242
|
+
callback associated with the entity.
|
|
242
243
|
|
|
243
244
|
Entity memory fields may also be set when an entity is spawned using the `spawn()` method.
|
|
244
245
|
|
|
245
246
|
Usage:
|
|
246
|
-
```
|
|
247
|
+
```python
|
|
247
248
|
class MyArchetype(PlayArchetype):
|
|
248
249
|
field: int = entity_memory()
|
|
249
250
|
|
|
@@ -257,10 +258,11 @@ def shared_memory() -> Any:
|
|
|
257
258
|
|
|
258
259
|
Shared memory is accessible from other entities.
|
|
259
260
|
|
|
260
|
-
Shared memory may only be updated by sequential callbacks
|
|
261
|
+
Shared memory may be read in any callback, but may only be updated by sequential callbacks
|
|
262
|
+
(`preprocess`, `update_sequential`, and `touch`).
|
|
261
263
|
|
|
262
264
|
Usage:
|
|
263
|
-
```
|
|
265
|
+
```python
|
|
264
266
|
class MyArchetype(PlayArchetype):
|
|
265
267
|
field: int = shared_memory()
|
|
266
268
|
```
|
|
@@ -280,7 +282,7 @@ class StandardImport:
|
|
|
280
282
|
"""Standard import annotations for Archetype fields.
|
|
281
283
|
|
|
282
284
|
Usage:
|
|
283
|
-
```
|
|
285
|
+
```python
|
|
284
286
|
class MyArchetype(WatchArchetype):
|
|
285
287
|
judgment: StandardImport.JUDGMENT
|
|
286
288
|
```
|
|
@@ -313,7 +315,7 @@ def callback[T: Callable](*, order: int = 0) -> Callable[[T], T]:
|
|
|
313
315
|
Callbacks are execute from lowest to highest order. By default, callbacks have an order of 0.
|
|
314
316
|
|
|
315
317
|
Usage:
|
|
316
|
-
```
|
|
318
|
+
```python
|
|
317
319
|
class MyArchetype(PlayArchetype):
|
|
318
320
|
@callback(order=1)
|
|
319
321
|
def update_sequential(self):
|
|
@@ -360,6 +362,8 @@ class ArchetypeSchema(TypedDict):
|
|
|
360
362
|
class _BaseArchetype:
|
|
361
363
|
_is_comptime_value_ = True
|
|
362
364
|
|
|
365
|
+
_removable_prefix: ClassVar[str] = ""
|
|
366
|
+
|
|
363
367
|
_supported_callbacks_: ClassVar[dict[str, CallbackInfo]]
|
|
364
368
|
_default_callbacks_: ClassVar[set[Callable]]
|
|
365
369
|
|
|
@@ -440,7 +444,7 @@ class _BaseArchetype:
|
|
|
440
444
|
"""Spawn an entity of this archetype, injecting the given values into entity memory.
|
|
441
445
|
|
|
442
446
|
Usage:
|
|
443
|
-
```
|
|
447
|
+
```python
|
|
444
448
|
class MyArchetype(PlayArchetype):
|
|
445
449
|
field: int = entity_memory()
|
|
446
450
|
|
|
@@ -492,7 +496,7 @@ class _BaseArchetype:
|
|
|
492
496
|
cls._default_callbacks_ = {getattr(cls, cb_info.py_name) for cb_info in cls._supported_callbacks_.values()}
|
|
493
497
|
return
|
|
494
498
|
if cls.name is None or cls.name in {getattr(mro_entry, "name", None) for mro_entry in cls.mro()[1:]}:
|
|
495
|
-
cls.name = cls.__name__
|
|
499
|
+
cls.name = cls.__name__.removeprefix(cls._removable_prefix)
|
|
496
500
|
cls._callbacks_ = []
|
|
497
501
|
for name in cls._supported_callbacks_:
|
|
498
502
|
cb = getattr(cls, name)
|
|
@@ -545,11 +549,19 @@ class _BaseArchetype:
|
|
|
545
549
|
metadata = _annotation_defaults.get(metadata, metadata)
|
|
546
550
|
if isinstance(metadata, _ArchetypeFieldInfo):
|
|
547
551
|
if field_info is not None:
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
552
|
+
if field_info.storage == metadata.storage and field_info.name is None:
|
|
553
|
+
field_info = metadata
|
|
554
|
+
elif field_info.storage == metadata.storage and (
|
|
555
|
+
metadata.name is None or field_info.name == metadata.name
|
|
556
|
+
):
|
|
557
|
+
pass
|
|
558
|
+
else:
|
|
559
|
+
raise TypeError(
|
|
560
|
+
f"Unexpected multiple field annotations for '{name}', "
|
|
561
|
+
f"expected exactly one of imported, exported, entity_memory, or shared_memory"
|
|
562
|
+
)
|
|
563
|
+
else:
|
|
564
|
+
field_info = metadata
|
|
553
565
|
if field_info is None:
|
|
554
566
|
raise TypeError(
|
|
555
567
|
f"Missing field annotation for '{name}', "
|
|
@@ -642,7 +654,7 @@ class PlayArchetype(_BaseArchetype):
|
|
|
642
654
|
"""Base class for play mode archetypes.
|
|
643
655
|
|
|
644
656
|
Usage:
|
|
645
|
-
```
|
|
657
|
+
```python
|
|
646
658
|
class MyArchetype(PlayArchetype):
|
|
647
659
|
# Set to True if the entity is a note and contributes to combo and score
|
|
648
660
|
# Default is False
|
|
@@ -659,6 +671,8 @@ class PlayArchetype(_BaseArchetype):
|
|
|
659
671
|
```
|
|
660
672
|
"""
|
|
661
673
|
|
|
674
|
+
_removable_prefix: ClassVar[str] = "Play"
|
|
675
|
+
|
|
662
676
|
_supported_callbacks_ = PLAY_CALLBACKS
|
|
663
677
|
|
|
664
678
|
is_scored: ClassVar[bool] = False
|
|
@@ -808,7 +822,7 @@ class WatchArchetype(_BaseArchetype):
|
|
|
808
822
|
"""Base class for watch mode archetypes.
|
|
809
823
|
|
|
810
824
|
Usage:
|
|
811
|
-
```
|
|
825
|
+
```python
|
|
812
826
|
class MyArchetype(WatchArchetype):
|
|
813
827
|
imported_field: int = imported()
|
|
814
828
|
entity_memory_field: int = entity_memory()
|
|
@@ -820,6 +834,8 @@ class WatchArchetype(_BaseArchetype):
|
|
|
820
834
|
```
|
|
821
835
|
"""
|
|
822
836
|
|
|
837
|
+
_removable_prefix: ClassVar[str] = "Watch"
|
|
838
|
+
|
|
823
839
|
_supported_callbacks_ = WATCH_ARCHETYPE_CALLBACKS
|
|
824
840
|
|
|
825
841
|
def preprocess(self):
|
|
@@ -872,7 +888,7 @@ class WatchArchetype(_BaseArchetype):
|
|
|
872
888
|
case _ArchetypeSelfData():
|
|
873
889
|
return _deref(ctx().blocks.EntityInfo, 0, WatchEntityInfo)
|
|
874
890
|
case _ArchetypeReferenceData(index=index):
|
|
875
|
-
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(),
|
|
891
|
+
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
|
|
876
892
|
case _:
|
|
877
893
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
878
894
|
|
|
@@ -923,7 +939,7 @@ class PreviewArchetype(_BaseArchetype):
|
|
|
923
939
|
"""Base class for preview mode archetypes.
|
|
924
940
|
|
|
925
941
|
Usage:
|
|
926
|
-
```
|
|
942
|
+
```python
|
|
927
943
|
class MyArchetype(PreviewArchetype):
|
|
928
944
|
imported_field: int = imported()
|
|
929
945
|
entity_memory_field: int = entity_memory()
|
|
@@ -935,6 +951,8 @@ class PreviewArchetype(_BaseArchetype):
|
|
|
935
951
|
```
|
|
936
952
|
"""
|
|
937
953
|
|
|
954
|
+
_removable_prefix: ClassVar[str] = "Preview"
|
|
955
|
+
|
|
938
956
|
_supported_callbacks_ = PREVIEW_CALLBACKS
|
|
939
957
|
|
|
940
958
|
def preprocess(self):
|
|
@@ -1078,7 +1096,7 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1078
1096
|
May be used with `Any` to reference an unknown archetype.
|
|
1079
1097
|
|
|
1080
1098
|
Usage:
|
|
1081
|
-
```
|
|
1099
|
+
```python
|
|
1082
1100
|
class MyArchetype(PlayArchetype):
|
|
1083
1101
|
ref_1: EntityRef[OtherArchetype] = imported()
|
|
1084
1102
|
ref_2: EntityRef[Any] = imported()
|
|
@@ -1096,10 +1114,20 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1096
1114
|
"""Return a new reference with the given archetype type."""
|
|
1097
1115
|
return EntityRef[archetype](index=self.index)
|
|
1098
1116
|
|
|
1117
|
+
@meta_fn
|
|
1099
1118
|
def get(self) -> A:
|
|
1100
1119
|
"""Get the entity."""
|
|
1120
|
+
if ref := getattr(self, "_ref_", None):
|
|
1121
|
+
return ref
|
|
1101
1122
|
return self.archetype().at(self.index)
|
|
1102
1123
|
|
|
1124
|
+
@meta_fn
|
|
1125
|
+
def get_as(self, archetype: type[_BaseArchetype]) -> _BaseArchetype:
|
|
1126
|
+
"""Get the entity as the given archetype type."""
|
|
1127
|
+
if getattr(archetype, "_ref_", None):
|
|
1128
|
+
raise TypeError("Using get_as in level data is not supported.")
|
|
1129
|
+
return self.with_archetype(archetype).get()
|
|
1130
|
+
|
|
1103
1131
|
def archetype_matches(self) -> bool:
|
|
1104
1132
|
"""Check if entity at the index is precisely of the archetype."""
|
|
1105
1133
|
return self.index >= 0 and self.archetype().is_at(self.index)
|
|
@@ -1107,7 +1135,7 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1107
1135
|
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
1108
1136
|
ref = getattr(self, "_ref_", None)
|
|
1109
1137
|
if ref is None:
|
|
1110
|
-
return
|
|
1138
|
+
return Num._accept_(self.index)._to_list_()
|
|
1111
1139
|
else:
|
|
1112
1140
|
if ref not in level_refs:
|
|
1113
1141
|
raise KeyError("Reference to entity not in level data")
|
sonolus/script/array.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# ruff: noqa: A005
|
|
2
1
|
from __future__ import annotations
|
|
3
2
|
|
|
4
3
|
from collections.abc import Iterable
|
|
@@ -6,7 +5,7 @@ from typing import Any, Self, final
|
|
|
6
5
|
|
|
7
6
|
from sonolus.backend.ir import IRConst, IRSet
|
|
8
7
|
from sonolus.backend.place import BlockPlace
|
|
9
|
-
from sonolus.script.array_like import ArrayLike
|
|
8
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
10
9
|
from sonolus.script.debug import assert_unreachable
|
|
11
10
|
from sonolus.script.internal.context import ctx
|
|
12
11
|
from sonolus.script.internal.error import InternalError
|
|
@@ -16,14 +15,22 @@ from sonolus.script.internal.value import BackingSource, DataValue, Value
|
|
|
16
15
|
from sonolus.script.num import Num
|
|
17
16
|
|
|
18
17
|
|
|
18
|
+
class ArrayMeta(type):
|
|
19
|
+
@meta_fn
|
|
20
|
+
def __pos__[T](cls: type[T]) -> T:
|
|
21
|
+
"""Create a zero-initialized array instance."""
|
|
22
|
+
return cls._zero_()
|
|
23
|
+
|
|
24
|
+
|
|
19
25
|
@final
|
|
20
|
-
class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
26
|
+
class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
21
27
|
"""A fixed size array of values.
|
|
22
28
|
|
|
23
29
|
Usage:
|
|
24
30
|
```python
|
|
25
31
|
array_1 = Array(1, 2, 3)
|
|
26
32
|
array_2 = Array[int, 0]()
|
|
33
|
+
array_3 = +Array[int, 3] # Create a zero-initialized array
|
|
27
34
|
```
|
|
28
35
|
"""
|
|
29
36
|
|
|
@@ -66,7 +73,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
66
73
|
else:
|
|
67
74
|
return parameterized_cls._with_value([value._copy_() for value in values])
|
|
68
75
|
|
|
69
|
-
def __init__(self, *
|
|
76
|
+
def __init__(self, *args: T):
|
|
70
77
|
super().__init__()
|
|
71
78
|
|
|
72
79
|
@classmethod
|
|
@@ -180,7 +187,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
180
187
|
|
|
181
188
|
@meta_fn
|
|
182
189
|
def __getitem__(self, index: Num) -> T:
|
|
183
|
-
index: Num = Num._accept_(index)
|
|
190
|
+
index: Num = Num._accept_(get_positive_index(index, self.size()))
|
|
184
191
|
if index._is_py_() and 0 <= index._as_py_() < self.size():
|
|
185
192
|
const_index = index._as_py_()
|
|
186
193
|
if isinstance(const_index, float) and not const_index.is_integer():
|
|
@@ -224,7 +231,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
224
231
|
|
|
225
232
|
@meta_fn
|
|
226
233
|
def __setitem__(self, index: Num, value: T):
|
|
227
|
-
index: Num = Num._accept_(index)
|
|
234
|
+
index: Num = Num._accept_(get_positive_index(index, self.size()))
|
|
228
235
|
value = self.element_type()._accept_(value)
|
|
229
236
|
if ctx():
|
|
230
237
|
if isinstance(self._value, list):
|
|
@@ -289,10 +296,15 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
289
296
|
if isinstance(self._value, BlockPlace) or callable(self._value):
|
|
290
297
|
return f"{type(self).__name__}({self._value}...)"
|
|
291
298
|
else:
|
|
292
|
-
return f"{type(self).__name__}({
|
|
299
|
+
return f"{type(self).__name__}({', '.join(str(self[i]) for i in range(self.size()))})"
|
|
293
300
|
|
|
294
301
|
def __repr__(self):
|
|
295
302
|
if isinstance(self._value, BlockPlace) or callable(self._value):
|
|
296
303
|
return f"{type(self).__name__}({self._value}...)"
|
|
297
304
|
else:
|
|
298
|
-
return f"{type(self).__name__}({
|
|
305
|
+
return f"{type(self).__name__}({', '.join(repr(self[i]) for i in range(self.size()))})"
|
|
306
|
+
|
|
307
|
+
@meta_fn
|
|
308
|
+
def __pos__(self) -> Self:
|
|
309
|
+
"""Return a copy of the array."""
|
|
310
|
+
return self._copy_()
|
sonolus/script/array_like.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import random
|
|
4
|
-
from abc import
|
|
5
|
-
from collections.abc import Callable
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from collections.abc import Callable
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from sonolus.script.internal.context import ctx
|
|
9
|
+
from sonolus.script.internal.impl import meta_fn
|
|
8
10
|
from sonolus.script.iterator import SonolusIterator
|
|
9
11
|
from sonolus.script.num import Num
|
|
10
12
|
from sonolus.script.record import Record
|
|
@@ -13,7 +15,7 @@ from sonolus.script.values import copy
|
|
|
13
15
|
# Note: we don't use Range in this file because Range itself inherits from ArrayLike
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
class ArrayLike[T]
|
|
18
|
+
class ArrayLike[T]:
|
|
17
19
|
"""Mixin for array-like objects.
|
|
18
20
|
|
|
19
21
|
Inheritors must implement `__len__`, `__getitem__`, and `__setitem__`.
|
|
@@ -32,6 +34,8 @@ class ArrayLike[T](Sequence, ABC):
|
|
|
32
34
|
```
|
|
33
35
|
"""
|
|
34
36
|
|
|
37
|
+
_allow_instance_check_ = True
|
|
38
|
+
|
|
35
39
|
@abstractmethod
|
|
36
40
|
def __len__(self) -> int:
|
|
37
41
|
"""Return the length of the array."""
|
|
@@ -300,3 +304,26 @@ class _ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
|
|
|
300
304
|
|
|
301
305
|
def advance(self):
|
|
302
306
|
self.i += 1
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@meta_fn
|
|
310
|
+
def get_positive_index(index: Num, length: Num) -> Num:
|
|
311
|
+
"""Get the positive index for the given index in the array of the given length.
|
|
312
|
+
|
|
313
|
+
This is used to convert negative indixes relative to the end of the array to positive indices.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
index: The index to convert.
|
|
317
|
+
length: The length of the array.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
The positive index.
|
|
321
|
+
"""
|
|
322
|
+
if not ctx():
|
|
323
|
+
return index if index >= 0 else index + length
|
|
324
|
+
index = Num._accept_(index)
|
|
325
|
+
length = Num._accept_(length)
|
|
326
|
+
if index._is_py_() and length._is_py_():
|
|
327
|
+
return Num._accept_(index._as_py_() + length._as_py_() if index._as_py_() < 0 else index._as_py_())
|
|
328
|
+
else:
|
|
329
|
+
return index + (index < 0) * length
|
sonolus/script/containers.py
CHANGED
|
@@ -2,15 +2,16 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.visitor import compile_and_call
|
|
4
4
|
from sonolus.script.array import Array
|
|
5
|
-
from sonolus.script.array_like import ArrayLike
|
|
5
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
6
6
|
from sonolus.script.debug import error
|
|
7
7
|
from sonolus.script.internal.context import ctx
|
|
8
8
|
from sonolus.script.internal.impl import meta_fn
|
|
9
|
+
from sonolus.script.interval import clamp
|
|
9
10
|
from sonolus.script.iterator import SonolusIterator
|
|
10
11
|
from sonolus.script.num import Num
|
|
11
12
|
from sonolus.script.pointer import _deref
|
|
12
13
|
from sonolus.script.record import Record
|
|
13
|
-
from sonolus.script.values import
|
|
14
|
+
from sonolus.script.values import copy, zeros
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class Box[T](Record):
|
|
@@ -104,7 +105,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
104
105
|
"""Create a new empty array."""
|
|
105
106
|
element_type = cls.type_var_value(T)
|
|
106
107
|
capacity = cls.type_var_value(Capacity)
|
|
107
|
-
return cls(0,
|
|
108
|
+
return cls(0, zeros(Array[element_type, capacity]))
|
|
108
109
|
|
|
109
110
|
def __len__(self) -> int:
|
|
110
111
|
"""Return the number of elements in the array."""
|
|
@@ -140,11 +141,11 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
140
141
|
assert p == Pair(5, 6) # The value of p has changed
|
|
141
142
|
```
|
|
142
143
|
"""
|
|
143
|
-
return self._array[item]
|
|
144
|
+
return self._array[get_positive_index(item, len(self))]
|
|
144
145
|
|
|
145
146
|
def __setitem__(self, key: int, value: T):
|
|
146
147
|
"""Update the element at the given index."""
|
|
147
|
-
self._array[key] = value
|
|
148
|
+
self._array[get_positive_index(key, len(self))] = value
|
|
148
149
|
|
|
149
150
|
def __delitem__(self, key: int):
|
|
150
151
|
"""Remove the element at the given index."""
|
|
@@ -190,6 +191,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
190
191
|
"""
|
|
191
192
|
if index is None:
|
|
192
193
|
index = self._size - 1
|
|
194
|
+
index = get_positive_index(index, len(self))
|
|
193
195
|
assert 0 <= index < self._size
|
|
194
196
|
value = copy(self._array[index])
|
|
195
197
|
self._size -= 1
|
|
@@ -207,7 +209,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
207
209
|
index: The index at which to insert the value. Must be in the range [0, size].
|
|
208
210
|
value: The value to insert.
|
|
209
211
|
"""
|
|
210
|
-
|
|
212
|
+
index = clamp(get_positive_index(index, len(self)), 0, self._size)
|
|
211
213
|
assert self._size < len(self._array)
|
|
212
214
|
self._size += 1
|
|
213
215
|
for i in range(self._size - 1, index, -1):
|
|
@@ -329,6 +331,7 @@ class ArrayPointer[T](Record, ArrayLike[T]):
|
|
|
329
331
|
|
|
330
332
|
@meta_fn
|
|
331
333
|
def _get_item(self, item: int) -> T:
|
|
334
|
+
item = get_positive_index(item, self.size)
|
|
332
335
|
if not ctx():
|
|
333
336
|
raise TypeError("ArrayPointer values cannot be accessed outside of a context")
|
|
334
337
|
return _deref(
|
|
@@ -457,7 +460,7 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
457
460
|
key_type = cls.type_var_value(K)
|
|
458
461
|
value_type = cls.type_var_value(V)
|
|
459
462
|
capacity = cls.type_var_value(Capacity)
|
|
460
|
-
return cls(0,
|
|
463
|
+
return cls(0, zeros(Array[_ArrayMapEntry[key_type, value_type], capacity]))
|
|
461
464
|
|
|
462
465
|
def __len__(self) -> int:
|
|
463
466
|
"""Return the number of key-value pairs in the map."""
|
|
@@ -536,6 +539,23 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
536
539
|
self._array[self._size] = _ArrayMapEntry(key, value)
|
|
537
540
|
self._size += 1
|
|
538
541
|
|
|
542
|
+
def __delitem__(self, key: K):
|
|
543
|
+
"""Remove the key-value pair associated with the given key.
|
|
544
|
+
|
|
545
|
+
Must be called with a key that is present in the map.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
key: The key to remove
|
|
549
|
+
"""
|
|
550
|
+
for i in range(self._size):
|
|
551
|
+
entry = self._array[i]
|
|
552
|
+
if entry.key == key:
|
|
553
|
+
self._size -= 1
|
|
554
|
+
if i < self._size:
|
|
555
|
+
self._array[i] = self._array[self._size]
|
|
556
|
+
return
|
|
557
|
+
error()
|
|
558
|
+
|
|
539
559
|
def __contains__(self, key: K) -> bool:
|
|
540
560
|
"""Return whether the given key is present in the map.
|
|
541
561
|
|
sonolus/script/debug.py
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
from collections.abc import Callable, Sequence
|
|
2
2
|
from contextvars import ContextVar
|
|
3
|
-
from typing import Any, Never
|
|
3
|
+
from typing import Any, Literal, Never
|
|
4
4
|
|
|
5
5
|
from sonolus.backend.mode import Mode
|
|
6
6
|
from sonolus.backend.ops import Op
|
|
7
|
+
from sonolus.backend.optimize.constant_evaluation import SparseConditionalConstantPropagation
|
|
8
|
+
from sonolus.backend.optimize.copy_coalesce import CopyCoalesce
|
|
9
|
+
from sonolus.backend.optimize.dead_code import (
|
|
10
|
+
AdvancedDeadCodeElimination,
|
|
11
|
+
DeadCodeElimination,
|
|
12
|
+
UnreachableCodeElimination,
|
|
13
|
+
)
|
|
7
14
|
from sonolus.backend.optimize.flow import cfg_to_mermaid
|
|
15
|
+
from sonolus.backend.optimize.inlining import InlineVars
|
|
8
16
|
from sonolus.backend.optimize.passes import CompilerPass, run_passes
|
|
9
|
-
from sonolus.backend.optimize.simplify import CoalesceFlow
|
|
10
|
-
from sonolus.
|
|
17
|
+
from sonolus.backend.optimize.simplify import CoalesceFlow, NormalizeSwitch, RewriteToSwitch
|
|
18
|
+
from sonolus.backend.optimize.ssa import FromSSA, ToSSA
|
|
19
|
+
from sonolus.script.internal.context import GlobalContextState, ReadOnlyMemory, ctx, set_ctx
|
|
11
20
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
12
21
|
from sonolus.script.internal.native import native_function
|
|
22
|
+
from sonolus.script.internal.simulation_context import SimulationContext
|
|
13
23
|
from sonolus.script.num import Num
|
|
14
24
|
|
|
15
25
|
debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
|
|
@@ -93,12 +103,60 @@ def terminate():
|
|
|
93
103
|
raise RuntimeError("Terminated")
|
|
94
104
|
|
|
95
105
|
|
|
96
|
-
def visualize_cfg(
|
|
106
|
+
def visualize_cfg(
|
|
107
|
+
fn: Callable[[], Any] | Callable[[type], Any],
|
|
108
|
+
/,
|
|
109
|
+
*,
|
|
110
|
+
mode: Mode = Mode.PLAY,
|
|
111
|
+
archetype: type | None = None,
|
|
112
|
+
archetypes: list[type] | None,
|
|
113
|
+
passes: Sequence[CompilerPass] | Literal["minimal", "basic", "standard"] = "basic",
|
|
114
|
+
) -> str:
|
|
97
115
|
from sonolus.build.compile import callback_to_cfg
|
|
98
116
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
match passes:
|
|
118
|
+
case "minimal":
|
|
119
|
+
passes = [
|
|
120
|
+
CoalesceFlow(),
|
|
121
|
+
]
|
|
122
|
+
case "basic":
|
|
123
|
+
passes = [
|
|
124
|
+
CoalesceFlow(),
|
|
125
|
+
UnreachableCodeElimination(),
|
|
126
|
+
AdvancedDeadCodeElimination(),
|
|
127
|
+
CoalesceFlow(),
|
|
128
|
+
]
|
|
129
|
+
case "standard":
|
|
130
|
+
passes = [
|
|
131
|
+
CoalesceFlow(),
|
|
132
|
+
UnreachableCodeElimination(),
|
|
133
|
+
DeadCodeElimination(),
|
|
134
|
+
ToSSA(),
|
|
135
|
+
SparseConditionalConstantPropagation(),
|
|
136
|
+
UnreachableCodeElimination(),
|
|
137
|
+
DeadCodeElimination(),
|
|
138
|
+
CoalesceFlow(),
|
|
139
|
+
InlineVars(),
|
|
140
|
+
DeadCodeElimination(),
|
|
141
|
+
RewriteToSwitch(),
|
|
142
|
+
FromSSA(),
|
|
143
|
+
CoalesceFlow(),
|
|
144
|
+
CopyCoalesce(),
|
|
145
|
+
AdvancedDeadCodeElimination(),
|
|
146
|
+
CoalesceFlow(),
|
|
147
|
+
NormalizeSwitch(),
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
global_state = GlobalContextState(
|
|
151
|
+
mode,
|
|
152
|
+
{a: i for i, a in enumerate(archetypes)} if archetypes is not None else None,
|
|
153
|
+
ReadOnlyMemory(),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
cfg = callback_to_cfg(global_state, fn, "", archetype=archetype)
|
|
103
157
|
cfg = run_passes(cfg, passes)
|
|
104
158
|
return cfg_to_mermaid(cfg)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def simulation_context() -> SimulationContext:
|
|
162
|
+
return SimulationContext()
|
sonolus/script/globals.py
CHANGED
|
@@ -5,6 +5,7 @@ from sonolus.backend.blocks import Block, PlayBlock, PreviewBlock, TutorialBlock
|
|
|
5
5
|
from sonolus.backend.mode import Mode
|
|
6
6
|
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
7
7
|
from sonolus.script.internal.generic import validate_concrete_type
|
|
8
|
+
from sonolus.script.internal.simulation_context import sim_ctx
|
|
8
9
|
from sonolus.script.internal.value import Value
|
|
9
10
|
|
|
10
11
|
|
|
@@ -27,6 +28,11 @@ class _GlobalField(SonolusDescriptor):
|
|
|
27
28
|
if instance is None:
|
|
28
29
|
return self
|
|
29
30
|
|
|
31
|
+
if sim_ctx():
|
|
32
|
+
from sonolus.script.values import zeros
|
|
33
|
+
|
|
34
|
+
return sim_ctx().get_or_put_value((instance, self), lambda: zeros(self.type))
|
|
35
|
+
|
|
30
36
|
from sonolus.script.internal.context import ctx
|
|
31
37
|
|
|
32
38
|
info = owner._global_info_
|
|
@@ -38,6 +44,12 @@ class _GlobalField(SonolusDescriptor):
|
|
|
38
44
|
def __set__(self, instance, value):
|
|
39
45
|
from sonolus.script.internal.context import ctx
|
|
40
46
|
|
|
47
|
+
if sim_ctx():
|
|
48
|
+
from sonolus.script.values import zeros
|
|
49
|
+
|
|
50
|
+
sim_ctx().set_or_put_value((instance, self), lambda: zeros(self.type), value)
|
|
51
|
+
return
|
|
52
|
+
|
|
41
53
|
info = instance._global_info_
|
|
42
54
|
if not ctx():
|
|
43
55
|
raise RuntimeError("Global field access outside of compilation")
|
|
@@ -64,6 +76,11 @@ class _GlobalPlaceholder:
|
|
|
64
76
|
base = ctx().get_global_base(self)
|
|
65
77
|
return self.type._from_place_(base)
|
|
66
78
|
|
|
79
|
+
def _get_sim_replacement_(self):
|
|
80
|
+
from sonolus.script.values import zeros
|
|
81
|
+
|
|
82
|
+
return sim_ctx().get_or_put_value(self, lambda: zeros(self.type))
|
|
83
|
+
|
|
67
84
|
|
|
68
85
|
def _create_global(cls: type, blocks: dict[Mode, Block], offset: int | None):
|
|
69
86
|
if issubclass(cls, Value):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
1
|
from collections.abc import Iterable
|
|
3
2
|
from typing import overload
|
|
4
3
|
|
|
4
|
+
from sonolus.script.array import Array
|
|
5
5
|
from sonolus.script.array_like import ArrayLike
|
|
6
6
|
from sonolus.script.internal.context import ctx
|
|
7
7
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
@@ -32,7 +32,7 @@ def _isinstance(value, type_):
|
|
|
32
32
|
return isinstance(value, TupleImpl)
|
|
33
33
|
if type_ in {_int, _float, _bool}:
|
|
34
34
|
raise TypeError("Instance check against int, float, or bool is not supported, use Num instead")
|
|
35
|
-
if not (isinstance(type_, type) and issubclass(type_, Value
|
|
35
|
+
if not (isinstance(type_, type) and (issubclass(type_, Value) or getattr(type_, "_allow_instance_check_", False))):
|
|
36
36
|
raise TypeError(f"Unsupported type: {type_} for isinstance")
|
|
37
37
|
return validate_value(isinstance(value, type_))
|
|
38
38
|
|
|
@@ -87,7 +87,7 @@ def _zip(*iterables):
|
|
|
87
87
|
if any(isinstance(iterable, TupleImpl) for iterable in iterables):
|
|
88
88
|
if not all(isinstance(iterable, TupleImpl) for iterable in iterables):
|
|
89
89
|
raise TypeError("Cannot mix tuples with other types in zip")
|
|
90
|
-
return TupleImpl._accept_(tuple(zip(*(iterable.
|
|
90
|
+
return TupleImpl._accept_(tuple(zip(*(iterable.value for iterable in iterables), strict=False)))
|
|
91
91
|
iterators = [compile_and_call(iterable.__iter__) for iterable in iterables]
|
|
92
92
|
if not all(isinstance(iterator, SonolusIterator) for iterator in iterators):
|
|
93
93
|
raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
|
|
@@ -130,6 +130,8 @@ def _max(*args, key: callable = _identity):
|
|
|
130
130
|
(iterable,) = args
|
|
131
131
|
if isinstance(iterable, ArrayLike):
|
|
132
132
|
return compile_and_call(iterable._max_, key=key)
|
|
133
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
134
|
+
return compile_and_call(Array(*iterable.value)._max_, key=key)
|
|
133
135
|
else:
|
|
134
136
|
raise TypeError(f"Unsupported type: {type(iterable)} for max")
|
|
135
137
|
else:
|
|
@@ -170,6 +172,8 @@ def _min(*args, key: callable = _identity):
|
|
|
170
172
|
(iterable,) = args
|
|
171
173
|
if isinstance(iterable, ArrayLike):
|
|
172
174
|
return compile_and_call(iterable._min_, key=key)
|
|
175
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
176
|
+
return compile_and_call(Array(*iterable.value)._min_, key=key)
|
|
173
177
|
else:
|
|
174
178
|
raise TypeError(f"Unsupported type: {type(iterable)} for min")
|
|
175
179
|
else:
|
|
@@ -222,12 +226,12 @@ def _float(value=0.0):
|
|
|
222
226
|
return value
|
|
223
227
|
|
|
224
228
|
|
|
225
|
-
@meta_fn
|
|
226
229
|
def _bool(value=False):
|
|
227
|
-
|
|
228
|
-
if
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
# Relies on the compiler to perform the conversion in a boolean context
|
|
231
|
+
if value: # noqa: SIM103
|
|
232
|
+
return True
|
|
233
|
+
else:
|
|
234
|
+
return False
|
|
231
235
|
|
|
232
236
|
|
|
233
237
|
_int._type_mapping_ = Num
|