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

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

Potentially problematic release.


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

Files changed (68) hide show
  1. sonolus/backend/allocate.py +125 -51
  2. sonolus/backend/blocks.py +756 -756
  3. sonolus/backend/coalesce.py +85 -0
  4. sonolus/backend/constant_evaluation.py +374 -0
  5. sonolus/backend/dead_code.py +80 -0
  6. sonolus/backend/dominance.py +111 -0
  7. sonolus/backend/excepthook.py +37 -37
  8. sonolus/backend/finalize.py +69 -69
  9. sonolus/backend/flow.py +121 -92
  10. sonolus/backend/inlining.py +150 -0
  11. sonolus/backend/ir.py +5 -3
  12. sonolus/backend/liveness.py +173 -0
  13. sonolus/backend/mode.py +24 -24
  14. sonolus/backend/node.py +40 -40
  15. sonolus/backend/ops.py +197 -197
  16. sonolus/backend/optimize.py +37 -9
  17. sonolus/backend/passes.py +52 -6
  18. sonolus/backend/simplify.py +47 -30
  19. sonolus/backend/ssa.py +187 -0
  20. sonolus/backend/utils.py +48 -48
  21. sonolus/backend/visitor.py +892 -882
  22. sonolus/build/cli.py +7 -1
  23. sonolus/build/compile.py +88 -90
  24. sonolus/build/level.py +24 -23
  25. sonolus/build/node.py +43 -43
  26. sonolus/script/archetype.py +23 -6
  27. sonolus/script/array.py +2 -2
  28. sonolus/script/bucket.py +191 -191
  29. sonolus/script/callbacks.py +127 -127
  30. sonolus/script/comptime.py +1 -1
  31. sonolus/script/containers.py +23 -0
  32. sonolus/script/debug.py +19 -3
  33. sonolus/script/easing.py +323 -0
  34. sonolus/script/effect.py +131 -131
  35. sonolus/script/globals.py +269 -269
  36. sonolus/script/graphics.py +200 -150
  37. sonolus/script/instruction.py +151 -151
  38. sonolus/script/internal/__init__.py +5 -5
  39. sonolus/script/internal/builtin_impls.py +144 -144
  40. sonolus/script/internal/context.py +12 -4
  41. sonolus/script/internal/descriptor.py +17 -17
  42. sonolus/script/internal/introspection.py +14 -14
  43. sonolus/script/internal/native.py +40 -38
  44. sonolus/script/internal/value.py +3 -3
  45. sonolus/script/interval.py +120 -112
  46. sonolus/script/iterator.py +214 -214
  47. sonolus/script/math.py +30 -1
  48. sonolus/script/num.py +1 -1
  49. sonolus/script/options.py +191 -191
  50. sonolus/script/particle.py +157 -157
  51. sonolus/script/pointer.py +30 -30
  52. sonolus/script/print.py +81 -81
  53. sonolus/script/random.py +14 -0
  54. sonolus/script/range.py +58 -58
  55. sonolus/script/record.py +3 -3
  56. sonolus/script/runtime.py +2 -0
  57. sonolus/script/sprite.py +333 -333
  58. sonolus/script/text.py +407 -407
  59. sonolus/script/timing.py +42 -42
  60. sonolus/script/transform.py +77 -23
  61. sonolus/script/ui.py +160 -160
  62. sonolus/script/vec.py +81 -78
  63. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/METADATA +1 -1
  64. sonolus_py-0.1.4.dist-info/RECORD +84 -0
  65. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/WHEEL +1 -1
  66. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/licenses/LICENSE +21 -21
  67. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  68. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.4.dist-info}/entry_points.txt +0 -0
