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
sonolus/script/math.py CHANGED
@@ -70,10 +70,38 @@ def trunc(x: float) -> float:
70
70
 
71
71
 
72
72
  @native_function(Op.Round)
73
- def _round(x: float) -> float:
73
+ def __round(x: float) -> float:
74
74
  return round(x)
75
75
 
76
76
 
77
+ def _round(x: float, n: int = 0) -> float:
78
+ if n == 0:
79
+ return __round(x)
80
+ return __round(x * 10**n) / 10**n
81
+
82
+
83
+ @native_function(Op.Frac)
84
+ def frac(x: float) -> float:
85
+ return x % 1
86
+
87
+
88
+ @native_function(Op.Log)
89
+ def _ln(x: float) -> float:
90
+ return math.log(x)
91
+
92
+
93
+ def log(x: float, base: float | None = None) -> float:
94
+ if base is None:
95
+ return _ln(x)
96
+ return _ln(x) / _ln(base)
97
+
98
+
99
+ @native_function(Op.Rem)
100
+ def _remainder(x: float, y: float) -> float:
101
+ # This is different from math.remainder in Python's math package, which could be confusing
102
+ return math.copysign(abs(x) % abs(y), x)
103
+
104
+
77
105
  MATH_BUILTIN_IMPLS = {
78
106
  id(math.sin): sin,
79
107
  id(math.cos): cos,
@@ -89,4 +117,5 @@ MATH_BUILTIN_IMPLS = {
89
117
  id(math.ceil): ceil,
90
118
  id(math.trunc): trunc,
91
119
  id(round): _round,
120
+ id(math.log): log,
92
121
  }
sonolus/script/num.py CHANGED
@@ -92,7 +92,7 @@ class _Num(Value, metaclass=NumMeta):
92
92
  value = next(iter(values))
93
93
  return Num(value)
94
94
 
95
- def _to_list_(self) -> list[float | BlockPlace]:
95
+ def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
96
96
  return [self.data]
97
97
 
98
98
  @classmethod
sonolus/script/options.py CHANGED
@@ -1,191 +1,191 @@
1
- # ruff: noqa: A002
2
- from dataclasses import dataclass
3
- from typing import Annotated, Any, NewType, dataclass_transform, get_origin
4
-
5
- from sonolus.backend.mode import Mode
6
- from sonolus.backend.place import BlockPlace
7
- from sonolus.script.debug import assert_unreachable
8
- from sonolus.script.internal.context import ctx
9
- from sonolus.script.internal.descriptor import SonolusDescriptor
10
- from sonolus.script.internal.generic import validate_concrete_type
11
- from sonolus.script.internal.introspection import get_field_specifiers
12
- from sonolus.script.num import Num
13
-
14
-
15
- @dataclass
16
- class SliderOption:
17
- name: str | None
18
- standard: bool
19
- advanced: bool
20
- scope: str | None
21
- default: float
22
- min: float
23
- max: float
24
- step: float
25
- unit: str | None
26
-
27
- def to_dict(self):
28
- result = {
29
- "type": "slider",
30
- "name": self.name,
31
- "standard": self.standard,
32
- "advanced": self.advanced,
33
- "def": self.default,
34
- "min": self.min,
35
- "max": self.max,
36
- "step": self.step,
37
- }
38
- if self.scope is not None:
39
- result["scope"] = self.scope
40
- if self.unit is not None:
41
- result["unit"] = self.unit
42
- return result
43
-
44
-
45
- @dataclass
46
- class ToggleOption:
47
- name: str | None
48
- standard: bool
49
- advanced: bool
50
- scope: str | None
51
- default: bool
52
-
53
- def to_dict(self):
54
- result = {
55
- "type": "toggle",
56
- "name": self.name,
57
- "standard": self.standard,
58
- "advanced": self.advanced,
59
- "def": int(self.default),
60
- }
61
- if self.scope is not None:
62
- result["scope"] = self.scope
63
- return result
64
-
65
-
66
- @dataclass
67
- class SelectOption:
68
- name: str | None
69
- standard: bool
70
- advanced: bool
71
- scope: str | None
72
- default: str
73
- values: list[str]
74
-
75
- def to_dict(self):
76
- result = {
77
- "type": "select",
78
- "name": self.name,
79
- "standard": self.standard,
80
- "advanced": self.advanced,
81
- "def": self.values.index(self.default),
82
- "values": self.values,
83
- }
84
- if self.scope is not None:
85
- result["scope"] = self.scope
86
- return result
87
-
88
-
89
- def slider_option(
90
- *,
91
- name: str | None = None,
92
- standard: bool = False,
93
- advanced: bool = False,
94
- default: float,
95
- min: float,
96
- max: float,
97
- step: float,
98
- unit: str | None = None,
99
- scope: str | None = None,
100
- ) -> Any:
101
- return SliderOption(name, standard, advanced, scope, default, min, max, step, unit)
102
-
103
-
104
- def toggle_option(
105
- *,
106
- name: str | None = None,
107
- standard: bool = False,
108
- advanced: bool = False,
109
- default: bool,
110
- scope: str | None = None,
111
- ) -> Any:
112
- return ToggleOption(name, standard, advanced, scope, default)
113
-
114
-
115
- def select_option(
116
- *,
117
- name: str | None = None,
118
- standard: bool = False,
119
- advanced: bool = False,
120
- default: str,
121
- values: list[str],
122
- scope: str | None = None,
123
- ) -> Any:
124
- return SelectOption(name, standard, advanced, scope, default, values)
125
-
126
-
127
- type Options = NewType("Options", Any)
128
- type OptionInfo = SliderOption | ToggleOption | SelectOption
129
-
130
-
131
- class OptionField(SonolusDescriptor):
132
- info: OptionInfo
133
- index: int
134
-
135
- def __init__(self, info: OptionInfo, index: int):
136
- self.info = info
137
- self.index = index
138
-
139
- def __get__(self, instance, owner):
140
- if ctx():
141
- match ctx().global_state.mode:
142
- case Mode.PLAY:
143
- block = ctx().blocks.LevelOption
144
- case Mode.WATCH:
145
- block = ctx().blocks.LevelOption
146
- case Mode.PREVIEW:
147
- block = ctx().blocks.PreviewOption
148
- case Mode.TUTORIAL:
149
- block = None
150
- case _:
151
- assert_unreachable()
152
- if block is not None:
153
- return Num._from_place_(BlockPlace(block, self.index))
154
- else:
155
- return Num._accept_(self.info.default)
156
-
157
- def __set__(self, instance, value):
158
- raise AttributeError("Options are read-only")
159
-
160
-
161
- @dataclass_transform()
162
- def options[T](cls: type[T]) -> T | Options:
163
- if len(cls.__bases__) != 1:
164
- raise ValueError("Options class must not inherit from any class (except object)")
165
- instance = cls()
166
- entries = []
167
- for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
168
- if get_origin(annotation) is not Annotated:
169
- raise TypeError(f"Invalid annotation for options: {annotation}")
170
- annotation_type = annotation.__args__[0]
171
- annotation_values = annotation.__metadata__
172
- if len(annotation_values) != 1:
173
- raise ValueError("Invalid annotation values for options")
174
- annotation_type = validate_concrete_type(annotation_type)
175
- if annotation_type is not Num:
176
- raise TypeError(f"Invalid annotation type for options: {annotation_type}")
177
- annotation_value = annotation_values[0]
178
- if not isinstance(annotation_value, SliderOption | ToggleOption | SelectOption):
179
- raise TypeError(f"Invalid annotation value for options: {annotation_value}")
180
- if annotation_value.name is None:
181
- annotation_value.name = name
182
- entries.append(annotation_value)
183
- setattr(cls, name, OptionField(annotation_value, i))
184
- instance._options_ = entries
185
- instance._is_comptime_value_ = True
186
- return instance
187
-
188
-
189
- @options
190
- class EmptyOptions:
191
- pass
1
+ # ruff: noqa: A002
2
+ from dataclasses import dataclass
3
+ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
4
+
5
+ from sonolus.backend.mode import Mode
6
+ from sonolus.backend.place import BlockPlace
7
+ from sonolus.script.debug import assert_unreachable
8
+ from sonolus.script.internal.context import ctx
9
+ from sonolus.script.internal.descriptor import SonolusDescriptor
10
+ from sonolus.script.internal.generic import validate_concrete_type
11
+ from sonolus.script.internal.introspection import get_field_specifiers
12
+ from sonolus.script.num import Num
13
+
14
+
15
+ @dataclass
16
+ class SliderOption:
17
+ name: str | None
18
+ standard: bool
19
+ advanced: bool
20
+ scope: str | None
21
+ default: float
22
+ min: float
23
+ max: float
24
+ step: float
25
+ unit: str | None
26
+
27
+ def to_dict(self):
28
+ result = {
29
+ "type": "slider",
30
+ "name": self.name,
31
+ "standard": self.standard,
32
+ "advanced": self.advanced,
33
+ "def": self.default,
34
+ "min": self.min,
35
+ "max": self.max,
36
+ "step": self.step,
37
+ }
38
+ if self.scope is not None:
39
+ result["scope"] = self.scope
40
+ if self.unit is not None:
41
+ result["unit"] = self.unit
42
+ return result
43
+
44
+
45
+ @dataclass
46
+ class ToggleOption:
47
+ name: str | None
48
+ standard: bool
49
+ advanced: bool
50
+ scope: str | None
51
+ default: bool
52
+
53
+ def to_dict(self):
54
+ result = {
55
+ "type": "toggle",
56
+ "name": self.name,
57
+ "standard": self.standard,
58
+ "advanced": self.advanced,
59
+ "def": int(self.default),
60
+ }
61
+ if self.scope is not None:
62
+ result["scope"] = self.scope
63
+ return result
64
+
65
+
66
+ @dataclass
67
+ class SelectOption:
68
+ name: str | None
69
+ standard: bool
70
+ advanced: bool
71
+ scope: str | None
72
+ default: str
73
+ values: list[str]
74
+
75
+ def to_dict(self):
76
+ result = {
77
+ "type": "select",
78
+ "name": self.name,
79
+ "standard": self.standard,
80
+ "advanced": self.advanced,
81
+ "def": self.values.index(self.default),
82
+ "values": self.values,
83
+ }
84
+ if self.scope is not None:
85
+ result["scope"] = self.scope
86
+ return result
87
+
88
+
89
+ def slider_option(
90
+ *,
91
+ name: str | None = None,
92
+ standard: bool = False,
93
+ advanced: bool = False,
94
+ default: float,
95
+ min: float,
96
+ max: float,
97
+ step: float,
98
+ unit: str | None = None,
99
+ scope: str | None = None,
100
+ ) -> Any:
101
+ return SliderOption(name, standard, advanced, scope, default, min, max, step, unit)
102
+
103
+
104
+ def toggle_option(
105
+ *,
106
+ name: str | None = None,
107
+ standard: bool = False,
108
+ advanced: bool = False,
109
+ default: bool,
110
+ scope: str | None = None,
111
+ ) -> Any:
112
+ return ToggleOption(name, standard, advanced, scope, default)
113
+
114
+
115
+ def select_option(
116
+ *,
117
+ name: str | None = None,
118
+ standard: bool = False,
119
+ advanced: bool = False,
120
+ default: str,
121
+ values: list[str],
122
+ scope: str | None = None,
123
+ ) -> Any:
124
+ return SelectOption(name, standard, advanced, scope, default, values)
125
+
126
+
127
+ type Options = NewType("Options", Any)
128
+ type OptionInfo = SliderOption | ToggleOption | SelectOption
129
+
130
+
131
+ class OptionField(SonolusDescriptor):
132
+ info: OptionInfo
133
+ index: int
134
+
135
+ def __init__(self, info: OptionInfo, index: int):
136
+ self.info = info
137
+ self.index = index
138
+
139
+ def __get__(self, instance, owner):
140
+ if ctx():
141
+ match ctx().global_state.mode:
142
+ case Mode.PLAY:
143
+ block = ctx().blocks.LevelOption
144
+ case Mode.WATCH:
145
+ block = ctx().blocks.LevelOption
146
+ case Mode.PREVIEW:
147
+ block = ctx().blocks.PreviewOption
148
+ case Mode.TUTORIAL:
149
+ block = None
150
+ case _:
151
+ assert_unreachable()
152
+ if block is not None:
153
+ return Num._from_place_(BlockPlace(block, self.index))
154
+ else:
155
+ return Num._accept_(self.info.default)
156
+
157
+ def __set__(self, instance, value):
158
+ raise AttributeError("Options are read-only")
159
+
160
+
161
+ @dataclass_transform()
162
+ def options[T](cls: type[T]) -> T | Options:
163
+ if len(cls.__bases__) != 1:
164
+ raise ValueError("Options class must not inherit from any class (except object)")
165
+ instance = cls()
166
+ entries = []
167
+ for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
168
+ if get_origin(annotation) is not Annotated:
169
+ raise TypeError(f"Invalid annotation for options: {annotation}")
170
+ annotation_type = annotation.__args__[0]
171
+ annotation_values = annotation.__metadata__
172
+ if len(annotation_values) != 1:
173
+ raise ValueError("Invalid annotation values for options")
174
+ annotation_type = validate_concrete_type(annotation_type)
175
+ if annotation_type is not Num:
176
+ raise TypeError(f"Invalid annotation type for options: {annotation_type}")
177
+ annotation_value = annotation_values[0]
178
+ if not isinstance(annotation_value, SliderOption | ToggleOption | SelectOption):
179
+ raise TypeError(f"Invalid annotation value for options: {annotation_value}")
180
+ if annotation_value.name is None:
181
+ annotation_value.name = name
182
+ entries.append(annotation_value)
183
+ setattr(cls, name, OptionField(annotation_value, i))
184
+ instance._options_ = entries
185
+ instance._is_comptime_value_ = True
186
+ return instance
187
+
188
+
189
+ @options
190
+ class EmptyOptions:
191
+ pass