sonolus.py 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

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

Potentially problematic release.


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

sonolus/backend/utils.py CHANGED
@@ -23,29 +23,48 @@ def get_tree_from_file(file: str | Path) -> ast.Module:
23
23
  class FindFunction(ast.NodeVisitor):
24
24
  def __init__(self, line):
25
25
  self.line = line
26
- self.node: ast.FunctionDef | None = None
26
+ self.results: list[ast.FunctionDef | ast.Lambda] = []
27
+ self.current_fn = None
27
28
 
28
29
  def visit_FunctionDef(self, node: ast.FunctionDef):
29
- if node.lineno == self.line or (
30
- node.decorator_list and (node.decorator_list[-1].end_lineno <= self.line <= node.lineno)
31
- ):
32
- self.node = node
33
- else:
34
- self.generic_visit(node)
30
+ self.results.append(node)
31
+ outer_fn = self.current_fn
32
+ self.current_fn = node
33
+ self.generic_visit(node)
34
+ self.current_fn = outer_fn
35
35
 
36
36
  def visit_Lambda(self, node: ast.Lambda):
37
- if node.lineno == self.line:
38
- if self.node is not None:
39
- raise ValueError("Multiple functions defined on the same line are not supported")
40
- self.node = node
41
- else:
42
- self.generic_visit(node)
37
+ self.results.append(node)
38
+ outer_fn = self.current_fn
39
+ self.current_fn = node
40
+ self.generic_visit(node)
41
+ self.current_fn = outer_fn
43
42
 
43
+ # Visitors have high overhead, so we detect generators here rather than in a separate pass.
44
44
 
45
- def find_function(tree: ast.Module, line: int):
46
- visitor = FindFunction(line)
45
+ def visit_Yield(self, node):
46
+ self.current_fn.has_yield = True
47
+
48
+ def visit_YieldFrom(self, node):
49
+ self.current_fn.has_yield = True
50
+
51
+
52
+ @cache
53
+ def get_functions(tree: ast.Module) -> list[ast.FunctionDef | ast.Lambda]:
54
+ visitor = FindFunction(0)
47
55
  visitor.visit(tree)
48
- return visitor.node
56
+ return visitor.results
57
+
58
+
59
+ def find_function(tree: ast.Module, line: int):
60
+ for node in get_functions(tree):
61
+ if node.lineno == line or (
62
+ isinstance(node, ast.FunctionDef)
63
+ and node.decorator_list
64
+ and (node.decorator_list[-1].end_lineno <= line <= node.lineno)
65
+ ):
66
+ return node
67
+ raise ValueError("Function not found")
49
68
 
50
69
 
51
70
  class ScanWrites(ast.NodeVisitor):
@@ -61,27 +80,3 @@ def scan_writes(node: ast.AST) -> set[str]:
61
80
  visitor = ScanWrites()
62
81
  visitor.visit(node)
63
82
  return set(visitor.writes)
64
-
65
-
66
- class HasDirectYield(ast.NodeVisitor):
67
- def __init__(self):
68
- self.started = False
69
- self.has_yield = False
70
-
71
- def visit_Yield(self, node: ast.Yield):
72
- self.has_yield = True
73
-
74
- def visit_YieldFrom(self, node: ast.YieldFrom):
75
- self.has_yield = True
76
-
77
- def visit_FunctionDef(self, node: ast.FunctionDef):
78
- if self.started:
79
- return
80
- self.started = True
81
- self.generic_visit(node)
82
-
83
-
84
- def has_yield(node: ast.AST) -> bool:
85
- visitor = HasDirectYield()
86
- visitor.visit(node)
87
- return visitor.has_yield
@@ -11,7 +11,7 @@ from types import FunctionType, MethodType, MethodWrapperType
11
11
  from typing import Any, Never
12
12
 
13
13
  from sonolus.backend.excepthook import install_excepthook
14
- from sonolus.backend.utils import get_function, has_yield, scan_writes
14
+ from sonolus.backend.utils import get_function, scan_writes
15
15
  from sonolus.script.debug import assert_true
16
16
  from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS, _bool, _float, _int, _len
17
17
  from sonolus.script.internal.constant import ConstantValue