@@ -1,144 +1,144 @@
1
- from collections.abc import Iterable
2
- from typing import overload
3
-
4
- from sonolus.script.internal.context import ctx
5
- from sonolus.script.internal.impl import meta_fn, validate_value
6
- from sonolus.script.iterator import ArrayLike, Enumerator, SonolusIterator
7
- from sonolus.script.math import MATH_BUILTIN_IMPLS
8
- from sonolus.script.num import is_num
9
- from sonolus.script.range import Range
10
-
11
-
12
- @meta_fn
13
- def _isinstance(value, type_):
14
- value = validate_value(value)
15
- type_ = validate_value(type_)._as_py_()
16
- return validate_value(isinstance(value, type_))
17
-
18
-
19
- @meta_fn
20
- def _len(value):
21
- from sonolus.backend.visitor import compile_and_call
22
-
23
- value = validate_value(value)
24
- if not hasattr(value, "__len__"):
25
- raise TypeError(f"object of type '{type(value).__name__}' has no len()")
26
- return compile_and_call(value.__len__)
27
-
28
-
29
- @meta_fn
30
- def _enumerate(iterable, start=0):
31
- from sonolus.backend.visitor import compile_and_call
32
-
33
- iterable = validate_value(iterable)
34
- if not hasattr(iterable, "__iter__"):
35
- raise TypeError(f"'{type(iterable).__name__}' object is not iterable")
36
- if isinstance(iterable, ArrayLike):
37
- return compile_and_call(iterable.enumerate, start)
38
- else:
39
- iterator = compile_and_call(iterable.__iter__)
40
- if not isinstance(iterator, SonolusIterator):
41
- raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
42
- return Enumerator(0, start, iterator)
43
-
44
-
45
- @meta_fn
46
- def _abs(value):
47
- from sonolus.backend.visitor import compile_and_call
48
-
49
- value = validate_value(value)
50
- if not hasattr(value, "__abs__"):
51
- raise TypeError(f"bad operand type for abs(): '{type(value).__name__}'")
52
- return compile_and_call(value.__abs__)
53
-
54
-
55
- @overload
56
- def _max[T](iterable: Iterable[T]) -> T: ...
57
-
58
-
59
- @overload
60
- def _max[T](a: T, b: T, *args: T) -> T: ...
61
-
62
-
63
- @meta_fn
64
- def _max(*args):
65
- from sonolus.backend.visitor import compile_and_call
66
-
67
- args = tuple(validate_value(arg) for arg in args)
68
- if len(args) == 0:
69
- raise ValueError("Expected at least one argument to max")
70
- elif len(args) == 1:
71
- (iterable,) = args
72
- if isinstance(iterable, ArrayLike):
73
- return iterable.max()
74
- else:
75
- raise TypeError(f"Unsupported type: {type(iterable)} for max")
76
- else:
77
- if not all(is_num(arg) for arg in args):
78
- raise TypeError("Arguments to max must be numbers")
79
- if ctx():
80
- result = compile_and_call(_max2, args[0], args[1])
81
- for arg in args[2:]:
82
- result = compile_and_call(_max2, result, arg)
83
- return result
84
- else:
85
- return max(arg._as_py_() for arg in args)
86
-
87
-
88
- def _max2(a, b):
89
- if a > b:
90
- return a
91
- else:
92
- return b
93
-
94
-
95
- @overload
96
- def _min[T](iterable: Iterable[T]) -> T: ...
97
-
98
-
99
- @overload
100
- def _min[T](a: T, b: T, *args: T) -> T: ...
101
-
102
-
103
- @meta_fn
104
- def _min(*args):
105
- from sonolus.backend.visitor import compile_and_call
106
-
107
- args = tuple(validate_value(arg) for arg in args)
108
- if len(args) == 0:
109
- raise ValueError("Expected at least one argument to min")
110
- elif len(args) == 1:
111
- (iterable,) = args
112
- if isinstance(iterable, ArrayLike):
113
- return iterable.min()
114
- else:
115
- raise TypeError(f"Unsupported type: {type(iterable)} for min")
116
- else:
117
- if not all(is_num(arg) for arg in args):
118
- raise TypeError("Arguments to min must be numbers")
119
- if ctx():
120
- result = compile_and_call(_min2, args[0], args[1])
121
- for arg in args[2:]:
122
- result = compile_and_call(_min2, result, arg)
123
- return result
124
- else:
125
- return min(arg._as_py_() for arg in args)
126
-
127
-
128
- def _min2(a, b):
129
- if a < b:
130
- return a
131
- else:
132
- return b
133
-
134
-
135
- BUILTIN_IMPLS = {
136
- id(isinstance): _isinstance,
137
- id(len): _len,
138
- id(enumerate): _enumerate,
139
- id(abs): _abs,
140
- id(max): _max,
141
- id(min): _min,
142
- id(range): Range,
143
- **MATH_BUILTIN_IMPLS,
144
- }
1
+ from collections.abc import Iterable
2
+ from typing import overload
3
+
4
+ from sonolus.script.internal.context import ctx
5
+ from sonolus.script.internal.impl import meta_fn, validate_value
6
+ from sonolus.script.iterator import ArrayLike, Enumerator, SonolusIterator
7
+ from sonolus.script.math import MATH_BUILTIN_IMPLS
8
+ from sonolus.script.num import is_num
9
+ from sonolus.script.range import Range
10
+
11
+
12
+ @meta_fn
13
+ def _isinstance(value, type_):
14
+ value = validate_value(value)
15
+ type_ = validate_value(type_)._as_py_()
16
+ return validate_value(isinstance(value, type_))
17
+
18
+
19
+ @meta_fn
20
+ def _len(value):
21
+ from sonolus.backend.visitor import compile_and_call
22
+
23
+ value = validate_value(value)
24
+ if not hasattr(value, "__len__"):
25
+ raise TypeError(f"object of type '{type(value).__name__}' has no len()")
26
+ return compile_and_call(value.__len__)
27
+
28
+
29
+ @meta_fn
30
+ def _enumerate(iterable, start=0):
31
+ from sonolus.backend.visitor import compile_and_call
32
+
33
+ iterable = validate_value(iterable)
34
+ if not hasattr(iterable, "__iter__"):
35
+ raise TypeError(f"'{type(iterable).__name__}' object is not iterable")
36
+ if isinstance(iterable, ArrayLike):
37
+ return compile_and_call(iterable.enumerate, start)
38
+ else:
39
+ iterator = compile_and_call(iterable.__iter__)
40
+ if not isinstance(iterator, SonolusIterator):
41
+ raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
42
+ return Enumerator(0, start, iterator)
43
+
44
+
45
+ @meta_fn
46
+ def _abs(value):
47
+ from sonolus.backend.visitor import compile_and_call
48
+
49
+ value = validate_value(value)
50
+ if not hasattr(value, "__abs__"):
51
+ raise TypeError(f"bad operand type for abs(): '{type(value).__name__}'")
52
+ return compile_and_call(value.__abs__)
53
+
54
+
55
+ @overload
56
+ def _max[T](iterable: Iterable[T]) -> T: ...
57
+
58
+
59
+ @overload
60
+ def _max[T](a: T, b: T, *args: T) -> T: ...
61
+
62
+
63
+ @meta_fn
64
+ def _max(*args):
65
+ from sonolus.backend.visitor import compile_and_call
66
+
67
+ args = tuple(validate_value(arg) for arg in args)
68
+ if len(args) == 0:
69
+ raise ValueError("Expected at least one argument to max")
70
+ elif len(args) == 1:
71
+ (iterable,) = args
72
+ if isinstance(iterable, ArrayLike):
73
+ return iterable.max()
74
+ else:
75
+ raise TypeError(f"Unsupported type: {type(iterable)} for max")
76
+ else:
77
+ if not all(is_num(arg) for arg in args):
78
+ raise TypeError("Arguments to max must be numbers")
79
+ if ctx():
80
+ result = compile_and_call(_max2, args[0], args[1])
81
+ for arg in args[2:]:
82
+ result = compile_and_call(_max2, result, arg)
83
+ return result
84
+ else:
85
+ return max(arg._as_py_() for arg in args)
86
+
87
+
88
+ def _max2(a, b):
89
+ if a > b:
90
+ return a
91
+ else:
92
+ return b
93
+
94
+
95
+ @overload
96
+ def _min[T](iterable: Iterable[T]) -> T: ...
97
+
98
+
99
+ @overload
100
+ def _min[T](a: T, b: T, *args: T) -> T: ...
101
+
102
+
103
+ @meta_fn
104
+ def _min(*args):
105
+ from sonolus.backend.visitor import compile_and_call
106
+
107
+ args = tuple(validate_value(arg) for arg in args)
108
+ if len(args) == 0:
109
+ raise ValueError("Expected at least one argument to min")
110
+ elif len(args) == 1:
111
+ (iterable,) = args
112
+ if isinstance(iterable, ArrayLike):
113
+ return iterable.min()
114
+ else:
115
+ raise TypeError(f"Unsupported type: {type(iterable)} for min")
116
+ else:
117
+ if not all(is_num(arg) for arg in args):
118
+ raise TypeError("Arguments to min must be numbers")
119
+ if ctx():
120
+ result = compile_and_call(_min2, args[0], args[1])
121
+ for arg in args[2:]:
122
+ result = compile_and_call(_min2, result, arg)
123
+ return result
124
+ else:
125
+ return min(arg._as_py_() for arg in args)
126
+
127
+
128
+ def _min2(a, b):
129
+ if a < b:
130
+ return a
131
+ else:
132
+ return b
133
+
134
+
135
+ BUILTIN_IMPLS = {
136
+ id(isinstance): _isinstance,
137
+ id(len): _len,
138
+ id(enumerate): _enumerate,
139
+ id(abs): _abs,
140
+ id(max): _max,
141
+ id(min): _min,
142
+ id(range): Range,
143
+ **MATH_BUILTIN_IMPLS,
144
+ }
@@ -164,7 +164,8 @@ class Context:
164
164
  header.scope.set_value(name, target_value)
