sonolus.py 0.3.4__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 (63) 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 +12 -9
  25. sonolus/script/array.py +23 -18
  26. sonolus/script/array_like.py +26 -29
  27. sonolus/script/bucket.py +1 -1
  28. sonolus/script/containers.py +22 -26
  29. sonolus/script/debug.py +20 -43
  30. sonolus/script/effect.py +1 -1
  31. sonolus/script/globals.py +3 -3
  32. sonolus/script/instruction.py +2 -2
  33. sonolus/script/internal/builtin_impls.py +155 -28
  34. sonolus/script/internal/constant.py +13 -3
  35. sonolus/script/internal/context.py +46 -15
  36. sonolus/script/internal/impl.py +9 -3
  37. sonolus/script/internal/introspection.py +8 -1
  38. sonolus/script/internal/native.py +2 -2
  39. sonolus/script/internal/range.py +8 -11
  40. sonolus/script/internal/simulation_context.py +1 -1
  41. sonolus/script/internal/transient.py +2 -2
  42. sonolus/script/internal/value.py +41 -3
  43. sonolus/script/interval.py +13 -13
  44. sonolus/script/iterator.py +38 -107
  45. sonolus/script/maybe.py +139 -0
  46. sonolus/script/num.py +17 -2
  47. sonolus/script/options.py +1 -1
  48. sonolus/script/particle.py +1 -1
  49. sonolus/script/project.py +24 -5
  50. sonolus/script/quad.py +15 -15
  51. sonolus/script/record.py +16 -12
  52. sonolus/script/runtime.py +22 -18
  53. sonolus/script/sprite.py +1 -1
  54. sonolus/script/stream.py +66 -82
  55. sonolus/script/transform.py +35 -34
  56. sonolus/script/values.py +10 -10
  57. sonolus/script/vec.py +21 -18
  58. {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.0.dist-info}/METADATA +1 -1
  59. sonolus_py-0.4.0.dist-info/RECORD +93 -0
  60. sonolus_py-0.3.4.dist-info/RECORD +0 -92
  61. {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.0.dist-info}/WHEEL +0 -0
  62. {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.0.dist-info}/entry_points.txt +0 -0
  63. {sonolus_py-0.3.4.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
 
@@ -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
@@ -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 _:
@@ -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:
@@ -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,7 +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
34
- array_4 = +array_1 # Create a copy of array_1
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
35
40
  ```
36
41
  """
37
42
 
@@ -78,7 +83,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
78
83
  super().__init__()
79
84
 
80
85
  @classmethod
81
- def _with_value(cls, value) -> Self:
86
+ def _with_value(cls, value) -> Array[T, Size]:
82
87
  result = object.__new__(cls)
83
88
  result._value = value
84
89
  return result
@@ -92,11 +97,11 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
92
97
  return False
93
98
 
94
99
  @classmethod
95
- def _from_backing_source_(cls, source: BackingSource) -> Self:
100
+ def _from_backing_source_(cls, source: BackingSource) -> Array[T, Size]:
96
101
  return cls._with_value(source)
97
102
 
98
103
  @classmethod
99
- def _from_place_(cls, place: BlockPlace) -> Self:
104
+ def _from_place_(cls, place: BlockPlace) -> Array[T, Size]:
100
105
  return cls._with_value(place)
101
106
 
102
107
  @classmethod
@@ -104,7 +109,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
104
109
  return isinstance(value, cls)
105
110
 
106
111
  @classmethod
107
- def _accept_(cls, value: Any) -> Self:
112
+ def _accept_(cls, value: Any) -> Array[T, Size]:
108
113
  if not cls._accepts_(value):
109
114
  raise TypeError(f"Cannot accept value {value} as {cls.__name__}")
110
115
  return value
@@ -118,7 +123,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
118
123
  return self
119
124
 
120
125
  @classmethod
121
- def _from_list_(cls, values: Iterable[DataValue]) -> Self:
126
+ def _from_list_(cls, values: Iterable[DataValue]) -> Array[T, Size]:
122
127
  iterator = iter(values)
123
128
  return cls(*(cls.element_type()._from_list_(iterator) for _ in range(cls.size())))
124
129
 
@@ -143,19 +148,19 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
143
148
  def _flat_keys_(cls, prefix: str) -> list[str]:
144
149
  return [entry for i in range(cls.size()) for entry in cls.element_type()._flat_keys_(f"{prefix}[{i}]")]
145
150
 
146
- def _get_(self) -> Self:
151
+ def _get_(self) -> Array[T, Size]:
147
152
  return self
148
153
 
149
- def _set_(self, value: Self):
154
+ def _set_(self, value: Any):
150
155
  raise TypeError("Array does not support _set_")
151
156
 
152
- def _copy_from_(self, value: Self):
157
+ def _copy_from_(self, value: Any):
153
158
  if not isinstance(value, type(self)):
154
159
  raise TypeError("Cannot copy from different type")
155
160
  for i in range(self.size()):
156
161
  self[i] = value[i]
157
162
 
158
- def _copy_(self) -> Self:
163
+ def _copy_(self) -> Array[T, Size]:
159
164
  if ctx():
160
165
  place = ctx().alloc(size=self._size_())
161
166
  result: Self = self._from_place_(place)
@@ -166,7 +171,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
166
171
  return self._with_value([value._copy_() for value in self._value])
167
172
 
168
173
  @classmethod
169
- def _alloc_(cls) -> Self:
174
+ def _alloc_(cls) -> Array[T, Size]:
170
175
  if ctx():
171
176
  place = ctx().alloc(size=cls._size_())
172
177
  return cls._from_place_(place)
@@ -174,7 +179,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
174
179
  return cls._with_value([cls.element_type()._alloc_() for _ in range(cls.size())])
175
180
 
176
181
  @classmethod
177
- def _zero_(cls) -> Self:
182
+ def _zero_(cls) -> Array[T, Size]:
178
183
  if ctx():
179
184
  place = ctx().alloc(size=cls._size_())
180
185
  result: Self = cls._from_place_(place)
@@ -207,7 +212,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
207
212
  )
208
213
  elif callable(self._value):
209
214
  return self.element_type()._from_backing_source_(
210
- 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
211
216
  )
212
217
  else:
213
218
  raise InternalError("Unexpected array value")
@@ -225,7 +230,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
225
230
  elif callable(self._value):
226
231
  base_offset = index * Num(self.element_type()._size_())
227
232
  return self.element_type()._from_backing_source_(
228
- lambda offset: self._value((Num(offset) + base_offset).ir())
233
+ lambda offset: self._value((Num(offset) + base_offset).ir()) # type: ignore
229
234
  )
230
235
  else:
231
236
  raise InternalError("Unexpected array value")
@@ -252,7 +257,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
252
257
  elif callable(self._value):
253
258
  base_offset = index * Num(self.element_type()._size_())
254
259
  dst = self.element_type()._from_backing_source_(
255
- lambda offset: self._value((Num(offset) + base_offset).ir())
260
+ lambda offset: self._value((Num(offset) + base_offset).ir()) # type: ignore
256
261
  )
257
262
  else:
258
263
  raise InternalError("Unexpected array value")
@@ -306,6 +311,6 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
306
311
  return f"{type(self).__name__}({', '.join(repr(self[i]) for i in range(self.size()))})"
307
312
 
308
313
  @meta_fn
309
- def __pos__(self) -> Self:
314
+ def __pos__(self) -> Array[T, Size]:
310
315
  """Return a copy of the array."""
311
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__`.
@@ -134,11 +135,11 @@ 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
@@ -152,11 +153,11 @@ 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
@@ -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."""
@@ -221,14 +222,14 @@ def _insertion_sort[T](array: ArrayLike[T], start: int, end: int, key: Callable[
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
@@ -239,9 +240,9 @@ def _heapify[T](array: ArrayLike[T], end: int, index: int, reverse: bool):
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
@@ -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):
@@ -296,14 +295,12 @@ 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
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()
@@ -8,6 +8,7 @@ from sonolus.script.internal.context import ctx
8
8
  from sonolus.script.internal.impl import meta_fn
9
9
  from sonolus.script.interval import clamp
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.pointer import _deref
13
14
  from sonolus.script.record import Record
@@ -335,7 +336,8 @@ class ArrayPointer[T](Record, ArrayLike[T]):
335
336
  if not ctx():
336
337
  raise TypeError("ArrayPointer values cannot be accessed outside of a context")
337
338
  return _deref(
338
- self.block,
339
+ # Allows a compile time constant block so we can warn based on callback read/write access
340
+ (self._value["block"]._is_py_() and self._value["block"]._as_py_()) or self.block,
339
341
  self.offset + Num._accept_(item) * Num._accept_(self.element_type()._size_()),
340
342
  self.element_type(),
341
343
  )
@@ -600,40 +602,34 @@ class _ArrayMapKeyIterator[K, V, Capacity](Record, SonolusIterator):
600
602
  _map: ArrayMap[K, V, Capacity]
601
603
  _index: int
602
604
 
603
- def has_next(self) -> bool:
604
- return self._index < len(self._map)
605
-
606
- def get(self) -> K:
607
- return self._map._array[self._index].key
608
-
609
- def advance(self):
610
- self._index += 1
605
+ def next(self) -> Maybe[K]:
606
+ if self._index < len(self._map):
607
+ key = self._map._array[self._index].key
608
+ self._index += 1
609
+ return Some(key)
610
+ return Nothing
611
611
 
612
612
 
613
613
  class _ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
614
614
  _map: ArrayMap[K, V, Capacity]
615
615
  _index: int
616
616
 
617
- def has_next(self) -> bool:
618
- return self._index < len(self._map)
619
-
620
- def get(self) -> V:
621
- return self._map._array[self._index].value
622
-
623
- def advance(self):
624
- self._index += 1
617
+ def next(self) -> Maybe[V]:
618
+ if self._index < len(self._map):
619
+ value = self._map._array[self._index].value
620
+ self._index += 1
621
+ return Some(value)
622
+ return Nothing
625
623
 
626
624
 
627
625
  class _ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
628
626
  _map: ArrayMap[K, V, Capacity]
629
627
  _index: int
630
628
 
631
- def has_next(self) -> bool:
632
- return self._index < len(self._map)
633
-
634
- def get(self) -> tuple[K, V]:
635
- entry = self._map._array[self._index]
636
- return entry.key, entry.value
637
-
638
- def advance(self):
639
- self._index += 1
629
+ def next(self) -> Maybe[tuple[K, V]]:
630
+ if self._index < len(self._map):
631
+ entry = self._map._array[self._index]
632
+ result = (entry.key, entry.value)
633
+ self._index += 1
634
+ return Some(result)
635
+ return Nothing
sonolus/script/debug.py CHANGED
@@ -4,18 +4,9 @@ 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
- )
14
7
  from sonolus.backend.optimize.flow import cfg_to_mermaid
15
- from sonolus.backend.optimize.inlining import InlineVars
16
- from sonolus.backend.optimize.passes import CompilerPass, run_passes
17
- from sonolus.backend.optimize.simplify import CoalesceFlow, NormalizeSwitch, RewriteToSwitch
18
- from sonolus.backend.optimize.ssa import FromSSA, ToSSA
8
+ from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig, run_passes
9
+ from sonolus.backend.optimize.simplify import RenumberVars
19
10
  from sonolus.script.internal.context import GlobalContextState, ReadOnlyMemory, ctx, set_ctx
20
11
  from sonolus.script.internal.impl import meta_fn, validate_value
21
12
  from sonolus.script.internal.native import native_function
@@ -26,13 +17,13 @@ debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
26
17
 
27
18
 
28
19
  @meta_fn
29
- def error(message: str | None = None) -> Never:
20
+ def error(message: str | None = None) -> Never: # type: ignore
30
21
  """Raise an error.
31
22
 
32
23
  This function is used to raise an error during runtime.
33
24
  When this happens, the game will pause in debug mode. The current callback will also immediately return 0.
34
25
  """
35
- message = validate_value(message)._as_py_() if message is not None else "Error"
26
+ message = validate_value(message)._as_py_() or "Error" # type: ignore
36
27
  if not isinstance(message, str):
37
28
  raise ValueError("Expected a string")
38
29
  if ctx():
@@ -50,7 +41,7 @@ def static_error(message: str | None = None) -> Never:
50
41
  This function is used to raise an error during compile-time if the compiler cannot guarantee that
51
42
  this function will not be called during runtime.
52
43
  """
53
- message = validate_value(message)._as_py_() if message is not None else "Error"
44
+ message = validate_value(message)._as_py_() or "Error" # type: ignore
54
45
  if not isinstance(message, str):
55
46
  raise ValueError("Expected a string")
56
47
  raise RuntimeError(message)
@@ -60,7 +51,7 @@ def static_error(message: str | None = None) -> Never:
60
51
  def debug_log(value: int | float | bool):
61
52
  """Log a value in debug mode."""
62
53
  if debug_log_callback.get(None):
63
- return debug_log_callback.get()(value)
54
+ return debug_log_callback.get()(value) # type: ignore
64
55
  else:
65
56
  return _debug_log(value)
66
57
 
@@ -91,7 +82,7 @@ def assert_false(value: int | float | bool, message: str | None = None):
91
82
 
92
83
  @meta_fn
93
84
  def assert_unreachable(message: str | None = None) -> Never:
94
- message = validate_value(message)._as_py_() or "Unreachable code reached"
85
+ message = validate_value(message)._as_py_() or "Unreachable code reached" # type: ignore
95
86
  raise RuntimeError(message)
96
87
 
97
88
 
@@ -108,43 +99,29 @@ def visualize_cfg(
108
99
  /,
109
100
  *,
110
101
  mode: Mode = Mode.PLAY,
102
+ callback: str = "",
111
103
  archetype: type | None = None,
112
- archetypes: list[type] | None,
113
- passes: Sequence[CompilerPass] | Literal["minimal", "basic", "standard"] = "basic",
104
+ archetypes: list[type] | None = None,
105
+ passes: Sequence[CompilerPass] | Literal["minimal", "fast", "standard"] = "fast",
114
106
  ) -> str:
107
+ from sonolus.backend.optimize.optimize import FAST_PASSES, MINIMAL_PASSES, STANDARD_PASSES
115
108
  from sonolus.build.compile import callback_to_cfg
116
109
 
117
110
  match passes:
118
111
  case "minimal":
119
112
  passes = [
120
- CoalesceFlow(),
113
+ *MINIMAL_PASSES[:-1],
114
+ RenumberVars(),
121
115
  ]
122
- case "basic":
116
+ case "fast":
123
117
  passes = [
124
- CoalesceFlow(),
125
- UnreachableCodeElimination(),
126
- AdvancedDeadCodeElimination(),
127
- CoalesceFlow(),
118
+ *FAST_PASSES[:-1],
119
+ RenumberVars(),
128
120
  ]
129
121
  case "standard":
130
122
  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(),
123
+ *STANDARD_PASSES[:-1],
124
+ RenumberVars(),
148
125
  ]
