sonolus.py 0.4.0__py3-none-any.whl → 0.4.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.

sonolus/backend/utils.py CHANGED
@@ -23,29 +23,33 @@ 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
27
 
28
28
  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)
29
+ self.results.append(node)
30
+ self.generic_visit(node)
35
31
 
36
32
  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)
33
+ self.results.append(node)
34
+ self.generic_visit(node)
43
35
 
44
36
 
45
- def find_function(tree: ast.Module, line: int):
46
- visitor = FindFunction(line)
37
+ @cache
38
+ def get_functions(tree: ast.Module) -> list[ast.FunctionDef | ast.Lambda]:
39
+ visitor = FindFunction(0)
47
40
  visitor.visit(tree)
48
- return visitor.node
41
+ return visitor.results
42
+
43
+
44
+ def find_function(tree: ast.Module, line: int):
45
+ for node in get_functions(tree):
46
+ if node.lineno == line or (
47
+ isinstance(node, ast.FunctionDef)
48
+ and node.decorator_list
49
+ and (node.decorator_list[-1].end_lineno <= line <= node.lineno)
50
+ ):
51
+ return node
52
+ raise ValueError("Function not found")
49
53
 
50
54
 
51
55
  class ScanWrites(ast.NodeVisitor):
@@ -261,7 +261,21 @@ class Visitor(ast.NodeVisitor):
261
261
  result = self.visit(body)
262
262
  ctx().scope.set_value("$return", result)
263
263
  case ast.GeneratorExp(elt=elt, generators=generators):
264
- self.construct_genexpr(generators, elt)
264
+ first_generator = generators[0]
265
+ iterable = self.visit(first_generator.iter)
266
+ if isinstance(iterable, TupleImpl):
267
+ initial_iterator = iterable
268
+ else:
269
+ if not hasattr(iterable, "__iter__"):
270
+ raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
271
+ initial_iterator = self.handle_call(first_generator.iter, iterable.__iter__)
272
+ if not isinstance(initial_iterator, SonolusIterator):
273
+ raise ValueError("Unsupported iterator")
274
+ # The initial iterator is evaluated eagerly in Python
275
+ before_ctx = ctx().branch_with_scope(None, before_ctx.scope.copy())
276
+ start_ctx = before_ctx.branch_with_scope(None, Scope())
277
+ set_ctx(start_ctx)
278
+ self.construct_genexpr(generators, elt, initial_iterator)
265
279
  ctx().scope.set_value("$return", validate_value(None))
266
280
  case _:
267
281
  raise NotImplementedError("Unsupported syntax")
@@ -323,7 +337,9 @@ class Visitor(ast.NodeVisitor):
323
337
  set_ctx(after_ctx.branch_with_scope(None, before_ctx.scope.copy()))
324
338
  return result_binding.value
325
339
 
326
- def construct_genexpr(self, generators: Iterable[ast.comprehension], elt: ast.expr):
340
+ def construct_genexpr(
341
+ self, generators: Iterable[ast.comprehension], elt: ast.expr, initial_iterator: Value | None = None
342
+ ):
327
343
  if not generators:
328
344
  # Note that there may effectively be multiple yields in an expression since
329
345
  # tuples are unrolled.
@@ -335,14 +351,22 @@ class Visitor(ast.NodeVisitor):
335
351
  set_ctx(resume_ctx)
336
352
  return
337
353
  generator, *others = generators
338
- iterable = self.visit(generator.iter)
354
+ if initial_iterator is not None:
355
+ iterable = initial_iterator
356
+ else:
357
+ iterable = self.visit(generator.iter)
339
358
  if isinstance(iterable, TupleImpl):
340
359
  for value in iterable.value:
341
360
  set_ctx(ctx().branch(None))
342
361
  self.handle_assign(generator.target, validate_value(value))
343
362
  self.construct_genexpr(others, elt)
344
363
  else:
345
- iterator = self.handle_call(generator.iter, iterable.__iter__)
364
+ if initial_iterator is not None:
365
+ iterator = initial_iterator
366
+ else:
367
+ if not hasattr(iterable, "__iter__"):
368
+ raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
369
+ iterator = self.handle_call(generator.iter, iterable.__iter__)
346
370
  if not isinstance(iterator, SonolusIterator):
347
371
  raise ValueError("Unsupported iterator")
348
372
  header_ctx = ctx().branch(None)
@@ -493,6 +517,8 @@ class Visitor(ast.NodeVisitor):
493
517
  if break_ctxs:
494
518
  set_ctx(Context.meet([*break_ctxs, ctx()]))
495
519
  return
520
+ if not hasattr(iterable, "__iter__"):
521
+ raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
496
522
  iterator = self.handle_call(node, iterable.__iter__)