165
165
  header.loop_variables[name] = target_value
166
166
  else:
167
- header.scope.set_binding(name, ConflictBinding())
167
+ header.scope.set_value(name, value)
168
+ header.loop_variables[name] = value
168
169
  return header
169
170
 
170
171
  def branch_to_loop_header(self, header: Self):
@@ -174,9 +175,16 @@ class Context:
174
175
  self.outgoing[None] = header
175
176
  for name, target_value in header.loop_variables.items():
176
177
  with using_ctx(self):
177
- value = self.scope.get_value(name)
178
- value = type(target_value)._accept_(value)
179
- target_value._set_(value)
178
+ if type(target_value)._is_value_type_():
179
+ value = self.scope.get_value(name)
180
+ value = type(target_value)._accept_(value)
181
+ target_value._set_(value)
182
+ else:
183
+ value = self.scope.get_value(name)
184
+ if target_value is not value:
185
+ raise RuntimeError(
186
+ f"Variable '{name}' may have conflicting definitions between loop iterations"
187
+ )
180
188
 
181
189
  def map_constant(self, value: Any) -> int:
182
190
  if value not in self.const_mappings:
@@ -1,17 +1,17 @@
1
- from abc import abstractmethod
2
-
3
-
4
- class SonolusDescriptor:
5
- """Base class for Sonolus descriptors.
6
-
7
- The compiler checks if a descriptor is an instance of a subclass of this class,
8
- so it knows that it's a supported descriptor.
9
- """
10
-
11
- @abstractmethod
12
- def __get__(self, instance, owner):
13
- pass
14
-
15
- @abstractmethod
16
- def __set__(self, instance, value):
17
- pass
1
+ from abc import abstractmethod
2
+
3
+
4
+ class SonolusDescriptor:
5
+ """Base class for Sonolus descriptors.
6
+
7
+ The compiler checks if a descriptor is an instance of a subclass of this class,
8
+ so it knows that it's a supported descriptor.
9
+ """
10
+
11
+ @abstractmethod
12
+ def __get__(self, instance, owner):
13
+ pass
14
+
15
+ @abstractmethod
16
+ def __set__(self, instance, value):
17
+ pass
@@ -1,14 +1,14 @@
1
- import inspect
2
- from typing import Annotated
3
-
4
- _missing = object()
5
-
6
-
7
- def get_field_specifiers(cls, *, globals=None, locals=None, eval_str=True): # noqa: A002
8
- """Like inspect.get_annotations, but also turns class attributes into Annotated."""
9
- results = inspect.get_annotations(cls, globals=globals, locals=locals, eval_str=eval_str)
10
- for key, value in results.items():
11
- class_value = getattr(cls, key, _missing)
12
- if class_value is not _missing:
13
- results[key] = Annotated[value, class_value]
14
- return results
1
+ import inspect
2
+ from typing import Annotated
3
+
4
+ _missing = object()
5
+
6
+
7
+ def get_field_specifiers(cls, *, globals=None, locals=None, eval_str=True): # noqa: A002
8
+ """Like inspect.get_annotations, but also turns class attributes into Annotated."""
9
+ results = inspect.get_annotations(cls, globals=globals, locals=locals, eval_str=eval_str)
10
+ for key, value in results.items():
11
+ class_value = getattr(cls, key, _missing)
12
+ if class_value is not _missing:
13
+ results[key] = Annotated[value, class_value]
14
+ return results
@@ -1,38 +1,40 @@
1
- import functools
2
- import inspect
3
- from collections.abc import Callable
4
-
5
- from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
6
- from sonolus.backend.ops import Op
7
- from sonolus.script.internal.context import ctx
8
- from sonolus.script.internal.impl import meta_fn, validate_value
9
- from sonolus.script.num import Num, is_num
10
-
11
-
12
- def native_call(op: Op, *args: Num) -> Num:
13
- if not ctx():
14
- raise RuntimeError("Unexpected native call")
15
- args = tuple(validate_value(arg) for arg in args)
16
- if not all(is_num(arg) for arg in args):
17
- raise RuntimeError("All arguments must be of type Num")
18
- result = ctx().alloc(size=1)
19
- ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
20
- return Num._from_place_(result)
21
-
22
-
23
- def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
24
- def decorator(fn: Callable[P, Num]) -> Callable[P, Num]:
25
- signature = inspect.signature(fn)
26
-
27
- @functools.wraps(fn)
28
- @meta_fn
29
- def wrapper(*args: Num) -> Num:
30
- if len(args) != len(signature.parameters):
31
- raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
32
- if ctx():
33
- return native_call(op, *args)
34
- return fn(*args)
35
-
36
- return wrapper
37
-
38
- return decorator
1
+ import functools
2
+ import inspect
3
+ from collections.abc import Callable
4
+
5
+ from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
6
+ from sonolus.backend.ops import Op
7
+ from sonolus.script.internal.context import ctx
8
+ from sonolus.script.internal.impl import meta_fn, validate_value
9
+ from sonolus.script.num import Num, is_num
10
+
11
+
12
+ def native_call(op: Op, *args: Num) -> Num:
13
+ if not ctx():
14
+ raise RuntimeError("Unexpected native call")
15
+ args = tuple(validate_value(arg) for arg in args)
16
+ if not all(is_num(arg) for arg in args):
17
+ raise RuntimeError("All arguments must be of type Num")
18
+ result = ctx().alloc(size=1)
19
+ ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
20
+ return Num._from_place_(result)
21
+
22
+
23
+ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
24
+ def decorator(fn: Callable[P, Num]) -> Callable[P, Num]:
25
+ signature = inspect.signature(fn)
26
+
27
+ @functools.wraps(fn)
28
+ @meta_fn
29
+ def wrapper(*args: Num) -> Num:
30
+ if len(args) < sum(1 for p in signature.parameters.values() if p.default == inspect.Parameter.empty):
31
+ raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
32
+ if ctx():
33
+ bound_args = signature.bind(*args)
34
+ bound_args.apply_defaults()
35
+ return native_call(op, *bound_args.args)
36
+ return fn(*args)
37
+
38
+ return wrapper
39
+
40
+ return decorator
@@ -66,7 +66,7 @@ class Value:
66
66
  raise NotImplementedError
67
67
 
68
68
  @abstractmethod
69
- def _to_list_(self) -> list[float | BlockPlace]:
69
+ def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
70
70
  """Converts this value to a list of floats."""
71
71
  raise NotImplementedError
72
72
 
@@ -76,9 +76,9 @@ class Value:
76
76
  """Returns the keys to a flat representation of this value."""
77
77
  raise NotImplementedError
78
78
 
79
- def _to_flat_dict_(self, prefix: str) -> dict[str, float | BlockPlace]:
79
+ def _to_flat_dict_(self, prefix: str, level_refs: dict[Any, int] | None = None) -> dict[str, float | BlockPlace]:
80
80
  """Converts this value to a flat dictionary."""
81
- return dict(zip(self._flat_keys_(prefix), self._to_list_(), strict=False))
81
+ return dict(zip(self._flat_keys_(prefix), self._to_list_(level_refs), strict=False))
82
82
 
83
83
  @abstractmethod
84
84
  def _get_(self) -> Self: