sonolus.py 0.1.0__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 (75) hide show
  1. sonolus/__init__.py +0 -0
  2. sonolus/backend/__init__.py +0 -0
  3. sonolus/backend/allocate.py +51 -0
  4. sonolus/backend/blocks.py +756 -0
  5. sonolus/backend/excepthook.py +37 -0
  6. sonolus/backend/finalize.py +69 -0
  7. sonolus/backend/flow.py +92 -0
  8. sonolus/backend/interpret.py +333 -0
  9. sonolus/backend/ir.py +89 -0
  10. sonolus/backend/mode.py +24 -0
  11. sonolus/backend/node.py +40 -0
  12. sonolus/backend/ops.py +197 -0
  13. sonolus/backend/optimize.py +9 -0
  14. sonolus/backend/passes.py +6 -0
  15. sonolus/backend/place.py +90 -0
  16. sonolus/backend/simplify.py +30 -0
  17. sonolus/backend/utils.py +48 -0
  18. sonolus/backend/visitor.py +880 -0
  19. sonolus/build/__init__.py +0 -0
  20. sonolus/build/cli.py +170 -0
  21. sonolus/build/collection.py +293 -0
  22. sonolus/build/compile.py +90 -0
  23. sonolus/build/defaults.py +32 -0
  24. sonolus/build/engine.py +149 -0
  25. sonolus/build/level.py +23 -0
  26. sonolus/build/node.py +43 -0
  27. sonolus/build/project.py +94 -0
  28. sonolus/py.typed +0 -0
  29. sonolus/script/__init__.py +0 -0
  30. sonolus/script/archetype.py +651 -0
  31. sonolus/script/array.py +241 -0
  32. sonolus/script/bucket.py +192 -0
  33. sonolus/script/callbacks.py +105 -0
  34. sonolus/script/comptime.py +146 -0
  35. sonolus/script/containers.py +247 -0
  36. sonolus/script/debug.py +70 -0
  37. sonolus/script/effect.py +132 -0
  38. sonolus/script/engine.py +101 -0
  39. sonolus/script/globals.py +234 -0
  40. sonolus/script/graphics.py +141 -0
  41. sonolus/script/icon.py +73 -0
  42. sonolus/script/internal/__init__.py +5 -0
  43. sonolus/script/internal/builtin_impls.py +144 -0
  44. sonolus/script/internal/context.py +365 -0
  45. sonolus/script/internal/descriptor.py +17 -0
  46. sonolus/script/internal/error.py +15 -0
  47. sonolus/script/internal/generic.py +197 -0
  48. sonolus/script/internal/impl.py +69 -0
  49. sonolus/script/internal/introspection.py +14 -0
  50. sonolus/script/internal/native.py +38 -0
  51. sonolus/script/internal/value.py +144 -0
  52. sonolus/script/interval.py +98 -0
  53. sonolus/script/iterator.py +211 -0
  54. sonolus/script/level.py +52 -0
  55. sonolus/script/math.py +92 -0
  56. sonolus/script/num.py +382 -0
  57. sonolus/script/options.py +194 -0
  58. sonolus/script/particle.py +158 -0
  59. sonolus/script/pointer.py +30 -0
  60. sonolus/script/project.py +17 -0
  61. sonolus/script/range.py +58 -0
  62. sonolus/script/record.py +293 -0
  63. sonolus/script/runtime.py +526 -0
  64. sonolus/script/sprite.py +332 -0
  65. sonolus/script/text.py +404 -0
  66. sonolus/script/timing.py +42 -0
  67. sonolus/script/transform.py +118 -0
  68. sonolus/script/ui.py +160 -0
  69. sonolus/script/values.py +43 -0
  70. sonolus/script/vec.py +48 -0
  71. sonolus_py-0.1.0.dist-info/METADATA +10 -0
  72. sonolus_py-0.1.0.dist-info/RECORD +75 -0
  73. sonolus_py-0.1.0.dist-info/WHEEL +4 -0
  74. sonolus_py-0.1.0.dist-info/entry_points.txt +2 -0
  75. sonolus_py-0.1.0.dist-info/licenses/LICENSE +21 -0
sonolus/script/num.py ADDED
@@ -0,0 +1,382 @@
1
+ # ruff: noqa: N801
2
+ import operator
3
+ from collections.abc import Callable, Iterable
4
+ from typing import TYPE_CHECKING, Any, Self, final
5
+
6
+ from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
7
+ from sonolus.backend.ops import Op
8
+ from sonolus.backend.place import BlockPlace, Place
9
+ from sonolus.script.internal.context import ctx
10
+ from sonolus.script.internal.error import InternalError
11
+ from sonolus.script.internal.impl import meta_fn
12
+ from sonolus.script.internal.value import Value
13
+
14
+
15
+ @final
16
+ class _Num(Value):
17
+ data: BlockPlace | float | int | bool
18
+
19
+ def __init__(self, data: Place | float | int | bool):
20
+ if isinstance(data, int):
21
+ data = float(data)
22
+ if isinstance(data, _Num):
23
+ raise InternalError("Cannot create a Num from a Num")
24
+ self.data = data
25
+
26
+ def __str__(self) -> str:
27
+ if isinstance(self.data, float) and self.data.is_integer():
28
+ return str(int(self.data))
29
+ return str(self.data)
30
+
31
+ def __repr__(self) -> str:
32
+ return f"Num({self.data})"
33
+
34
+ @classmethod
35
+ def _is_concrete_(cls) -> bool:
36
+ return True
37
+
38
+ @classmethod
39
+ def _size_(cls) -> int:
40
+ return 1
41
+
42
+ @classmethod
43
+ def _is_value_type_(cls) -> bool:
44
+ return True
45
+
46
+ @classmethod
47
+ def _from_place_(cls, place: BlockPlace) -> Self:
48
+ return cls(place)
49
+
50
+ @classmethod
51
+ def _accepts_(cls, value: Value) -> bool:
52
+ return isinstance(value, Num | float | int | bool)
53
+
54
+ @classmethod
55
+ def _accept_(cls, value: Any) -> Self:
56
+ if not cls._accepts_(value):
57
+ raise TypeError(f"Cannot accept {value}")
58
+ if isinstance(value, Num):
59
+ return value
60
+ return cls(value)
61
+
62
+ def _is_py_(self) -> bool:
63
+ return not isinstance(self.data, BlockPlace)
64
+
65
+ def _as_py_(self) -> Any:
66
+ if not self._is_py_():
67
+ raise ValueError("Not a compile time constant Num")
68
+ if self.data.is_integer():
69
+ return int(self.data)
70
+ return self.data
71
+
72
+ @classmethod
73
+ def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
74
+ value = next(iter(values))
75
+ return Num(value)
76
+
77
+ def _to_list_(self) -> list[float | BlockPlace]:
78
+ return [self.data]
79
+
80
+ @classmethod
81
+ def _flat_keys_(cls, prefix: str) -> list[str]:
82
+ return [prefix]
83
+
84
+ def _get_(self) -> Self:
85
+ if ctx():
86
+ place = ctx().alloc(size=1)
87
+ if isinstance(self.data, BlockPlace):
88
+ ctx().check_readable(self.data)
89
+ ctx().add_statements(IRSet(place, self.ir()))
90
+ return Num(place)
91
+ else:
92
+ return Num(self.data)
93
+
94
+ def _set_(self, value: Self):
95
+ if ctx():
96
+ if not isinstance(self.data, BlockPlace):
97
+ raise ValueError("Cannot set a compile time constant value")
98
+ ctx().check_writable(self.data)
99
+ ctx().add_statements(IRSet(self.data, value.ir()))
100
+ else:
101
+ self.data = value.data
102
+
103
+ def _copy_from_(self, value: Self):
104
+ raise ValueError("Cannot assign to a number")
105
+
106
+ def _copy_(self) -> Self:
107
+ return self
108
+
109
+ @classmethod
110
+ def _alloc_(cls) -> Self:
111
+ if ctx():
112
+ return Num(ctx().alloc(size=1))
113
+ else:
114
+ return Num(-1)
115
+
116
+ def ir(self):
117
+ if isinstance(self.data, BlockPlace):
118
+ return IRGet(self.data)
119
+ else:
120
+ return IRConst(self.data)
121
+
122
+ def index(self) -> int | BlockPlace:
123
+ return self.data
124
+
125
+ def _bin_op(self, other: Self, const_fn: Callable[[Self, Self], Self | None], ir_op: Op) -> Self:
126
+ if not Num._accepts_(other):
127
+ return NotImplemented
128
+ other = Num._accept_(other)
129
+ const_value = const_fn(self, other)
130
+ if const_value is not None:
131
+ return const_value
132
+ if ctx():
133
+ result_place = ctx().alloc(size=1)
134
+ ctx().add_statements(IRSet(result_place, IRPureInstr(ir_op, [self.ir(), other.ir()])))
135
+ return Num(result_place)
136
+ else:
137
+ raise InternalError("Unexpected call on non-comptime Num instance outside a compilation context")
138
+
139
+ def _unary_op(self, py_fn: Callable[[float], float], ir_op: Op) -> Self:
140
+ if self._is_py_():
141
+ return Num(py_fn(self._as_py_()))
142
+ elif ctx():
143
+ result_place = ctx().alloc(size=1)
144
+ ctx().add_statements(IRSet(result_place, IRPureInstr(ir_op, [self.ir()])))
145
+ return Num(result_place)
146
+ else:
147
+ raise InternalError("Unexpected call on non-comptime Num instance outside a compilation context")
148
+
149
+ @meta_fn
150
+ def __eq__(self, other) -> Self:
151
+ def const_fn(a: Self, b: Self) -> Num | None:
152
+ if a._is_py_() and b._is_py_():
153
+ return Num(a.data == b.data)
154
+ if a.data == b.data:
155
+ return Num(True)
156
+ return None
157
+
158
+ return self._bin_op(other, const_fn, Op.Equal)
159
+
160
+ def __hash__(self):
161
+ if self._is_py_():
162
+ return hash(self._as_py_())
163
+ raise ValueError("Cannot hash non compile time constant Num")
164
+
165
+ @meta_fn
166
+ def __ne__(self, other) -> Self:
167
+ def const_fn(a: Self, b: Self) -> Num | None:
168
+ if a._is_py_() and b._is_py_():
169
+ return Num(a.data != b.data)
170
+ if a.data == b.data:
171
+ return Num(False)
172
+ return None
173
+
174
+ return self._bin_op(other, const_fn, Op.NotEqual)
175
+
176
+ @meta_fn
177
+ def __lt__(self, other) -> Self:
178
+ def const_fn(a: Self, b: Self) -> Num | None:
179
+ if a._is_py_() and b._is_py_():
180
+ return Num(a.data < b.data)
181
+ if a.data == b.data:
182
+ return Num(False)
183
+ return None
184
+
185
+ return self._bin_op(other, const_fn, Op.Less)
186
+
187
+ @meta_fn
188
+ def __le__(self, other) -> Self:
189
+ def const_fn(a: Self, b: Self) -> Num | None:
190
+ if a._is_py_() and b._is_py_():
191
+ return Num(a.data <= b.data)
192
+ return None
193
+
194
+ return self._bin_op(other, const_fn, Op.LessOr)
195
+
196
+ @meta_fn
197
+ def __gt__(self, other) -> Self:
198
+ def const_fn(a: Self, b: Self) -> Num | None:
199
+ if a._is_py_() and b._is_py_():
200
+ return Num(a.data > b.data)
201
+ if a.data == b.data:
202
+ return Num(False)
203
+ return None
204
+
205
+ return self._bin_op(other, const_fn, Op.Greater)
206
+
207
+ @meta_fn
208
+ def __ge__(self, other) -> Self:
209
+ def const_fn(a: Self, b: Self) -> Num | None:
210
+ if a._is_py_() and b._is_py_():
211
+ return Num(a.data >= b.data)
212
+ return None
213
+
214
+ return self._bin_op(other, const_fn, Op.GreaterOr)
215
+
216
+ @meta_fn
217
+ def __add__(self, other) -> Self:
218
+ def const_fn(a: Self, b: Self) -> Num | None:
219
+ if a._is_py_() and b._is_py_():
220
+ return Num(a.data + b.data)
221
+ if a._is_py_() and a.data == 0:
222
+ return b
223
+ if b._is_py_() and b.data == 0:
224
+ return a
225
+ return None
226
+
227
+ return self._bin_op(other, const_fn, Op.Add)
228
+
229
+ @meta_fn
230
+ def __sub__(self, other) -> Self:
231
+ def const_fn(a: Self, b: Self) -> Num | None:
232
+ if a._is_py_() and b._is_py_():
233
+ return Num(a.data - b.data)
234
+ if a._is_py_() and a.data == 0:
235
+ return -b
236
+ if b._is_py_() and b.data == 0:
237
+ return a
238
+ return None
239
+
240
+ return self._bin_op(other, const_fn, Op.Subtract)
241
+
242
+ @meta_fn
243
+ def __mul__(self, other) -> Self:
244
+ def const_fn(a: Self, b: Self) -> Num | None:
245
+ if a._is_py_() and b._is_py_():
246
+ return Num(a.data * b.data)
247
+ if a._is_py_():
248
+ if a.data == 0:
249
+ return Num(0)
250
+ if a.data == 1:
251
+ return b
252
+ if b._is_py_():
253
+ if b.data == 0:
254
+ return Num(0)
255
+ if b.data == 1:
256
+ return a
257
+ return None
258
+
259
+ return self._bin_op(other, const_fn, Op.Multiply)
260
+
261
+ @meta_fn
262
+ def __truediv__(self, other) -> Self:
263
+ def const_fn(a: Self, b: Self) -> Num | None:
264
+ if a._is_py_() and b._is_py_():
265
+ if b.data == 0:
266
+ return None
267
+ return Num(a.data / b.data)
268
+ if b._is_py_():
269
+ if b.data == 1:
270
+ return a
271
+ if b.data == -1:
272
+ return -a
273
+ return None
274
+
275
+ return self._bin_op(other, const_fn, Op.Divide)
276
+
277
+ @meta_fn
278
+ def __floordiv__(self, other) -> Self:
279
+ def const_fn(a: Self, b: Self) -> Num | None:
280
+ if a._is_py_() and b._is_py_():
281
+ if b.data == 0:
282
+ return None
283
+ return Num(a.data // b.data)
284
+ if b._is_py_():
285
+ if b.data == 1:
286
+ return a
287
+ if b.data == -1:
288
+ return -a
289
+ return None
290
+
291
+ return self._bin_op(other, const_fn, Op.Divide)._unary_op(lambda x: x, Op.Floor)
292
+
293
+ @meta_fn
294
+ def __mod__(self, other) -> Self:
295
+ def const_fn(a: Self, b: Self) -> Num | None:
296
+ if a._is_py_() and b._is_py_():
297
+ if b.data == 0:
298
+ return None
299
+ return Num(a.data % b.data)
300
+ return None
301
+
302
+ return self._bin_op(other, const_fn, Op.Mod)
303
+
304
+ @meta_fn
305
+ def __pow__(self, other) -> Self:
306
+ def const_fn(a: Self, b: Self) -> Num | None:
307
+ if a._is_py_() and b._is_py_():
308
+ try:
309
+ return Num(a.data**b.data)
310
+ except OverflowError:
311
+ return None
312
+ if b._is_py_():
313
+ if b.data == 0:
314
+ return Num(1)
315
+ if b.data == 1:
316
+ return a
317
+ return None
318
+
319
+ return self._bin_op(other, const_fn, Op.Power)
320
+
321
+ @meta_fn
322
+ def __neg__(self) -> Self:
323
+ return self._unary_op(operator.neg, Op.Negate)
324
+
325
+ @meta_fn
326
+ def __pos__(self) -> Self:
327
+ return self
328
+
329
+ @meta_fn
330
+ def __abs__(self) -> Self:
331
+ return self._unary_op(abs, Op.Abs)
332
+
333
+ @meta_fn
334
+ def __bool__(self):
335
+ if ctx():
336
+ return self != 0
337
+ else:
338
+ if self._is_py_():
339
+ return bool(self._as_py_())
340
+ raise ValueError("Cannot convert non compile time constant Num to bool")
341
+
342
+ def and_(self, other) -> Self:
343
+ def const_fn(a: Self, b: Self) -> Num | None:
344
+ if a._is_py_() and b._is_py_():
345
+ return Num(a.data and b.data)
346
+ if a._is_py_():
347
+ if a.data == 0:
348
+ return a
349
+ else:
350
+ return b
351
+ return None
352
+
353
+ return self._bin_op(other, const_fn, Op.And)
354
+
355
+ def or_(self, other) -> Self:
356
+ def const_fn(a: Self, b: Self) -> Num | None:
357
+ if a._is_py_() and b._is_py_():
358
+ return Num(a.data or b.data)
359
+ if a._is_py_():
360
+ if a.data == 0:
361
+ return b
362
+ else:
363
+ return a
364
+ return None
365
+
366
+ return self._bin_op(other, const_fn, Op.Or)
367
+
368
+ def not_(self) -> Self:
369
+ return self._unary_op(operator.not_, Op.Not)
370
+
371
+
372
+ if TYPE_CHECKING:
373
+
374
+ class __Num(float, int, bool, _Num): # type: ignore
375
+ pass
376
+
377
+ Num = __Num | _Num | float | int | bool
378
+ else:
379
+ # Need to do this to satisfy type checkers (especially Pycharm)
380
+ _Num.__name__ = "Num"
381
+ _Num.__qualname__ = "Num"
382
+ globals()["Num"] = _Num
@@ -0,0 +1,194 @@
1
+ # ruff: noqa: A002
2
+ from dataclasses import dataclass
3
+ from typing import Annotated, Any, ClassVar, Protocol, 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
+ class Options(Protocol):
128
+ _options_: ClassVar[list[SliderOption | ToggleOption | SelectOption]]
129
+
130
+
131
+ type OptionInfo = SliderOption | ToggleOption | SelectOption
132
+
133
+
134
+ class OptionField(SonolusDescriptor):
135
+ info: OptionInfo
136
+ index: int
137
+
138
+ def __init__(self, info: OptionInfo, index: int):
139
+ self.info = info
140
+ self.index = index
141
+
142
+ def __get__(self, instance, owner):
143
+ if ctx():
144
+ match ctx().global_state.mode:
145
+ case Mode.Play:
146
+ block = ctx().blocks.LevelOption
147
+ case Mode.Watch:
148
+ block = ctx().blocks.LevelOption
149
+ case Mode.Preview:
150
+ block = ctx().blocks.PreviewOption
151
+ case Mode.Tutorial:
152
+ block = None
153
+ case _:
154
+ assert_unreachable()
155
+ if block is not None:
156
+ return Num._from_place_(BlockPlace(block, self.index))
157
+ else:
158
+ return Num._accept_(self.info.default)
159
+
160
+ def __set__(self, instance, value):
161
+ raise AttributeError("Options are read-only")
162
+
163
+
164
+ @dataclass_transform()
165
+ def options[T](cls: type[T]) -> T | Options:
166
+ if len(cls.__bases__) != 1:
167
+ raise ValueError("Options class must not inherit from any class (except object)")
168
+ instance = cls()
169
+ entries = []
170
+ for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
171
+ if get_origin(annotation) is not Annotated:
172
+ raise TypeError(f"Invalid annotation for options: {annotation}")
173
+ annotation_type = annotation.__args__[0]
174
+ annotation_values = annotation.__metadata__
175
+ if len(annotation_values) != 1:
176
+ raise ValueError("Invalid annotation values for options")
177
+ annotation_type = validate_concrete_type(annotation_type)
178
+ if annotation_type is not Num:
179
+ raise TypeError(f"Invalid annotation type for options: {annotation_type}")
180
+ annotation_value = annotation_values[0]
181
+ if not isinstance(annotation_value, SliderOption | ToggleOption | SelectOption):
182
+ raise TypeError(f"Invalid annotation value for options: {annotation_value}")
183
+ if annotation_value.name is None:
184
+ annotation_value.name = name
185
+ entries.append(annotation_value)
186
+ setattr(cls, name, OptionField(annotation_value, i))
187
+ instance._options_ = entries
188
+ instance._is_comptime_value_ = True
189
+ return instance
190
+
191
+
192
+ @options
193
+ class EmptyOptions:
194
+ pass