sonolus.py 0.1.3__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 (90) hide show
  1. sonolus/backend/blocks.py +756 -756
  2. sonolus/backend/excepthook.py +37 -37
  3. sonolus/backend/finalize.py +77 -69
  4. sonolus/backend/interpret.py +7 -7
  5. sonolus/backend/ir.py +29 -3
  6. sonolus/backend/mode.py +24 -24
  7. sonolus/backend/node.py +40 -40
  8. sonolus/backend/ops.py +197 -197
  9. sonolus/backend/optimize/__init__.py +0 -0
  10. sonolus/backend/optimize/allocate.py +126 -0
  11. sonolus/backend/optimize/constant_evaluation.py +374 -0
  12. sonolus/backend/optimize/copy_coalesce.py +85 -0
  13. sonolus/backend/optimize/dead_code.py +185 -0
  14. sonolus/backend/optimize/dominance.py +96 -0
  15. sonolus/backend/{flow.py → optimize/flow.py} +122 -92
  16. sonolus/backend/optimize/inlining.py +137 -0
  17. sonolus/backend/optimize/liveness.py +177 -0
  18. sonolus/backend/optimize/optimize.py +44 -0
  19. sonolus/backend/optimize/passes.py +52 -0
  20. sonolus/backend/optimize/simplify.py +191 -0
  21. sonolus/backend/optimize/ssa.py +200 -0
  22. sonolus/backend/place.py +17 -25
  23. sonolus/backend/utils.py +58 -48
  24. sonolus/backend/visitor.py +1151 -882
  25. sonolus/build/cli.py +7 -1
  26. sonolus/build/compile.py +88 -90
  27. sonolus/build/engine.py +10 -5
  28. sonolus/build/level.py +24 -23
  29. sonolus/build/node.py +43 -43
  30. sonolus/script/archetype.py +438 -139
  31. sonolus/script/array.py +27 -10
  32. sonolus/script/array_like.py +297 -0
  33. sonolus/script/bucket.py +253 -191
  34. sonolus/script/containers.py +257 -51
  35. sonolus/script/debug.py +26 -10
  36. sonolus/script/easing.py +365 -0
  37. sonolus/script/effect.py +191 -131
  38. sonolus/script/engine.py +71 -4
  39. sonolus/script/globals.py +303 -269
  40. sonolus/script/instruction.py +205 -151
  41. sonolus/script/internal/__init__.py +5 -5
  42. sonolus/script/internal/builtin_impls.py +255 -144
  43. sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
  44. sonolus/script/internal/constant.py +139 -0
  45. sonolus/script/internal/context.py +26 -9
  46. sonolus/script/internal/descriptor.py +17 -17
  47. sonolus/script/internal/dict_impl.py +65 -0
  48. sonolus/script/internal/generic.py +6 -9
  49. sonolus/script/internal/impl.py +38 -13
  50. sonolus/script/internal/introspection.py +17 -14
  51. sonolus/script/internal/math_impls.py +121 -0
  52. sonolus/script/internal/native.py +40 -38
  53. sonolus/script/internal/random.py +67 -0
  54. sonolus/script/internal/range.py +81 -0
  55. sonolus/script/internal/transient.py +51 -0
  56. sonolus/script/internal/tuple_impl.py +113 -0
  57. sonolus/script/internal/value.py +3 -3
  58. sonolus/script/interval.py +338 -112
  59. sonolus/script/iterator.py +167 -214
  60. sonolus/script/level.py +24 -0
  61. sonolus/script/num.py +80 -48
  62. sonolus/script/options.py +257 -191
  63. sonolus/script/particle.py +190 -157
  64. sonolus/script/pointer.py +30 -30
  65. sonolus/script/print.py +102 -81
  66. sonolus/script/project.py +8 -0
  67. sonolus/script/quad.py +263 -0
  68. sonolus/script/record.py +47 -16
  69. sonolus/script/runtime.py +52 -1
  70. sonolus/script/sprite.py +418 -333
  71. sonolus/script/text.py +409 -407
  72. sonolus/script/timing.py +114 -42
  73. sonolus/script/transform.py +332 -48
  74. sonolus/script/ui.py +216 -160
  75. sonolus/script/values.py +6 -13
  76. sonolus/script/vec.py +196 -78
  77. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  78. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  79. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
  80. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
  81. sonolus/backend/allocate.py +0 -51
  82. sonolus/backend/optimize.py +0 -9
  83. sonolus/backend/passes.py +0 -6
  84. sonolus/backend/simplify.py +0 -30
  85. sonolus/script/comptime.py +0 -160
  86. sonolus/script/graphics.py +0 -150
  87. sonolus/script/math.py +0 -92
  88. sonolus/script/range.py +0 -58
  89. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  90. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
