sonolus.py 0.10.0__py3-none-any.whl → 0.10.1__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.

@@ -158,7 +158,8 @@ class SparseConditionalConstantPropagation(CompilerPass):
158
158
  test_value = values[block]
159
159
  new_test_value = self.evaluate_stmt(block.test, values)
160
160
  if new_test_value != test_value:
161
- assert new_test_value is not UNDEF
161
+ if new_test_value is UNDEF:
162
+ continue
162
163
  values[block] = new_test_value
163
164
  if new_test_value is NAC:
164
165
  flow_worklist.update(block.outgoing)
@@ -195,7 +196,8 @@ class SparseConditionalConstantPropagation(CompilerPass):
195
196
  test_value = values[p]
196
197
  new_test_value = self.evaluate_stmt(defn, values)
197
198
  if new_test_value != test_value:
198
- assert new_test_value is not UNDEF
199
+ if new_test_value is UNDEF:
200
+ continue
199
201
  values[p] = new_test_value
200
202
  if new_test_value is NAC:
201
203
  flow_worklist.update(p.outgoing)
@@ -10,6 +10,7 @@ from sonolus.backend.optimize.inlining import InlineVars
10
10
  from sonolus.backend.optimize.simplify import (
11
11
  CoalesceFlow,
12
12
  CoalesceSmallConditionalBlocks,
13
+ CombineExitBlocks,
13
14
  NormalizeSwitch,
14
15
  RemoveRedundantArguments,
15
16
  RewriteToSwitch,
@@ -55,5 +56,6 @@ STANDARD_PASSES = (
55
56
  AdvancedDeadCodeElimination(),
56
57
  CoalesceFlow(),
57
58
  NormalizeSwitch(),
59
+ CombineExitBlocks(),
58
60
  Allocate(),
59
61
  )
@@ -77,6 +77,21 @@ class CoalesceFlow(CompilerPass):
77
77
  return entry
78
78
 
79
79
 
80
+ class CombineExitBlocks(CompilerPass):
81
+ def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
82
+ first_exit_block = None
83
+ for block in traverse_cfg_preorder(entry):
84
+ if not block.outgoing and not block.phis and not block.statements:
85
+ if first_exit_block is None:
86
+ first_exit_block = block
87
+ else:
88
+ for edge in [*block.incoming]:
89
+ edge.dst = first_exit_block
90
+ first_exit_block.incoming.add(edge)
91
+ block.incoming.clear()
92
+ return entry
93
+
94
+
80
95
  class CoalesceSmallConditionalBlocks(CompilerPass):
81
96
  def run(self, entry: BasicBlock, config: OptimizerConfig) -> BasicBlock:
82
97
  queue = [entry]
@@ -117,7 +117,7 @@ class RebuildCommand:
117
117
  print("Rebuilding...")
118
118
  try:
119
119
  start_time = perf_counter()
120
- server_state.project_state = ProjectContextState()
120
+ server_state.project_state = ProjectContextState(dev=True)
121
121
  server_state.project = project_module.project
122
122
  build_collection(
123
123
  server_state.project,
@@ -156,11 +156,11 @@ class HelpCommand:
156
156
  print("Available Commands:\n")
157
157
 
158
158
  for entry in DETAILED_HELP_TEXT:
159
- print(f" [{entry.alias}] {entry.command}")
159
+ print(f"[{entry.alias}] {entry.command}")
160
160
 
161
161
  for paragraph in entry.description:
162
- initial_indent = " "
163
- subsequent_indent = " "
162
+ initial_indent = " "
163
+ subsequent_indent = " "
164
164
  wrapped = textwrap.fill(
165
165
  paragraph,
166
166
  width=max_width - len(initial_indent),
@@ -260,7 +260,7 @@ def run_server(
260
260
  from sonolus.build.cli import build_collection
261
261
 
262
262
  cache = CompileCache()
263
- project_state = ProjectContextState()
263
+ project_state = ProjectContextState(dev=True)
264
264
 
265
265
  start_time = perf_counter()
266
266
  build_collection(project, build_dir, config, cache=cache, project_state=project_state)
sonolus/script/array.py CHANGED
@@ -194,7 +194,22 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
194
194
 
195
195
  @meta_fn
196
196
  def __getitem__(self, index: int) -> T:
197
- index: Num = Num._accept_(get_positive_index(index, self.size()))
197
+ return self.get_unchecked(get_positive_index(index, self.size()))
198
+
199
+ @meta_fn
200
+ def get_unchecked(self, index: Num) -> T:
201
+ """Get the element at the given index possibly without bounds checking.
202
+
203
+ The compiler may still determine that the index is out of bounds and throw an error, but it may skip these
204
+ checks at runtime.
205
+
206
+ Args:
207
+ index: The index to get.
208
+
209
+ Returns:
210
+ The element at the given index.
211
+ """
212
+ index = Num._accept_(index)
198
213
  if index._is_py_() and 0 <= index._as_py_() < self.size():
199
214
  const_index = index._as_py_()
200
215
  if isinstance(const_index, float) and not const_index.is_integer():
@@ -213,7 +228,8 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
213
228
  )
214
229
  elif callable(self._value):
215
230
  return self.element_type()._from_backing_source_(
216
- lambda offset: self._value((Num(offset) + Num(const_index * self.element_type()._size_())).ir()) # type: ignore
231
+ lambda offset: self._value((Num(offset) + Num(const_index * self.element_type()._size_())).ir())
232
+ # type: ignore
217
233
  )
218
234
  else:
219
235
  raise InternalError("Unexpected array value")
@@ -238,7 +254,20 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
238
254
 
239
255
  @meta_fn
240
256
  def __setitem__(self, index: int, value: T):
241
- index: Num = Num._accept_(get_positive_index(index, self.size()))
257
+ self.set_unchecked(get_positive_index(index, self.size()), value)
258
+
259
+ @meta_fn
260
+ def set_unchecked(self, index: Num, value: T):
261
+ """Set the element at the given index possibly without bounds checking.
262
+
263
+ The compiler may still determine that the index is out of bounds and throw an error, but it may skip these
264
+ checks at runtime.
265
+
266
+ Args:
267
+ index: The index to set.
268
+ value: The value to set.
269
+ """
270
+ index = Num._accept_(index)
242
271
  value = self.element_type()._accept_(value)
243
272
  if ctx():
244
273
  if isinstance(self._value, list):
@@ -288,7 +317,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
288
317
  return False
289
318
  i = 0
290
319
  while i < self.size():
291
- if self[i] != other[i]:
320
+ if self.get_unchecked(i) != other.get_unchecked(i):
292
321
  return False
293
322
  i += 1
294
323
  return True
@@ -5,8 +5,10 @@ from abc import abstractmethod
5
5
  from collections.abc import Callable, Sequence
6
6
  from typing import Any
7
7
 
8
+ from sonolus.script.debug import assert_true
8
9
  from sonolus.script.internal.context import ctx
9
10
  from sonolus.script.internal.impl import meta_fn
11
+ from sonolus.script.internal.math_impls import _trunc
10
12
  from sonolus.script.iterator import SonolusIterator
11
13
  from sonolus.script.maybe import Maybe, Nothing, Some
12
14
  from sonolus.script.num import Num
@@ -58,9 +60,37 @@ class ArrayLike[T](Sequence[T]):
58
60
  value: The value to set.
59
61
  """
60
62
 
63
+ @meta_fn
64
+ def get_unchecked(self, index: Num) -> T:
65
+ """Get the element at the given index possibly without bounds checking or conversion of negative indexes.
66
+
67
+ The compiler may still determine that the index is out of bounds and throw an error, but it may skip these
68
+ checks at runtime.
69
+
70
+ Args:
71
+ index: The index to get.
72
+
73
+ Returns:
74
+ The element at the given index.
75
+ """
76
+ return self[index]
77
+
78
+ @meta_fn
79
+ def set_unchecked(self, index: Num, value: T):
80
+ """Set the element at the given index possibly without bounds checking or conversion of negative indexes.
81
+
82
+ The compiler may still determine that the index is out of bounds and throw an error, but it may skip these
83
+ checks at runtime.
84
+
85
+ Args:
86
+ index: The index to set.
87
+ value: The value to set.
88
+ """
89
+ self[index] = value
90
+
61
91
  def __iter__(self) -> SonolusIterator[T]:
62
92
  """Return an iterator over the array."""
63
- return _ArrayIterator(0, self)
93
+ return _ArrayIterator(0, self.unchecked())
64
94
 
65
95
  def __contains__(self, value: Any) -> bool:
66
96
  """Return whether any element in the array is equal to the given value.
@@ -70,7 +100,7 @@ class ArrayLike[T](Sequence[T]):
70
100
  """
71
101
  i = 0
72
102
  while i < len(self):
73
- if self[i] == value:
103
+ if self.get_unchecked(i) == value:
74
104
  return True
75
105
  i += 1
76
106
  return False
@@ -92,9 +122,13 @@ class ArrayLike[T](Sequence[T]):
92
122
  """
93
123
  if stop is None:
94
124
  stop = len(self)
95
- i = start
125
+ else:
126
+ stop = get_positive_index(stop, len(self), check=False)
127
+ stop = min(stop, len(self))
128
+ start = get_positive_index(start, len(self), check=False)
129
+ i = max(start, 0)
96
130
  while i < stop:
97
- if self[i] == value:
131
+ if self.get_unchecked(i) == value:
98
132
  return i
99
133
  i += 1
100
134
  return -1
@@ -108,7 +142,7 @@ class ArrayLike[T](Sequence[T]):
108
142
  count = 0
109
143
  i = 0
110
144
  while i < len(self):
111
- if self[i] == value:
145
+ if self.get_unchecked(i) == value:
112
146
  count += 1
113
147
  i += 1
114
148
  return count
@@ -121,7 +155,7 @@ class ArrayLike[T](Sequence[T]):
121
155
  """
122
156
  i = len(self) - 1
123
157
  while i >= 0:
124
- if self[i] == value:
158
+ if self.get_unchecked(i) == value:
125
159
  return i
126
160
  i -= 1
127
161
  return -1
@@ -139,7 +173,7 @@ class ArrayLike[T](Sequence[T]):
139
173
  max_index = 0
140
174
  i = 1
141
175
  while i < len(self):
142
- if key(self[i]) > key(self[max_index]): # type: ignore
176
+ if key(self.get_unchecked(i)) > key(self.get_unchecked(max_index)): # type: ignore
143
177
  max_index = i
144
178
  i += 1
145
179
  return max_index
@@ -157,7 +191,7 @@ class ArrayLike[T](Sequence[T]):
157
191
  min_index = 0
158
192
  i = 1
159
193
  while i < len(self):
160
- if key(self[i]) < key(self[min_index]): # type: ignore
194
+ if key(self.get_unchecked(i)) < key(self.get_unchecked(min_index)): # type: ignore
161
195
  min_index = i
162
196
  i += 1
163
197
  return min_index
@@ -165,23 +199,25 @@ class ArrayLike[T](Sequence[T]):
165
199
  def _max_(self, key: Callable[[T], Any] | None = None) -> T:
166
200
  index = self.index_of_max(key=key)
167
201
  assert index != -1
168
- return self[index]
202
+ return self.get_unchecked(index)
169
203
 
170
204
  def _min_(self, key: Callable[[T], Any] | None = None) -> T:
171
205
  index = self.index_of_min(key=key)
172
206
  assert index != -1
173
- return self[index]
207
+ return self.get_unchecked(index)
174
208
 
175
209
  def swap(self, i: int, j: int, /):
176
- """Swap the values at the given indices.
210
+ """Swap the values at the given positive indices.
177
211
 
178
212
  Args:
179
213
  i: The first index.
180
214
  j: The second index.
181
215
  """
182
- temp = copy(self[i])
183
- self[i] = self[j]
184
- self[j] = temp
216
+ check_positive_index(i, len(self))
217
+ check_positive_index(j, len(self))
218
+ temp = copy(self.get_unchecked(i))
219
+ self.set_unchecked(i, self.get_unchecked(j))
220
+ self.set_unchecked(j, temp)
185
221
 
186
222
  def sort(self, *, key: Callable[[T], Any] | None = None, reverse: bool = False):
187
223
  """Sort the values in the array in place.
@@ -194,10 +230,10 @@ class ArrayLike[T](Sequence[T]):
194
230
  if key is None:
195
231
  key = _identity # type: ignore
196
232
  # May be worth adding a block sort variant for better performance on large arrays in the future
197
- _insertion_sort(self, 0, len(self), key, reverse) # type: ignore
233
+ _insertion_sort(self.unchecked(), 0, len(self), key, reverse) # type: ignore
198
234
  else:
199
235
  # Heap sort is unstable, so if there's a key, we can't rely on it
200
- _heap_sort(self, 0, len(self), reverse) # type: ignore
236
+ _heap_sort(self.unchecked(), 0, len(self), reverse) # type: ignore
201
237
 
202
238
  def shuffle(self):
203
239
  """Shuffle the values in the array in place."""
@@ -212,6 +248,10 @@ class ArrayLike[T](Sequence[T]):
212
248
  i += 1
213
249
  j -= 1
214
250
 
251
+ def unchecked(self) -> ArrayLike[T]:
252
+ """Return a proxy object that may skip bounds checking and may not support negative indexes."""
253
+ return UncheckedArrayProxy(self)
254
+
215
255
 
216
256
  def _identity[T](value: T) -> T:
217
257
  return value
@@ -268,7 +308,7 @@ class _ArrayIterator[V: ArrayLike](Record, SonolusIterator):
268
308
 
269
309
  def next(self) -> Maybe[V]:
270
310
  if self.i < len(self.array):
271
- value = self.array[self.i]
311
+ value = self.array.get_unchecked(self.i)
272
312
  self.i += 1
273
313
  return Some(value)
274
314
  return Nothing
@@ -286,6 +326,12 @@ class _ArrayReverser[V: ArrayLike](Record, ArrayLike):
286
326
  def __setitem__(self, index: int, value: V):
287
327
  self.array[len(self) - 1 - index] = value
288
328
 
329
+ def get_unchecked(self, index: Num) -> V:
330
+ return self.array.get_unchecked(len(self) - 1 - index)
331
+
332
+ def set_unchecked(self, index: Num, value: V):
333
+ self.array.set_unchecked(len(self) - 1 - index, value)
334
+
289
335
  def reversed(self) -> ArrayLike[V]:
290
336
  return self.array
291
337
 
@@ -297,30 +343,99 @@ class _ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
297
343
 
298
344
  def next(self) -> Maybe[tuple[int, Any]]:
299
345
  if self.i < len(self.array):
300
- result = (self.i + self.offset, self.array[self.i])
346
+ result = (self.i + self.offset, self.array.get_unchecked(self.i))
301
347
  self.i += 1
302
348
  return Some(result)
303
349
  return Nothing
304
350
 
305
351
 
306
352
  @meta_fn
307
- def get_positive_index(index: int, length: int) -> int:
308
- """Get the positive index for the given index in the array of the given length.
353
+ def get_positive_index(
354
+ index: int | float, length: int | float, *, include_end: bool = False, check: bool = True
355
+ ) -> int:
356
+ """Get the positive index for the given index in the array of the given length, and also perform bounds checking.
309
357
 
310
- This is used to convert negative indixes relative to the end of the array to positive indices.
358
+ This is used to convert negative indices relative to the end of the array to positive indices.
311
359
 
312
360
  Args:
313
361
  index: The index to convert.
314
362
  length: The length of the array.
363
+ include_end: Whether to allow the index to be equal to the length of the array (i.e., one past the end).
364
+ check: Whether to perform bounds checking. Must be a compile-time constant.
315
365
 
316
366
  Returns:
317
- The positive index.
367
+ The positive integer index.
318
368
  """
319
369
  if not ctx():
320
- return index if index >= 0 else index + length
370
+ if check:
371
+ if (include_end and not -length <= index <= length) or (not include_end and not -length <= index < length):
372
+ raise IndexError("Index out of range")
373
+ if int(index) != index:
374
+ raise ValueError("Index must be an integer")
375
+ if int(length) != length:
376
+ raise ValueError("Length must be an integer")
377
+ if length < 0:
378
+ raise ValueError("Length must be non-negative")
379
+ return int(index + (index < 0) * length)
321
380
  index = Num._accept_(index)
322
381
  length = Num._accept_(length)
323
- if index._is_py_() and length._is_py_():
324
- return Num._accept_(index._as_py_() + length._as_py_() if index._as_py_() < 0 else index._as_py_())
382
+ if Num._accept_(check)._as_py_():
383
+ include_end = Num._accept_(include_end)
384
+ if not include_end._is_py_():
385
+ is_in_bounds = Num.and_(index >= -length, index < (length + include_end))
386
+ elif include_end._as_py_():
387
+ is_in_bounds = Num.and_(index >= -length, index <= length)
388
+ else:
389
+ is_in_bounds = Num.and_(index >= -length, index < length)
390
+ assert_true(Num.and_(is_in_bounds, _trunc(index) == index), "Invalid index")
391
+ # Skipping length check since typically these are managed by the library and unlikely to be wrong
392
+ return index + (index < 0) * length
393
+
394
+
395
+ @meta_fn
396
+ def check_positive_index(index: int, length: int, include_end: bool = False) -> int | float:
397
+ """Check that the given index is a valid index for the array of the given length and convert it to an integer.
398
+
399
+ Args:
400
+ index: The index to check.
401
+ length: The length of the array.
402
+ include_end: Whether to allow the index to be equal to the length of the array (i.e., one past the end).
403
+
404
+ Returns:
405
+ The index as an integer.
406
+ """
407
+ if not ctx():
408
+ if (include_end and not 0 <= index <= length) or (not include_end and not 0 <= index < length):
409
+ raise IndexError("Index out of range")
410
+ if int(index) != index:
411
+ raise ValueError("Index must be an integer")
412
+ if int(length) != length:
413
+ raise ValueError("Length must be an integer")
414
+ if length < 0:
415
+ raise ValueError("Length must be non-negative")
416
+ return int(index)
417
+ index = Num._accept_(index)
418
+ length = Num._accept_(length)
419
+ include_end = Num._accept_(include_end)
420
+ if not include_end._is_py_():
421
+ is_in_bounds = Num.and_(index >= 0, index < (length + include_end))
422
+ elif include_end._as_py_():
423
+ is_in_bounds = Num.and_(index >= 0, index <= length)
325
424
  else:
326
- return index + (index < 0) * length
425
+ is_in_bounds = Num.and_(index >= 0, index < length)
426
+ assert_true(Num.and_(is_in_bounds, _trunc(index) == index), "Invalid index")
427
+ # Skipping length check since typically these are managed by the library and unlikely to be wrong
428
+ return index
429
+
430
+
431
+ class UncheckedArrayProxy[T](Record, ArrayLike):
432
+ array: T
433
+
434
+ def __len__(self) -> int:
435
+ return len(self.array)
436
+
437
+ def __getitem__(self, index: int) -> Any:
438
+ return self.array.get_unchecked(index)
439
+
440
+ def __setitem__(self, index: int, value: Any):
441
+ self.array.set_unchecked(index, value)
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Self
4
4
 
5
- from sonolus.backend.visitor import compile_and_call
6
5
  from sonolus.script.array import Array
7
6
  from sonolus.script.array_like import ArrayLike, get_positive_index
8
7
  from sonolus.script.debug import error
@@ -144,11 +143,11 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
144
143
  assert p == Pair(5, 6) # The value of p has changed
145
144
  ```
146
145
  """
147
- return self._array[get_positive_index(item, len(self))]
146
+ return self._array.get_unchecked(get_positive_index(item, self._size))
148
147
 
149
148
  def __setitem__(self, key: int, value: T):
150
149
  """Update the element at the given index."""
151
- self._array[get_positive_index(key, len(self))] = value
150
+ self._array.set_unchecked(get_positive_index(key, self._size), value)
152
151
 
153
152
  def __delitem__(self, key: int):
154
153
  """Remove the element at the given index."""
@@ -160,8 +159,8 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
160
159
  Args:
161
160
  value: The value to append.
162
161
  """
163
- assert self._size < len(self._array)
164
- self._array[self._size] = value
162
+ assert self._size < len(self._array), "Array is full"
163
+ self._array.set_unchecked(self._size, value)
165
164
  self._size += 1
166
165
 
167
166
  def append_unchecked(self, value: T):
@@ -172,7 +171,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
172
171
  Args:
173
172
  value: The value to append.
174
173
  """
175
- self._array[self._size] = value
174
+ self._array.set_unchecked(self._size, value)
176
175
  self._size += 1
177
176
 
178
177
  def extend(self, values: ArrayLike[T]):
@@ -181,8 +180,12 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
181
180
  Args:
182
181
  values: The values to append.
183
182
  """
184
- for value in values:
185
- self.append(value)
183
+ assert self._size + len(values) <= len(self._array), "Array is full"
184
+ i = 0
185
+ while i < len(values):
186
+ self._array.set_unchecked(self._size + i, values[i])
187
+ i += 1
188
+ self._size += len(values)
186
189
 
187
190
  def pop(self, index: int | None = None) -> T:
188
191
  """Remove and return a copy of the value at the given index.
@@ -194,13 +197,12 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
194
197
  """
195
198
  if index is None:
196
199
  index = self._size - 1
197
- index = get_positive_index(index, len(self))
198
- assert 0 <= index < self._size
199
- value = copy(self._array[index])
200
+ index = get_positive_index(index, self._size)
201
+ value = copy(self._array.get_unchecked(index))
200
202
  self._size -= 1
201
203
  if index < self._size:
202
204
  for i in range(index, self._size):
203
- self._array[i] = self._array[i + 1]
205
+ self._array.set_unchecked(i, self._array.get_unchecked(i + 1))
204
206
  return value
205
207
 
206
208
  def insert(self, index: int, value: T):
@@ -212,12 +214,12 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
212
214
  index: The index at which to insert the value. Must be in the range [0, size].
213
215
  value: The value to insert.
214
216
  """
215
- index = clamp(get_positive_index(index, len(self)), 0, self._size)
216
- assert self._size < len(self._array)
217
+ index = clamp(get_positive_index(index, self._size, include_end=True), 0, self._size)
218
+ assert self._size < len(self._array), "Array is full"
217
219
  self._size += 1
218
220
  for i in range(self._size - 1, index, -1):
219
- self._array[i] = self._array[i - 1]
220
- self._array[index] = value
221
+ self._array.set_unchecked(i, self._array.get_unchecked(i - 1))
222
+ self._array.set_unchecked(index, value)
221
223
 
222
224
  def remove(self, value: T) -> bool:
223
225
  """Remove the first occurrence of the given value, returning whether the value was removed.
@@ -277,7 +279,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
277
279
  if index < 0:
278
280
  return False
279
281
  if index < self._size - 1:
280
- self._array[index] = self._array[self._size - 1]
282
+ self._array.set_unchecked(index, self._array.get_unchecked(self._size - 1))
281
283
  self._size -= 1
282
284
  return True
283
285
 
@@ -293,7 +295,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
293
295
  return False
294
296
  i = 0
295
297
  while i < len(self):
296
- if self[i] != other[i]:
298
+ if self.get_unchecked(i) != other.get_unchecked(i):
297
299
  return False
298
300
  i += 1
299
301
  return True
@@ -329,12 +331,8 @@ class ArrayPointer[T](Record, ArrayLike[T]):
329
331
  """Return the type of the elements in the array."""
330
332
  return cls.type_var_value(T)
331
333
 
332
- def _check_index(self, index: int):
333
- assert 0 <= index < self.size
334
-
335
334
  @meta_fn
336
335
  def _get_item(self, item: int) -> T:
337
- item = get_positive_index(item, self.size)
338
336
  if not ctx():
339
337
  raise TypeError("ArrayPointer values cannot be accessed outside of a context")
340
338
  return _deref(
@@ -344,14 +342,18 @@ class ArrayPointer[T](Record, ArrayLike[T]):
344
342
  self.element_type(),
345
343
  )
346
344
 
345
+ def __getitem__(self, item) -> T:
346
+ return self.get_unchecked(get_positive_index(item, self.size))
347
+
348
+ def __setitem__(self, key: int, value: T):
349
+ self.set_unchecked(get_positive_index(key, self.size), value)
350
+
347
351
  @meta_fn
348
- def __getitem__(self, item: int) -> T:
349
- compile_and_call(self._check_index, item)
352
+ def get_unchecked(self, item: int) -> T:
350
353
  return self._get_item(item)._get_()
351
354
 
352
355
  @meta_fn
353
- def __setitem__(self, key: int, value: T):
354
- compile_and_call(self._check_index, key)
356
+ def set_unchecked(self, key: int, value: T):
355
357
  dst = self._get_item(key)
356
358
  if self.element_type()._is_value_type_():
357
359
  dst._set_(value)
@@ -519,7 +521,7 @@ class ArrayMap[K, V, Capacity](Record):
519
521
  ```
520
522
  """
521
523
  for i in range(self._size):
522
- entry = self._array[i]
524
+ entry = self._array.get_unchecked(i)
523
525
  if entry.key == key:
524
526
  return entry.value
525
527
  error()
@@ -535,12 +537,12 @@ class ArrayMap[K, V, Capacity](Record):
535
537
  value: The value to associate with the key
536
538
  """
537
539
  for i in range(self._size):
538
- entry = self._array[i]
540
+ entry = self._array.get_unchecked(i)
539
541
  if entry.key == key:
540
542
  entry.value = value
541
543
  return
542
- assert self._size < self.capacity()
543
- self._array[self._size] = _ArrayMapEntry(key, value)
544
+ assert self._size < self.capacity(), "Map is full"
545
+ self._array.set_unchecked(self._size, _ArrayMapEntry(key, value))
544
546
  self._size += 1
545
547
 
546
548
  def __delitem__(self, key: K):
@@ -552,11 +554,11 @@ class ArrayMap[K, V, Capacity](Record):
552
554
  key: The key to remove
553
555
  """
554
556
  for i in range(self._size):
555
- entry = self._array[i]
557
+ entry = self._array.get_unchecked(i)
556
558
  if entry.key == key:
557
559
  self._size -= 1
558
560
  if i < self._size:
559
- self._array[i] = self._array[self._size]
561
+ self._array.set_unchecked(i, self._array.get_unchecked(self._size))
560
562
  return
561
563
  error()
562
564
 
@@ -570,7 +572,7 @@ class ArrayMap[K, V, Capacity](Record):
570
572
  True if the key is present, False otherwise.
571
573
  """
572
574
  for i in range(self._size): # noqa: SIM110
573
- if self._array[i].key == key:
575
+ if self._array.get_unchecked(i).key == key:
574
576
  return True
575
577
  return False
576
578
 
@@ -586,12 +588,12 @@ class ArrayMap[K, V, Capacity](Record):
586
588
  The value associated with the key
587
589
  """
588
590
  for i in range(self._size):
589
- entry = self._array[i]
591
+ entry = self._array.get_unchecked(i)
590
592
  if entry.key == key:
591
593
  value = copy(entry.value)
592
594
  self._size -= 1
593
595
  if i < self._size:
594
- self._array[i] = self._array[self._size]
596
+ self._array.set_unchecked(i, self._array.get_unchecked(self._size))
595
597
  return value
596
598
  error()
597
599
 
@@ -606,7 +608,7 @@ class _ArrayMapKeyIterator[K, V, Capacity](Record, SonolusIterator):
606
608
 
607
609
  def next(self) -> Maybe[K]:
608
610
  if self._index < len(self._map):
609
- key = self._map._array[self._index].key
611
+ key = self._map._array.get_unchecked(self._index).key
610
612
  self._index += 1
611
613
  return Some(key)
612
614
  return Nothing
@@ -618,7 +620,7 @@ class _ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
618
620
 
619
621
  def next(self) -> Maybe[V]:
620
622
  if self._index < len(self._map):
621
- value = self._map._array[self._index].value
623
+ value = self._map._array.get_unchecked(self._index).value
622
624
  self._index += 1
623
625
  return Some(value)
624
626
  return Nothing
@@ -630,7 +632,7 @@ class _ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
630
632
 
631
633
  def next(self) -> Maybe[tuple[K, V]]:
632
634
  if self._index < len(self._map):
633
- entry = self._map._array[self._index]
635
+ entry = self._map._array.get_unchecked(self._index)
634
636
  result = (entry.key, entry.value)
635
637
  self._index += 1
636
638
  return Some(result)
sonolus/script/debug.py CHANGED
@@ -18,17 +18,23 @@ debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
18
18
 
19
19
  @meta_fn
20
20
  def error(message: str | None = None) -> Never: # type: ignore
21
- """Raise an error.
21
+ """Raise an error, and if in a dev build, log a message and pause the game.
22
22
 
23
23
  This function is used to raise an error during runtime.
24
24
  When this happens, the game will pause in debug mode. The current callback will also immediately return 0.
25
+
26
+ In non-dev builds, this function will terminate the current callback silently.
27
+
28
+ Args:
29
+ message: The message to log.
25
30
  """
26
31
  message = validate_value(message)._as_py_() or "Error" # type: ignore
27
32
  if not isinstance(message, str):
28
33
  raise ValueError("Expected a string")
29
34
  if ctx():
30
- debug_log(ctx().map_debug_message(message))
31
- debug_pause()
35
+ if ctx().project_state.dev:
36
+ debug_log(ctx().map_debug_message(message))
37
+ debug_pause()
32
38
  terminate()
33
39
  else:
34
40
  raise RuntimeError(message)
@@ -40,6 +46,9 @@ def static_error(message: str | None = None) -> Never:
40
46
 
41
47
  This function is used to raise an error during compile-time if the compiler cannot guarantee that
42
48
  this function will not be called during runtime.
49
+
50
+ Args:
51
+ message: The message to log.
43
52
  """
44
53
  message = validate_value(message)._as_py_() or "Error" # type: ignore
45
54
  if not isinstance(message, str):
@@ -70,19 +79,38 @@ def debug_pause():
70
79
 
71
80
  @meta_fn
72
81
  def notify(message: str):
73
- """Log a code that can be decoded by the dev server and pause the game if in debug mode."""
82
+ """Log a code that can be decoded by the dev server and pause the game if in debug mode and in a dev build.
83
+
84
+ Does nothing if not a dev build.
85
+
86
+ Args:
87
+ message: The message to log.
88
+ """
74
89
  message = validate_value(message)._as_py_() # type: ignore
75
90
  if not isinstance(message, str):
76
91
  raise ValueError("Expected a string")
77
92
  if ctx():
78
- debug_log(ctx().map_debug_message(message))
79
- debug_pause()
93
+ if ctx().project_state.dev:
94
+ debug_log(ctx().map_debug_message(message))
95
+ debug_pause()
80
96
  else:
81
97
  print(f"[NOTIFY] {message}")
82
98
 
83
99
 
84
100
  @meta_fn
85
- def assert_true(value: int | float | bool, message: str | None = None):
101
+ def require(value: int | float | bool, message: str | None = None):
102
+ """Require a condition to be true, or raise an error.
103
+
104
+ Similar to assert, but does not get stripped in non-dev builds.
105
+
106
+ If in a dev build, this function will log a message and pause the game if the condition is false.
107
+
108
+ In non-dev builds, this function will terminate the current callback silently if the condition is false.
109
+
110
+ Args:
111
+ value: The condition to check.
112
+ message: The message to log if the condition is false.
113
+ """
86
114
  if not ctx():
87
115
  if not value:
88
116
  raise AssertionError(message if message is not None else "Assertion failed")
@@ -104,6 +132,13 @@ def assert_true(value: int | float | bool, message: str | None = None):
104
132
  set_ctx(t_branch)
105
133
 
106
134
 
135
+ @meta_fn
136
+ def assert_true(value: int | float | bool, message: str | None = None):
137
+ if ctx() and not ctx().project_state.dev:
138
+ return
139
+ require(value, message)
140
+
141
+
107
142
  def assert_false(value: int | float | bool, message: str | None = None):
108
143
  assert_true(not value, message)
109
144
 
sonolus/script/effect.py CHANGED
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  from typing import Annotated, Any, NewType, dataclass_transform, get_origin
6
6
 
7
7
  from sonolus.backend.ops import Op
8
- from sonolus.script.array_like import ArrayLike
8
+ from sonolus.script.array_like import ArrayLike, check_positive_index
9
9
  from sonolus.script.debug import static_error
10
10
  from sonolus.script.internal.introspection import get_field_specifiers
11
11
  from sonolus.script.internal.native import native_function
@@ -111,7 +111,10 @@ class EffectGroup(Record, ArrayLike[Effect]):
111
111
  return self.size
112
112
 
113
113
  def __getitem__(self, index: int) -> Effect:
114
- assert 0 <= index < self.size
114
+ check_positive_index(index, self.size)
115
+ return Effect(self.start_id + index)
116
+
117
+ def get_unchecked(self, index: int) -> Effect:
115
118
  return Effect(self.start_id + index)
116
119
 
117
120
  def __setitem__(self, index: int, value: Effect) -> None:
@@ -3,7 +3,7 @@ from typing import Never, assert_never
3
3
  from sonolus.backend.ops import Op
4
4
  from sonolus.script.array import Array
5
5
  from sonolus.script.array_like import ArrayLike
6
- from sonolus.script.debug import error
6
+ from sonolus.script.debug import error, require
7
7
  from sonolus.script.internal.context import ctx
8
8
  from sonolus.script.internal.dict_impl import DictImpl
9
9
  from sonolus.script.internal.impl import meta_fn, validate_value
@@ -192,7 +192,7 @@ def _max_num_iterator(iterable, default, key):
192
192
  iterator = iterable.__iter__() # noqa: PLC2801
193
193
  initial = iterator.next()
194
194
  if initial.is_nothing:
195
- assert default is not None
195
+ require(default is not None, "default must be provided if the iterator is empty")
196
196
  return default
197
197
  if key is not None:
198
198
  result = initial.get_unsafe()
@@ -285,7 +285,7 @@ def _min_num_iterator(iterable, default, key):
285
285
  iterator = iterable.__iter__() # noqa: PLC2801
286
286
  initial = iterator.next()
287
287
  if initial.is_nothing:
288
- assert default is not None
288
+ require(default is not None, "default must be provided if the iterator is empty")
289
289
  return default
290
290
  if key is not None:
291
291
  result = initial.get_unsafe()
@@ -371,7 +371,7 @@ def _sum(iterable, /, start=0):
371
371
 
372
372
 
373
373
  def _next(iterator):
374
- assert isinstance(iterator, SonolusIterator)
374
+ require(isinstance(iterator, SonolusIterator), "Only subclasses of SonolusIterator are supported as iterators")
375
375
  value = iterator.next()
376
376
  if value.is_some:
377
377
  return value.get_unsafe()
@@ -44,17 +44,20 @@ class ProjectContextState:
44
44
  const_mappings: dict[Any, int]
45
45
  debug_str_mappings: dict[str, int]
46
46
  lock: Lock
47
+ dev: bool
47
48
 
48
49
  def __init__(
49
50
  self,
50
51
  rom: ReadOnlyMemory | None = None,
51
52
  const_mappings: dict[Any, int] | None = None,
52
53
  debug_str_mappings: dict[str, int] | None = None,
54
+ dev: bool = False,
53
55
  ):
54
56
  self.rom = ReadOnlyMemory() if rom is None else rom
55
57
  self.const_mappings = {} if const_mappings is None else const_mappings
56
58
  self.debug_str_mappings = {} if debug_str_mappings is None else debug_str_mappings
57
59
  self.lock = Lock()
60
+ self.dev = dev
58
61
 
59
62
 
60
63
  class ModeContextState:
@@ -4,72 +4,72 @@ from sonolus.backend.ops import Op
4
4
  from sonolus.script.internal.native import native_function
5
5
 
6
6
 
7
- @native_function(Op.Sin)
7
+ @native_function(Op.Sin, const_eval=True)
8
8
  def _sin(x: float) -> float:
9
9
  return math.sin(x)
10
10
 
11
11
 
12
- @native_function(Op.Cos)
12
+ @native_function(Op.Cos, const_eval=True)
13
13
  def _cos(x: float) -> float:
14
14
  return math.cos(x)
15
15
 
16
16
 
17
- @native_function(Op.Tan)
17
+ @native_function(Op.Tan, const_eval=True)
18
18
  def _tan(x: float) -> float:
19
19
  return math.tan(x)
20
20
 
21
21
 
22
- @native_function(Op.Arcsin)
22
+ @native_function(Op.Arcsin, const_eval=True)
23
23
  def _asin(x: float) -> float:
24
24
  return math.asin(x)
25
25
 
26
26
 
27
- @native_function(Op.Arccos)
27
+ @native_function(Op.Arccos, const_eval=True)
28
28
  def _acos(x: float) -> float:
29
29
  return math.acos(x)
30
30
 
31
31
 
32
- @native_function(Op.Arctan)
32
+ @native_function(Op.Arctan, const_eval=True)
33
33
  def _atan(x: float) -> float:
34
34
  return math.atan(x)
35
35
 
36
36
 
37
- @native_function(Op.Arctan2)
37
+ @native_function(Op.Arctan2, const_eval=True)
38
38
  def _atan2(y: float, x: float) -> float:
39
39
  return math.atan2(y, x)
40
40
 
41
41
 
42
- @native_function(Op.Sinh)
42
+ @native_function(Op.Sinh, const_eval=True)
43
43
  def _sinh(x: float) -> float:
44
44
  return math.sinh(x)
45
45
 
46
46
 
47
- @native_function(Op.Cosh)
47
+ @native_function(Op.Cosh, const_eval=True)
48
48
  def _cosh(x: float) -> float:
49
49
  return math.cosh(x)
50
50
 
51
51
 
52
- @native_function(Op.Tanh)
52
+ @native_function(Op.Tanh, const_eval=True)
53
53
  def _tanh(x: float) -> float:
54
54
  return math.tanh(x)
55
55
 
56
56
 
57
- @native_function(Op.Floor)
57
+ @native_function(Op.Floor, const_eval=True)
58
58
  def _floor(x: float) -> float:
59
59
  return math.floor(x)
60
60
 
61
61
 
62
- @native_function(Op.Ceil)
62
+ @native_function(Op.Ceil, const_eval=True)
63
63
  def _ceil(x: float) -> float:
64
64
  return math.ceil(x)
65
65
 
66
66
 
67
- @native_function(Op.Trunc)
67
+ @native_function(Op.Trunc, const_eval=True)
68
68
  def _trunc(x: float) -> float:
69
69
  return math.trunc(x)
70
70
 
71
71
 
72
- @native_function(Op.Round)
72
+ @native_function(Op.Round, const_eval=True)
73
73
  def __round(x: float) -> float:
74
74
  return round(x)
75
75
 
@@ -80,12 +80,12 @@ def _round(x: float, n: int = 0) -> float:
80
80
  return __round(x * 10**n) / 10**n
81
81
 
82
82
 
83
- @native_function(Op.Frac)
83
+ @native_function(Op.Frac, const_eval=True)
84
84
  def frac(x: float) -> float:
85
85
  return x % 1
86
86
 
87
87
 
88
- @native_function(Op.Log)
88
+ @native_function(Op.Log, const_eval=True)
89
89
  def _ln(x: float) -> float:
90
90
  return math.log(x)
91
91
 
@@ -101,19 +101,19 @@ def _sqrt(x: float) -> float:
101
101
  return x**0.5
102
102
 
103
103
 
104
- @native_function(Op.Degree)
104
+ @native_function(Op.Degree, const_eval=True)
105
105
  def _degrees(x: float) -> float:
106
106
  """Convert radians to degrees."""
107
107
  return math.degrees(x)
108
108
 
109
109
 
110
- @native_function(Op.Radian)
110
+ @native_function(Op.Radian, const_eval=True)
111
111
  def _radians(x: float) -> float:
112
112
  """Convert degrees to radians."""
113
113
  return math.radians(x)
114
114
 
115
115
 
116
- @native_function(Op.Rem)
116
+ @native_function(Op.Rem, const_eval=True)
117
117
  def _remainder(x: float, y: float) -> float:
118
118
  # This is different from math.remainder in Python's math package, which could be confusing
119
119
  return math.copysign(abs(x) % abs(y), x)
@@ -20,7 +20,7 @@ def native_call(op: Op, *args: int | float | bool) -> Num:
20
20
  return Num._from_place_(result)
21
21
 
22
22
 
23
- def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
23
+ def native_function[**P, R](op: Op, const_eval: bool = False) -> Callable[[Callable[P, R]], Callable[P, R]]:
24
24
  def decorator(fn: Callable[P, int | float | bool]) -> Callable[P, Num]:
25
25
  signature = inspect.signature(fn)
26
26
 
@@ -30,6 +30,12 @@ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]
30
30
  if len(args) < sum(1 for p in signature.parameters.values() if p.default == inspect.Parameter.empty):
31
31
  raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
32
32
  if ctx():
33
+ if const_eval:
34
+ args = tuple(validate_value(arg) for arg in args)
35
+ if not all(_is_num(arg) for arg in args):
36
+ raise RuntimeError("All arguments must be of type Num")
37
+ if all(arg._is_py_() for arg in args):
38
+ return Num._accept_(fn(*[arg._as_py_() for arg in args]))
33
39
  bound_args = signature.bind(*args)
34
40
  bound_args.apply_defaults()
35
41
  return native_call(op, *bound_args.args)
@@ -41,6 +41,9 @@ class Range(Record, ArrayLike[int]):
41
41
  def __getitem__(self, index: int) -> int:
42
42
  return self.start + get_positive_index(index, len(self)) * self.step
43
43
 
44
+ def get_unchecked(self, index: Num) -> int:
45
+ return self.start + index * self.step
46
+
44
47
  def __setitem__(self, index: int, value: int):
45
48
  raise TypeError("Range does not support item assignment")
46
49
 
@@ -374,9 +374,10 @@ def interp(
374
374
  Returns:
375
375
  The interpolated value.
376
376
  """
377
- assert len(xp) == len(fp)
378
- assert len(xp) >= 2
377
+ assert len(xp) == len(fp), "xp and fp must have the same length"
378
+ assert len(xp) >= 2, "xp and fp must have at least 2 elements"
379
379
  for i in range_or_tuple(1, len(xp) - 1):
380
+ assert xp[i] > xp[i - 1], "xp must be in increasing order"
380
381
  # At i == 1, x may be less than x[0], but since we're extrapolating, we use the first segment regardless.
381
382
  if x <= xp[i]:
382
383
  return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
@@ -402,11 +403,12 @@ def interp_clamped(
402
403
  Returns:
403
404
  The interpolated value.
404
405
  """
405
- assert len(xp) == len(fp)
406
- assert len(xp) >= 2
406
+ assert len(xp) == len(fp), "xp and fp must have the same length"
407
+ assert len(xp) >= 2, "xp and fp must have at least 2 elements"
407
408
  if x <= xp[0]:
408
409
  return fp[0]
409
410
  for i in range_or_tuple(1, len(xp)):
411
+ assert xp[i] > xp[i - 1], "xp must be in increasing order"
410
412
  if x <= xp[i]:
411
413
  return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
412
414
  return fp[-1]
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  from typing import Annotated, Any, NewType, dataclass_transform, get_origin
6
6
 
7
7
  from sonolus.backend.ops import Op
8
- from sonolus.script.array_like import ArrayLike
8
+ from sonolus.script.array_like import ArrayLike, check_positive_index
9
9
  from sonolus.script.debug import static_error
10
10
  from sonolus.script.internal.introspection import get_field_specifiers
11
11
  from sonolus.script.internal.native import native_function
@@ -71,7 +71,10 @@ class ParticleGroup(Record, ArrayLike[Particle]):
71
71
  return self.size
72
72
 
73
73
  def __getitem__(self, index: int) -> Particle:
74
- assert 0 <= index < self.size
74
+ check_positive_index(index, self.size)
75
+ return Particle(self.start_id + index)
76
+
77
+ def get_unchecked(self, index: int) -> Particle:
75
78
  return Particle(self.start_id + index)
76
79
 
77
80
  def __setitem__(self, index: int, value: Particle) -> None:
sonolus/script/sprite.py CHANGED
@@ -4,11 +4,12 @@ from enum import StrEnum
4
4
  from typing import Annotated, Any, NewType, dataclass_transform, get_origin
5
5
 
6
6
  from sonolus.backend.ops import Op
7
- from sonolus.script.array_like import ArrayLike
7
+ from sonolus.script.array_like import ArrayLike, check_positive_index
8
8
  from sonolus.script.debug import static_error
9
9
  from sonolus.script.internal.impl import perf_meta_fn
10
10
  from sonolus.script.internal.introspection import get_field_specifiers
11
11
  from sonolus.script.internal.native import native_function
12
+ from sonolus.script.num import Num
12
13
  from sonolus.script.quad import QuadLike, flatten_quad
13
14
  from sonolus.script.record import Record
14
15
  from sonolus.script.vec import Vec2
@@ -138,7 +139,10 @@ class SpriteGroup(Record, ArrayLike[Sprite]):
138
139
  return self.size
139
140
 
140
141
  def __getitem__(self, index: int) -> Sprite:
141
- assert 0 <= index < self.size
142
+ check_positive_index(index, self.size)
143
+ return Sprite(self.start_id + index)
144
+
145
+ def get_unchecked(self, index: Num) -> Sprite:
142
146
  return Sprite(self.start_id + index)
143
147
 
144
148
  def __setitem__(self, index: int, value: Sprite) -> None:
@@ -325,6 +325,7 @@ class Transform2d(Record):
325
325
  Returns:
326
326
  A new normalized transform.
327
327
  """
328
+ assert self.a22 != 0, "Cannot normalize transform with a22 == 0"
328
329
  return Transform2d(
329
330
  self.a00 / self.a22,
330
331
  self.a01 / self.a22,
sonolus/script/vec.py CHANGED
@@ -158,6 +158,7 @@ class Vec2(Record):
158
158
  A new vector with magnitude 1.
159
159
  """
160
160
  magnitude = (self.x**2 + self.y**2) ** 0.5
161
+ assert magnitude != 0, "Cannot normalize a zero vector"
161
162
  return Vec2._quick_construct(x=self.x / magnitude, y=self.y / magnitude)
162
163
 
163
164
  @perf_meta_fn
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.10.0
3
+ Version: 0.10.1
4
4
  Summary: Sonolus engine development in Python
5
5
  Project-URL: Documentation, https://sonolus.py.qwewqa.xyz/
6
6
  Project-URL: Repository, https://github.com/qwewqa/sonolus.py
@@ -14,81 +14,81 @@ sonolus/backend/utils.py,sha256=OwD1EPh8j-hsfkLzeKNzPQojT_3kklpJou0WTJNoCbc,2337
14
14
  sonolus/backend/visitor.py,sha256=PxxIvhP3AzGS3hZMulaF5CO2aswiBMyZc_Hwo040hTk,64421
15
15
  sonolus/backend/optimize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  sonolus/backend/optimize/allocate.py,sha256=CuumoMphkpQlGRNeKLHT4FBGE0XVj5pwhfNdrqiLFSs,7535
17
- sonolus/backend/optimize/constant_evaluation.py,sha256=U--9moGsXFzrgweWWwHIiEuuMzwetd1IOjjtrCscoNM,21450
17
+ sonolus/backend/optimize/constant_evaluation.py,sha256=_u_VfLmd4Rlq9aKyRSeKb47352CXuf8uNgNhNTK1qe0,21510
18
18
  sonolus/backend/optimize/copy_coalesce.py,sha256=AS-TmOoVG2oHxU8fejMxkOdRqfcaWdN53H4POL3G-O4,4797
19
19
  sonolus/backend/optimize/dead_code.py,sha256=ZRJ95zJ49R-wZTzJtcSSbl5LYKHWI-byHM3n6jOyAqc,8307
20
20
  sonolus/backend/optimize/dominance.py,sha256=3jAgXqXTbuYLpXvIm8UB06NkIOLtaoVp7pBVPcLb5vY,3259
21
21
  sonolus/backend/optimize/flow.py,sha256=xUoBpWIYi-NjqXahA6obAZaPvLj_HaDNNv7cO13e2ps,7192
22
22
  sonolus/backend/optimize/inlining.py,sha256=BEXjPbJMGTJbgA4ydC38TbEuYEFqb6oxDS0roZTmuds,10417
23
23
  sonolus/backend/optimize/liveness.py,sha256=KYQlXdKuwnRvY9JeAjwm1bzPbFwshcUxtYs7ycMRS-M,7279
24
- sonolus/backend/optimize/optimize.py,sha256=sY1GFLjAslVgMsLzocC3Ctk0R9SqwybWyqzFB6WMntI,1624
24
+ sonolus/backend/optimize/optimize.py,sha256=2gW0n1AIlwgVjY6teQlt9YP-GsFUxU-mr1ZqAZamnUo,1672
25
25
  sonolus/backend/optimize/passes.py,sha256=YyFKy6qCwcR_Ua2_SXpcBODfvBbm_ygVYcqloOlfDZI,1911
26
- sonolus/backend/optimize/simplify.py,sha256=RDNVTKfC7ByRyxY5z30_ShimOAKth_pKlVFV_36pDG4,14082
26
+ sonolus/backend/optimize/simplify.py,sha256=wvhixe0SfditrGMh0nX0Wt0JR00JqAmz4BKBzMoBAVI,14701
27
27
  sonolus/backend/optimize/ssa.py,sha256=raQO0furQQRPYb8iIBKfNrJlj-_5wqtI4EWNfLZ8QFo,10834
28
28
  sonolus/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  sonolus/build/cli.py,sha256=GQHzXGj55pqz84AnOKa21t74Nrbt7gUKfQe88gXIKtI,9357
30
30
  sonolus/build/collection.py,sha256=6hniAzriPWBKUeGDkXabNXpbdHiHnqiK9shs6U1OExM,12748
31
31
  sonolus/build/compile.py,sha256=KOmncDKmGfgzC_FWB_LTxAl0s9w4wnaDe-luACMlCVs,8397
32
- sonolus/build/dev_server.py,sha256=WSih91Df2dPIRHdObjZ_sGnL3LKPJgv9m9RD_Zr7BqU,9881
32
+ sonolus/build/dev_server.py,sha256=EOnIgAkFZAKiCwhFqDyKMLkJfYtGdjlacqYWZ2F2mAU,9891
33
33
  sonolus/build/engine.py,sha256=No_q4O6PRMwxEPHvSlbaBKVe5CXJswWFZdR3xgfIuI8,14672
34
34
  sonolus/build/level.py,sha256=KLqUAtxIuIqrzeFURJA97rdqjA5pcvYSmwNZQhElaMQ,702
35
35
  sonolus/build/node.py,sha256=gnX71RYDUOK_gYMpinQi-bLWO4csqcfiG5gFmhxzSec,1330
36
36
  sonolus/build/project.py,sha256=Uuz82QtTNFdklrVJ_i7EPp8hSjyOxLU1xAeOloa6G00,8579
37
37
  sonolus/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  sonolus/script/archetype.py,sha256=ck_LR8z0ipVq3T9b735VwvQI2mxVUyjHylr4BFagXT8,49631
39
- sonolus/script/array.py,sha256=dzuAd72DcVGG8Ix5H69eIZpGiFSoH_rErKFNgQ5DEKI,12399
40
- sonolus/script/array_like.py,sha256=hUDdDaP306kflVv9YomdHIMXogrVjxsBXCrLvB9QpuE,9681
39
+ sonolus/script/array.py,sha256=0ZUI0alrwKztpQOpZodZPSPafu5cGwaiffBGQbY19LQ,13316
40
+ sonolus/script/array_like.py,sha256=E6S4TW2muXgcyVkhUASQVt7JSYUkpvdJPgHz6YiSHNo,14708
41
41
  sonolus/script/bucket.py,sha256=yIod3DgX7Hv7RLe-4Cn81FcydvbkbdMt26FzpRj7oUI,7794
42
- sonolus/script/containers.py,sha256=L3rzPfN1cDb2m02k9qRyjnGxJx07X3q3MccCaL5tNJ8,18773
43
- sonolus/script/debug.py,sha256=tok7uKzoVgps0V8ANAlcrZ0NJf5wVWK7ICozn8IVd30,6351
42
+ sonolus/script/containers.py,sha256=qKIyTs5Q_UpQggR6s0AfgWQAvPv-IM2DW9s951koKSc,19303
43
+ sonolus/script/debug.py,sha256=YOq06q5ahWI-uEG5naonOBWF62Qn_Af7JSV_Ra8Zr6E,7415
44
44
  sonolus/script/easing.py,sha256=2FUJI_nfp990P_armCcRqHm2329O985glJAhSC6tnxs,11379
45
- sonolus/script/effect.py,sha256=pYihzdVOS3ekiiTwPVg6czUW1UNv0CJNIk-xBsYRdq8,7792
45
+ sonolus/script/effect.py,sha256=aOxhBmX6I8vUS-bE53YFNBEN3wwdmpraqZjLeCqbgIY,7920
46
46
  sonolus/script/engine.py,sha256=etI9dJsQ7V9YZICVNZg54WqpLijPxG8eTPHiV-_EiG8,10687
47
47
  sonolus/script/globals.py,sha256=nlXSNS4NRXsgQU2AJImVIs752h1WqsMnShSKgU011c4,10270
48
48
  sonolus/script/instruction.py,sha256=Dd-14D5Amo8nhPBr6DNyg2lpYw_rqZkT8Kix3HkfE7k,6793
49
- sonolus/script/interval.py,sha256=2gjnEr11RExyXGhZuUjRRgx-ZrNynWLPj-b89lQCnLQ,11688
49
+ sonolus/script/interval.py,sha256=vnwm3CiB0rurRolA53wRzb_jTPguvOKnie4glE1MwsE,11986
50
50
  sonolus/script/iterator.py,sha256=_ICY_yX7FG0Zbgs3NhVnaIBdVDpAeXjxJ_CQtq30l7Y,3774
51
51
  sonolus/script/level.py,sha256=X3-V99ihruYYCcPdch66dHi_ydCWXXn7epviLLjxW8w,8288
52
52
  sonolus/script/maybe.py,sha256=VYvTWgEfPzoXqI3i3zXhc4dz0pWBVoHmW8FtWH0GQvM,8194
53
53
  sonolus/script/metadata.py,sha256=ttRK27eojHf3So50KQJ-8yj3udZoN1bli5iD-knaeLw,753
54
54
  sonolus/script/num.py,sha256=9pahERQcIh16ytoDjJB3u3L6fH1Xh11Y99l8SYxkjMA,15927
55
55
  sonolus/script/options.py,sha256=05y_4j2kr8fzct5FLqmSp5ZAjnq6-slmNgtsh4fVEpg,9451
56
- sonolus/script/particle.py,sha256=rCEJGT7frqJqZLi4EBCqDs4QBvLW6Ys640nD1FxiVec,10326
56
+ sonolus/script/particle.py,sha256=BuBM7fvLAj79upLf9yI4FyZhVUK7-H2dFj0D7UiYS7I,10458
57
57
  sonolus/script/pointer.py,sha256=FoOfyD93r0G5d_2BaKfeOT9SqkOP3hq6sqtOs_Rb0c8,1511
58
58
  sonolus/script/printing.py,sha256=mNYu9QWiacBBGZrnePZQMVwbbguoelUps9GiOK_aVRU,2096
59
59
  sonolus/script/project.py,sha256=4svmMWYihF7olmYSMS5uoSjlnzbd7Ip2zTRY86oL1L8,4629
60
60
  sonolus/script/quad.py,sha256=8lZ_5-eWeqePldNGBkNZTuOgS_IRb41URgGwSW4h2T0,14445
61
61
  sonolus/script/record.py,sha256=BrQ8k-O4WX9FT_EfoRmNnKC1BZM9gWydZ4R4swh3chc,13051
62
62
  sonolus/script/runtime.py,sha256=TjxcfIIPRH6oxlEjWfLHDj1oo7fPfwTBdwETfnhN7h4,33331
63
- sonolus/script/sprite.py,sha256=q2i9AwFHO6X_li2hgwpP79fSG-ZriX0gMGhr3yTI8p8,18245
63
+ sonolus/script/sprite.py,sha256=d_wqUn7oMbkLZMdvnyDZVBykycTtiwegcarWXMcZMUI,18408
64
64
  sonolus/script/stream.py,sha256=Fu02SNjH8j1FQ9_7ncacR9uRIhoWtAZR-sTi8qBT7rA,24707
65
65
  sonolus/script/text.py,sha256=wxujIgKYcCfl2AD2_Im8g3vh0lDEHYwTSRZg9wsBPEU,13402
66
66
  sonolus/script/timing.py,sha256=DklMvuxcFg3MzXsecUo6Yhdk7pScOJ7STwXvAiTvLKM,3067
67
- sonolus/script/transform.py,sha256=w5mr7hTuNYU0eTAdnN_wTVibaQa0mZrkl-W-kgewJxQ,21345
67
+ sonolus/script/transform.py,sha256=4aS7-NNzX0v9KMXZ4gIGOaU1Cd-ok7DO_OvIBca0mGU,21418
68
68
  sonolus/script/ui.py,sha256=DYPGWIjHj1IFPxW1zaEuIUQx0b32FJPXtiwCvrtJ6oo,7528
69
69
  sonolus/script/values.py,sha256=6iJG6h4IDlbcK8FH4GENSHOQc7C_7fCGa34wM80qToA,1629
70
- sonolus/script/vec.py,sha256=-BV4UpqhYUjWZp1AfwuDqEoc1hLzvoch_C2L9Npk-Rs,8047
70
+ sonolus/script/vec.py,sha256=UIgXRNum420VrXXbkHjZuQx2gFsvgV8tydi9_zOqwuc,8111
71
71
  sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtUwu-Ms8,136
72
- sonolus/script/internal/builtin_impls.py,sha256=R1h3IOlWzolPfc9yoma2cBN0F5cBhj_JNP-TTdKoBlc,13186
72
+ sonolus/script/internal/builtin_impls.py,sha256=tpNbaH6fLICd8TYj9Hf_wrPSWk3RkhmSPVN9nqOuqj4,13372
73
73
  sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
74
74
  sonolus/script/internal/constant.py,sha256=3ycbGkDJVUwcrCZ96vLjAoAARgsvaqDM8rJ_YCrLrvo,4289
75
- sonolus/script/internal/context.py,sha256=Q_0wfTqjM0BF81dK69OZcIftRF082XtGJLW47SfLpsU,18715
75
+ sonolus/script/internal/context.py,sha256=C0VMHBRppsnwPDVPc03Lpz7tO9djQMB5ELdtpFPMdsk,18779
76
76
  sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
77
77
  sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
78
78
  sonolus/script/internal/error.py,sha256=ZNnsvQVQAnFKzcvsm6-sste2lo-tP5pPI8sD7XlAZWc,490
79
79
  sonolus/script/internal/generic.py,sha256=_3d5Rn_tn214-77fPE67vdbdqt1PQF8-2WB_XDu5YRg,7551
80
80
  sonolus/script/internal/impl.py,sha256=U9D8A207yBbA-R9Qa9xE9zK4f0uDvGbHuFhwaIO81Ew,3364
81
81
  sonolus/script/internal/introspection.py,sha256=guL9_NR2D3OJAnNpeFdyYkO_vVXk-3KQr2-y4YielM0,1133
82
- sonolus/script/internal/math_impls.py,sha256=nHSLgA7Tcx7jY1p07mYBCeSRmVx713bwdNayCIcaXSE,2652
83
- sonolus/script/internal/native.py,sha256=DQxmzxgLG_UsLpXhIEtBdO7eIeDFprU78UBDC4OZzw0,1597
82
+ sonolus/script/internal/math_impls.py,sha256=ox2pBJ6ELRO0LdLn_RZxgHHs_PCgQOHIhmDkwmLxJaU,2975
83
+ sonolus/script/internal/native.py,sha256=zOuRtgI3XJ_ExyR_ZkvbDABVc_JIWaKl62lFEL_bMaw,2007
84
84
  sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z50pI,1695
85
- sonolus/script/internal/range.py,sha256=YeqB1TPh7JdvW6kDuA5tpQL5psVxYQjepBZs7RNcP5Y,3426
85
+ sonolus/script/internal/range.py,sha256=j94uV1NTZoCdZ8mOw3v51vD8L7h8l5vZpOAp6breD9I,3521
86
86
  sonolus/script/internal/simulation_context.py,sha256=LGxLTvxbqBIhoe1R-SfwGajNIDwIJMVsHle0kvzd500,4818
87
87
  sonolus/script/internal/transient.py,sha256=y2AWABqF1aoaP6H4_2u4MMpNioC4OsZQCtPyNI0txqo,1634
88
88
  sonolus/script/internal/tuple_impl.py,sha256=DPNdmmRmupU8Ah4_XKq6-PdT336l4nt15_uCJKQGkkk,3587
89
89
  sonolus/script/internal/value.py,sha256=OngrCdmY_h6mV2Zgwqhuo4eYFad0kTk6263UAxctZcY,6963
90
- sonolus_py-0.10.0.dist-info/METADATA,sha256=DsXCiBAkO45upsutT8V7g_5jfLj9Duu1JCMGegtRSMg,554
91
- sonolus_py-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- sonolus_py-0.10.0.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
93
- sonolus_py-0.10.0.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
94
- sonolus_py-0.10.0.dist-info/RECORD,,
90
+ sonolus_py-0.10.1.dist-info/METADATA,sha256=hWRJb86bOgMAB7N1x5H_hdY8zK77ZwIia41--jjZTdI,554
91
+ sonolus_py-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ sonolus_py-0.10.1.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
93
+ sonolus_py-0.10.1.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
94
+ sonolus_py-0.10.1.dist-info/RECORD,,