@@ -39,6 +39,8 @@ def compile_and_call[**P, R](fn: Callable[P, R], /, *args: P.args, **kwargs: P.k
39
39
  def compile_and_call_at_definition[**P, R](fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R:
40
40
  if not ctx():
41
41
  return fn(*args, **kwargs)
42
+ if ctx().no_eval:
43
+ return compile_and_call(fn, *args, **kwargs)
42
44
  source_file, node = get_function(fn)
43
45
  location_args = {
44
46
  "lineno": node.lineno,
@@ -261,11 +263,26 @@ class Visitor(ast.NodeVisitor):
261
263
  result = self.visit(body)
262
264
  ctx().scope.set_value("$return", result)
263
265
  case ast.GeneratorExp(elt=elt, generators=generators):
264
- self.construct_genexpr(generators, elt)
266
+ first_generator = generators[0]
267
+ iterable = self.visit(first_generator.iter)
268
+ if isinstance(iterable, TupleImpl):
269
+ initial_iterator = iterable
270
+ else:
271
+ if not hasattr(iterable, "__iter__"):
272
+ raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
273
+ initial_iterator = self.handle_call(first_generator.iter, iterable.__iter__)
274
+ if not isinstance(initial_iterator, SonolusIterator):
275
+ raise ValueError("Unsupported iterator")
276
+ # The initial iterator is evaluated eagerly in Python
277
+ before_ctx = ctx().branch_with_scope(None, before_ctx.scope.copy())
278
+ start_ctx = before_ctx.branch_with_scope(None, Scope())
279
+ set_ctx(start_ctx)
280
+ self.construct_genexpr(generators, elt, initial_iterator)
265
281
  ctx().scope.set_value("$return", validate_value(None))
266
282
  case _:
267
283
  raise NotImplementedError("Unsupported syntax")
268
- if has_yield(node) or isinstance(node, ast.GeneratorExp):
284
+ # has_yield is set by the find_function visitor
285
+ if getattr(node, "has_yield", False) or isinstance(node, ast.GeneratorExp):
269
286
  return_ctx = Context.meet([*self.return_ctxs, ctx()])
270
287
  result_binding = return_ctx.scope.get_binding("$return")
271
288
  if not isinstance(result_binding, ValueBinding):
@@ -323,7 +340,9 @@ class Visitor(ast.NodeVisitor):
323
340
  set_ctx(after_ctx.branch_with_scope(None, before_ctx.scope.copy()))
324
341
  return result_binding.value
325
342
 
326
- def construct_genexpr(self, generators: Iterable[ast.comprehension], elt: ast.expr):
343
+ def construct_genexpr(
344
+ self, generators: Iterable[ast.comprehension], elt: ast.expr, initial_iterator: Value | None = None
345
+ ):
327
346
  if not generators:
328
347
  # Note that there may effectively be multiple yields in an expression since
329
348
  # tuples are unrolled.
@@ -335,14 +354,22 @@ class Visitor(ast.NodeVisitor):
335
354
  set_ctx(resume_ctx)
336
355
  return
337
356
  generator, *others = generators
338
- iterable = self.visit(generator.iter)
357
+ if initial_iterator is not None:
358
+ iterable = initial_iterator
359
+ else:
360
+ iterable = self.visit(generator.iter)
339
361
  if isinstance(iterable, TupleImpl):
340
362
  for value in iterable.value:
341
363
  set_ctx(ctx().branch(None))
342
364
  self.handle_assign(generator.target, validate_value(value))
343
365
  self.construct_genexpr(others, elt)
344
366
  else:
345
- iterator = self.handle_call(generator.iter, iterable.__iter__)
367
+ if initial_iterator is not None:
368
+ iterator = initial_iterator
369
+ else:
370
+ if not hasattr(iterable, "__iter__"):
371
+ raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
372
+ iterator = self.handle_call(generator.iter, iterable.__iter__)
346
373
  if not isinstance(iterator, SonolusIterator):
347
374
  raise ValueError("Unsupported iterator")
348
375
  header_ctx = ctx().branch(None)
@@ -493,6 +520,8 @@ class Visitor(ast.NodeVisitor):
493
520
  if break_ctxs:
494
521
  set_ctx(Context.meet([*break_ctxs, ctx()]))
495
522
  return
523
+ if not hasattr(iterable, "__iter__"):
524
+ raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
496
525
  iterator = self.handle_call(node, iterable.__iter__)
497
526
  if not isinstance(iterator, SonolusIterator):
498
527
  raise ValueError("Unsupported iterator")
@@ -980,6 +1009,8 @@ class Visitor(ast.NodeVisitor):
980
1009
  self.resume_ctxs.append(resume_ctx)
981
1010
  set_ctx(resume_ctx)
982
1011
  return validate_value(None)
1012
+ if not hasattr(value, "__iter__"):
1013
+ raise TypeError(f"Object of type '{type(value).__name__}' is not iterable")
983
1014
  iterator = self.handle_call(node, value.__iter__)
984
1015
  if not isinstance(iterator, SonolusIterator):
985
1016
  raise ValueError("Expected a SonolusIterator")
@@ -1395,6 +1426,9 @@ class Visitor(ast.NodeVisitor):
1395
1426
  self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
1396
1427
  ) -> R:
1397
1428
  """Executes the given function at the given node for a better traceback."""
1429
+ if ctx().no_eval:
1430
+ return fn(*args, **kwargs)
1431
+
1398
1432
  location_args = {
1399
1433
  "lineno": node.lineno,
1400
1434
  "col_offset": node.col_offset,
sonolus/build/cli.py CHANGED
@@ -166,13 +166,13 @@ def main():
166
166
  def add_common_arguments(parser):
167
167
  optimization_group = parser.add_mutually_exclusive_group()
168
168
  optimization_group.add_argument(
169
- "-o0", "--optimize-minimal", action="store_true", help="Use minimal optimization passes"
169
+ "-O0", "--optimize-minimal", action="store_true", help="Use minimal optimization passes"
170
170
  )
171
171
  optimization_group.add_argument(
172
- "-o1", "--optimize-fast", action="store_true", help="Use fast optimization passes"
172
+ "-O1", "--optimize-fast", action="store_true", help="Use fast optimization passes"
173
173
  )
174
174
  optimization_group.add_argument(
175
- "-o2", "--optimize-standard", action="store_true", help="Use standard optimization passes"
175
+ "-O2", "--optimize-standard", action="store_true", help="Use standard optimization passes"
176
176
  )
177
177
 
178
178
  build_components = parser.add_argument_group("build components")
sonolus/build/compile.py CHANGED
@@ -21,6 +21,7 @@ from sonolus.script.internal.context import (
21
21
  ctx,
22
22
  using_ctx,
23
23
  )
24
+ from sonolus.script.internal.error import CompilationError
24
25
  from sonolus.script.num import _is_num
25
26
 
26
27
 
@@ -140,7 +141,21 @@ def callback_to_cfg(
140
141
  name: str,
141
142
  archetype: type[_BaseArchetype] | None = None,
142
143
  ) -> BasicBlock:
143
- callback_state = CallbackContextState(name)
144
+ try:
145
+ # Default to no_eval=True for performance unless there's an error.
146
+ return _callback_to_cfg(global_state, callback, name, archetype, no_eval=True)
147
+ except CompilationError:
148
+ return _callback_to_cfg(global_state, callback, name, archetype, no_eval=False)
149
+
150
+
151
+ def _callback_to_cfg(
152
+ global_state: GlobalContextState,
153
+ callback: Callable,
154
+ name: str,
155
+ archetype: type[_BaseArchetype] | None,
156
+ no_eval: bool,
157
+ ) -> BasicBlock:
158
+ callback_state = CallbackContextState(name, no_eval=no_eval)
144
159
  context = Context(global_state, callback_state)
145
160
  with using_ctx(context):
146
161
  if archetype is not None:
@@ -190,7 +190,8 @@ def imported(*, name: str | None = None) -> Any:
190
190
 
191
191
  In watch mode, data may also be loaded from a corresponding exported field in play mode.
192
192
 
193
- Imported fields may only be updated in the `preprocess` callback, and are read-only in other callbacks.
193
+ Imported fields may only be updated in the [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess]
194
+ callback, and are read-only in other callbacks.
194
195
 
195
196
  Usage:
196
197
  ```python
@@ -205,10 +206,12 @@ def imported(*, name: str | None = None) -> Any:
205
206
  def entity_data() -> Any:
206
207
  """Declare a field as entity data.
207
208
 
208
- Entity data is accessible from other entities, but may only be updated in the `preprocess` callback
209
+ Entity data is accessible from other entities, but may only be updated in the
210
+ [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess] callback
209
211
  and is read-only in other callbacks.
210
212
 
211
- It functions like `imported` and shares the same underlying storage, except that it is not loaded from a level.
213
+ It functions like [`imported`][sonolus.script.archetype.imported] and shares the same underlying storage,
214
+ except that it is not loaded from a level.
212
215
 
213
216
  Usage:
214
217
  ```python
@@ -242,7 +245,8 @@ def entity_memory() -> Any:
242
245
  Entity memory is private to the entity and is not accessible from other entities. It may be read or updated in any
243
246
  callback associated with the entity.
244
247
 
245
- Entity memory fields may also be set when an entity is spawned using the `spawn()` method.
248
+ Entity memory fields may also be set when an entity is spawned using the
249
+ [`spawn()`][sonolus.script.archetype.PlayArchetype.spawn] method.
246
250
 
247
251
  Usage:
248
252
  ```python
@@ -260,7 +264,9 @@ def shared_memory() -> Any:
260
264
  Shared memory is accessible from other entities.
261
265
 
262
266
  Shared memory may be read in any callback, but may only be updated by sequential callbacks
263
- (`preprocess`, `update_sequential`, and `touch`).
267
+ ([`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess],
268
+ [`update_sequential`][sonolus.script.archetype.PlayArchetype.update_sequential],
269
+ and [`touch`][sonolus.script.archetype.PlayArchetype.touch]).
264
270
 
265
271
  Usage:
266
272
  ```python
@@ -688,7 +694,7 @@ class PlayArchetype(_BaseArchetype):
688
694
  def spawn_order(self) -> float:
689
695
  """Return the spawn order of the entity.
690
696
 
691
- Runs when the level is loaded after `preprocess`.
697
+ Runs when the level is loaded after [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess].
692
698
  """
693
699
 
694
700
  def should_spawn(self) -> bool:
@@ -709,13 +715,15 @@ class PlayArchetype(_BaseArchetype):
709
715
  Runs first each frame.
710
716
 
711
717
  This is where logic affecting shared memory should be placed.
712
- Other logic should be placed in `update_parallel` for better performance.
718
+ Other logic should typically be placed in
719
+ [`update_parallel`][sonolus.script.archetype.PlayArchetype.update_parallel]
720
+ for better performance.
713
721
  """
714
722
 
715
723
  def update_parallel(self):
716
724
  """Perform parallel actions for this frame.
717
725
 
718
- Runs after `touch` each frame.
726
+ Runs after [`touch`][sonolus.script.archetype.PlayArchetype.touch] each frame.
719
727
 
720
728
  This is where most gameplay logic should be placed.
721
729
  """
@@ -723,7 +731,7 @@ class PlayArchetype(_BaseArchetype):
723
731
  def touch(self):
724
732
  """Handle user input.
725
733
 
726
- Runs after `update_sequential` each frame.
734
+ Runs after [`update_sequential`][sonolus.script.archetype.PlayArchetype.update_sequential] each frame.
727
735
  """
728
736
 
729
737
  def terminate(self):
@@ -863,13 +871,14 @@ class WatchArchetype(_BaseArchetype):
863
871
  Runs first each frame.
864
872
 
865
873
  This is where logic affecting shared memory should be placed.
866
- Other logic should be placed in `update_parallel` for better performance.
874
+ Other logic should typically be placed in
875
+ [`update_parallel`][sonolus.script.archetype.PlayArchetype.update_parallel] for better performance.
867
876
  """
868
877
 
869
878
  def update_parallel(self):
870
879
  """Parallel update callback.
871
880
 
872
- Runs after `touch` each frame.
881
+ Runs after [`touch`][sonolus.script.archetype.PlayArchetype.touch] each frame.
873
882
 
874
883
  This is where most gameplay logic should be placed.
875
884
  """
@@ -1094,7 +1103,7 @@ class WatchEntityInput(Record):
1094
1103
  class EntityRef[A: _BaseArchetype](Record):
1095
1104
  """Reference to another entity.
1096
1105
 
1097
- May be used with `Any` to reference an unknown archetype.
1106
+ May be used with `typing.Any` to reference an unknown archetype.
1098
1107
 
1099
1108
  Usage:
1100
1109
  ```python
sonolus/script/array.py CHANGED
@@ -16,7 +16,7 @@ from sonolus.script.internal.value import BackingSource, DataValue, Value
16
16
  from sonolus.script.num import Num
17
17
 
18
18
  Dim = Literal
19
- """Shorthand for `Literal` intended for use in array dimensions for type checker compatibility."""
19
+ """Shorthand for `typing.Literal` intended for use in array dimensions for type checker compatibility."""
20
20
 
21
21
 
22
22
  class ArrayMeta(ABCMeta):
@@ -33,7 +33,7 @@ class Box[T](Record):
33
33
  x: T = ...
34
34
  y: T = ...
35
35
  box = Box(x)
36
- box.value = y # Works regardless of whether x is a Num or not
36
+ box.value = y # Works regardless of whether x is a Num, array, or record
37
37
  ```
38
38
  """
39
39
 
@@ -337,7 +337,7 @@ class ArrayPointer[T](Record, ArrayLike[T]):
337
337
  raise TypeError("ArrayPointer values cannot be accessed outside of a context")
338
338
  return _deref(
339
339
  # Allows a compile time constant block so we can warn based on callback read/write access
340
- (self._value["block"]._is_py_() and self._value["block"]._as_py_()) or self.block,
340
+ (self._value_["block"]._is_py_() and self._value_["block"]._as_py_()) or self.block,
341
341
  self.offset + Num._accept_(item) * Num._accept_(self.element_type()._size_()),
342
342
  self.element_type(),
343
343
  )
sonolus/script/easing.py CHANGED
@@ -1,4 +1,3 @@
1
- # ruff: noqa: E501
2
1
  import math
3
2
 
4
3
  from sonolus.backend.ops import Op
sonolus/script/effect.py CHANGED
@@ -38,7 +38,8 @@ class Effect(Record):
38
38
  def schedule(self, time: float, distance: float = 0) -> None:
39
39
  """Schedule the effect clip to play at a specific time.
40
40
 
41
- This is not suitable for real-time effects such as responses to user input. Use `play` instead.
41
+ This is not suitable for real-time effects such as responses to user input.
42
+ Use [`play`][sonolus.script.effect.Effect.play] instead.
42
43
 
43
44
  This may be called in preprocess to schedule effects upfront.
44
45
 
@@ -61,7 +62,8 @@ class Effect(Record):
61
62
  def schedule_loop(self, start_time: float) -> ScheduledLoopedEffectHandle:
62
63
  """Schedule the effect clip to play in a loop until stopped.
63
64
 
64
- This is not suitable for real-time effects such as responses to user input. Use `loop` instead.
65
+ This is not suitable for real-time effects such as responses to user input.
66
+ Use [`loop`][sonolus.script.effect.Effect.loop] instead.
65
67
 
66
68
  Returns:
67
69
  A handle to stop the loop.
sonolus/script/globals.py CHANGED
@@ -235,7 +235,10 @@ def _tutorial_instruction[T](cls: type[T]) -> T:
235
235
  def level_memory[T](cls: type[T]) -> T:
236
236
  """Define level memory.
237
237
 
238
- Level memory may be modified during gameplay in sequential callbacks (`preprocess`, `update_sequential`, `touch`).
238
+ Level memory may be modified during gameplay in sequential callbacks
239
+ ([`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess],
240
+ [`update_sequential`][sonolus.script.archetype.PlayArchetype.update_sequential],
241
+ [`touch`][sonolus.script.archetype.PlayArchetype.touch]).
239
242
 
