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
@@ -0,0 +1,241 @@
1
+ # ruff: noqa: A005
2
+ from __future__ import annotations
3
+
4
+ from collections.abc import Iterable
5
+ from typing import Any, Self, final
6
+
7
+ from sonolus.backend.place import BlockPlace
8
+ from sonolus.script.debug import assert_unreachable
9
+ from sonolus.script.internal.context import ctx
10
+ from sonolus.script.internal.error import InternalError
11
+ from sonolus.script.internal.generic import GenericValue
12
+ from sonolus.script.internal.impl import meta_fn, validate_value
13
+ from sonolus.script.internal.value import Value
14
+ from sonolus.script.iterator import ArrayLike
15
+ from sonolus.script.num import Num
16
+
17
+
18
+ @final
19
+ class Array[T, Size](GenericValue, ArrayLike[T]):
20
+ _value: list[T] | BlockPlace
21
+
22
+ @classmethod
23
+ @meta_fn
24
+ def element_type(cls) -> type[T] | type[Value]:
25
+ return cls._get_type_arg_(T)
26
+
27
+ @classmethod
28
+ @meta_fn
29
+ def size(cls) -> int:
30
+ return cls._get_type_arg_(Size)
31
+
32
+ def __new__(cls, *args: T) -> Array[T, Any]:
33
+ if cls._type_args_ is None:
34
+ values = [validate_value(arg) for arg in args]
35
+ types = {type(value) for value in values}
36
+ if len(types) == 0:
37
+ raise ValueError(
38
+ f"{cls.__name__} constructor should be used with at least one value if type is not specified"
39
+ )
40
+ if len(types) > 1:
41
+ raise TypeError(f"{cls.__name__} constructor should be used with values of the same type, got {types}")
42
+ parameterized_cls = cls[types.pop(), len(args)]
43
+ else:
44
+ values = [cls.element_type()._accept_(arg) for arg in args]
45
+ if len(args) != cls.size():
46
+ raise ValueError(f"{cls.__name__} constructor should be used with {cls.size()} values, got {len(args)}")
47
+ parameterized_cls = cls
48
+ if ctx():
49
+ place = ctx().alloc(size=parameterized_cls.size())
50
+ result: parameterized_cls = parameterized_cls._from_place_(place)
51
+ result._copy_from_(parameterized_cls._with_value(values))
52
+ return result
53
+ else:
54
+ return parameterized_cls._with_value([value._copy_() for value in values])
55
+
56
+ def __init__(self, *_args: T):
57
+ super().__init__()
58
+
59
+ @classmethod
60
+ def _with_value(cls, value) -> Self:
61
+ result = object.__new__(cls)
62
+ result._value = value
63
+ return result
64
+
65
+ @classmethod
66
+ def _size_(cls) -> int:
67
+ return cls.size() * cls.element_type()._size_()
68
+
69
+ @classmethod
70
+ def _is_value_type_(cls) -> bool:
71
+ return False
72
+
73
+ @classmethod
74
+ def _from_place_(cls, place: BlockPlace) -> Self:
75
+ return cls._with_value(place)
76
+
77
+ @classmethod
78
+ def _accepts_(cls, value: Any) -> bool:
79
+ return isinstance(value, cls)
80
+
81
+ @classmethod
82
+ def _accept_(cls, value: Any) -> Self:
83
+ if not cls._accepts_(value):
84
+ raise TypeError(f"Cannot accept value {value} as {cls.__name__}")
85
+ return value
86
+
87
+ def _is_py_(self) -> bool:
88
+ return isinstance(self._value, list)
89
+
90
+ def _as_py_(self) -> Any:
91
+ if not self._is_py_():
92
+ raise ValueError("Not a python value")
93
+ return self
94
+
95
+ @classmethod
96
+ def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
97
+ iterator = iter(values)
98
+ return cls(*(cls.element_type()._from_list_(iterator) for _ in range(cls.size())))
99
+
100
+ def _to_list_(self) -> list[float | BlockPlace]:
101
+ match self._value:
102
+ case list():
103
+ return [entry for value in self._value for entry in value._to_list_()]
104
+ case BlockPlace():
105
+ return [
106
+ entry
107
+ for i in range(self.size())
108
+ for entry in self.element_type()
109
+ ._from_place_(self._value.add_offset(i * self.element_type()._size_()))
110
+ ._to_list_()
111
+ ]
112
+ case _:
113
+ assert_unreachable()
114
+
115
+ @classmethod
116
+ def _flat_keys_(cls, prefix: str) -> list[str]:
117
+ return [entry for i in range(cls.size()) for entry in cls.element_type()._flat_keys_(f"{prefix}[{i}]")]
118
+
119
+ def _get_(self) -> Self:
120
+ return self
121
+
122
+ def _set_(self, value: Self):
123
+ raise TypeError("Array does not support set_")
124
+
125
+ def _copy_from_(self, value: Self):
126
+ if not isinstance(value, type(self)):
127
+ raise TypeError("Cannot copy from different type")
128
+ for i in range(self.size()):
129
+ self[i] = value[i]
130
+
131
+ def _copy_(self) -> Self:
132
+ if ctx():
133
+ place = ctx().alloc(size=self.size())
134
+ result: Self = self._from_place_(place)
135
+ result._copy_from_(self)
136
+ return result
137
+ else:
138
+ return self._with_value([value._copy_() for value in self._value])
139
+
140
+ @classmethod
141
+ def _alloc_(cls) -> Self:
142
+ if ctx():
143
+ place = ctx().alloc(size=cls._size_())
144
+ return cls._from_place_(place)
145
+ else:
146
+ return cls._with_value([cls.element_type()._alloc_() for _ in range(cls.size())])
147
+
148
+ @meta_fn
149
+ def __getitem__(self, index: Num) -> T:
150
+ index: Num = Num._accept_(index)
151
+ if index._is_py_():
152
+ const_index = index._as_py_()
153
+ if isinstance(const_index, float) and not const_index.is_integer():
154
+ raise ValueError("Array index must be an integer")
155
+ const_index = int(const_index)
156
+ if not 0 <= const_index < self.size():
157
+ raise IndexError("Array index out of range")
158
+ if isinstance(self._value, list):
159
+ if ctx():
160
+ return self._value[const_index]._get_()
161
+ else:
162
+ return self._value[const_index]._get_()._as_py_()
163
+ else:
164
+ return (
165
+ self.element_type()
166
+ ._from_place_(self._value.add_offset(const_index * self.element_type()._size_()))
167
+ ._get_()
168
+ )
169
+ else:
170
+ if not ctx():
171
+ raise InternalError("Unexpected non-constant index")
172
+ base = ctx().rom[tuple(self._to_list_())] if isinstance(self._value, list) else self._value
173
+ place = BlockPlace(
174
+ block=base.block,
175
+ index=(Num(base.index) + index * self.element_type()._size_()).index(),
176
+ offset=base.offset,
177
+ )
178
+ return self.element_type()._from_place_(place)._get_()
179
+
180
+ @meta_fn
181
+ def __setitem__(self, index: Num, value: T):
182
+ index: Num = Num._accept_(index)
183
+ value = self.element_type()._accept_(value)
184
+ if ctx():
185
+ if isinstance(self._value, list):
186
+ raise ValueError("Cannot mutate a compile time constant array")
187
+ base = self._value
188
+ place = (
189
+ base.add_offset(int(index._as_py_()) * self.element_type()._size_())
190
+ if index._is_py_()
191
+ else BlockPlace(
192
+ block=base.block,
193
+ index=(Num(base.index) + index * self.element_type()._size_()).index(),
194
+ offset=base.offset,
195
+ )
196
+ )
197
+ dst = self.element_type()._from_place_(place)
198
+ if self.element_type()._is_value_type_():
199
+ dst._set_(value)
200
+ else:
201
+ dst._copy_from_(value)
202
+ else:
203
+ if not isinstance(self._value, list):
204
+ raise InternalError("Unexpected mutation of non compile time constant array")
205
+ const_index = index._as_py_()
206
+ if isinstance(const_index, float) and not const_index.is_integer():
207
+ raise ValueError("Array index must be an integer")
208
+ const_index = int(const_index)
209
+ if not 0 <= const_index < self.size():
210
+ raise IndexError("Array index out of range")
211
+ dst = self._value[const_index]
212
+ if self.element_type()._is_value_type_():
213
+ dst._set_(value)
214
+ else:
215
+ dst._copy_from_(value)
216
+
217
+ def __eq__(self, other):
218
+ if self.size() != other.size():
219
+ return False
220
+ i = 0
221
+ while i < self.size():
222
+ if self[i] != other[i]:
223
+ return False
224
+ i += 1
225
+ return True
226
+
227
+ def __ne__(self, other):
228
+ return not self == other
229
+
230
+ def __hash__(self):
231
+ return hash(tuple(self[i] for i in range(self.size())))
232
+
233
+ def __str__(self):
234
+ if isinstance(self._value, BlockPlace):
235
+ return f"{type(self).__name__}({self._value}...)"
236
+ return f"{type(self).__name__}({", ".join(str(self[i]) for i in range(self.size()))})"
237
+
238
+ def __repr__(self):
239
+ if isinstance(self._value, BlockPlace):
240
+ return f"{type(self).__name__}({self._value}...)"
241
+ return f"{type(self).__name__}({", ".join(repr(self[i]) for i in range(self.size()))})"
@@ -0,0 +1,192 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import IntEnum
5
+ from typing import Annotated, Any, Protocol, dataclass_transform, get_origin
6
+
7
+ from sonolus.backend.mode import Mode
8
+ from sonolus.backend.ops import Op
9
+ from sonolus.script.internal.context import ctx
10
+ from sonolus.script.internal.impl import meta_fn
11
+ from sonolus.script.internal.introspection import get_field_specifiers
12
+ from sonolus.script.internal.native import native_function
13
+ from sonolus.script.interval import Interval
14
+ from sonolus.script.pointer import deref
15
+ from sonolus.script.record import Record
16
+ from sonolus.script.sprite import Sprite
17
+
18
+
19
+ class JudgmentWindow(Record):
20
+ perfect: Interval
21
+ great: Interval
22
+ good: Interval
23
+
24
+ def update(
25
+ self,
26
+ perfect: Interval | None = None,
27
+ great: Interval | None = None,
28
+ good: Interval | None = None,
29
+ ):
30
+ if perfect is not None:
31
+ self.perfect = perfect
32
+ if great is not None:
33
+ self.great = great
34
+ if good is not None:
35
+ self.good = good
36
+
37
+ def judge(self, actual: float, target: float) -> Judgment:
38
+ return _judge(
39
+ actual,
40
+ target,
41
+ *self.perfect.tuple,
42
+ *self.great.tuple,
43
+ *self.good.tuple,
44
+ )
45
+
46
+ def __mul__(self, other: float | int) -> JudgmentWindow:
47
+ return JudgmentWindow(
48
+ self.perfect * other,
49
+ self.great * other,
50
+ self.good * other,
51
+ )
52
+
53
+
54
+ class Judgment(IntEnum):
55
+ MISS = 0
56
+ PERFECT = 1
57
+ GREAT = 2
58
+ GOOD = 3
59
+
60
+
61
+ @native_function(Op.Judge)
62
+ def _judge(
63
+ actual: float,
64
+ target: float,
65
+ perfect_min: float,
66
+ perfect_max: float,
67
+ great_min: float,
68
+ great_max: float,
69
+ good_min: float,
70
+ good_max: float,
71
+ ) -> Judgment:
72
+ diff = actual - target
73
+ if perfect_min <= diff <= perfect_max:
74
+ return Judgment.PERFECT
75
+ if great_min <= diff <= great_max:
76
+ return Judgment.GREAT
77
+ if good_min <= diff <= good_max:
78
+ return Judgment.GOOD
79
+ return Judgment.MISS
80
+
81
+
82
+ class Bucket(Record):
83
+ id: int
84
+
85
+ @property
86
+ @meta_fn
87
+ def window(self) -> JudgmentWindow:
88
+ if not ctx():
89
+ raise RuntimeError("Bucket window access outside of compilation")
90
+ match ctx().global_state.mode:
91
+ case Mode.Play:
92
+ return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
93
+ case Mode.Watch:
94
+ return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
95
+ case _:
96
+ raise RuntimeError("Invalid mode for bucket window access")
97
+
98
+ @window.setter
99
+ @meta_fn
100
+ def window(self, value: JudgmentWindow):
101
+ if not ctx():
102
+ raise RuntimeError("Bucket window access outside of compilation")
103
+ self.window.update(value.perfect, value.great, value.good)
104
+
105
+
106
+ @dataclass
107
+ class BucketSprite:
108
+ id: int
109
+ fallback_id: int | None
110
+ x: int
111
+ y: int
112
+ w: int
113
+ h: int
114
+ rotation: float
115
+
116
+ def to_dict(self):
117
+ results = {
118
+ "id": self.id,
119
+ "x": self.x,
120
+ "y": self.y,
121
+ "w": self.w,
122
+ "h": self.h,
123
+ "rotation": self.rotation,
124
+ }
125
+ if self.fallback_id is not None:
126
+ results["fallbackId"] = self.fallback_id
127
+ return results
128
+
129
+
130
+ @dataclass
131
+ class BucketInfo:
132
+ sprites: list[BucketSprite]
133
+ unit: str | None = None
134
+
135
+ def to_dict(self):
136
+ results = {
137
+ "sprites": [sprite.to_dict() for sprite in self.sprites],
138
+ }
139
+ if self.unit is not None:
140
+ results["unit"] = self.unit
141
+ return results
142
+
143
+
144
+ def bucket_sprite(
145
+ *,
146
+ sprite: Sprite,
147
+ fallback_sprite: Sprite | None = None,
148
+ x: int,
149
+ y: int,
150
+ w: int,
151
+ h: int,
152
+ rotation: float = 0,
153
+ ) -> BucketSprite:
154
+ return BucketSprite(sprite.id, fallback_sprite.id if fallback_sprite else None, x, y, w, h, rotation)
155
+
156
+
157
+ def bucket(*, sprites: list[BucketSprite], unit: str | None = None) -> Any:
158
+ return BucketInfo(sprites, unit)
159
+
160
+
161
+ class Buckets(Protocol):
162
+ _buckets_: list[BucketInfo]
163
+
164
+
165
+ @dataclass_transform()
166
+ def buckets[T](cls: type[T]) -> T | Buckets:
167
+ if len(cls.__bases__) != 1:
168
+ raise ValueError("Buckets class must not inherit from any class (except object)")
169
+ instance = cls()
170
+ bucket_info = []
171
+ for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
172
+ if get_origin(annotation) is not Annotated:
173
+ raise TypeError(f"Invalid annotation for buckets: {annotation}")
174
+ annotation_type = annotation.__args__[0]
175
+ annotation_values = annotation.__metadata__
176
+ if annotation_type is not Bucket:
177
+ raise TypeError(f"Invalid annotation for buckets: {annotation}, expected annotation of type Bucket")
178
+ if len(annotation_values) != 1 or not isinstance(annotation_values[0], BucketInfo):
179
+ raise TypeError(
180
+ f"Invalid annotation for buckets: {annotation}, expected a single BucketInfo annotation value"
181
+ )
182
+ info = annotation_values[0]
183
+ bucket_info.append(info)
184
+ setattr(instance, name, Bucket(i))
185
+ instance._buckets_ = bucket_info
186
+ instance._is_comptime_value_ = True
187
+ return instance
188
+
189
+
190
+ @buckets
191
+ class EmptyBuckets:
192
+ pass
@@ -0,0 +1,105 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class CallbackInfo:
6
+ name: str
7
+ py_name: str
8
+ supports_order: bool
9
+ returns_value: bool
10
+
11
+
12
+ preprocess_callback = CallbackInfo(
13
+ name="preprocess",
14
+ py_name="preprocess",
15
+ supports_order=True,
16
+ returns_value=False,
17
+ )
18
+ spawn_order_callback = CallbackInfo(
19
+ name="spawnOrder",
20
+ py_name="spawn_order",
21
+ supports_order=True,
22
+ returns_value=True,
23
+ )
24
+ should_spawn_callback = CallbackInfo(
25
+ name="shouldSpawn",
26
+ py_name="should_spawn",
27
+ supports_order=False,
28
+ returns_value=True,
29
+ )
30
+ initialize_callback = CallbackInfo(
31
+ name="initialize",
32
+ py_name="initialize",
33
+ supports_order=False,
34
+ returns_value=False,
35
+ )
36
+ update_sequential_callback = CallbackInfo(
37
+ name="updateSequential",
38
+ py_name="update_sequential",
39
+ supports_order=True,
40
+ returns_value=False,
41
+ )
42
+ touch_callback = CallbackInfo(
43
+ name="touch",
44
+ py_name="touch",
45
+ supports_order=True,
46
+ returns_value=False,
47
+ )
48
+ update_parallel_callback = CallbackInfo(
49
+ name="updateParallel",
50
+ py_name="update_parallel",
51
+ supports_order=False,
52
+ returns_value=False,
53
+ )
54
+ terminate_callback = CallbackInfo(
55
+ name="terminate",
56
+ py_name="terminate",
57
+ supports_order=False,
58
+ returns_value=False,
59
+ )
60
+ spawn_time_callback = CallbackInfo(
61
+ name="spawnTime",
62
+ py_name="spawn_time",
63
+ supports_order=False,
64
+ returns_value=True,
65
+ )
66
+ despawn_time_callback = CallbackInfo(
67
+ name="despawnTime",
68
+ py_name="despawn_time",
69
+ supports_order=False,
70
+ returns_value=True,
71
+ )
72
+ update_spawn_callback = CallbackInfo(
73
+ name="updateSpawn",
74
+ py_name="update_spawn",
75
+ supports_order=False,
76
+ returns_value=True,
77
+ )
78
+
79
+
80
+ def _by_name(*callbacks: CallbackInfo) -> dict[str, CallbackInfo]:
81
+ return {cb.py_name: cb for cb in callbacks}
82
+
83
+
84
+ PLAY_CALLBACKS = _by_name(
85
+ preprocess_callback,
86
+ spawn_order_callback,
87
+ should_spawn_callback,
88
+ initialize_callback,
89
+ update_sequential_callback,
90
+ touch_callback,
91
+ update_parallel_callback,
92
+ terminate_callback,
93
+ )
94
+ WATCH_ARCHETYPE_CALLBACKS = _by_name(
95
+ preprocess_callback,
96
+ spawn_time_callback,
97
+ despawn_time_callback,
98
+ initialize_callback,
99
+ update_sequential_callback,
100
+ update_parallel_callback,
101
+ terminate_callback,
102
+ )
103
+ WATCH_GLOBAL_CALLBACKS = _by_name(
104
+ update_spawn_callback,
105
+ )
@@ -0,0 +1,146 @@
1
+ from collections.abc import Iterable
2
+ from typing import TYPE_CHECKING, Any, Self, TypeVar, final
3
+
4
+ from sonolus.backend.place import BlockPlace
5
+ from sonolus.script.internal.generic import GenericValue
6
+
7
+
8
+ @final
9
+ class _Comptime[T, V](GenericValue):
10
+ _instance: Self | None = None
11
+
12
+ def __init__(self):
13
+ super().__init__()
14
+ raise TypeError("Comptime cannot be instantiated")
15
+
16
+ @classmethod
17
+ def value(cls):
18
+ _, value = cls._type_args_
19
+ if isinstance(value, Identity):
20
+ return value.value
21
+ return value
22
+
23
+ @classmethod
24
+ def _get_parameterized(cls, args: tuple[Any, ...]) -> type[Self]:
25
+ result = super()._get_parameterized(args)
26
+ result._instance = object.__new__(result)
27
+ return result
28
+
29
+ @classmethod
30
+ def _size_(cls) -> int:
31
+ return 0
32
+
33
+ @classmethod
34
+ def _is_value_type_(cls) -> bool:
35
+ return False
36
+
37
+ @classmethod
38
+ def _from_place_(cls, place: BlockPlace) -> Self:
39
+ return cls._instance
40
+
41
+ @classmethod
42
+ def _accepts_(cls, value: Any) -> bool:
43
+ from sonolus.script.internal.impl import validate_value
44
+
45
+ value = validate_value(value)
46
+ if not value._is_py_():
47
+ return False
48
+ if cls._type_args_ is None:
49
+ return True
50
+ return value._as_py_() == cls.value()
51
+
52
+ @classmethod
53
+ def _accept_(cls, value: Any) -> Self:
54
+ from sonolus.script.internal.impl import validate_value
55
+
56
+ if not cls._accepts_(value):
57
+ raise TypeError("Value does not match this Comptime instance")
58
+ # This might not actually return a Comptime instance, but it will be a compile-time constant
59
+ return validate_value(value)
60
+
61
+ def _is_py_(self) -> bool:
62
+ return True
63
+
64
+ def _as_py_(self) -> Any:
65
+ return self.value()
66
+
67
+ @classmethod
68
+ def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
69
+ return cls._instance
70
+
71
+ def _to_list_(self) -> list[float | BlockPlace]:
72
+ return []
73
+
74
+ @classmethod
75
+ def _flat_keys_(cls, prefix: str) -> list[str]:
76
+ return []
77
+
78
+ def _get_(self) -> Self:
79
+ from sonolus.script.internal.impl import validate_value
80
+
81
+ # Converts numbers out of comptime, although _accept_ may end up returning a non-comptime instance anyway
82
+ return validate_value(self.value())
83
+
84
+ def _set_(self, value: Self):
85
+ if value is not self:
86
+ raise TypeError("Comptime value cannot be changed")
87
+
88
+ def _copy_from_(self, value: Self):
89
+ if value is not self:
90
+ raise TypeError("Comptime value cannot be changed")
91
+
92
+ def _copy_(self) -> Self:
93
+ return self
94
+
95
+ @classmethod
96
+ def _alloc_(cls) -> Self:
97
+ return cls._instance
98
+
99
+ @classmethod
100
+ def _validate__type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
101
+ if len(args) == 2:
102
+ _, value = args
103
+ # We want the type to be there for documentation,
104
+ # but not enforced since they might not match up, e.g. a Callable is really FunctionType
105
+ if isinstance(value, TypeVar):
106
+ args = Any, value
107
+ else:
108
+ args = type(value), value
109
+ return super()._validate__type_args_(args)
110
+
111
+ @classmethod
112
+ def accept_unchecked(cls, value: Any) -> Self:
113
+ if isinstance(value, dict | tuple):
114
+ args = type(value), Identity(value)
115
+ else:
116
+ args = type(value), value
117
+ if args not in cls._parameterized_:
118
+ cls._parameterized_[args] = cls._get_parameterized(args)
119
+ return cls._parameterized_[args]._instance
120
+
121
+
122
+ class Identity[T]: # This is to allow accepting potentially unhashable values by using identity comparison
123
+ value: T
124
+
125
+ def __init__(self, value: T):
126
+ self.value = value
127
+
128
+ def __eq__(self, other):
129
+ return self is other
130
+
131
+ def __hash__(self):
132
+ return id(self)
133
+
134
+ def __str__(self):
135
+ return f"{type(self).__name__}({self.value})"
136
+
137
+ def __repr__(self):
138
+ return f"{type(self).__name__}({self.value!r})"
139
+
140
+
141
+ if TYPE_CHECKING:
142
+ type Comptime[T, V] = T | V
143
+ else:
144
+ _Comptime.__name__ = "Comptime"
145
+ _Comptime.__qualname__ = "Comptime"
146
+ globals()["Comptime"] = _Comptime