sonolus.py 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sonolus.py might be problematic. Click here for more details.

Files changed (77) hide show
  1. sonolus/backend/finalize.py +18 -10
  2. sonolus/backend/interpret.py +7 -7
  3. sonolus/backend/ir.py +24 -0
  4. sonolus/backend/optimize/__init__.py +0 -0
  5. sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
  6. sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
  7. sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
  8. sonolus/backend/optimize/dead_code.py +185 -0
  9. sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
  10. sonolus/backend/{flow.py → optimize/flow.py} +6 -5
  11. sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
  12. sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
  13. sonolus/backend/optimize/optimize.py +44 -0
  14. sonolus/backend/{passes.py → optimize/passes.py} +1 -1
  15. sonolus/backend/optimize/simplify.py +191 -0
  16. sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
  17. sonolus/backend/place.py +17 -25
  18. sonolus/backend/utils.py +10 -0
  19. sonolus/backend/visitor.py +360 -101
  20. sonolus/build/compile.py +8 -8
  21. sonolus/build/engine.py +10 -5
  22. sonolus/script/archetype.py +419 -137
  23. sonolus/script/array.py +25 -8
  24. sonolus/script/array_like.py +297 -0
  25. sonolus/script/bucket.py +73 -11
  26. sonolus/script/containers.py +234 -51
  27. sonolus/script/debug.py +8 -8
  28. sonolus/script/easing.py +147 -105
  29. sonolus/script/effect.py +60 -0
  30. sonolus/script/engine.py +71 -4
  31. sonolus/script/globals.py +66 -32
  32. sonolus/script/instruction.py +79 -25
  33. sonolus/script/internal/builtin_impls.py +138 -27
  34. sonolus/script/internal/constant.py +139 -0
  35. sonolus/script/internal/context.py +14 -5
  36. sonolus/script/internal/dict_impl.py +65 -0
  37. sonolus/script/internal/generic.py +6 -9
  38. sonolus/script/internal/impl.py +38 -13
  39. sonolus/script/internal/introspection.py +5 -2
  40. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  41. sonolus/script/internal/native.py +3 -3
  42. sonolus/script/internal/random.py +67 -0
  43. sonolus/script/internal/range.py +81 -0
  44. sonolus/script/internal/transient.py +51 -0
  45. sonolus/script/internal/tuple_impl.py +113 -0
  46. sonolus/script/interval.py +234 -16
  47. sonolus/script/iterator.py +120 -167
  48. sonolus/script/level.py +24 -0
  49. sonolus/script/num.py +79 -47
  50. sonolus/script/options.py +78 -12
  51. sonolus/script/particle.py +37 -4
  52. sonolus/script/pointer.py +4 -4
  53. sonolus/script/print.py +22 -1
  54. sonolus/script/project.py +8 -0
  55. sonolus/script/{graphics.py → quad.py} +75 -12
  56. sonolus/script/record.py +44 -13
  57. sonolus/script/runtime.py +50 -1
  58. sonolus/script/sprite.py +197 -112
  59. sonolus/script/text.py +2 -0
  60. sonolus/script/timing.py +72 -0
  61. sonolus/script/transform.py +296 -66
  62. sonolus/script/ui.py +134 -78
  63. sonolus/script/values.py +6 -13
  64. sonolus/script/vec.py +118 -3
  65. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  66. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  67. sonolus/backend/dead_code.py +0 -80
  68. sonolus/backend/optimize.py +0 -37
  69. sonolus/backend/simplify.py +0 -47
  70. sonolus/script/comptime.py +0 -160
  71. sonolus/script/random.py +0 -14
  72. sonolus/script/range.py +0 -58
  73. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  74. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  75. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +0 -0
  76. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -1,16 +1,33 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from sonolus.script.array import Array
4
+ from sonolus.script.array_like import ArrayLike
4
5
  from sonolus.script.debug import error
5
- from sonolus.script.iterator import ArrayLike, SonolusIterator
6
- from sonolus.script.range import Range
6
+ from sonolus.script.iterator import SonolusIterator
7
7
  from sonolus.script.record import Record
8
8
  from sonolus.script.values import alloc, copy
9
9
 
10
10
 
11
11
  class Pair[T, U](Record):
12
+ """A generic pair of values.
13
+
14
+ Usage:
15
+ ```python
16
+ Pair[T, U](first: T, second: U)
17
+ ```
18
+
19
+ Examples:
20
+ ```python
21
+ pair = Pair(1, 2)
22
+ pair = Pair[int, Pair[int, int]](1, Pair(2, 3))
23
+ ```
24
+ """
25
+
12
26
  first: T
27
+ """The first value."""
28
+
13
29
  second: U
30
+ """The second value."""
14
31
 
15
32
  def __lt__(self, other):
16
33
  if self.first == other.first:
@@ -34,46 +51,100 @@ class Pair[T, U](Record):
34
51
 
35
52
 
36
53
  class VarArray[T, Capacity](Record, ArrayLike[T]):
54
+ """An array with a variable size and fixed maximum capacity.
55
+
56
+ Usage:
57
+ ```python
58
+ VarArray[T, Capacity].new() # Create a new empty array
59
+ ```
60
+
61
+ Examples:
62
+ ```python
63
+ array = VarArray[int, 10].new()
64
+ array.append(1)
65
+ ```
66
+ """
67
+
37
68
  _size: int
38
69
  _array: Array[T, Capacity]
39
70
 
40
71
  @classmethod
41
72
  def new(cls):
42
- element_type = cls._get_type_arg_(T)
43
- capacity = cls._get_type_arg_(Capacity)
73
+ """Create a new empty array."""
74
+ element_type = cls.type_var_value(T)
75
+ capacity = cls.type_var_value(Capacity)
44
76
  return cls(0, alloc(Array[element_type, capacity]))
45
77
 
46
- def size(self) -> int:
78
+ def __len__(self) -> int:
79
+ """Return the number of elements in the array."""
47
80
  return self._size
48
81
 
49
82
  @classmethod
50
83
  def capacity(cls) -> int:
51
- return cls._get_type_arg_(Capacity)
84
+ """Return the maximum number of elements the array can hold."""
85
+ return cls.type_var_value(Capacity)
52
86
 
53
87
  def is_full(self) -> bool:
88
+ """Return whether the array is full."""
54
89
  return self._size == self.capacity()
55
90
 
56
91
  def __getitem__(self, item) -> T:
92
+ """Return the element at the given index.
93
+
94
+ The returned value continues to be part of the array.
95
+ Future modifications to the array will affect the returned value.
96
+
97
+ Note:
98
+ Future modifications to the array may cause unexpected changes to the returned value.
99
+ If the array may be modified in the future, it's recommended to make a copy of the value.
100
+
101
+ For example:
102
+ ```python
103
+ a = VarArray[Pair, 10].new()
104
+ a.append(Pair(1, 2))
105
+ a.append(Pair(3, 4))
106
+ a.append(Pair(5, 6))
107
+ p = a[1]
108
+ a.pop(0) # Elements are shifted back
109
+ assert p == Pair(5, 6) # The value of p has changed
110
+ ```
111
+ """
57
112
  return self._array[item]
58
113
 
59
114
  def __setitem__(self, key: int, value: T):
115
+ """Update the element at the given index."""
60
116
  self._array[key] = value
61
117
 
118
+ def __delitem__(self, key: int):
119
+ """Remove the element at the given index."""
120
+ self.pop(key)
121
+
62
122
  def append(self, value: T):
63
- """Appends a copy of the given value to the end of the array."""
64
- assert self._size < self._array.size()
123
+ """Append a copy of the given value to the end of the array.
124
+
125
+ Args:
126
+ value: The value to append.
127
+ """
128
+ assert self._size < len(self._array)
65
129
  self._array[self._size] = value
66
130
  self._size += 1
67
131
 
68
132
  def extend(self, values: ArrayLike[T]):
69
- """Appends copies of the values in the given array to the end of the array."""
133
+ """Appends copies of the values in the given array to the end of the array.
134
+
135
+ Args:
136
+ values: The values to append.
137
+ """
70
138
  for value in values:
71
139
  self.append(value)
72
140
 
73
141
  def pop(self, index: int | None = None) -> T:
74
- """Removes and returns a copy of the value at the given index.
142
+ """Remove and return a copy of the value at the given index.
75
143
 
76
144
  Preserves the relative order of the elements.
145
+
146
+ Args:
147
+ index: The index of the value to remove. If None, the last element is removed.
77
148
  """
78
149
  if index is None:
79
150
  index = self._size - 1
@@ -81,35 +152,48 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
81
152
  value = copy(self._array[index])
82
153
  self._size -= 1
83
154
  if index < self._size:
84
- for i in Range(index, self._size):
155
+ for i in range(index, self._size):
85
156
  self._array[i] = self._array[i + 1]
86
157
  return value
87
158
 
88
159
  def insert(self, index: int, value: T):
89
- """Inserts a copy of the given value at the given index.
160
+ """Insert a copy of the given value at the given index.
90
161
 
91
162
  Preserves the relative order of the elements.
163
+
164
+ Args:
165
+ index: The index at which to insert the value. Must be in the range [0, size].
166
+ value: The value to insert.
92
167
  """