240
243
  Usage:
241
244
  ```python
@@ -265,7 +268,7 @@ def level_memory[T](cls: type[T]) -> T:
265
268
  def level_data[T](cls: type[T]) -> T:
266
269
  """Define level data.
267
270
 
268
- Level data may only be modified during preprocessing.
271
+ Level data may only be modified during [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess].
269
272
 
270
273
  Usage:
271
274
  ```python
@@ -61,10 +61,12 @@ class GlobalContextState:
61
61
  class CallbackContextState:
62
62
  callback: str
63
63
  used_names: dict[str, int]
64
+ no_eval: bool
64
65
 
65
- def __init__(self, callback: str):
66
+ def __init__(self, callback: str, no_eval: bool = False):
66
67
  self.callback = callback
67
68
  self.used_names = {}
69
+ self.no_eval = no_eval
68
70
 
69
71
 
70
72
  class Context:
@@ -111,6 +113,10 @@ class Context:
111
113
  def used_names(self) -> dict[str, int]:
112
114
  return self.callback_state.used_names
113
115
 
116
+ @property
117
+ def no_eval(self) -> bool:
118
+ return self.callback_state.no_eval
119
+
114
120
  def check_readable(self, place: BlockPlace):
115
121
  if debug_config().unchecked_reads:
116
122
  return
@@ -32,7 +32,7 @@ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]
32
32
  if ctx():
33
33
  bound_args = signature.bind(*args)
34
34
  bound_args.apply_defaults()
35
- return native_call(op, *(Num._accept_(arg) for arg in bound_args.args))
35
+ return native_call(op, *bound_args.args)
36
36
  return fn(*args) # type: ignore
37
37
 
38
38
  return wrapper
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Iterator
3
4
  from typing import Any
4
5
 
6
+ from sonolus.script.internal.context import ctx
5
7
  from sonolus.script.internal.impl import meta_fn
6
8
  from sonolus.script.maybe import Maybe, Nothing, Some
7
9
  from sonolus.script.record import Record
@@ -12,7 +14,8 @@ class SonolusIterator[T]:
12
14
 
13
15
  This class is used to define custom iterators that can be used in Sonolus.py.
14
16
 
15
- Inheritors must implement the `next` method, which should return a `Maybe[T]`.
17
+ Inheritors must implement the [`next`][sonolus.script.iterator.SonolusIterator.next] method,
18
+ which should return a [`Maybe[T]`][sonolus.script.maybe.Maybe].
16
19
 
17
20
  Usage:
18
21
  ```python
@@ -26,6 +29,7 @@ class SonolusIterator[T]:
26
29
 
27
30
  @meta_fn
28
31
  def next(self) -> Maybe[T]:
32
+ """Return the next item from the iterator as a [`Maybe`][sonolus.script.maybe.Maybe]."""
29
33
  raise NotImplementedError
30
34
 
31
35
  def __next__(self) -> T:
@@ -114,3 +118,16 @@ class _FilteringIterator[T, Fn](Record, SonolusIterator):
114
118
  inside = value.get_unsafe()
115
119
  if self.fn(inside):
116
120
  return Some(inside)
121
+
122
+
123
+ @meta_fn
124
+ def maybe_next[T](iterator: Iterator[T]) -> Maybe[T]:
125
+ """Get the next item from an iterator as a [`Maybe`][sonolus.script.maybe.Maybe]."""
126
+ from sonolus.backend.visitor import compile_and_call
127
+
128
+ if not isinstance(iterator, SonolusIterator):
129
+ raise TypeError("Iterator must be an instance of SonolusIterator.")
130
+ if ctx():
131
+ return compile_and_call(iterator.next)
132
+ else:
133
+ return iterator.next()
sonolus/script/level.py CHANGED
@@ -145,7 +145,7 @@ class Level:
145
145
  )
146
146
 
147
147
 
148
- type EntityListArg = list[PlayArchetype | EntityListArg]
148
+ type EntityListArg = list[list[PlayArchetype] | PlayArchetype] | PlayArchetype
149
149
 
150
150
 
151
151
  def flatten_entities(entities: EntityListArg) -> Iterator[PlayArchetype]:
@@ -175,7 +175,7 @@ class LevelData:
175
175
  bgm_offset: float
176
176
  entities: list[PlayArchetype]
177
177
 
178
- def __init__(self, bgm_offset: float, entities: list[PlayArchetype]) -> None:
178
+ def __init__(self, bgm_offset: float, entities: EntityListArg) -> None:
179
179
  self.bgm_offset = bgm_offset
180
180
  self.entities = [*flatten_entities(entities)]
181
181
 
sonolus/script/maybe.py CHANGED
@@ -4,19 +4,21 @@ from collections.abc import Callable
4
4
  from typing import Any
5
5
 
6
6
  from sonolus.script.internal.context import ctx
7
- from sonolus.script.internal.impl import meta_fn
7
+ from sonolus.script.internal.impl import meta_fn, validate_value
8
8
  from sonolus.script.internal.transient import TransientValue
9
9
  from sonolus.script.internal.value import Value
10
10
  from sonolus.script.num import Num
11
+ from sonolus.script.values import copy, zeros
11
12
 
12
13
 
13
14
  class Maybe[T](TransientValue):
14
15
  """A type that either has a value or is empty.
15
16
 
16
- Maybe has special behavior when returned from a function: it may be returned from multiple places
17
- in a function, provided that all but one return statement returns the literal `Nothing`.
17
+ `Maybe` has special behavior when returned from a function: unlike records and arrays, it may be returned from
18
+ multiple places in a function, provided that all but one return statement returns the literal
19
+ [`Nothing`][sonolus.script.maybe.Nothing].
18
20
 
19
- This type is not intended for other uses, such as being stored in a Record, Array, or Archetype.
21
+ Storing values of this type in a Record, Array, or Archetype is not supported.
20
22
 
21
23
  Usage:
22
24
  ```python
@@ -38,13 +40,16 @@ class Maybe[T](TransientValue):
38
40
 
39
41
  def __init__(self, *, present: bool, value: T):
40
42
  self._present = Num._accept_(present)
41
- self._value = value
43
+ self._value = validate_value(value)
42
44
 
43
45
  @property
44
46
  @meta_fn
45
47
  def is_some(self) -> bool:
46
48
  """Check if the value is present."""
47
49
  if ctx():
50
+ if self._present._is_py_():
51
+ # Makes this a compile time constant.
52
+ return self._present
48
53
  return self._present._get_readonly_()
49
54
  else:
50
55
  return self._present._as_py_()
@@ -61,13 +66,106 @@ class Maybe[T](TransientValue):
61
66
 
62
67
  @meta_fn
63
68
  def get_unsafe(self) -> T:
64
- return self._value
69
+ if ctx():
70
+ return self._value
71
+ else:
72
+ return self._value._as_py_()
65
73
 
66
74
  def map[R](self, fn: Callable[[T], R], /) -> Maybe[R]:
75
+ """Map the contained value to a new value using the provided function.
76
+
77
+ If the value is not present, returns [`Nothing`][sonolus.script.maybe.Nothing].
78
+
79
+ Args:
80
+ fn: A function that takes the contained value and returns a new value.
81
+
82
+ Returns:
83
+ A [`Maybe`][sonolus.script.maybe.Maybe] instance containing the result of the function if the value
84
+ is present, otherwise [`Nothing`][sonolus.script.maybe.Nothing].
85
+ """
67
86
  if self.is_some:
68
87
  return Some(fn(self.get_unsafe()))
69
88
  return Nothing
70
89
 
90
+ def flat_map[R](self, fn: Callable[[T], Maybe[R]], /) -> Maybe[R]:
91
+ """Flat map the contained value to a new [`Maybe`][sonolus.script.maybe.Maybe] using the provided function.
92
+
93
+ If the value is not present, returns [`Nothing`][sonolus.script.maybe.Nothing].
94
+
95
+ Args:
96
+ fn: A function that takes the contained value and returns a new [`Maybe`][sonolus.script.maybe.Maybe].
97
+
98
+ Returns:
99
+ A [`Maybe`][sonolus.script.maybe.Maybe] instance containing the result of the function if the value
100
+ is present, otherwise [`Nothing`][sonolus.script.maybe.Nothing].
101
+ """
102
+ if self.is_some:
103
+ return fn(self.get_unsafe())
104
+ return Nothing
105
+
106
+ def or_default(self, default: T) -> T:
107
+ """Return a copy of the contained value if present, otherwise return a copy of the given default value.
108
+
109
+ Args:
110
+ default: The default value to return if the contained value is not present.
111
+
112
+ Returns:
113
+ A copy of the contained value if present, otherwise a copy of the default value.
114
+ """
115
+ result = _box(copy(default))
116
+ if self.is_some:
117
+ result.value = self.get_unsafe()
118
+ return result.value
119
+
120
+ @meta_fn
121
+ def or_else(self, fn: Callable[[], T], /) -> T:
122
+ """Return a copy of the contained value if present, otherwise return a copy of the result of the given function.
123
+
124
+ Args:
125
+ fn: A function that returns a value to use if the contained value is not present.
126
+
127
+ Returns:
128
+ A copy of the contained value if present, otherwise a copy of the result of calling the function.
129
+ """
130
+ from sonolus.backend.visitor import compile_and_call
131
+
132
+ if ctx():
133
+ if self.is_some._is_py_(): # type: ignore
134
+ if self.is_some._as_py_(): # type: ignore
135
+ return copy(self.get_unsafe())
136
+ else:
137
+ return copy(compile_and_call(fn))
138
+ else:
139
+ return compile_and_call(self._or_else, fn)
140
+ elif self.is_some:
141
+ return copy(self.get_unsafe())
142
+ else:
143
+ return copy(fn())
144
+
145
+ def _or_else(self, fn: Callable[[], T], /) -> T:
146
+ result = _box(zeros(self.contained_type))
147
+ if self.is_some:
148
+ result.value = self.get_unsafe()
149
+ else:
150
+ result.value = fn()
151
+ return result.value
152
+
153
+ @property
154
+ def tuple(self) -> tuple[bool, T]:
155
+ """Return whether the value is present and a copy of the contained value if present as a tuple.
156
+
157
+ If the value is not present, the tuple will contain `False` and a zero initialized value of the contained type.
158
+ """
159
+ result_value = _box(zeros(self.contained_type))
160
+ if self.is_some:
161
+ result_value.value = self.get_unsafe()
162
+ return self.is_some, result_value.value
163
+
164
+ @property
165
+ @meta_fn
166
+ def contained_type(self):
167
+ return type(self._value)
168
+
71
169
  @classmethod
72
170
  def _accepts_(cls, value: Any) -> bool:
73
171
  return isinstance(value, cls)
@@ -121,13 +219,13 @@ class Maybe[T](TransientValue):
121
219
 
122
220
 
123
221
  def Some[T](value: T) -> Maybe[T]: # noqa: N802
124
- """Create a `Maybe` instance with a value.
222
+ """Create a [`Maybe`][sonolus.script.maybe.Maybe] instance with a value.
125
223
 
126
224
  Args:
127
225
  value: The contained value.
128
226
 
129
227
  Returns:
130
- A `Maybe` instance that contains the provided value.
228
+ A [`Maybe`][sonolus.script.maybe.Maybe] instance that contains the provided value.
131
229
  """
132
230
  return Maybe(present=True, value=value)
133
231
 
@@ -136,4 +234,11 @@ Nothing: Maybe[Any] = Maybe(present=False, value=None) # type: ignore
136
234
 
137
235
  # Note: has to come after the definition to hide the definition in the docs.
138
236
  Nothing: Maybe[Any]
139
- """The empty `Maybe` instance."""
237
+ """The empty [`Maybe`][sonolus.script.maybe.Maybe] instance."""
238
+
239
+
240
+ @meta_fn
241
+ def _box(value):
242
+ from sonolus.script.containers import Box
243
+
244
+ return Box(value)
sonolus/script/num.py CHANGED
@@ -453,14 +453,14 @@ if TYPE_CHECKING:
453
453
  from typing import Protocol
454
454
 
455
455
  @runtime_checkable
456
- class Num[T](Protocol, int, bool, float):
457
- def __add__(self, other: T, /) -> Num | int | bool | float: ...
458
- def __sub__(self, other: T, /) -> Num | int | bool | float: ...
459
- def __mul__(self, other: T, /) -> Num | int | bool | float: ...
460
- def __truediv__(self, other: T, /) -> Num | int | bool | float: ...
461
- def __floordiv__(self, other: T, /) -> Num | int | bool | float: ...
462
- def __mod__(self, other: T, /) -> Num | int | bool | float: ...
463
- def __pow__(self, other: T, /) -> Num | int | bool | float: ...
456
+ class Num(Protocol, int, bool, float):
457
+ def __add__(self, other: Any, /) -> Num | int | bool | float: ...
458
+ def __sub__(self, other: Any, /) -> Num | int | bool | float: ...
459
+ def __mul__(self, other: Any, /) -> Num | int | bool | float: ...
460
+ def __truediv__(self, other: Any, /) -> Num | int | bool | float: ...
461
+ def __floordiv__(self, other: Any, /) -> Num | int | bool | float: ...
462
+ def __mod__(self, other: Any, /) -> Num | int | bool | float: ...
463
+ def __pow__(self, other: Any, /) -> Num | int | bool | float: ...
464
464
 
465
465
  def __neg__(self, /) -> Num | int | bool | float: ...
466
466
  def __pos__(self, /) -> Num | int | bool | float: ...
@@ -468,10 +468,10 @@ if TYPE_CHECKING:
468
468
 
469
469
  def __eq__(self, other: Any, /) -> bool: ...
470
470
  def __ne__(self, other: Any, /) -> bool: ...
471
- def __lt__(self, other: T, /) -> bool: ...
472
- def __le__(self, other: T, /) -> bool: ...
473
- def __gt__(self, other: T, /) -> bool: ...
474
- def __ge__(self, other: T, /) -> bool: ...
471
+ def __lt__(self, other: Any, /) -> bool: ...
472
+ def __le__(self, other: Any, /) -> bool: ...
473
+ def __gt__(self, other: Any, /) -> bool: ...
474
+ def __ge__(self, other: Any, /) -> bool: ...
475
475
 
476
476
  def __hash__(self, /) -> int: ...
477
477
 
sonolus/script/project.py CHANGED
@@ -41,7 +41,7 @@ class Project:
41
41
  self._levels = None
42
42
  self.resources = Path(resources or "resources")
43
43
 
44
- def with_levels(self, levels: list[Level]) -> Project:
44
+ def with_levels(self, levels: Iterable[Level] | Callable[[], Iterable[Level]] | None) -> Project:
45
45
  """Create a new project with the specified levels.
46
46
 
47
47
  Args:
sonolus/script/record.py CHANGED
@@ -62,13 +62,13 @@ class Record(GenericValue, metaclass=RecordMeta):
62
62
  ```
63
63
  """
64
64
 
65
- _value: dict[str, Value]
66
- _fields: ClassVar[list[_RecordField] | None] = None
67
- _constructor_signature: ClassVar[inspect.Signature]
65
+ _value_: dict[str, Value]
66
+ _fields_: ClassVar[list[_RecordField] | None] = None
67
+ _constructor_signature_: ClassVar[inspect.Signature]
68
68
 
69
69
  @classmethod
70
70
  def _validate_type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
71
- if cls._fields is None:
71
+ if cls._fields_ is None:
72
72
  raise TypeError("Base Record class cannot have type arguments")
73
73
  return super()._validate_type_args_(args)
74
74
 
@@ -80,16 +80,16 @@ class Record(GenericValue, metaclass=RecordMeta):
80
80
  if is_parameterizing:
81
81
  fields = []
82
82
  offset = 0
83
- for generic_field in cls._fields:
83
+ for generic_field in cls._fields_:
84
84
  resolved_type = validate_and_resolve_type(generic_field.type, cls._type_vars_to_args_)
85
85
  resolved_type = validate_concrete_type(resolved_type)
86
86
  field = _RecordField(generic_field.name, resolved_type, generic_field.index, offset)
87
87
  fields.append(field)
88
88
  setattr(cls, field.name, field)
89
89
  offset += resolved_type._size_()
90
- cls._fields = fields
90
+ cls._fields_ = fields
91
91
  return
92
- is_inheriting_from_existing_record_class = cls._fields is not None
92
+ is_inheriting_from_existing_record_class = cls._fields_ is not None
93
93
  if is_inheriting_from_existing_record_class and not is_parameterizing:
94
94
  # The main reason this is disallowed is that subclasses wouldn't be substitutable for their parent classes
95
95
  # Assignment of a subclass instance to a variable of the parent class would either be disallowed or would
@@ -124,8 +124,8 @@ class Record(GenericValue, metaclass=RecordMeta):
124
124
  )
125
125
 
126
126
  cls._parameterized_ = {}
127
- cls._fields = fields
128
- cls._constructor_signature = inspect.Signature(params)
127
+ cls._fields_ = fields
128
+ cls._constructor_signature_ = inspect.Signature(params)
129
129
 
130
130
  _add_inplace_ops(cls)
131
131
 
@@ -139,13 +139,13 @@ class Record(GenericValue, metaclass=RecordMeta):
139
139
 
140
140
  def __new__(cls, *args, **kwargs):
141
141
  # We override __new__ to allow changing to the parameterized version
142
- if cls._constructor_signature is None:
142
+ if cls._constructor_signature_ is None:
143
143
  raise TypeError(f"Cannot instantiate {cls.__name__}")
144
- bound = cls._constructor_signature.bind(*args, **kwargs)
144
+ bound = cls._constructor_signature_.bind(*args, **kwargs)
145
145
  bound.apply_defaults()
146
146
  values = {}
147
147
  type_vars = {}
148
- for field in cls._fields:
148
+ for field in cls._fields_:
149
149
  value = bound.arguments[field.name]
150
150
  value = accept_and_infer_types(field.type, value, type_vars)
151
151
  values[field.name] = value._get_()
@@ -158,7 +158,7 @@ class Record(GenericValue, metaclass=RecordMeta):
158
158
  else:
159
159
  parameterized = cls[type_args]
160
160
  result: cls = object.__new__(parameterized) # type: ignore
161
- result._value = values
161
+ result._value_ = values
162
162
  return result
163
163
 
164
164
  def __init__(self, *args, **kwargs):
@@ -168,12 +168,12 @@ class Record(GenericValue, metaclass=RecordMeta):
168
168
  @classmethod
169
169
  def _raw(cls, **kwargs) -> Self:
170
170
  result = object.__new__(cls)
171
- result._value = kwargs
171
+ result._value_ = kwargs
172
172
  return result
173
173
 
174
174
  @classmethod
175
175
  def _size_(cls) -> int:
176
- return sum(field.type._size_() for field in cls._fields)
176
+ return sum(field.type._size_() for field in cls._fields_)
177
177
 
178
178
  @classmethod
179
179
  def _is_value_type_(cls) -> bool:
@@ -182,18 +182,18 @@ class Record(GenericValue, metaclass=RecordMeta):
182
182
  @classmethod
183
183
  def _from_backing_source_(cls, source: BackingSource) -> Self:
184
184
  result = object.__new__(cls)
185
- result._value = {
185
+ result._value_ = {
186
186
  field.name: field.type._from_backing_source_(
187
187
  lambda offset, field_offset=field.offset: source((Num(offset) + Num(field_offset)).ir()) # type: ignore
188
188
  )
189
- for field in cls._fields
189
+ for field in cls._fields_
190
190
  }
191
191
  return result
192
192
 
193
193
  @classmethod
194
194
  def _from_place_(cls, place: BlockPlace) -> Self:
195
195
  result = object.__new__(cls)
196
- result._value = {field.name: field.type._from_place_(place.add_offset(field.offset)) for field in cls._fields}
196
+ result._value_ = {field.name: field.type._from_place_(place.add_offset(field.offset)) for field in cls._fields_}
197
197
  return result
198
198
 
199
199
  @classmethod
@@ -207,7 +207,7 @@ class Record(GenericValue, metaclass=RecordMeta):
207
207
  return value
208
208
 
209
209
  def _is_py_(self) -> bool:
210
- return all(value._is_py_() for value in self._value.values())
210
+ return all(value._is_py_() for value in self._value_.values())
211
211
 
212
212
  def _as_py_(self) -> Self:
213
213
  if not self._is_py_():
@@ -217,18 +217,18 @@ class Record(GenericValue, metaclass=RecordMeta):
217
217
  @classmethod
218
218
  def _from_list_(cls, values: Iterable[DataValue]) -> Self:
219
219
  iterator = iter(values)
220
- return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields})
220
+ return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields_})
221
221
 
222
222
  def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
223
223
  result = []
224
- for field in self._fields:
225
- result.extend(self._value[field.name]._to_list_(level_refs))
224
+ for field in self._fields_:
225
+ result.extend(self._value_[field.name]._to_list_(level_refs))
226
226
  return result
227
227
 
228
228
  @classmethod
229
229
  def _flat_keys_(cls, prefix: str) -> list[str]:
230
230
  result = []
231
- for field in cls._fields:
231
+ for field in cls._fields_:
232
232
  result.extend(field.type._flat_keys_(f"{prefix}.{field.name}"))
233
233
  return result
234
234
 
@@ -240,33 +240,33 @@ class Record(GenericValue, metaclass=RecordMeta):
240
240
 
241
241
  def _copy_from_(self, value: Any):
242
242
  value = self._accept_(value)
243
- for field in self._fields:
243
+ for field in self._fields_:
244
244
  field.__set__(self, field.__get__(value))
245
245
 
246
246
  def _copy_(self) -> Self:
247
- return type(self)._raw(**{field.name: self._value[field.name]._copy_() for field in self._fields})
247
+ return type(self)._raw(**{field.name: self._value_[field.name]._copy_() for field in self._fields_})
248
248
 
249
249
  @classmethod
250
250
  def _alloc_(cls) -> Self:
251
251
  # Compared to using the constructor, this avoids unnecessary _get_ calls
252
252
  result = object.__new__(cls)
253
- result._value = {field.name: field.type._alloc_() for field in cls._fields}
253
+ result._value_ = {field.name: field.type._alloc_() for field in cls._fields_}
254
254
  return result
255
255
 
256
256
  @classmethod
257
257
  def _zero_(cls) -> Self:
258
258
  result = object.__new__(cls)
259
- result._value = {field.name: field.type._zero_() for field in cls._fields}
259
+ result._value_ = {field.name: field.type._zero_() for field in cls._fields_}
260
260
  return result
261
261
 
262
262
  def __str__(self):
263
263
  return f"{self.__class__.__name__}({
264
- ', '.join(f'{field.name}={field.get_internal(self)}' for field in self._fields)
264
+ ', '.join(f'{field.name}={field.get_internal(self)}' for field in self._fields_)
265
265
  })"
266
266
 
267
267
  def __repr__(self):
268
268
  return f"{self.__class__.__name__}({
269
- ', '.join(f'{field.name}={field.get_internal(self)!r}' for field in self._fields)
269
+ ', '.join(f'{field.name}={field.get_internal(self)!r}' for field in self._fields_)
270
270
  })"
271
271
 
272
272
  @meta_fn
@@ -274,7 +274,7 @@ class Record(GenericValue, metaclass=RecordMeta):
274
274
  if not isinstance(other, type(self)):
275
275
  return False
276
276
  result: Num = Num._accept_(True)
277
- for field in self._fields:
277
+ for field in self._fields_:
278
278
  result = result.and_(field.__get__(self) == field.__get__(other))
279
279
  return result
280
280
 
@@ -283,12 +283,12 @@ class Record(GenericValue, metaclass=RecordMeta):
283
283
  if not isinstance(other, type(self)):
284
284
  return True
285
285
  result: Num = Num._accept_(False)
286
- for field in self._fields:
286
+ for field in self._fields_:
287
287
  result = result.or_(field.__get__(self) != field.__get__(other))
288
288
  return result
289
289
 
290
290
  def __hash__(self):
291
- return hash(tuple(field.__get__(self) for field in self._fields))
291
+ return hash(tuple(field.__get__(self) for field in self._fields_))
292
292
 
293
293
  @meta_fn
294
294
  def __pos__(self) -> Self:
@@ -317,12 +317,12 @@ class _RecordField(SonolusDescriptor):
317
317
  self.offset = offset
318
318
 
319
319
  def get_internal(self, instance: Record) -> Value:
320
- return instance._value[self.name]
320
+ return instance._value_[self.name]
321
321
 
322
322
  def __get__(self, instance: Record | None, owner=None):
323
323
  if instance is None:
324
324
  return self
325
- result = instance._value[self.name]._get_readonly_()
325
+ result = instance._value_[self.name]._get_readonly_()
326
326
  if ctx():
327
327
  return result
328
328
  else:
@@ -331,9 +331,9 @@ class _RecordField(SonolusDescriptor):
331
331
  def __set__(self, instance: Record, value):
332
332
  value = self.type._accept_(value)
333
333
  if self.type._is_value_type_():
334
- instance._value[self.name]._set_(value)
334
+ instance._value_[self.name]._set_(value)
335
335
  else:
336
- instance._value[self.name]._copy_from_(value)
336
+ instance._value_[self.name]._copy_from_(value)
337
337
 
338
338
 
339
339
  _ops_to_inplace_ops = {
sonolus/script/sprite.py CHANGED
@@ -291,7 +291,7 @@ def skin[T](cls: type[T]) -> T | Skin:
291
291
  ```python
292
292
  @skin
293
293
  class Skin:
294
- render_mode: RenderMode
294
+ render_mode: RenderMode = RenderMode.LIGHTWEIGHT
295
295
 
296
296
  note: StandardSprite.NOTE_HEAD_RED
297
297
  other: Sprite = skin_sprite("other")
sonolus/script/stream.py CHANGED
@@ -63,7 +63,8 @@ class _StreamDataField(SonolusDescriptor):
63
63
  def streams[T](cls: type[T]) -> T:
64
64
  """Decorator to define streams and stream groups.
65
65
 
66
- Streams and stream groups are declared by annotating class attributes with `Stream` or `StreamGroup`.
66
+ Streams and stream groups are declared by annotating class attributes with
67
+ [`Stream`][sonolus.script.stream.Stream] or [`StreamGroup`][sonolus.script.stream.StreamGroup].
67
68
 
68
69
  Other types are also supported in the form of data fields. They may be used to store additional data to export from
69
70
  Play to Watch mode.
@@ -180,8 +181,8 @@ class _SparseStreamBacking(BackingValue):
180
181
  class Stream[T](Record):
181
182
  """Represents a stream.
182
183
 