@@ -1,59 +1,150 @@
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."""
31
+
32
+ def __lt__(self, other):
33
+ if self.first == other.first:
34
+ return self.second < other.second
35
+ return self.first < other.first
36
+
37
+ def __le__(self, other):
38
+ if self.first == other.first:
39
+ return self.second <= other.second
40
+ return self.first <= other.first
41
+
42
+ def __gt__(self, other):
43
+ if self.first == other.first:
44
+ return self.second > other.second
45
+ return self.first > other.first
46
+
47
+ def __ge__(self, other):
48
+ if self.first == other.first:
49
+ return self.second >= other.second
50
+ return self.first >= other.first
14
51
 
15
52
 
16
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
+
17
68
  _size: int
18
69
  _array: Array[T, Capacity]
19
70
 
20
71
  @classmethod
21
72
  def new(cls):
22
- element_type = cls._get_type_arg_(T)
23
- 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)
24
76
  return cls(0, alloc(Array[element_type, capacity]))
25
77
 
26
- def size(self) -> int:
78
+ def __len__(self) -> int:
79
+ """Return the number of elements in the array."""
27
80
  return self._size
28
81
 
29
82
  @classmethod
30
83
  def capacity(cls) -> int:
31
- return cls._get_type_arg_(Capacity)
84
+ """Return the maximum number of elements the array can hold."""
85
+ return cls.type_var_value(Capacity)
32
86
 
33
87
  def is_full(self) -> bool:
88
+ """Return whether the array is full."""
34
89
  return self._size == self.capacity()
35
90
 
36
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
+ """
37
112
  return self._array[item]
38
113
 
39
114
  def __setitem__(self, key: int, value: T):
115
+ """Update the element at the given index."""
40
116
  self._array[key] = value
41
117
 
118
+ def __delitem__(self, key: int):
119
+ """Remove the element at the given index."""
120
+ self.pop(key)
121
+
42
122
  def append(self, value: T):
43
- """Appends a copy of the given value to the end of the array."""
44
- 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)
45
129
  self._array[self._size] = value
46
130
  self._size += 1
47
131
 
48
132
  def extend(self, values: ArrayLike[T]):
49
- """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
+ """
50
138
  for value in values:
51
139
  self.append(value)
52
140
 
53
141
  def pop(self, index: int | None = None) -> T:
54
- """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.
55
143
 
56
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.
57
148
  """
58
149
  if index is None:
59
150
  index = self._size - 1
@@ -61,35 +152,48 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
61
152
  value = copy(self._array[index])
62
153
  self._size -= 1
63
154
  if index < self._size:
64
- for i in Range(index, self._size):
155
+ for i in range(index, self._size):
65
156
  self._array[i] = self._array[i + 1]
66
157
  return value
67
158
 
68
159
  def insert(self, index: int, value: T):
69
- """Inserts a copy of the given value at the given index.
160
+ """Insert a copy of the given value at the given index.
70
161
 
71
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.
72
167
  """
73
168
  assert 0 <= index <= self._size
74
- assert self._size < self._array.size()
169
+ assert self._size < len(self._array)
75
170
  self._size += 1
76
- for i in Range(self._size - 1, index, -1):
171
+ for i in range(self._size - 1, index, -1):
77
172
  self._array[i] = self._array[i - 1]
78
173
  self._array[index] = value
79
174
 
80
175
  def remove(self, value: T) -> bool:
81
- """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.
82
177
 
83
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.
84
185
  """
85
- index = self.index_of(value)
186
+ index = self.index(value)
86
187
  if index < 0:
87
188
  return False
88
189
  self.pop(index)
89
190
  return True
90
191
 
91
192
  def clear(self):
92
- """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
+ """
93
197
  self._size = 0
94
198
 
95
199
  def set_add(self, value: T) -> bool:
@@ -97,8 +201,14 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
97
201
 
98
202
  If the value is already present, the array is not modified.
99
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.
100
210
  """
101
- if self._size >= self._array.size():
211
+ if self._size >= len(self._array):
102
212
  return False
103
213
  if value in self:
104
214
  return False
