sonolus.py 0.1.4__py3-none-any.whl → 0.1.6__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/finalize.py +18 -10
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +24 -0
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
- sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
- sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
- sonolus/backend/{flow.py → optimize/flow.py} +6 -5
- sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
- sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/{passes.py → optimize/passes.py} +1 -1
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +10 -0
- sonolus/backend/visitor.py +360 -101
- sonolus/build/cli.py +14 -3
- sonolus/build/compile.py +8 -8
- sonolus/build/engine.py +10 -5
- sonolus/build/project.py +30 -1
- sonolus/script/archetype.py +429 -138
- sonolus/script/array.py +25 -8
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +73 -11
- sonolus/script/containers.py +234 -51
- sonolus/script/debug.py +8 -8
- sonolus/script/easing.py +147 -105
- sonolus/script/effect.py +60 -0
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +66 -32
- sonolus/script/instruction.py +79 -25
- sonolus/script/internal/builtin_impls.py +138 -27
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +14 -5
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +5 -2
- sonolus/script/{math.py → internal/math_impls.py} +28 -28
- sonolus/script/internal/native.py +3 -3
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/interval.py +234 -16
- sonolus/script/iterator.py +120 -167
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +79 -47
- sonolus/script/options.py +78 -12
- sonolus/script/particle.py +37 -4
- sonolus/script/pointer.py +4 -4
- sonolus/script/print.py +22 -1
- sonolus/script/project.py +59 -0
- sonolus/script/{graphics.py → quad.py} +75 -12
- sonolus/script/record.py +44 -13
- sonolus/script/runtime.py +50 -1
- sonolus/script/sprite.py +198 -115
- sonolus/script/text.py +2 -0
- sonolus/script/timing.py +72 -0
- sonolus/script/transform.py +296 -66
- sonolus/script/ui.py +134 -78
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +118 -3
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/METADATA +1 -1
- sonolus_py-0.1.6.dist-info/RECORD +89 -0
- sonolus/backend/dead_code.py +0 -80
- sonolus/backend/optimize.py +0 -37
- sonolus/backend/simplify.py +0 -47
- sonolus/script/comptime.py +0 -160
- sonolus/script/random.py +0 -14
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.4.dist-info/RECORD +0 -84
- /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/WHEEL +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,18 +1,39 @@
|
|
|
1
|
+
from abc import ABC
|
|
1
2
|
from collections.abc import Iterable
|
|
2
3
|
from typing import overload
|
|
3
4
|
|
|
5
|
+
from sonolus.script.array_like import ArrayLike
|
|
4
6
|
from sonolus.script.internal.context import ctx
|
|
7
|
+
from sonolus.script.internal.dict_impl import DictImpl
|
|
5
8
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
6
|
-
from sonolus.script.
|
|
7
|
-
from sonolus.script.
|
|
8
|
-
from sonolus.script.
|
|
9
|
-
from sonolus.script.
|
|
9
|
+
from sonolus.script.internal.math_impls import MATH_BUILTIN_IMPLS, _trunc
|
|
10
|
+
from sonolus.script.internal.random import RANDOM_BUILTIN_IMPLS
|
|
11
|
+
from sonolus.script.internal.range import Range
|
|
12
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
13
|
+
from sonolus.script.internal.value import Value
|
|
14
|
+
from sonolus.script.iterator import (
|
|
15
|
+
SonolusIterator,
|
|
16
|
+
_EmptyIterator,
|
|
17
|
+
_Enumerator,
|
|
18
|
+
_FilteringIterator,
|
|
19
|
+
_MappingIterator,
|
|
20
|
+
_Zipper,
|
|
21
|
+
)
|
|
22
|
+
from sonolus.script.num import Num, _is_num
|
|
10
23
|
|
|
11
24
|
|
|
12
25
|
@meta_fn
|
|
13
26
|
def _isinstance(value, type_):
|
|
14
27
|
value = validate_value(value)
|
|
15
28
|
type_ = validate_value(type_)._as_py_()
|
|
29
|
+
if type_ is dict:
|
|
30
|
+
return isinstance(value, DictImpl)
|
|
31
|
+
if type_ is tuple:
|
|
32
|
+
return isinstance(value, TupleImpl)
|
|
33
|
+
if type_ in {_int, _float, _bool}:
|
|
34
|
+
raise TypeError("Instance check against int, float, or bool is not supported, use Num instead")
|
|
35
|
+
if not (isinstance(type_, type) and issubclass(type_, Value | ABC)):
|
|
36
|
+
raise TypeError(f"Unsupported type: {type_} for isinstance")
|
|
16
37
|
return validate_value(isinstance(value, type_))
|
|
17
38
|
|
|
18
39
|
|
|
@@ -34,12 +55,44 @@ def _enumerate(iterable, start=0):
|
|
|
34
55
|
if not hasattr(iterable, "__iter__"):
|
|
35
56
|
raise TypeError(f"'{type(iterable).__name__}' object is not iterable")
|
|
36
57
|
if isinstance(iterable, ArrayLike):
|
|
37
|
-
return compile_and_call(iterable.
|
|
58
|
+
return compile_and_call(iterable._enumerate_, start)
|
|
38
59
|
else:
|
|
39
60
|
iterator = compile_and_call(iterable.__iter__)
|
|
40
61
|
if not isinstance(iterator, SonolusIterator):
|
|
41
62
|
raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
|
|
42
|
-
return
|
|
63
|
+
return _Enumerator(0, start, iterator)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@meta_fn
|
|
67
|
+
def _reversed(iterable):
|
|
68
|
+
from sonolus.backend.visitor import compile_and_call
|
|
69
|
+
|
|
70
|
+
iterable = validate_value(iterable)
|
|
71
|
+
if not isinstance(iterable, ArrayLike):
|
|
72
|
+
raise TypeError(f"Unsupported type: {type(iterable)} for reversed")
|
|
73
|
+
return compile_and_call(iterable.__reversed__)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@meta_fn
|
|
77
|
+
def _zip(*iterables):
|
|
78
|
+
from sonolus.backend.visitor import compile_and_call
|
|
79
|
+
from sonolus.script.containers import Pair
|
|
80
|
+
|
|
81
|
+
if not iterables:
|
|
82
|
+
return _EmptyIterator()
|
|
83
|
+
|
|
84
|
+
iterables = [validate_value(iterable) for iterable in iterables]
|
|
85
|
+
if any(isinstance(iterable, TupleImpl) for iterable in iterables):
|
|
86
|
+
if not all(isinstance(iterable, TupleImpl) for iterable in iterables):
|
|
87
|
+
raise TypeError("Cannot mix tuples with other types in zip")
|
|
88
|
+
return TupleImpl._accept_(tuple(zip(*(iterable._as_py_() for iterable in iterables), strict=False)))
|
|
89
|
+
iterators = [compile_and_call(iterable.__iter__) for iterable in iterables]
|
|
90
|
+
if not all(isinstance(iterator, SonolusIterator) for iterator in iterators):
|
|
91
|
+
raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
|
|
92
|
+
v = iterators.pop()
|
|
93
|
+
while iterators:
|
|
94
|
+
v = Pair(iterators.pop(), v)
|
|
95
|
+
return _Zipper(v)
|
|
43
96
|
|
|
44
97
|
|
|
45
98
|
@meta_fn
|
|
@@ -52,16 +105,20 @@ def _abs(value):
|
|
|
52
105
|
return compile_and_call(value.__abs__)
|
|
53
106
|
|
|
54
107
|
|
|
108
|
+
def _identity(value):
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
|
|
55
112
|
@overload
|
|
56
|
-
def _max[T](iterable: Iterable[T]) -> T: ...
|
|
113
|
+
def _max[T](iterable: Iterable[T], *, key: callable = ...) -> T: ...
|
|
57
114
|
|
|
58
115
|
|
|
59
116
|
@overload
|
|
60
|
-
def _max[T](a: T, b: T, *args: T) -> T: ...
|
|
117
|
+
def _max[T](a: T, b: T, *args: T, key: callable = ...) -> T: ...
|
|
61
118
|
|
|
62
119
|
|
|
63
120
|
@meta_fn
|
|
64
|
-
def _max(*args):
|
|
121
|
+
def _max(*args, key: callable = _identity):
|
|
65
122
|
from sonolus.backend.visitor import compile_and_call
|
|
66
123
|
|
|
67
124
|
args = tuple(validate_value(arg) for arg in args)
|
|
@@ -70,38 +127,38 @@ def _max(*args):
|
|
|
70
127
|
elif len(args) == 1:
|
|
71
128
|
(iterable,) = args
|
|
72
129
|
if isinstance(iterable, ArrayLike):
|
|
73
|
-
return iterable.
|
|
130
|
+
return compile_and_call(iterable._max_, key=key)
|
|
74
131
|
else:
|
|
75
132
|
raise TypeError(f"Unsupported type: {type(iterable)} for max")
|
|
76
133
|
else:
|
|
77
|
-
if not all(
|
|
134
|
+
if not all(_is_num(arg) for arg in args):
|
|
78
135
|
raise TypeError("Arguments to max must be numbers")
|
|
79
136
|
if ctx():
|
|
80
|
-
result = compile_and_call(_max2, args[0], args[1])
|
|
137
|
+
result = compile_and_call(_max2, args[0], args[1], key=key)
|
|
81
138
|
for arg in args[2:]:
|
|
82
|
-
result = compile_and_call(_max2, result, arg)
|
|
139
|
+
result = compile_and_call(_max2, result, arg, key=key)
|
|
83
140
|
return result
|
|
84
141
|
else:
|
|
85
142
|
return max(arg._as_py_() for arg in args)
|
|
86
143
|
|
|
87
144
|
|
|
88
|
-
def _max2(a, b):
|
|
89
|
-
if a > b:
|
|
145
|
+
def _max2(a, b, key=_identity):
|
|
146
|
+
if key(a) > key(b):
|
|
90
147
|
return a
|
|
91
148
|
else:
|
|
92
149
|
return b
|
|
93
150
|
|
|
94
151
|
|
|
95
152
|
@overload
|
|
96
|
-
def _min[T](iterable: Iterable[T]) -> T: ...
|
|
153
|
+
def _min[T](iterable: Iterable[T], *, key: callable = ...) -> T: ...
|
|
97
154
|
|
|
98
155
|
|
|
99
156
|
@overload
|
|
100
|
-
def _min[T](a: T, b: T, *args: T) -> T: ...
|
|
157
|
+
def _min[T](a: T, b: T, *args: T, key: callable = ...) -> T: ...
|
|
101
158
|
|
|
102
159
|
|
|
103
160
|
@meta_fn
|
|
104
|
-
def _min(*args):
|
|
161
|
+
def _min(*args, key: callable = _identity):
|
|
105
162
|
from sonolus.backend.visitor import compile_and_call
|
|
106
163
|
|
|
107
164
|
args = tuple(validate_value(arg) for arg in args)
|
|
@@ -110,35 +167,89 @@ def _min(*args):
|
|
|
110
167
|
elif len(args) == 1:
|
|
111
168
|
(iterable,) = args
|
|
112
169
|
if isinstance(iterable, ArrayLike):
|
|
113
|
-
return iterable.
|
|
170
|
+
return compile_and_call(iterable._min_, key=key)
|
|
114
171
|
else:
|
|
115
172
|
raise TypeError(f"Unsupported type: {type(iterable)} for min")
|
|
116
173
|
else:
|
|
117
|
-
if not all(
|
|
174
|
+
if not all(_is_num(arg) for arg in args):
|
|
118
175
|
raise TypeError("Arguments to min must be numbers")
|
|
119
176
|
if ctx():
|
|
120
|
-
result = compile_and_call(_min2, args[0], args[1])
|
|
177
|
+
result = compile_and_call(_min2, args[0], args[1], key=key)
|
|
121
178
|
for arg in args[2:]:
|
|
122
|
-
result = compile_and_call(_min2, result, arg)
|
|
179
|
+
result = compile_and_call(_min2, result, arg, key=key)
|
|
123
180
|
return result
|
|
124
181
|
else:
|
|
125
182
|
return min(arg._as_py_() for arg in args)
|
|
126
183
|
|
|
127
184
|
|
|
128
|
-
def _min2(a, b):
|
|
129
|
-
if a < b:
|
|
185
|
+
def _min2(a, b, key=_identity):
|
|
186
|
+
if key(a) < key(b):
|
|
130
187
|
return a
|
|
131
188
|
else:
|
|
132
189
|
return b
|
|
133
190
|
|
|
134
191
|
|
|
192
|
+
@meta_fn
|
|
193
|
+
def _callable(value):
|
|
194
|
+
return callable(value)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _map(fn, iterable, *iterables):
|
|
198
|
+
return _MappingIterator(lambda args: fn(*args), zip(iterable, *iterables)) # noqa: B905
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _filter(fn, iterable):
|
|
202
|
+
if fn is None:
|
|
203
|
+
fn = _identity
|
|
204
|
+
return _FilteringIterator(fn, iterable.__iter__()) # noqa: PLC2801
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@meta_fn
|
|
208
|
+
def _int(value=0):
|
|
209
|
+
value = validate_value(value)
|
|
210
|
+
if not _is_num(value):
|
|
211
|
+
raise TypeError("Only numeric arguments to int() are supported")
|
|
212
|
+
return _trunc(value)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@meta_fn
|
|
216
|
+
def _float(value=0.0):
|
|
217
|
+
value = validate_value(value)
|
|
218
|
+
if not _is_num(value):
|
|
219
|
+
raise TypeError("Only numeric arguments to float() are supported")
|
|
220
|
+
return value
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@meta_fn
|
|
224
|
+
def _bool(value=False):
|
|
225
|
+
value = validate_value(value)
|
|
226
|
+
if not _is_num(value):
|
|
227
|
+
raise TypeError("Only numeric arguments to bool() are supported")
|
|
228
|
+
return value != 0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
_int._type_mapping_ = Num
|
|
232
|
+
_float._type_mapping_ = Num
|
|
233
|
+
_bool._type_mapping_ = Num
|
|
234
|
+
|
|
235
|
+
# classmethod, property, staticmethod are supported as decorators, but not within functions
|
|
236
|
+
|
|
135
237
|
BUILTIN_IMPLS = {
|
|
238
|
+
id(abs): _abs,
|
|
239
|
+
id(bool): _bool,
|
|
240
|
+
id(callable): _callable,
|
|
241
|
+
id(enumerate): _enumerate,
|
|
242
|
+
id(filter): _filter,
|
|
243
|
+
id(float): _float,
|
|
244
|
+
id(int): _int,
|
|
136
245
|
id(isinstance): _isinstance,
|
|
137
246
|
id(len): _len,
|
|
138
|
-
id(
|
|
139
|
-
id(abs): _abs,
|
|
247
|
+
id(map): _map,
|
|
140
248
|
id(max): _max,
|
|
141
249
|
id(min): _min,
|
|
142
250
|
id(range): Range,
|
|
143
|
-
|
|
251
|
+
id(reversed): _reversed,
|
|
252
|
+
id(zip): _zip,
|
|
253
|
+
**MATH_BUILTIN_IMPLS, # Includes round
|
|
254
|
+
**RANDOM_BUILTIN_IMPLS,
|
|
144
255
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from typing import Any, ClassVar, Self
|
|
3
|
+
|
|
4
|
+
from sonolus.backend.place import BlockPlace
|
|
5
|
+
from sonolus.script.internal.impl import meta_fn
|
|
6
|
+
from sonolus.script.internal.value import Value
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _Missing:
|
|
10
|
+
def __repr__(self) -> str:
|
|
11
|
+
return "MISSING"
|
|
12
|
+
|
|
13
|
+
def __bool__(self) -> bool:
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_MISSING = _Missing()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConstantValue(Value):
|
|
21
|
+
_parameterized_: ClassVar[dict[Any, type[Self]]] = {}
|
|
22
|
+
_value: ClassVar[Any] = _MISSING
|
|
23
|
+
instance: ClassVar[Self | _Missing] = _MISSING
|
|
24
|
+
|
|
25
|
+
def __new__(cls) -> Self:
|
|
26
|
+
if cls.value() is _MISSING:
|
|
27
|
+
raise TypeError(f"Class {cls.__name__} is not parameterized")
|
|
28
|
+
return cls.instance
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def value(cls):
|
|
32
|
+
# We need this to avoid descriptors getting in the way
|
|
33
|
+
return cls._value[0] if cls._value is not _MISSING else _MISSING
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def of(cls, value: Any) -> Self:
|
|
37
|
+
if value in cls._parameterized_:
|
|
38
|
+
return cls._parameterized_[value]()
|
|
39
|
+
|
|
40
|
+
parameterized = cls._get_parameterized(value)
|
|
41
|
+
cls._parameterized_[value] = parameterized
|
|
42
|
+
return parameterized()
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _get_parameterized(cls, parameter: Any) -> type[Self]:
|
|
46
|
+
class Parameterized(cls):
|
|
47
|
+
_value = (parameter,)
|
|
48
|
+
|
|
49
|
+
Parameterized.__name__ = f"{parameter}"
|
|
50
|
+
Parameterized.__qualname__ = f"{parameter}"
|
|
51
|
+
Parameterized.__module__ = cls.__module__
|
|
52
|
+
Parameterized.instance = object.__new__(Parameterized)
|
|
53
|
+
return Parameterized
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def _is_concrete_(cls) -> bool:
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def _size_(cls) -> int:
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _is_value_type_(cls) -> bool:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def _from_place_(cls, place: BlockPlace) -> Self:
|
|
69
|
+
if cls.value() is _MISSING:
|
|
70
|
+
raise TypeError(f"Class {cls.__name__} is not parameterized")
|
|
71
|
+
return cls()
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def _accepts_(cls, value: Any) -> bool:
|
|
75
|
+
from sonolus.script.internal.impl import validate_value
|
|
76
|
+
|
|
77
|
+
# We rely on validate_value to create the correct instance
|
|
78
|
+
return isinstance(validate_value(value), cls)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def _accept_(cls, value: Any) -> Self:
|
|
82
|
+
from sonolus.script.internal.impl import validate_value
|
|
83
|
+
|
|
84
|
+
# We rely on validate_value to create the correct instance
|
|
85
|
+
value = validate_value(value)
|
|
86
|
+
if not isinstance(value, cls):
|
|
87
|
+
raise ValueError(f"Value {value} is not of type {cls}")
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
def _is_py_(self) -> bool:
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def _as_py_(self) -> Any:
|
|
94
|
+
return self.value()
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
|
|
98
|
+
return cls()
|
|
99
|
+
|
|
100
|
+
def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def _flat_keys_(cls, prefix: str) -> list[str]:
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
def _get_(self) -> Self:
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def _set_(self, value: Any) -> Self:
|
|
111
|
+
if value is not self:
|
|
112
|
+
raise ValueError(f"{type(self).__name__} is immutable")
|
|
113
|
+
|
|
114
|
+
def _copy_from_(self, value: Self):
|
|
115
|
+
if value is not self:
|
|
116
|
+
raise ValueError(f"{type(self).__name__} is immutable")
|
|
117
|
+
|
|
118
|
+
def _copy_(self) -> Self:
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def _alloc_(cls) -> Self:
|
|
123
|
+
return cls()
|
|
124
|
+
|
|
125
|
+
@meta_fn
|
|
126
|
+
def __eq__(self, other):
|
|
127
|
+
return self is other
|
|
128
|
+
|
|
129
|
+
@meta_fn
|
|
130
|
+
def __ne__(self, other):
|
|
131
|
+
return self is not other
|
|
132
|
+
|
|
133
|
+
@meta_fn
|
|
134
|
+
def __hash__(self):
|
|
135
|
+
return hash(self.value())
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class MiscConstantValue(ConstantValue):
|
|
139
|
+
"""For constants without any special behavior."""
|
|
@@ -6,11 +6,11 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import Any, Self
|
|
7
7
|
|
|
8
8
|
from sonolus.backend.blocks import BlockData, PlayBlock
|
|
9
|
-
from sonolus.backend.flow import BasicBlock, FlowEdge
|
|
10
9
|
from sonolus.backend.ir import IRConst, IRStmt
|
|
11
10
|
from sonolus.backend.mode import Mode
|
|
11
|
+
from sonolus.backend.optimize.flow import BasicBlock, FlowEdge
|
|
12
12
|
from sonolus.backend.place import Block, BlockPlace, TempBlock
|
|
13
|
-
from sonolus.script.globals import
|
|
13
|
+
from sonolus.script.globals import _GlobalInfo, _GlobalPlaceholder
|
|
14
14
|
from sonolus.script.internal.value import Value
|
|
15
15
|
|
|
16
16
|
_compiler_internal_ = True
|
|
@@ -22,7 +22,7 @@ class GlobalContextState:
|
|
|
22
22
|
archetypes: dict[type, int]
|
|
23
23
|
rom: ReadOnlyMemory
|
|
24
24
|
const_mappings: dict[Any, int]
|
|
25
|
-
environment_mappings: dict[
|
|
25
|
+
environment_mappings: dict[_GlobalInfo, int]
|
|
26
26
|
environment_offsets: dict[Block, int]
|
|
27
27
|
mode: Mode
|
|
28
28
|
|
|
@@ -60,6 +60,7 @@ class Context:
|
|
|
60
60
|
callback_state: CallbackContextState,
|
|
61
61
|
scope: Scope | None = None,
|
|
62
62
|
live: bool = True,
|
|
63
|
+
foldable_constants: dict[TempBlock, list[float | None]] | None = None,
|
|
63
64
|
):
|
|
64
65
|
self.global_state = global_state
|
|
65
66
|
self.callback_state = callback_state
|
|
@@ -69,6 +70,7 @@ class Context:
|
|
|
69
70
|
self.scope = scope if scope is not None else Scope()
|
|
70
71
|
self.loop_variables = {}
|
|
71
72
|
self.live = live
|
|
73
|
+
self.foldable_constants = foldable_constants if foldable_constants is not None else {}
|
|
72
74
|
|
|
73
75
|
@property
|
|
74
76
|
def rom(self) -> ReadOnlyMemory:
|
|
@@ -102,8 +104,14 @@ class Context:
|
|
|
102
104
|
if isinstance(place.block, BlockData) and self.callback not in self.blocks(place.block).writable:
|
|
103
105
|
raise RuntimeError(f"Block {place.block} is not writable in {self.callback}")
|
|
104
106
|
|
|
107
|
+
def add_statement(self, statement: IRStmt):
|
|
108
|
+
if not self.live:
|
|
109
|
+
return
|
|
110
|
+
self.statements.append(statement)
|
|
111
|
+
|
|
105
112
|
def add_statements(self, *statements: IRStmt):
|
|
106
|
-
|
|
113
|
+
for statement in statements:
|
|
114
|
+
self.add_statement(statement)
|
|
107
115
|
|
|
108
116
|
def alloc(self, name: str | None = None, size: int = 1) -> BlockPlace:
|
|
109
117
|
if size == 0:
|
|
@@ -124,6 +132,7 @@ class Context:
|
|
|
124
132
|
callback_state=self.callback_state,
|
|
125
133
|
scope=scope,
|
|
126
134
|
live=self.live,
|
|
135
|
+
foldable_constants={**self.foldable_constants}, # We want this to persist even if the scope changes
|
|
127
136
|
)
|
|
128
137
|
|
|
129
138
|
def branch(self, condition: float | None):
|
|
@@ -191,7 +200,7 @@ class Context:
|
|
|
191
200
|
self.const_mappings[value] = len(self.const_mappings)
|
|
192
201
|
return self.const_mappings[value]
|
|
193
202
|
|
|
194
|
-
def get_global_base(self, value:
|
|
203
|
+
def get_global_base(self, value: _GlobalInfo | _GlobalPlaceholder) -> BlockPlace:
|
|
195
204
|
block = value.blocks.get(self.global_state.mode)
|
|
196
205
|
if block is None:
|
|
197
206
|
raise RuntimeError(f"Global {value.name} is not available in '{self.global_state.mode.name}' mode")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import Any, Self
|
|
2
|
+
|
|
3
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
4
|
+
from sonolus.script.internal.transient import TransientValue
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DictImpl(TransientValue):
|
|
8
|
+
def __init__(self, value: dict):
|
|
9
|
+
self.value = value
|
|
10
|
+
|
|
11
|
+
@meta_fn
|
|
12
|
+
def __getitem__(self, item):
|
|
13
|
+
item = validate_value(item)
|
|
14
|
+
if not item._is_py_():
|
|
15
|
+
raise TypeError("Key must be a compile-time constant")
|
|
16
|
+
item = item._as_py_()
|
|
17
|
+
if item not in self.value:
|
|
18
|
+
raise KeyError(item)
|
|
19
|
+
return self.value[item]
|
|
20
|
+
|
|
21
|
+
@meta_fn
|
|
22
|
+
def __contains__(self, item):
|
|
23
|
+
item = validate_value(item)
|
|
24
|
+
if not item._is_py_():
|
|
25
|
+
raise TypeError("Key must be a compile-time constant")
|
|
26
|
+
item = item._as_py_()
|
|
27
|
+
return item in self.value
|
|
28
|
+
|
|
29
|
+
@meta_fn
|
|
30
|
+
def __len__(self):
|
|
31
|
+
return len(self.value)
|
|
32
|
+
|
|
33
|
+
def __eq__(self, other):
|
|
34
|
+
raise TypeError("Comparing dicts is not supported")
|
|
35
|
+
|
|
36
|
+
__hash__ = None
|
|
37
|
+
|
|
38
|
+
@meta_fn
|
|
39
|
+
def __or__(self, other):
|
|
40
|
+
if not isinstance(other, DictImpl):
|
|
41
|
+
raise TypeError("Only dicts can be merged")
|
|
42
|
+
return DictImpl({**self.value, **other.value})
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _accepts_(cls, value: Any) -> bool:
|
|
46
|
+
return isinstance(value, cls | dict)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _accept_(cls, value: Any) -> Self:
|
|
50
|
+
if not cls._accepts_(value):
|
|
51
|
+
raise TypeError(f"Cannot accept {value} as {cls.__name__}")
|
|
52
|
+
if isinstance(value, cls):
|
|
53
|
+
return value
|
|
54
|
+
else:
|
|
55
|
+
return cls({validate_value(k)._as_py_(): validate_value(v) for k, v in value.items()})
|
|
56
|
+
|
|
57
|
+
def _is_py_(self) -> bool:
|
|
58
|
+
return all(v._is_py_() for v in self.value.values())
|
|
59
|
+
|
|
60
|
+
def _as_py_(self) -> Any:
|
|
61
|
+
return {k: v._as_py_() for k, v in self.value.items()}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
DictImpl.__name__ = "dict"
|
|
65
|
+
DictImpl.__qualname__ = "dict"
|
|
@@ -3,19 +3,19 @@ from __future__ import annotations
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Annotated, Any, ClassVar, Literal, Self, TypeVar, get_origin
|
|
5
5
|
|
|
6
|
-
from sonolus.script.internal.impl import meta_fn
|
|
6
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
7
7
|
from sonolus.script.internal.value import Value
|
|
8
8
|
|
|
9
9
|
type AnyType = type[Value] | PartialGeneric | TypeVar
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def validate_type_arg(arg: Any) -> Any:
|
|
13
|
-
from sonolus.script.internal.impl import validate_value
|
|
14
|
-
|
|
15
13
|
arg = validate_value(arg)
|
|
16
14
|
if not arg._is_py_():
|
|
17
15
|
raise TypeError(f"Expected a compile-time constant type argument, got {arg}")
|
|
18
16
|
result = arg._as_py_()
|
|
17
|
+
if hasattr(result, "_type_mapping_"):
|
|
18
|
+
return result._type_mapping_
|
|
19
19
|
if get_origin(result) is Annotated:
|
|
20
20
|
return result.__args__[0]
|
|
21
21
|
if get_origin(result) is Literal:
|
|
@@ -69,7 +69,7 @@ class GenericValue(Value):
|
|
|
69
69
|
raise TypeError(f"Missing type arguments for {self.__class__.__name__}")
|
|
70
70
|
|
|
71
71
|
@classmethod
|
|
72
|
-
def
|
|
72
|
+
def _validate_type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
73
73
|
"""Validate the type arguments and return them as a tuple.
|
|
74
74
|
|
|
75
75
|
This may be called with PartialGeneric or TypeVar instances inside args.
|
|
@@ -84,7 +84,7 @@ class GenericValue(Value):
|
|
|
84
84
|
|
|
85
85
|
@classmethod
|
|
86
86
|
@meta_fn
|
|
87
|
-
def
|
|
87
|
+
def type_var_value(cls, var: TypeVar) -> Any:
|
|
88
88
|
if isinstance(var, Value):
|
|
89
89
|
var = var._as_py_()
|
|
90
90
|
if cls._type_args_ is None:
|
|
@@ -93,12 +93,11 @@ class GenericValue(Value):
|
|
|
93
93
|
return cls._type_vars_to_args_[var]
|
|
94
94
|
raise TypeError(f"Missing type argument for {var}")
|
|
95
95
|
|
|
96
|
-
@classmethod
|
|
97
96
|
def __class_getitem__(cls, args: Any) -> type[Self]:
|
|
98
97
|
if cls._type_args_ is not None:
|
|
99
98
|
raise TypeError(f"Type {cls.__name__} is already parameterized")
|
|
100
99
|
args = validate_type_args(args)
|
|
101
|
-
args = cls.
|
|
100
|
+
args = cls._validate_type_args_(args)
|
|
102
101
|
if contains_incomplete_type(args):
|
|
103
102
|
return PartialGeneric(cls, args)
|
|
104
103
|
if args not in cls._parameterized_:
|
|
@@ -168,8 +167,6 @@ def infer_and_validate_types(dst: Any, src: Any, results: dict[TypeVar, Any] | N
|
|
|
168
167
|
|
|
169
168
|
|
|
170
169
|
def accept_and_infer_types(dst: Any, val: Any, results: dict[TypeVar, Any]) -> Value:
|
|
171
|
-
from sonolus.script.internal.impl import validate_value
|
|
172
|
-
|
|
173
170
|
val = validate_value(val)
|
|
174
171
|
match dst:
|
|
175
172
|
case TypeVar():
|
sonolus/script/internal/impl.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from types import FunctionType, MethodType, NoneType, NotImplementedType
|
|
5
|
+
from types import FunctionType, MethodType, ModuleType, NoneType, NotImplementedType, UnionType
|
|
6
6
|
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, get_origin, overload
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
@@ -18,6 +18,11 @@ def meta_fn[T: Callable]() -> Callable[[T], T]: ...
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def meta_fn(fn=None):
|
|
21
|
+
"""Marks a function as a meta function to be called directly without the AST visitor.
|
|
22
|
+
|
|
23
|
+
This can also improve performance in some cases by avoiding the overhead of the AST visitor.
|
|
24
|
+
"""
|
|
25
|
+
|
|
21
26
|
# noinspection PyShadowingNames
|
|
22
27
|
def decorator(fn):
|
|
23
28
|
fn._meta_fn_ = True
|
|
@@ -36,12 +41,23 @@ def validate_value(value: Any) -> Value:
|
|
|
36
41
|
|
|
37
42
|
|
|
38
43
|
def try_validate_value(value: Any) -> Value | None:
|
|
39
|
-
from sonolus.script.
|
|
40
|
-
from sonolus.script.
|
|
44
|
+
from sonolus.script.globals import _GlobalPlaceholder
|
|
45
|
+
from sonolus.script.internal.constant import MiscConstantValue
|
|
46
|
+
from sonolus.script.internal.dict_impl import DictImpl
|
|
41
47
|
from sonolus.script.internal.generic import PartialGeneric
|
|
48
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
42
49
|
from sonolus.script.internal.value import Value
|
|
43
50
|
from sonolus.script.num import Num
|
|
44
51
|
|
|
52
|
+
try:
|
|
53
|
+
# Unfortunately this is called during import, so this may fail
|
|
54
|
+
from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS
|
|
55
|
+
|
|
56
|
+
if id(value) in BUILTIN_IMPLS:
|
|
57
|
+
return validate_value(BUILTIN_IMPLS[id(value)])
|
|
58
|
+
except ImportError:
|
|
59
|
+
pass
|
|
60
|
+
|
|
45
61
|
match value:
|
|
46
62
|
case Enum():
|
|
47
63
|
return validate_value(value.value)
|
|
@@ -49,21 +65,30 @@ def try_validate_value(value: Any) -> Value | None:
|
|
|
49
65
|
return value
|
|
50
66
|
case type():
|
|
51
67
|
if value in {int, float, bool}:
|
|
52
|
-
return
|
|
53
|
-
return
|
|
68
|
+
return MiscConstantValue.of(Num)
|
|
69
|
+
return MiscConstantValue.of(value)
|
|
54
70
|
case int() | float() | bool():
|
|
55
71
|
return Num._accept_(value)
|
|
56
72
|
case tuple():
|
|
57
|
-
return
|
|
73
|
+
return TupleImpl._accept_(value)
|
|
58
74
|
case dict():
|
|
59
|
-
return
|
|
60
|
-
case
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
return DictImpl._accept_(value)
|
|
76
|
+
case (
|
|
77
|
+
PartialGeneric()
|
|
78
|
+
| TypeVar()
|
|
79
|
+
| FunctionType()
|
|
80
|
+
| MethodType()
|
|
81
|
+
| NotImplementedType()
|
|
82
|
+
| str()
|
|
83
|
+
| NoneType()
|
|
84
|
+
| ModuleType()
|
|
85
|
+
):
|
|
86
|
+
return MiscConstantValue.of(value)
|
|
87
|
+
case other_type if get_origin(value) in {Literal, Annotated, UnionType, tuple}:
|
|
88
|
+
return MiscConstantValue.of(other_type)
|
|
89
|
+
case _GlobalPlaceholder():
|
|
65
90
|
return value.get()
|
|
66
91
|
case comptime_value if getattr(comptime_value, "_is_comptime_value_", False):
|
|
67
|
-
return
|
|
92
|
+
return MiscConstantValue.of(comptime_value)
|
|
68
93
|
case _:
|
|
69
94
|
return None
|