183
- Most users should use [`@streams`][sonolus.script.stream.streams] to declare streams and stream groups rather than
184
- using this class directly.
184
+ Most users should use [`@streams`][sonolus.script.stream.streams] to declare streams and stream groups, rather than
185
+ creating instances of this class directly.
185
186
 
186
187
  If used directly, it is important that streams do not overlap. No other streams should have an offset in
187
188
  `range(self.offset, self.offset + max(1, sizeof(self.element_type())))`, or they will overlap and interfere
@@ -285,12 +286,12 @@ class Stream[T](Record):
285
286
  return previous_key < key
286
287
 
287
288
  def next_key_inclusive(self, key: int | float) -> int | float:
288
- """Like `next_key`, but returns the key itself if it is in the stream."""
289
+ """Like [`next_key`][sonolus.script.stream.Stream.next_key], but returns the key itself if it is in the stream."""
289
290
  _check_can_read_stream()
290
291
  return key if key in self else self.next_key(key)
291
292
 
292
293
  def previous_key_inclusive(self, key: int | float) -> int | float:
293
- """Like `previous_key`, but returns the key itself if it is in the stream."""
294
+ """Like [`previous_key`][sonolus.script.stream.Stream.previous_key], but returns the key itself if it is in the stream."""
294
295
  _check_can_read_stream()
295
296
  return key if key in self else self.previous_key(key)
296
297
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Sonolus engine development in Python
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -10,8 +10,8 @@ sonolus/backend/mode.py,sha256=NkcPZJm8dn83LX35uP24MtQOCnfRDFZ280dHeEEfauE,613
10
10
  sonolus/backend/node.py,sha256=eEzPP14jzWJp2xrZCAaPlNtokxdoqg0bSM7xQiwx1j8,1254
11
11
  sonolus/backend/ops.py,sha256=5weB_vIxbkwCSJuzYZyKUk7vVXsSIEDJYRlvE-2ke8A,10572
12
12
  sonolus/backend/place.py,sha256=7qwV732hZ4WP-9GNN8FQSEKssPJZELip1wLXTWfop7Y,4717
13
- sonolus/backend/utils.py,sha256=QM0VMYfDmkqRkaSq0y0DagKPTMbD-NTOnMKFNoQQLHk,2387
14
- sonolus/backend/visitor.py,sha256=sxg64PLxakHaA7w6eGD5XMGBDXxJM5Hjp1ZE5-FZ1m0,60570
13
+ sonolus/backend/utils.py,sha256=DDgYUVHh4h_eSY65v2KcxUaLSBYGYS6oHar5gTdV7QU,2356
14
+ sonolus/backend/visitor.py,sha256=6QjQ9tYOCP2a8-8WPgm8EB-hzwV5pcwLap3eDDrFG8I,62364
15
15
  sonolus/backend/optimize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  sonolus/backend/optimize/allocate.py,sha256=CuumoMphkpQlGRNeKLHT4FBGE0XVj5pwhfNdrqiLFSs,7535
17
17
  sonolus/backend/optimize/constant_evaluation.py,sha256=OyjlgHIT6oPKCyBtNzEpo1nWYjr-_mwYUo8FrNV4eO4,21331
@@ -26,41 +26,41 @@ sonolus/backend/optimize/passes.py,sha256=YyFKy6qCwcR_Ua2_SXpcBODfvBbm_ygVYcqloO
26
26
  sonolus/backend/optimize/simplify.py,sha256=RDNVTKfC7ByRyxY5z30_ShimOAKth_pKlVFV_36pDG4,14082
27
27
  sonolus/backend/optimize/ssa.py,sha256=raQO0furQQRPYb8iIBKfNrJlj-_5wqtI4EWNfLZ8QFo,10834
28
28
  sonolus/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- sonolus/build/cli.py,sha256=jzJVdPdEn0uaEbv5B7BuQZioytAeG9P2vDXwiRTXA8s,10113
29
+ sonolus/build/cli.py,sha256=-_lTN8zT7nQB2lySM8itAEPVutcEQI-TJ13BcPIfGb4,10113
30
30
  sonolus/build/collection.py,sha256=kOVnpQC_KHAsyTM4nAplSh6QE16CgO-H6PP1GItWm78,12187
31
- sonolus/build/compile.py,sha256=hF8CnaqMQqak4OyEQvxJLo_mpzZDQipMa14syZsWLEA,5976
31
+ sonolus/build/compile.py,sha256=B7w6Uqa4HlZPNklNhhjJoq9ncXMVXLCYabGLxtxWGV4,6521
32
32
  sonolus/build/engine.py,sha256=zUl0KfRygqNhIM8BABNJkKG-0zXFwcYwck-5hJy59yk,13338
33
33
  sonolus/build/level.py,sha256=yXsQtnabkJK0vuVmZ_Wr1jx37jFLgInCS7lTlXjkv9Q,706
34
34
  sonolus/build/node.py,sha256=gnX71RYDUOK_gYMpinQi-bLWO4csqcfiG5gFmhxzSec,1330
35
35
  sonolus/build/project.py,sha256=mv2OuzgIDG-E9G4SIRiUDL5XiPCd1i81wVl1p7itFHA,6329
36
36
  sonolus/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- sonolus/script/archetype.py,sha256=vNNX6YGi_CUQcjf0xG1Rkpz9kUd_84hEPuY1Ew9uFxA,41666
38
- sonolus/script/array.py,sha256=lnobVrrEOE6PgPasq3DovrshlDKPbX8kKUsGmJz2oK0,12360
37
+ sonolus/script/archetype.py,sha256=HJeKJ_vj7xUYaCAmNdGYO45rhFMSLMwOLyvpKjulfVY,42418
38
+ sonolus/script/array.py,sha256=9uOUHZIDMyMT9q3APcXJWXWt97yG-AZoRlxwrvSY6SU,12367
39
39
  sonolus/script/array_like.py,sha256=jFOfXkniTLrIK4ER6HO_tUyKn_TvwjyM4B3SDd9cUS8,9678
40
40
  sonolus/script/bucket.py,sha256=5VU-Bh7qYifl6NcgZFCo7eAEgi3iGTX-PUTmXiPCJaQ,7535
41
- sonolus/script/containers.py,sha256=O1Pjfpz9RmbdSTBi5yqzyoxfhnl_Zk0fiSTUfHBO0lE,18667
41
+ sonolus/script/containers.py,sha256=WX0qRnr6gNtn3C0q7MwMyrzvY-C0Bd1tzLGxSkUwL9U,18680
42
42
  sonolus/script/debug.py,sha256=_Hg1cXQJ8fBXMiwhmoPb2X9CKcQ8QO26WNa59K518og,4305
43
- sonolus/script/easing.py,sha256=7zaDKIfM_whUpb4FBz1DAF4NNG2vk_nDjl8kL2Y90aU,11396
44
- sonolus/script/effect.py,sha256=pqfsOhmGVDMkifumkRr9rD2trWZU6Rw-_gTApNIaz7g,5854
43
+ sonolus/script/easing.py,sha256=txf0OPFP5v7cOFDvJKCyKLK-d2uKIOu56ntLEHfD9QI,11377
44
+ sonolus/script/effect.py,sha256=mhl58IgHUl7GI6kDdwK6l_KWVXYWxxpAcfOjSkdrLj4,5944
45
45
  sonolus/script/engine.py,sha256=etI9dJsQ7V9YZICVNZg54WqpLijPxG8eTPHiV-_EiG8,10687
46
- sonolus/script/globals.py,sha256=FOv8uiLM5U6X-yMLCXukMvLgV3rs-xVJbobST_MhLcU,9782
46
+ sonolus/script/globals.py,sha256=USwh_TuyZh1qORPVLk6x9qe3ca9iLhf5gnqT773gVvY,10007
47
47
  sonolus/script/instruction.py,sha256=iBjY7nCNDT3w0SBJKlix3Z-85e7eE2qKeHp6C2Nq7KA,6753