@@ -109,8 +219,14 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
109
219
  """Removes the first occurrence of the given value, returning whether the value was removed.
110
220
 
111
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.
112
228
  """
113
- index = self.index_of(value)
229
+ index = self.index(value)
114
230
  if index < 0:
115
231
  return False
116
232
  if index < self._size - 1:
@@ -118,11 +234,18 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
118
234
  self._size -= 1
119
235
  return True
120
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
+
121
242
  def __eq__(self, other):
122
- if self.size() != other.size():
243
+ if not isinstance(other, ArrayLike):
244
+ return False
245
+ if len(self) != len(other):
123
246
  return False
124
247
  i = 0
125
- while i < self.size():
248
+ while i < len(self):
126
249
  if self[i] != other[i]:
127
250
  return False
128
251
  i += 1
@@ -135,66 +258,144 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
135
258
  raise TypeError("unhashable type: 'VarArray'")
136
259
 
137
260
 
138
- class ArrayMapEntry[K, V](Record):
261
+ class _ArrayMapEntry[K, V](Record):
139
262
  key: K
140
263
  value: V
141
264
 
142
265
 
143
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
+
144
285
  _size: int
145
- _array: Array[ArrayMapEntry[K, V], Capacity]
286
+ _array: Array[_ArrayMapEntry[K, V], Capacity]
146
287
 
147
288
  @classmethod
148
289
  def new(cls):