93
168
  assert 0 <= index <= self._size
94
- assert self._size < self._array.size()
169
+ assert self._size < len(self._array)
95
170
  self._size += 1
96
- for i in Range(self._size - 1, index, -1):
171
+ for i in range(self._size - 1, index, -1):
97
172
  self._array[i] = self._array[i - 1]
98
173
  self._array[index] = value
99
174
 
100
175
  def remove(self, value: T) -> bool:
101
- """Removes the first occurrence of the given value, returning whether the value was removed.
176
+ """Remove the first occurrence of the given value, returning whether the value was removed.
102
177
 
103
178
  Preserves the relative order of the elements.
179
+
180
+ Args:
181
+ value: The value to remove
182
+
183
+ Returns:
184
+ True if the value was removed, False otherwise.
104
185
  """
105
- index = self.index_of(value)
186
+ index = self.index(value)
106
187
  if index < 0:
107
188
  return False
108
189
  self.pop(index)
109
190
  return True
110
191
 
111
192
  def clear(self):
112
- """Sets size to zero."""
193
+ """Clear the array, removing all elements.
194
+
195
+ References to elements are not immediately changed, but future insertions may overwrite them.
196
+ """
113
197
  self._size = 0
114
198
 
115
199
  def set_add(self, value: T) -> bool:
@@ -117,8 +201,14 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
117
201
 
118
202
  If the value is already present, the array is not modified.
119
203
  If the array is full, the value is not added.
204
+
205
+ Args:
206
+ value: The value to add
207
+
208
+ Returns:
209
+ True if the value was added, False otherwise.
120
210
  """
121
- if self._size >= self._array.size():
211
+ if self._size >= len(self._array):
122
212
  return False
123
213
  if value in self:
124
214
  return False
@@ -129,8 +219,14 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
129
219
  """Removes the first occurrence of the given value, returning whether the value was removed.
130
220
 
131
221
  Does not preserve the relative order of the elements.
222
+
223
+ Args:
224
+ value: The value to remove
225
+
226
+ Returns:
227
+ True if the value was removed, False otherwise.
132
228
  """
133
- index = self.index_of(value)
229
+ index = self.index(value)
134
230
  if index < 0:
135
231
  return False
136
232
  if index < self._size - 1:
@@ -138,11 +234,18 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
138
234
  self._size -= 1
139
235
  return True
140
236
 
237
+ def __iadd__(self, other):
238
+ """Appends copies of the values in the given array to the end of the array."""
239
+ self.extend(other)
240
+ return self
241
+
141
242
  def __eq__(self, other):
142
- if self.size() != other.size():
243
+ if not isinstance(other, ArrayLike):
244
+ return False
245
+ if len(self) != len(other):
143
246
  return False
144
247
  i = 0
145
- while i < self.size():
248
+ while i < len(self):
146
249
  if self[i] != other[i]:
147
250
  return False
148
251
  i += 1
@@ -155,69 +258,144 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
155
258
  raise TypeError("unhashable type: 'VarArray'")
156
259
 
157
260
 
158
- class ArrayMapEntry[K, V](Record):
261
+ class _ArrayMapEntry[K, V](Record):
159
262
  key: K
160
263
  value: V
161
264
 
162
265
 
163
266
  class ArrayMap[K, V, Capacity](Record):
267
+ """A map implemented as an array of key-value pairs with a fixed maximum capacity.
268
+
269
+ Usage:
270
+ ```python
271
+ ArrayMap[K, V, Capacity].new() # Create a new empty map
272
+ ```
273
+
274
+ Examples:
275
+ ```python
276
+ map = ArrayMap[int, int, 10].new()
277
+ map[1] = 2
278
+ map[3] = 4
279
+ assert 1 in map
280
+ assert 2 not in map
281
+ assert map[3] == 4
282
+ ```
283
+ """
284
+
164
285
  _size: int
165
- _array: Array[ArrayMapEntry[K, V], Capacity]
286
+ _array: Array[_ArrayMapEntry[K, V], Capacity]
166
287
 
167
288
  @classmethod
168
289
  def new(cls):