497
523
  if not isinstance(iterator, SonolusIterator):
498
524
  raise ValueError("Unsupported iterator")
@@ -980,6 +1006,8 @@ class Visitor(ast.NodeVisitor):
980
1006
  self.resume_ctxs.append(resume_ctx)
981
1007
  set_ctx(resume_ctx)
982
1008
  return validate_value(None)
1009
+ if not hasattr(value, "__iter__"):
1010
+ raise TypeError(f"Object of type '{type(value).__name__}' is not iterable")
983
1011
  iterator = self.handle_call(node, value.__iter__)
984
1012
  if not isinstance(iterator, SonolusIterator):
985
1013
  raise ValueError("Expected a SonolusIterator")
@@ -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
  )
@@ -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
@@ -114,3 +116,16 @@ class _FilteringIterator[T, Fn](Record, SonolusIterator):
114
116
  inside = value.get_unsafe()
115
117
  if self.fn(inside):
116
118
  return Some(inside)
119
+
120
+
121
+ @meta_fn
122
+ def maybe_next[T](iterator: Iterator[T]) -> Maybe[T]:
123
+ """Get the next item from an iterator as a `Maybe` if it exists or `Nothing` otherwise."""
124
+ from sonolus.backend.visitor import compile_and_call
125
+
126
+ if not isinstance(iterator, SonolusIterator):
127
+ raise TypeError("Iterator must be an instance of SonolusIterator.")
128
+ if ctx():
129
+ return compile_and_call(iterator.next)
130
+ else:
131
+ 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,10 +4,11 @@ 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):
@@ -38,13 +39,16 @@ class Maybe[T](TransientValue):
38
39
 
39
40
  def __init__(self, *, present: bool, value: T):
40
41
  self._present = Num._accept_(present)
41
- self._value = value
42
+ self._value = validate_value(value)
42
43
 
43
44
  @property
44
45
  @meta_fn
45
46
  def is_some(self) -> bool:
46
47
  """Check if the value is present."""
47
48
  if ctx():
49
+ if self._present._is_py_():
50
+ # Makes this a compile time constant.
51
+ return self._present
48
52
  return self._present._get_readonly_()
49
53
  else:
50
54
  return self._present._as_py_()
@@ -61,13 +65,104 @@ class Maybe[T](TransientValue):
61
65
 
62
66
  @meta_fn
63
67
  def get_unsafe(self) -> T:
64
- return self._value
68
+ if ctx():
69
+ return self._value
70
+ else:
71
+ return self._value._as_py_()
65
72
 
66
73
  def map[R](self, fn: Callable[[T], R], /) -> Maybe[R]:
74
+ """Map the contained value to a new value using the provided function.
75
+
76
+ If the value is not present, returns `Nothing`.
77
+
78
+ Args:
79
+ fn: A function that takes the contained value and returns a new value.
80
+
81
+ Returns:
82
+ A `Maybe` instance containing the result of the function if the value is present, otherwise `Nothing`.
83
+ """
67
84
  if self.is_some:
68
85
  return Some(fn(self.get_unsafe()))
69
86
  return Nothing
70
87
 
88
+ def flat_map[R](self, fn: Callable[[T], Maybe[R]], /) -> Maybe[R]:
89
+ """Flat map the contained value to a new `Maybe` using the provided function.
90
+
91
+ If the value is not present, returns `Nothing`.
92
+
93
+ Args:
94
+ fn: A function that takes the contained value and returns a new `Maybe`.
95
+
96
+ Returns:
97
+ A `Maybe` instance containing the result of the function if the value is present, otherwise `Nothing`.
98
+ """
99
+ if self.is_some:
100
+ return fn(self.get_unsafe())
101
+ return Nothing
102
+
103
+ def or_default(self, default: T) -> T:
104
+ """Return a copy of the contained value if present, otherwise return a copy of the given default value.
105
+
106
+ Args:
107
+ default: The default value to return if the contained value is not present.
108
+
109
+ Returns:
110
+ A copy of the contained value if present, otherwise a copy of the default value.
111
+ """
112
+ result = _box(copy(default))
113
+ if self.is_some:
114
+ result.value = self.get_unsafe()
115
+ return result.value
116
+
117
+ @meta_fn
118
+ def or_else(self, fn: Callable[[], T], /) -> T:
119
+ """Return a copy of the contained value if present, otherwise return a copy of the result of the given function.
120
+
121
+ Args:
122
+ fn: A function that returns a value to use if the contained value is not present.
123
+
124
+ Returns:
125
+ A copy of the contained value if present, otherwise a copy of the result of calling the function.
126
+ """
127
+ from sonolus.backend.visitor import compile_and_call
128
+
129
+ if ctx():
130
+ if self.is_some._is_py_(): # type: ignore
131
+ if self.is_some._as_py_(): # type: ignore
132
+ return copy(self.get_unsafe())
133
+ else:
134
+ return copy(compile_and_call(fn))
135
+ else:
136
+ return compile_and_call(self._or_else, fn)
137
+ elif self.is_some:
138
+ return copy(self.get_unsafe())
139
+ else:
140
+ return copy(fn())
141
+
142
+ def _or_else(self, fn: Callable[[], T], /) -> T:
143
+ result = _box(zeros(self.contained_type))
144
+ if self.is_some:
145
+ result.value = self.get_unsafe()
146
+ else:
147
+ result.value = fn()
148
+ return result.value
149
+
150
+ @property
151
+ def tuple(self) -> tuple[bool, T]:
152
+ """Return whether the value is present and a copy of the contained value if present as a tuple.
153
+
154
+ If the value is not present, the tuple will contain `False` and a zero initialized value of the contained type.
155
+ """
156
+ result_value = _box(zeros(self.contained_type))
157
+ if self.is_some:
158
+ result_value.value = self.get_unsafe()
159
+ return self.is_some, result_value.value
160
+
161
+ @property
162
+ @meta_fn
163
+ def contained_type(self):
164
+ return type(self._value)
165
+
71
166
  @classmethod
72
167
  def _accepts_(cls, value: Any) -> bool:
73
168
  return isinstance(value, cls)
@@ -137,3 +232,10 @@ Nothing: Maybe[Any] = Maybe(present=False, value=None) # type: ignore
137
232
  # Note: has to come after the definition to hide the definition in the docs.
138
233
  Nothing: Maybe[Any]
139
234
  """The empty `Maybe` instance."""
235
+
236
+
237
+ @meta_fn
238
+ def _box(value):
239
+ from sonolus.script.containers import Box
240
+
241
+ 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 = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.4.0
3
+ Version: 0.4.1
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=c8zje-7-lmRHW14YbVZPGbLbvN9pe3eFqTRus5i2NR8,2441
14
+ sonolus/backend/visitor.py,sha256=NIs3OPBhGoHjoApem2MSLxqKwhA8xvuVSxKJlG3HrMI,62160
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
@@ -38,7 +38,7 @@ sonolus/script/archetype.py,sha256=vNNX6YGi_CUQcjf0xG1Rkpz9kUd_84hEPuY1Ew9uFxA,4
38
38
  sonolus/script/array.py,sha256=lnobVrrEOE6PgPasq3DovrshlDKPbX8kKUsGmJz2oK0,12360
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=FDz3cJlxUdxtXe_vbmuonob1xnb_QzH66uVF--bfDu0,18669
42
42
  sonolus/script/debug.py,sha256=_Hg1cXQJ8fBXMiwhmoPb2X9CKcQ8QO26WNa59K518og,4305
43
43
  sonolus/script/easing.py,sha256=7zaDKIfM_whUpb4FBz1DAF4NNG2vk_nDjl8kL2Y90aU,11396
44
44
  sonolus/script/effect.py,sha256=pqfsOhmGVDMkifumkRr9rD2trWZU6Rw-_gTApNIaz7g,5854
@@ -46,18 +46,18 @@ sonolus/script/engine.py,sha256=etI9dJsQ7V9YZICVNZg54WqpLijPxG8eTPHiV-_EiG8,1068
46
46
  sonolus/script/globals.py,sha256=FOv8uiLM5U6X-yMLCXukMvLgV3rs-xVJbobST_MhLcU,9782
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=ESZ3opzDTXmkRMxF8itp9_bywXDiufn9cRtgWAw9m-w,3601
50
+ sonolus/script/level.py,sha256=vnotMbdr_4-MJUsTXMbvWiw2MlMjMHme3q0XRdNFXRg,6349
51
+ sonolus/script/maybe.py,sha256=UfF26g6BF_KGuHYGuEeuxeGKSmIH-fYAVHi9I-adp9E,7785
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
62
  sonolus/script/sprite.py,sha256=mMDTXckn58YR8mrx3fzdBaduQ8cn9YTtTVruXXK1ow0,16272
63
63
  sonolus/script/stream.py,sha256=4b0AV5MRPo4wzTHmaN95jwODxPVt6WuN3QxmGccdwRU,24517
@@ -86,8 +86,8 @@ sonolus/script/internal/simulation_context.py,sha256=LGxLTvxbqBIhoe1R-SfwGajNIDw
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.1.dist-info/METADATA,sha256=WBzfeJ7FRbh942MqkM1vjS7k98pHoZ52YsseEMn9OlY,302
90
+ sonolus_py-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ sonolus_py-0.4.1.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
92
+ sonolus_py-0.4.1.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
93
+ sonolus_py-0.4.1.dist-info/RECORD,,