149
- key_type = cls._get_type_arg_(K)
150
- value_type = cls._get_type_arg_(V)
151
- capacity = cls._get_type_arg_(Capacity)
152
- return cls(0, alloc(Array[ArrayMapEntry[key_type, value_type], capacity]))
153
-
154
- 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."""
155
298
  return self._size
156
299
 
157
300
  @classmethod
158
301
  def capacity(cls) -> int:
159
- 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)
160
304
 
161
305
  def is_full(self) -> bool:
306
+ """Return whether the map is full."""
162
307
  return self._size == self.capacity()
163
308
 
164
309
  def keys(self) -> SonolusIterator[K]:
310
+ """Return an iterator over the keys in the map."""
165
311
  return _ArrayMapKeyIterator(self, 0)
166
312
 
167
313
  def values(self) -> SonolusIterator[V]:
168
- return ArrayMapValueIterator(self, 0)
314
+ """Return an iterator over the values in the map."""
315
+ return _ArrayMapValueIterator(self, 0)
169
316
 
170
317
  def items(self) -> SonolusIterator[tuple[K, V]]:
171
- return ArrayMapEntryIterator(self, 0)
318
+ """Return an iterator over the key-value pairs in the map."""
319
+ return _ArrayMapEntryIterator(self, 0)
320
+
321
+ def __iter__(self):
322
+ """Return an iterator over the keys in the map."""
323
+ return self.keys()
172
324
 
173
325
  def __getitem__(self, key: K) -> V:
174
- 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):
175
349
  entry = self._array[i]
176
350
  if entry.key == key:
177
351
  return entry.value
178
352
  error()
179
353
 
180
354
  def __setitem__(self, key: K, value: V):
181
- 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):
182
365
  entry = self._array[i]
183
366
  if entry.key == key:
184
367
  entry.value = value
185
368
  return
186
- # assert self._size < self.capacity()
187
- self._array[self._size] = ArrayMapEntry(key, value)
369
+ assert self._size < self.capacity()
370
+ self._array[self._size] = _ArrayMapEntry(key, value)
188
371
  self._size += 1
189
372
 
190
373
  def __contains__(self, key: K) -> bool:
191
- 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
192
383
  if self._array[i].key == key:
193
384
  return True
194
385
  return False
195
386
 
196
387
  def pop(self, key: K) -> V:
197
- 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):
198
399
  entry = self._array[i]
199
400
  if entry.key == key:
200
401
  value = copy(entry.value)
@@ -205,6 +406,7 @@ class ArrayMap[K, V, Capacity](Record):
205
406
  error()
206
407
 
207
408
  def clear(self):
409
+ """Clear the map, removing all key-value pairs."""
208
410
  self._size = 0
209
411
 
210
412
 
@@ -213,35 +415,39 @@ class _ArrayMapKeyIterator[K, V, Capacity](Record, SonolusIterator):
213
415
  _index: int
214
416
 
215
417
  def has_next(self) -> bool:
216
- return self._index < self._map.size()
418
+ return self._index < len(self._map)
217
419
 
218
- def next(self) -> K:
219
- key = self._map._array[self._index].key
420
+ def get(self) -> K:
421
+ return self._map._array[self._index].key
422
+
423
+ def advance(self):
220
424
  self._index += 1
221
- return key
222
425
 
223
426
 
224
- class ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
427
+ class _ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
225
428
  _map: ArrayMap[K, V, Capacity]
226
429
  _index: int
227
430
 
228
431
  def has_next(self) -> bool:
229
- return self._index < self._map.size()
432
+ return self._index < len(self._map)
433
+
434
+ def get(self) -> V:
435
+ return self._map._array[self._index].value
230
436
 
231
- def next(self) -> V:
232
- value = self._map._array[self._index].value
437
+ def advance(self):
233
438
  self._index += 1
234
- return value
235
439
 
236
440
 
237
- class ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
441
+ class _ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
238
442
  _map: ArrayMap[K, V, Capacity]
239
443
  _index: int
240
444
 
241
445
  def has_next(self) -> bool:
242
- return self._index < self._map.size()
446
+ return self._index < len(self._map)
243
447
 
244
- def next(self) -> tuple[K, V]:
448
+ def get(self) -> tuple[K, V]:
245
449
  entry = self._map._array[self._index]
246
- self._index += 1
247
450
  return entry.key, entry.value
451
+
452
+ def advance(self):
453
+ self._index += 1
sonolus/script/debug.py CHANGED
@@ -1,21 +1,23 @@
1
1
  from collections.abc import Callable
2
+ from contextvars import ContextVar
2
3
  from typing import Any, Never
3
4
 
4
- from sonolus.backend.flow import cfg_to_mermaid
5
5
  from sonolus.backend.mode import Mode
6
6
  from sonolus.backend.ops import Op
7
- from sonolus.backend.simplify import CoalesceFlow
8
- 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
9
10
  from sonolus.script.internal.context import GlobalContextState, ctx, set_ctx
10
11
  from sonolus.script.internal.impl import meta_fn, validate_value
11
12
  from sonolus.script.internal.native import native_function
12
13
  from sonolus.script.num import Num
13
- from sonolus.script.values import with_default
14
+
15
+ debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
14
16
 
15
17
 
16
18
  @meta_fn
17
19
  def error(message: str | None = None) -> None:
18
- 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"
19
21
  if not isinstance(message, str):
20
22
  raise ValueError("Expected a string")
21
23
  if ctx():
@@ -26,24 +28,35 @@ def error(message: str | None = None) -> None:
26
28
  raise RuntimeError(message)
27
29
 
28
30
 
29
- @native_function(Op.DebugLog)
31
+ @meta_fn
30
32
  def debug_log(value: Num):
33
+ """Log a value in debug mode."""
34
+ if debug_log_callback.get(None):
35
+ return debug_log_callback.get()(value)
36
+ else:
37
+ return _debug_log(value)
38
+
39
+
40
+ @native_function(Op.DebugLog)
41
+ def _debug_log(value: Num):
31
42
  print(f"[DEBUG] {value}")
43
+ return 0
32
44
 
33
45
 
34
46
  @native_function(Op.DebugPause)
35
47
  def debug_pause():
48
+ """Pause the game if in debug mode."""
36
49
  input("[DEBUG] Paused")
37
50
 
38
51
 
39
52
  def assert_true(value: Num, message: str | None = None):
40
- message = with_default(message, "Assertion failed")
53
+ message = message if message is not None else "Assertion failed"
41
54
  if not value:
42
55
  error(message)
43
56
 
44
57
 
45
58
  def assert_false(value: Num, message: str | None = None):
46
- message = with_default(message, "Assertion failed")
59
+ message = message if message is not None else "Assertion failed"
47
60
  if value:
48
61
  error(message)
49
62
 
@@ -62,9 +75,12 @@ def terminate():
62
75
  raise RuntimeError("Terminated")
63
76
 
64
77
 
65
- def visualize_cfg(fn: Callable[[], Any]) -> str:
78
+ def visualize_cfg(fn: Callable[[], Any], passes: list[CompilerPass] | None = None) -> str:
66
79
  from sonolus.build.compile import callback_to_cfg
67
80
 
81
+ if passes is None:
82
+ passes = [CoalesceFlow()]
83
+
68
84
  cfg = callback_to_cfg(GlobalContextState(Mode.PLAY), fn, "")
69
- cfg = CoalesceFlow().run(cfg)
85
+ cfg = run_passes(cfg, passes)
70
86
  return cfg_to_mermaid(cfg)