169
- key_type = cls._get_type_arg_(K)
170
- value_type = cls._get_type_arg_(V)
171
- capacity = cls._get_type_arg_(Capacity)
172
- return cls(0, alloc(Array[ArrayMapEntry[key_type, value_type], capacity]))
173
-
174
- def size(self) -> int:
290
+ """Create a new empty map."""
291
+ key_type = cls.type_var_value(K)
292
+ value_type = cls.type_var_value(V)
293
+ capacity = cls.type_var_value(Capacity)
294
+ return cls(0, alloc(Array[_ArrayMapEntry[key_type, value_type], capacity]))
295
+
296
+ def __len__(self) -> int:
297
+ """Return the number of key-value pairs in the map."""
175
298
  return self._size
176
299
 
177
300
  @classmethod
178
301
  def capacity(cls) -> int:
179
- return cls._get_type_arg_(Capacity)
302
+ """Return the maximum number of key-value pairs the map can hold."""
303
+ return cls.type_var_value(Capacity)
180
304
 
181
305
  def is_full(self) -> bool:
306
+ """Return whether the map is full."""
182
307
  return self._size == self.capacity()
183
308
 
184
309
  def keys(self) -> SonolusIterator[K]:
310
+ """Return an iterator over the keys in the map."""
185
311
  return _ArrayMapKeyIterator(self, 0)
186
312
 
187
313
  def values(self) -> SonolusIterator[V]:
188
- return ArrayMapValueIterator(self, 0)
314
+ """Return an iterator over the values in the map."""
315
+ return _ArrayMapValueIterator(self, 0)
189
316
 
190
317
  def items(self) -> SonolusIterator[tuple[K, V]]:
191
- return ArrayMapEntryIterator(self, 0)
318
+ """Return an iterator over the key-value pairs in the map."""
319
+ return _ArrayMapEntryIterator(self, 0)
192
320
 
193
321
  def __iter__(self):
322
+ """Return an iterator over the keys in the map."""
194
323
  return self.keys()
195
324
 
196
325
  def __getitem__(self, key: K) -> V:
197
- for i in Range(self._size):
326
+ """Return the value associated with the given key.
327
+
328
+ Must be called with a key that is present in the map.
329
+
330
+ The returned value continues to be part of the map.
331
+ Future modifications to the map will affect the returned value.
332
+
333
+ Notes:
334
+ Future modifications to the map may cause unexpected changes to the returned value.
335
+ If the map may be modified in the future, it's recommended to make a copy of the value.
336
+
337
+ For example:
338
+ ```python
339
+ map = ArrayMap[int, Pair[int, int], 10].new()
340
+ map[1] = Pair(2, 3)
341
+ map[3] = Pair(4, 5)
342
+ map[5] = Pair(6, 7)
343
+ p = map[3]
344
+ map.pop(1)
345
+ # The value of `p` may now be different
346
+ ```
347
+ """
348
+ for i in range(self._size):
198
349
  entry = self._array[i]
199
350
  if entry.key == key:
200
351
  return entry.value
201
352
  error()
202
353
 
203
354
  def __setitem__(self, key: K, value: V):
204
- for i in Range(self._size):
355
+ """Associate the given key with the given value.
356
+
357
+ If the key is already present in the map, the value is updated.
358
+ Must not be called if the map is full.
359
+
360
+ Args:
361
+ key: The key to associate with the value.
362
+ value: The value to associate with the key
363
+ """
364
+ for i in range(self._size):
205
365
  entry = self._array[i]
206
366
  if entry.key == key:
207
367
  entry.value = value
208
368
  return
209
- # assert self._size < self.capacity()
210
- self._array[self._size] = ArrayMapEntry(key, value)
369
+ assert self._size < self.capacity()
370
+ self._array[self._size] = _ArrayMapEntry(key, value)
211
371
  self._size += 1
212
372
 
213
373
  def __contains__(self, key: K) -> bool:
214
- for i in Range(self._size): # noqa: SIM110
374
+ """Return whether the given key is present in the map.
375
+
376
+ Args:
377
+ key: The key to check for
378
+
379
+ Returns:
380
+ True if the key is present, False otherwise.
381
+ """
382
+ for i in range(self._size): # noqa: SIM110
215
383
  if self._array[i].key == key:
216
384
  return True
217
385
  return False
218
386
 
219
387
  def pop(self, key: K) -> V:
220
- for i in Range(self._size):
388
+ """Remove and return a copy of the value associated with the given key.
389
+
390
+ Must be called with a key that is present in the map.
391
+
392
+ Args:
393
+ key: The key to remove
394
+
395
+ Returns:
396
+ The value associated with the key
397
+ """
398
+ for i in range(self._size):
221
399
  entry = self._array[i]
222
400
  if entry.key == key:
223
401
  value = copy(entry.value)
@@ -228,6 +406,7 @@ class ArrayMap[K, V, Capacity](Record):
228
406
  error()
229
407
 
230
408
  def clear(self):
409
+ """Clear the map, removing all key-value pairs."""
231
410
  self._size = 0
232
411
 
233
412
 
@@ -236,35 +415,39 @@ class _ArrayMapKeyIterator[K, V, Capacity](Record, SonolusIterator):
236
415
  _index: int
237
416
 
238
417
  def has_next(self) -> bool:
239
- return self._index < self._map.size()
418
+ return self._index < len(self._map)
419
+
420
+ def get(self) -> K:
421
+ return self._map._array[self._index].key
240
422
 
241
- def next(self) -> K:
242
- key = self._map._array[self._index].key
423
+ def advance(self):
243
424
  self._index += 1
244
- return key
245
425
 
246
426
 
247
- class ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
427
+ class _ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
248
428
  _map: ArrayMap[K, V, Capacity]
249
429
  _index: int
250
430
 
251
431
  def has_next(self) -> bool:
252
- return self._index < self._map.size()
432
+ return self._index < len(self._map)
253
433
 
254
- def next(self) -> V:
255
- value = self._map._array[self._index].value
434
+ def get(self) -> V:
435
+ return self._map._array[self._index].value
436
+
437
+ def advance(self):
256
438
  self._index += 1
257
- return value
258
439
 
259
440
 
260
- class ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
441
+ class _ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
261
442
  _map: ArrayMap[K, V, Capacity]
262
443
  _index: int
263
444
 
264
445
  def has_next(self) -> bool:
265
- return self._index < self._map.size()
446
+ return self._index < len(self._map)
266
447
 
267
- def next(self) -> tuple[K, V]:
448
+ def get(self) -> tuple[K, V]:
268
449
  entry = self._map._array[self._index]
269
- self._index += 1
270
450
  return entry.key, entry.value
451
+
452
+ def advance(self):
453
+ self._index += 1
sonolus/script/debug.py CHANGED
@@ -2,24 +2,22 @@ from collections.abc import Callable
2
2
  from contextvars import ContextVar
3
3
  from typing import Any, Never
4
4
 
5
- from sonolus.backend.flow import cfg_to_mermaid
6
5
  from sonolus.backend.mode import Mode
7
6
  from sonolus.backend.ops import Op
8
- from sonolus.backend.passes import CompilerPass, run_passes
9
- from sonolus.backend.simplify import CoalesceFlow
10
- from sonolus.script.comptime import Comptime
7
+ from sonolus.backend.optimize.flow import cfg_to_mermaid
8
+ from sonolus.backend.optimize.passes import CompilerPass, run_passes
9
+ from sonolus.backend.optimize.simplify import CoalesceFlow
11
10
  from sonolus.script.internal.context import GlobalContextState, ctx, set_ctx
12
11
  from sonolus.script.internal.impl import meta_fn, validate_value
13
12
  from sonolus.script.internal.native import native_function
14
13
  from sonolus.script.num import Num
15
- from sonolus.script.values import with_default
16
14
 
17
15
  debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
18
16
 
19
17
 
20
18
  @meta_fn
21
19
  def error(message: str | None = None) -> None:
22
- message = Comptime._accept_(message)._as_py_() if message is not None else "Error"
20
+ message = message._as_py_() if message is not None else "Error"
23
21
  if not isinstance(message, str):
24
22
  raise ValueError("Expected a string")
25
23
  if ctx():
@@ -32,6 +30,7 @@ def error(message: str | None = None) -> None:
32
30
 
33
31
  @meta_fn
34
32
  def debug_log(value: Num):
33
+ """Log a value in debug mode."""
35
34
  if debug_log_callback.get(None):
36
35
  return debug_log_callback.get()(value)
37
36
  else:
@@ -46,17 +45,18 @@ def _debug_log(value: Num):
46
45
 
47
46
  @native_function(Op.DebugPause)
48
47
  def debug_pause():
48
+ """Pause the game if in debug mode."""
49
49
  input("[DEBUG] Paused")
50
50
 
51
51
 
52
52
  def assert_true(value: Num, message: str | None = None):
53
- message = with_default(message, "Assertion failed")
53
+ message = message if message is not None else "Assertion failed"
54
54
  if not value:
55
55
  error(message)
56
56
 
57
57
 
58
58
  def assert_false(value: Num, message: str | None = None):
59
- message = with_default(message, "Assertion failed")
59
+ message = message if message is not None else "Assertion failed"
60
60
  if value:
61
61
  error(message)
62
62