149
126
 
150
127
  global_state = GlobalContextState(
@@ -153,8 +130,8 @@ def visualize_cfg(
153
130
  ReadOnlyMemory(),
154
131
  )
155
132
 
156
- cfg = callback_to_cfg(global_state, fn, "", archetype=archetype)
157
- cfg = run_passes(cfg, passes)
133
+ cfg = callback_to_cfg(global_state, fn, callback, archetype=archetype) # type: ignore
134
+ cfg = run_passes(cfg, passes, OptimizerConfig(mode=mode))
158
135
  return cfg_to_mermaid(cfg)
159
136
 
160
137
 
sonolus/script/effect.py CHANGED
@@ -134,7 +134,7 @@ def effect(name: str) -> Any:
134
134
  return EffectInfo(name)
135
135
 
136
136
 
137
- type Effects = NewType("Effects", Any)
137
+ type Effects = NewType("Effects", Any) # type: ignore
138
138
 
139
139
 
140
140
  @dataclass_transform()
sonolus/script/globals.py CHANGED
@@ -39,7 +39,7 @@ class _GlobalField(SonolusDescriptor):
39
39
  if not ctx():
40
40
  raise RuntimeError("Global field access outside of compilation")
41
41
  base = ctx().get_global_base(info)
42
- return self.type._from_place_(base.add_offset(self.offset))._get_()
42
+ return self.type._from_place_(base.add_offset(self.offset))._get_readonly_()
43
43
 
44
44
  def __set__(self, instance, value):
45
45
  from sonolus.script.internal.context import ctx
@@ -96,8 +96,8 @@ def _create_global(cls: type, blocks: dict[Mode, Block], offset: int | None):
96
96
  type_ = validate_concrete_type(annotation)
97
97
  setattr(cls, name, _GlobalField(name, type_, i, field_offset))
98
98
  field_offset += type_._size_()
99
- cls._global_info_ = _GlobalInfo(cls.__name__, field_offset, blocks, offset)
100
- cls._is_comptime_value_ = True
99
+ cls._global_info_ = _GlobalInfo(cls.__name__, field_offset, blocks, offset) # type: ignore
100
+ cls._is_comptime_value_ = True # type: ignore
101
101
  return cls()
102
102
 
103
103
 
@@ -70,8 +70,8 @@ def instruction_icon(name: str) -> Any:
70
70
  return _InstructionIconInfo(name=name)
71
71
 
72
72
 
73
- type TutorialInstructions = NewType("TutorialInstructions", Any)
74
- type TutorialInstructionIcons = NewType("TutorialInstructionIcons", Any)
73
+ type TutorialInstructions = NewType("TutorialInstructions", Any) # type: ignore
74
+ type TutorialInstructionIcons = NewType("TutorialInstructionIcons", Any) # type: ignore
75
75
 
76
76
 
77
77
  @dataclass_transform()