48
48
  sonolus/script/interval.py,sha256=dj6F2wn5uP6I6_mcZn-wIREgRUQbsLzhvhzB0oEyAdU,11290
49
- sonolus/script/iterator.py,sha256=LqXi6062G-vLa3taxFz3IsWwFsFrOwfxx7_0aWDi4as,3070
50
- sonolus/script/level.py,sha256=wR23xk-NOcW_JMRb3R12sqIXCLSZL-7cM3y7IpMF1J0,6333
51
- sonolus/script/maybe.py,sha256=p2fGgdtNlnozkJ80pOLoo9-M0dp2iKRtLT5RXf-JF4U,4199
49
+ sonolus/script/iterator.py,sha256=_ICY_yX7FG0Zbgs3NhVnaIBdVDpAeXjxJ_CQtq30l7Y,3774
50
+ sonolus/script/level.py,sha256=vnotMbdr_4-MJUsTXMbvWiw2MlMjMHme3q0XRdNFXRg,6349
51
+ sonolus/script/maybe.py,sha256=VYvTWgEfPzoXqI3i3zXhc4dz0pWBVoHmW8FtWH0GQvM,8194
52
52
  sonolus/script/metadata.py,sha256=ttRK27eojHf3So50KQJ-8yj3udZoN1bli5iD-knaeLw,753
53
- sonolus/script/num.py,sha256=uGNBnpE3AAGA1yo0_SADZ57raWowpcsE0DEopgjpohI,16022
53
+ sonolus/script/num.py,sha256=924kWWZusW7oaWuvtQzdAMzkb4ZItWSJwNj3W9XrqZU,16041
54
54
  sonolus/script/options.py,sha256=KlOud4QOf_lW1o6avKXbkjcMCDPkhLcEwt5PW7ZCH3s,9435
55
55
  sonolus/script/particle.py,sha256=XczhwTJvU3dMOXXTxJI_5Mskro2LgVlNgrCSwYreO0Q,8369
56
56
  sonolus/script/pointer.py,sha256=FoOfyD93r0G5d_2BaKfeOT9SqkOP3hq6sqtOs_Rb0c8,1511
57
57
  sonolus/script/printing.py,sha256=mNYu9QWiacBBGZrnePZQMVwbbguoelUps9GiOK_aVRU,2096
58
- sonolus/script/project.py,sha256=IyWMM-Sf7-Kd0AHzlAUyJBKBWRRX2rOpjlN7b8UOTE8,3966
58
+ sonolus/script/project.py,sha256=2XVUXcW49iiTfljvcFuYqtFzqhQIRvD7H7OwH1Mm98w,4009
59
59
  sonolus/script/quad.py,sha256=XoAjaUqR60zIrC_CwheZs7HwS-DRS58yUmlj9GIjX7k,11179
60
- sonolus/script/record.py,sha256=JiRvI_uZjJ-DWzfbsOHDfrOUxO3ZlRPxJP90Hc0z9hM,12673
60
+ sonolus/script/record.py,sha256=-Ff60wBoF1v4-MJWzCNI9n5K3os6WphswZpdTBezeRs,12713
61
61
  sonolus/script/runtime.py,sha256=rJZM_KbKmnwpjhDEpR0DrM6EMSEu46apIErWA_pfLJA,33321
62
- sonolus/script/sprite.py,sha256=mMDTXckn58YR8mrx3fzdBaduQ8cn9YTtTVruXXK1ow0,16272
63
- sonolus/script/stream.py,sha256=4b0AV5MRPo4wzTHmaN95jwODxPVt6WuN3QxmGccdwRU,24517
62
+ sonolus/script/sprite.py,sha256=pgFQvzWB-uy9MsOfV8QabXKjnNgx5vN6md9uNYztHco,16297
63
+ sonolus/script/stream.py,sha256=q3uJUfUP9fx0Go7qXQopl9DMh_RJkT-96p5WlAo4x0M,24693
64
64
  sonolus/script/text.py,sha256=wxujIgKYcCfl2AD2_Im8g3vh0lDEHYwTSRZg9wsBPEU,13402
65
65
  sonolus/script/timing.py,sha256=ZR0ypV2PIoDCMHHGOMfCeezStCsBQdzomdqaz5VKex0,2981
66
66
  sonolus/script/transform.py,sha256=w5mr7hTuNYU0eTAdnN_wTVibaQa0mZrkl-W-kgewJxQ,21345
@@ -71,7 +71,7 @@ sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtU
71
71
  sonolus/script/internal/builtin_impls.py,sha256=vzyaSTsxoZoE6a-aBBHn4aIfjGxxYzojEnOog3-HETI,12686
72
72
  sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
73
73
  sonolus/script/internal/constant.py,sha256=3ycbGkDJVUwcrCZ96vLjAoAARgsvaqDM8rJ_YCrLrvo,4289
74
- sonolus/script/internal/context.py,sha256=8wLclCnLyDJt7BGbSqMD6rngYt9jI2JYji6XV8cbCAw,16742
74
+ sonolus/script/internal/context.py,sha256=qn8xp5BB1C3IaUS8jsSUfkMUJgPzeaIV7K4FY9mHHQo,16903
75
75
  sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
76
76
  sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
77
77
  sonolus/script/internal/error.py,sha256=ZNnsvQVQAnFKzcvsm6-sste2lo-tP5pPI8sD7XlAZWc,490
@@ -79,15 +79,15 @@ sonolus/script/internal/generic.py,sha256=F0-cCiRNGTaUJvYlpmkiOsU3Xge_XjoBpBwBhH
79
79
  sonolus/script/internal/impl.py,sha256=I3IlPZZZtXVJb044cpULg5FDYUjiLtAucnsA6w_0xUk,3233
80
80
  sonolus/script/internal/introspection.py,sha256=MfdXo3GFHNZT2-vAKuvsE54V-5TOfbg4vU9dBI6sLqo,1032
81
81
  sonolus/script/internal/math_impls.py,sha256=nHSLgA7Tcx7jY1p07mYBCeSRmVx713bwdNayCIcaXSE,2652
82
- sonolus/script/internal/native.py,sha256=WrZ3sVYfrc9plFxJJSWCqwq3ymruw3WhET_UjmhKj38,1628
82
+ sonolus/script/internal/native.py,sha256=DQxmzxgLG_UsLpXhIEtBdO7eIeDFprU78UBDC4OZzw0,1597
83
83
  sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z50pI,1695
84
84
  sonolus/script/internal/range.py,sha256=YeqB1TPh7JdvW6kDuA5tpQL5psVxYQjepBZs7RNcP5Y,3426
85
85
  sonolus/script/internal/simulation_context.py,sha256=LGxLTvxbqBIhoe1R-SfwGajNIDwIJMVsHle0kvzd500,4818
86
86
  sonolus/script/internal/transient.py,sha256=y2AWABqF1aoaP6H4_2u4MMpNioC4OsZQCtPyNI0txqo,1634
87
87
  sonolus/script/internal/tuple_impl.py,sha256=DPNdmmRmupU8Ah4_XKq6-PdT336l4nt15_uCJKQGkkk,3587
88
88
  sonolus/script/internal/value.py,sha256=OngrCdmY_h6mV2Zgwqhuo4eYFad0kTk6263UAxctZcY,6963
89
- sonolus_py-0.4.0.dist-info/METADATA,sha256=5YcGXNkUzFc1F27UWp_AeCbWPjEuOLJviTkIoVgdTxw,302
90
- sonolus_py-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
- sonolus_py-0.4.0.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
92
- sonolus_py-0.4.0.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
93
- sonolus_py-0.4.0.dist-info/RECORD,,
89
+ sonolus_py-0.4.2.dist-info/METADATA,sha256=rV2d-Y0jxrhlOQfcrYRAVAKiuobluI5omhDztt5mmLo,302
90
+ sonolus_py-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ sonolus_py-0.4.2.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
92
+ sonolus_py-0.4.2.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
93
+ sonolus_py-0.4.2.dist-info/RECORD,,