sonolus.py 0.3.4__py3-none-any.whl → 0.4.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.
- sonolus/backend/excepthook.py +30 -0
- sonolus/backend/finalize.py +15 -1
- sonolus/backend/ops.py +4 -0
- sonolus/backend/optimize/allocate.py +5 -5
- sonolus/backend/optimize/constant_evaluation.py +124 -19
- sonolus/backend/optimize/copy_coalesce.py +15 -12
- sonolus/backend/optimize/dead_code.py +7 -6
- sonolus/backend/optimize/dominance.py +2 -2
- sonolus/backend/optimize/flow.py +54 -8
- sonolus/backend/optimize/inlining.py +137 -30
- sonolus/backend/optimize/liveness.py +2 -2
- sonolus/backend/optimize/optimize.py +15 -1
- sonolus/backend/optimize/passes.py +11 -3
- sonolus/backend/optimize/simplify.py +137 -8
- sonolus/backend/optimize/ssa.py +47 -13
- sonolus/backend/place.py +5 -4
- sonolus/backend/utils.py +24 -0
- sonolus/backend/visitor.py +260 -17
- sonolus/build/cli.py +47 -19
- sonolus/build/compile.py +12 -5
- sonolus/build/engine.py +70 -1
- sonolus/build/level.py +3 -3
- sonolus/build/project.py +2 -2
- sonolus/script/archetype.py +12 -9
- sonolus/script/array.py +23 -18
- sonolus/script/array_like.py +26 -29
- sonolus/script/bucket.py +1 -1
- sonolus/script/containers.py +22 -26
- sonolus/script/debug.py +20 -43
- sonolus/script/effect.py +1 -1
- sonolus/script/globals.py +3 -3
- sonolus/script/instruction.py +2 -2
- sonolus/script/internal/builtin_impls.py +155 -28
- sonolus/script/internal/constant.py +13 -3
- sonolus/script/internal/context.py +46 -15
- sonolus/script/internal/impl.py +9 -3
- sonolus/script/internal/introspection.py +8 -1
- sonolus/script/internal/native.py +2 -2
- sonolus/script/internal/range.py +8 -11
- sonolus/script/internal/simulation_context.py +1 -1
- sonolus/script/internal/transient.py +2 -2
- sonolus/script/internal/value.py +41 -3
- sonolus/script/interval.py +13 -13
- sonolus/script/iterator.py +38 -107
- sonolus/script/maybe.py +139 -0
- sonolus/script/num.py +17 -2
- sonolus/script/options.py +1 -1
- sonolus/script/particle.py +1 -1
- sonolus/script/project.py +24 -5
- sonolus/script/quad.py +15 -15
- sonolus/script/record.py +16 -12
- sonolus/script/runtime.py +22 -18
- sonolus/script/sprite.py +1 -1
- sonolus/script/stream.py +66 -82
- sonolus/script/transform.py +35 -34
- sonolus/script/values.py +10 -10
- sonolus/script/vec.py +21 -18
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.0.dist-info}/METADATA +1 -1
- sonolus_py-0.4.0.dist-info/RECORD +93 -0
- sonolus_py-0.3.4.dist-info/RECORD +0 -92
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.0.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.0.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.4.dist-info → sonolus_py-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
from typing import overload
|
|
3
|
-
|
|
1
|
+
from sonolus.backend.ops import Op
|
|
4
2
|
from sonolus.script.array import Array
|
|
5
3
|
from sonolus.script.array_like import ArrayLike
|
|
4
|
+
from sonolus.script.debug import error
|
|
6
5
|
from sonolus.script.internal.context import ctx
|
|
7
6
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
8
7
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
9
8
|
from sonolus.script.internal.math_impls import MATH_BUILTIN_IMPLS, _trunc
|
|
9
|
+
from sonolus.script.internal.native import native_function
|
|
10
10
|
from sonolus.script.internal.random import RANDOM_BUILTIN_IMPLS
|
|
11
11
|
from sonolus.script.internal.range import Range
|
|
12
12
|
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
@@ -21,6 +21,8 @@ from sonolus.script.iterator import (
|
|
|
21
21
|
)
|
|
22
22
|
from sonolus.script.num import Num, _is_num
|
|
23
23
|
|
|
24
|
+
_empty = object()
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
@meta_fn
|
|
26
28
|
def _isinstance(value, type_):
|
|
@@ -44,7 +46,7 @@ def _len(value):
|
|
|
44
46
|
value = validate_value(value)
|
|
45
47
|
if not hasattr(value, "__len__"):
|
|
46
48
|
raise TypeError(f"object of type '{type(value).__name__}' has no len()")
|
|
47
|
-
return compile_and_call(value.__len__)
|
|
49
|
+
return compile_and_call(value.__len__) # type: ignore
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
@meta_fn
|
|
@@ -59,7 +61,7 @@ def _enumerate(iterable, start=0):
|
|
|
59
61
|
elif isinstance(iterable, ArrayLike):
|
|
60
62
|
return compile_and_call(iterable._enumerate_, start)
|
|
61
63
|
else:
|
|
62
|
-
iterator = compile_and_call(iterable.__iter__)
|
|
64
|
+
iterator = compile_and_call(iterable.__iter__) # type: ignore
|
|
63
65
|
if not isinstance(iterator, SonolusIterator):
|
|
64
66
|
raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
|
|
65
67
|
return _Enumerator(0, start, iterator)
|
|
@@ -104,25 +106,20 @@ def _abs(value):
|
|
|
104
106
|
value = validate_value(value)
|
|
105
107
|
if not hasattr(value, "__abs__"):
|
|
106
108
|
raise TypeError(f"bad operand type for abs(): '{type(value).__name__}'")
|
|
107
|
-
return compile_and_call(value.__abs__)
|
|
109
|
+
return compile_and_call(value.__abs__) # type: ignore
|
|
108
110
|
|
|
109
111
|
|
|
110
112
|
def _identity(value):
|
|
111
113
|
return value
|
|
112
114
|
|
|
113
115
|
|
|
114
|
-
@overload
|
|
115
|
-
def _max[T](iterable: Iterable[T], *, key: callable = ...) -> T: ...
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
@overload
|
|
119
|
-
def _max[T](a: T, b: T, *args: T, key: callable = ...) -> T: ...
|
|
120
|
-
|
|
121
|
-
|
|
122
116
|
@meta_fn
|
|
123
|
-
def _max(*args, key
|
|
117
|
+
def _max(*args, default=_empty, key=None):
|
|
124
118
|
from sonolus.backend.visitor import compile_and_call
|
|
125
119
|
|
|
120
|
+
if key is None:
|
|
121
|
+
key = _identity
|
|
122
|
+
|
|
126
123
|
args = tuple(validate_value(arg) for arg in args)
|
|
127
124
|
if len(args) == 0:
|
|
128
125
|
raise ValueError("Expected at least one argument to max")
|
|
@@ -131,40 +128,91 @@ def _max(*args, key: callable = _identity):
|
|
|
131
128
|
if isinstance(iterable, ArrayLike):
|
|
132
129
|
return compile_and_call(iterable._max_, key=key)
|
|
133
130
|
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
131
|
+
if len(iterable.value) == 0:
|
|
132
|
+
if default is not _empty:
|
|
133
|
+
return default
|
|
134
|
+
raise ValueError("max() arg is an empty sequence")
|
|
134
135
|
return compile_and_call(Array(*iterable.value)._max_, key=key)
|
|
136
|
+
elif isinstance(iterable, SonolusIterator):
|
|
137
|
+
if not (default is _empty or Num._accepts_(default)):
|
|
138
|
+
raise TypeError("default argument must be a number")
|
|
139
|
+
return compile_and_call(
|
|
140
|
+
_max_num_iterator,
|
|
141
|
+
iterable,
|
|
142
|
+
Num._accept_(default) if default is not _empty else None,
|
|
143
|
+
key=key if key is not _identity else None,
|
|
144
|
+
)
|
|
135
145
|
else:
|
|
136
146
|
raise TypeError(f"Unsupported type: {type(iterable)} for max")
|
|
137
147
|
else:
|
|
148
|
+
if default is not _empty:
|
|
149
|
+
raise TypeError("default argument is not supported for max with multiple arguments")
|
|
138
150
|
if not all(_is_num(arg) for arg in args):
|
|
139
151
|
raise TypeError("Arguments to max must be numbers")
|
|
140
152
|
if ctx():
|
|
141
|
-
result =
|
|
153
|
+
result = _max2(args[0], args[1], key=key)
|
|
142
154
|
for arg in args[2:]:
|
|
143
|
-
result =
|
|
155
|
+
result = _max2(result, arg, key=key)
|
|
144
156
|
return result
|
|
145
157
|
else:
|
|
146
158
|
return max(arg._as_py_() for arg in args)
|
|
147
159
|
|
|
148
160
|
|
|
149
161
|
def _max2(a, b, key=_identity):
|
|
150
|
-
|
|
162
|
+
from sonolus.backend.visitor import compile_and_call
|
|
163
|
+
|
|
164
|
+
a = validate_value(a)
|
|
165
|
+
b = validate_value(b)
|
|
166
|
+
if _is_num(a) and _is_num(b) and key == _identity:
|
|
167
|
+
return compile_and_call(_max2_num, a, b)
|
|
168
|
+
return compile_and_call(_max2_generic, a, b, key=key)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@native_function(Op.Max)
|
|
172
|
+
def _max2_num(a, b):
|
|
173
|
+
if a > b:
|
|
151
174
|
return a
|
|
152
175
|
else:
|
|
153
176
|
return b
|
|
154
177
|
|
|
155
178
|
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
def _max2_generic(a, b, key=_identity):
|
|
180
|
+
if key(a) > key(b):
|
|
181
|
+
return a
|
|
182
|
+
else:
|
|
183
|
+
return b
|
|
158
184
|
|
|
159
185
|
|
|
160
|
-
|
|
161
|
-
|
|
186
|
+
def _max_num_iterator(iterable, default, key):
|
|
187
|
+
iterator = iterable.__iter__() # noqa: PLC2801
|
|
188
|
+
initial = iterator.next()
|
|
189
|
+
if initial.is_nothing:
|
|
190
|
+
assert default is not None
|
|
191
|
+
return default
|
|
192
|
+
if key is not None:
|
|
193
|
+
result = initial.get_unsafe()
|
|
194
|
+
best_key = key(result)
|
|
195
|
+
for value in iterator:
|
|
196
|
+
new_key = key(value)
|
|
197
|
+
if new_key > best_key:
|
|
198
|
+
result = value
|
|
199
|
+
best_key = new_key
|
|
200
|
+
return result
|
|
201
|
+
else:
|
|
202
|
+
result = initial.get_unsafe()
|
|
203
|
+
for value in iterator:
|
|
204
|
+
if value > result: # noqa: PLR1730
|
|
205
|
+
result = value
|
|
206
|
+
return result
|
|
162
207
|
|
|
163
208
|
|
|
164
209
|
@meta_fn
|
|
165
|
-
def _min(*args, key
|
|
210
|
+
def _min(*args, default=_empty, key=None):
|
|
166
211
|
from sonolus.backend.visitor import compile_and_call
|
|
167
212
|
|
|
213
|
+
if key is None:
|
|
214
|
+
key = _identity
|
|
215
|
+
|
|
168
216
|
args = tuple(validate_value(arg) for arg in args)
|
|
169
217
|
if len(args) == 0:
|
|
170
218
|
raise ValueError("Expected at least one argument to min")
|
|
@@ -173,34 +221,92 @@ def _min(*args, key: callable = _identity):
|
|
|
173
221
|
if isinstance(iterable, ArrayLike):
|
|
174
222
|
return compile_and_call(iterable._min_, key=key)
|
|
175
223
|
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
224
|
+
if len(iterable.value) == 0:
|
|
225
|
+
if default is not _empty:
|
|
226
|
+
return default
|
|
227
|
+
raise ValueError("min() arg is an empty sequence")
|
|
176
228
|
return compile_and_call(Array(*iterable.value)._min_, key=key)
|
|
229
|
+
elif isinstance(iterable, SonolusIterator):
|
|
230
|
+
if not (default is _empty or Num._accepts_(default)):
|
|
231
|
+
raise TypeError("default argument must be a number")
|
|
232
|
+
return compile_and_call(
|
|
233
|
+
_min_num_iterator,
|
|
234
|
+
iterable,
|
|
235
|
+
Num._accept_(default) if default is not _empty else None,
|
|
236
|
+
key=key if key is not _identity else None,
|
|
237
|
+
)
|
|
177
238
|
else:
|
|
178
239
|
raise TypeError(f"Unsupported type: {type(iterable)} for min")
|
|
179
240
|
else:
|
|
241
|
+
if default is not _empty:
|
|
242
|
+
raise TypeError("default argument is not supported for min with multiple arguments")
|
|
180
243
|
if not all(_is_num(arg) for arg in args):
|
|
181
244
|
raise TypeError("Arguments to min must be numbers")
|
|
182
245
|
if ctx():
|
|
183
|
-
result =
|
|
246
|
+
result = _min2(args[0], args[1], key=key)
|
|
184
247
|
for arg in args[2:]:
|
|
185
|
-
result =
|
|
248
|
+
result = _min2(result, arg, key=key)
|
|
186
249
|
return result
|
|
187
250
|
else:
|
|
188
251
|
return min(arg._as_py_() for arg in args)
|
|
189
252
|
|
|
190
253
|
|
|
191
254
|
def _min2(a, b, key=_identity):
|
|
255
|
+
from sonolus.backend.visitor import compile_and_call
|
|
256
|
+
|
|
257
|
+
a = validate_value(a)
|
|
258
|
+
b = validate_value(b)
|
|
259
|
+
if _is_num(a) and _is_num(b) and key == _identity:
|
|
260
|
+
return compile_and_call(_min2_num, a, b)
|
|
261
|
+
return compile_and_call(_min2_generic, a, b, key=key)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@native_function(Op.Min)
|
|
265
|
+
def _min2_num(a, b):
|
|
266
|
+
if a < b:
|
|
267
|
+
return a
|
|
268
|
+
else:
|
|
269
|
+
return b
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _min2_generic(a, b, key=_identity):
|
|
192
273
|
if key(a) < key(b):
|
|
193
274
|
return a
|
|
194
275
|
else:
|
|
195
276
|
return b
|
|
196
277
|
|
|
197
278
|
|
|
279
|
+
def _min_num_iterator(iterable, default, key):
|
|
280
|
+
iterator = iterable.__iter__() # noqa: PLC2801
|
|
281
|
+
initial = iterator.next()
|
|
282
|
+
if initial.is_nothing:
|
|
283
|
+
assert default is not None
|
|
284
|
+
return default
|
|
285
|
+
if key is not None:
|
|
286
|
+
result = initial.get_unsafe()
|
|
287
|
+
best_key = key(result)
|
|
288
|
+
for value in iterator:
|
|
289
|
+
new_key = key(value)
|
|
290
|
+
if new_key < best_key:
|
|
291
|
+
result = value
|
|
292
|
+
best_key = new_key
|
|
293
|
+
return result
|
|
294
|
+
else:
|
|
295
|
+
result = initial.get_unsafe()
|
|
296
|
+
for value in iterator:
|
|
297
|
+
if value < result: # noqa: PLR1730
|
|
298
|
+
result = value
|
|
299
|
+
return result
|
|
300
|
+
|
|
301
|
+
|
|
198
302
|
@meta_fn
|
|
199
303
|
def _callable(value):
|
|
200
304
|
return callable(value)
|
|
201
305
|
|
|
202
306
|
|
|
203
307
|
def _map(fn, iterable, *iterables):
|
|
308
|
+
if len(iterables) == 0:
|
|
309
|
+
return _MappingIterator(fn, iterable.__iter__()) # noqa: PLC2801
|
|
204
310
|
return _MappingIterator(lambda args: fn(*args), zip(iterable, *iterables)) # noqa: B905
|
|
205
311
|
|
|
206
312
|
|
|
@@ -234,9 +340,9 @@ def _bool(value=False):
|
|
|
234
340
|
return False
|
|
235
341
|
|
|
236
342
|
|
|
237
|
-
_int._type_mapping_ = Num
|
|
238
|
-
_float._type_mapping_ = Num
|
|
239
|
-
_bool._type_mapping_ = Num
|
|
343
|
+
_int._type_mapping_ = Num # type: ignore
|
|
344
|
+
_float._type_mapping_ = Num # type: ignore
|
|
345
|
+
_bool._type_mapping_ = Num # type: ignore
|
|
240
346
|
|
|
241
347
|
|
|
242
348
|
def _any(iterable):
|
|
@@ -253,12 +359,31 @@ def _all(iterable):
|
|
|
253
359
|
return True
|
|
254
360
|
|
|
255
361
|
|
|
362
|
+
def _sum(iterable, /, start=0):
|
|
363
|
+
for value in iterable:
|
|
364
|
+
start += value
|
|
365
|
+
return start
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _next(iterator):
|
|
369
|
+
assert isinstance(iterator, SonolusIterator)
|
|
370
|
+
value = iterator.next()
|
|
371
|
+
if value.is_some:
|
|
372
|
+
return value.get_unsafe()
|
|
373
|
+
error("Iterator has been exhausted")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _iter(iterable):
|
|
377
|
+
return iterable.__iter__() # type: ignore # noqa: PLC2801
|
|
378
|
+
|
|
379
|
+
|
|
256
380
|
# classmethod, property, staticmethod are supported as decorators, but not within functions
|
|
257
381
|
|
|
258
382
|
BUILTIN_IMPLS = {
|
|
259
383
|
id(abs): _abs,
|
|
260
384
|
id(all): _all,
|
|
261
385
|
id(any): _any,
|
|
386
|
+
id(sum): _sum,
|
|
262
387
|
id(bool): _bool,
|
|
263
388
|
id(callable): _callable,
|
|
264
389
|
id(enumerate): _enumerate,
|
|
@@ -266,10 +391,12 @@ BUILTIN_IMPLS = {
|
|
|
266
391
|
id(float): _float,
|
|
267
392
|
id(int): _int,
|
|
268
393
|
id(isinstance): _isinstance,
|
|
394
|
+
id(iter): _iter,
|
|
269
395
|
id(len): _len,
|
|
270
396
|
id(map): _map,
|
|
271
397
|
id(max): _max,
|
|
272
398
|
id(min): _min,
|
|
399
|
+
id(next): _next,
|
|
273
400
|
id(range): Range,
|
|
274
401
|
id(reversed): _reversed,
|
|
275
402
|
id(zip): _zip,
|
|
@@ -48,8 +48,8 @@ class ConstantValue(Value):
|
|
|
48
48
|
class Parameterized(cls):
|
|
49
49
|
_value = (parameter,)
|
|
50
50
|
|
|
51
|
-
Parameterized.__name__ = f"{parameter}"
|
|
52
|
-
Parameterized.__qualname__ =
|
|
51
|
+
Parameterized.__name__ = f"Const[{object.__repr__(parameter)}]" # noqa: PLC2801
|
|
52
|
+
Parameterized.__qualname__ = Parameterized.__name__
|
|
53
53
|
Parameterized.__module__ = cls.__module__
|
|
54
54
|
Parameterized.instance = object.__new__(Parameterized)
|
|
55
55
|
return Parameterized
|
|
@@ -113,7 +113,7 @@ class ConstantValue(Value):
|
|
|
113
113
|
if value is not self:
|
|
114
114
|
raise ValueError(f"{type(self).__name__} is immutable")
|
|
115
115
|
|
|
116
|
-
def _copy_from_(self, value:
|
|
116
|
+
def _copy_from_(self, value: Any):
|
|
117
117
|
if value is not self:
|
|
118
118
|
raise ValueError(f"{type(self).__name__} is immutable")
|
|
119
119
|
|
|
@@ -143,3 +143,13 @@ class ConstantValue(Value):
|
|
|
143
143
|
|
|
144
144
|
class BasicConstantValue(ConstantValue):
|
|
145
145
|
"""For constants without any special behavior."""
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TypingSpecialFormConstant(ConstantValue):
|
|
149
|
+
"""For constants that are typing special forms that have a [] operator."""
|
|
150
|
+
|
|
151
|
+
@meta_fn
|
|
152
|
+
def __getitem__(self, item: Any) -> Self:
|
|
153
|
+
if not item._is_py_():
|
|
154
|
+
raise TypeError(f"Invalid value for type parameter: {item}")
|
|
155
|
+
return self.value()[item._as_py_()]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
from contextlib import contextmanager
|
|
4
5
|
from contextvars import ContextVar
|
|
5
6
|
from dataclasses import dataclass
|
|
@@ -7,7 +8,7 @@ from threading import Lock
|
|
|
7
8
|
from typing import Any, Self
|
|
8
9
|
|
|
9
10
|
from sonolus.backend.blocks import BlockData, PlayBlock
|
|
10
|
-
from sonolus.backend.ir import IRConst, IRStmt
|
|
11
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRStmt
|
|
11
12
|
from sonolus.backend.mode import Mode
|
|
12
13
|
from sonolus.backend.optimize.flow import BasicBlock, FlowEdge
|
|
13
14
|
from sonolus.backend.place import Block, BlockPlace, TempBlock
|
|
@@ -16,7 +17,7 @@ from sonolus.script.internal.value import Value
|
|
|
16
17
|
|
|
17
18
|
_compiler_internal_ = True
|
|
18
19
|
|
|
19
|
-
context_var = ContextVar("context_var", default=None)
|
|
20
|
+
context_var: ContextVar[Context | None] = ContextVar("context_var", default=None) # type: ignore
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@dataclass(frozen=True)
|
|
@@ -70,7 +71,7 @@ class Context:
|
|
|
70
71
|
global_state: GlobalContextState
|
|
71
72
|
callback_state: CallbackContextState
|
|
72
73
|
statements: list[IRStmt]
|
|
73
|
-
test:
|
|
74
|
+
test: IRExpr
|
|
74
75
|
outgoing: dict[float | None, Context]
|
|
75
76
|
scope: Scope
|
|
76
77
|
loop_variables: dict[str, Value]
|
|
@@ -115,7 +116,7 @@ class Context:
|
|
|
115
116
|
return
|
|
116
117
|
if not self.callback:
|
|
117
118
|
return
|
|
118
|
-
if isinstance(place.block, BlockData) and
|
|
119
|
+
if isinstance(place.block, BlockData) and not self.is_readable(place):
|
|
119
120
|
raise RuntimeError(f"Block {place.block} is not readable in {self.callback}")
|
|
120
121
|
|
|
121
122
|
def check_writable(self, place: BlockPlace):
|
|
@@ -123,9 +124,19 @@ class Context:
|
|
|
123
124
|
return
|
|
124
125
|
if not self.callback:
|
|
125
126
|
return
|
|
126
|
-
if isinstance(place.block, BlockData) and
|
|
127
|
+
if isinstance(place.block, BlockData) and not self.is_writable(place):
|
|
127
128
|
raise RuntimeError(f"Block {place.block} is not writable in {self.callback}")
|
|
128
129
|
|
|
130
|
+
def is_readable(self, place: BlockPlace) -> bool:
|
|
131
|
+
if debug_config().unchecked_reads:
|
|
132
|
+
return True
|
|
133
|
+
return self.callback and self.callback in self.blocks(place.block).readable
|
|
134
|
+
|
|
135
|
+
def is_writable(self, place: BlockPlace) -> bool:
|
|
136
|
+
if debug_config().unchecked_writes:
|
|
137
|
+
return True
|
|
138
|
+
return self.callback and self.callback in self.blocks(place.block).writable
|
|
139
|
+
|
|
129
140
|
def add_statement(self, statement: IRStmt):
|
|
130
141
|
if not self.live:
|
|
131
142
|
return
|
|
@@ -169,20 +180,26 @@ class Context:
|
|
|
169
180
|
self.outgoing[condition] = result
|
|
170
181
|
return result
|
|
171
182
|
|
|
183
|
+
def new_disconnected(self):
|
|
184
|
+
return self.copy_with_scope(self.scope.copy())
|
|
185
|
+
|
|
186
|
+
def new_empty_disconnected(self):
|
|
187
|
+
return self.copy_with_scope(Scope())
|
|
188
|
+
|
|
172
189
|
def into_dead(self):
|
|
173
190
|
"""Create a new context for code that is unreachable, like after a return statement."""
|
|
174
191
|
result = self.copy_with_scope(self.scope.copy())
|
|
175
192
|
result.live = False
|
|
176
193
|
return result
|
|
177
194
|
|
|
178
|
-
def prepare_loop_header(self, to_merge: set[str]) ->
|
|
195
|
+
def prepare_loop_header(self, to_merge: set[str]) -> Context:
|
|
179
196
|
# to_merge is the set of bindings set anywhere in the loop
|
|
180
197
|
# we need to invalidate them in the header if they're reference types
|
|
181
198
|
# or merge them if they're value types
|
|
182
199
|
# structure is self -> intermediate -> header -> body (continue -> header) | exit
|
|
183
200
|
assert len(self.outgoing) == 0
|
|
184
201
|
header = self.branch(None)
|
|
185
|
-
for name in to_merge:
|
|
202
|
+
for name in sorted(to_merge):
|
|
186
203
|
binding = self.scope.get_binding(name)
|
|
187
204
|
if not isinstance(binding, ValueBinding):
|
|
188
205
|
continue
|
|
@@ -205,12 +222,15 @@ class Context:
|
|
|
205
222
|
assert len(self.outgoing) == 0
|
|
206
223
|
self.outgoing[None] = header
|
|
207
224
|
values = {}
|
|
208
|
-
# First do a pass through and
|
|
225
|
+
# First do a pass through and get every value
|
|
209
226
|
for name, target_value in header.loop_variables.items():
|
|
210
227
|
with using_ctx(self):
|
|
211
228
|
if type(target_value)._is_value_type_():
|
|
212
229
|
value = self.scope.get_value(name)
|
|
213
|
-
|
|
230
|
+
# We make this call to _get_readonly_() to ensure that we're reading the value at this
|
|
231
|
+
# point in time specifically, since _get_readonly_ will make a copy if the value is
|
|
232
|
+
# e.g. a Num backed by a TempBlock which could be mutated.
|
|
233
|
+
values[name] = value._get_readonly_()
|
|
214
234
|
# Then actually set them
|
|
215
235
|
for name, target_value in header.loop_variables.items():
|
|
216
236
|
with using_ctx(self):
|
|
@@ -236,7 +256,7 @@ class Context:
|
|
|
236
256
|
with self.global_state.lock:
|
|
237
257
|
block = value.blocks.get(self.global_state.mode)
|
|
238
258
|
if block is None:
|
|
239
|
-
raise RuntimeError(f"Global {value
|
|
259
|
+
raise RuntimeError(f"Global {value} is not available in '{self.global_state.mode.name}' mode")
|
|
240
260
|
if value not in self.global_state.environment_mappings:
|
|
241
261
|
if value.offset is None:
|
|
242
262
|
offset = self.global_state.environment_offsets.get(block, 0)
|
|
@@ -268,7 +288,7 @@ class Context:
|
|
|
268
288
|
return self.global_state.archetypes[type_]
|
|
269
289
|
|
|
270
290
|
|
|
271
|
-
def ctx() -> Context | None
|
|
291
|
+
def ctx() -> Context | Any: # Using Any to silence type checker warnings if it's None
|
|
272
292
|
return context_var.get()
|
|
273
293
|
|
|
274
294
|
|
|
@@ -368,7 +388,7 @@ class Scope:
|
|
|
368
388
|
def set_binding(self, name: str, binding: Binding):
|
|
369
389
|
self.bindings[name] = binding
|
|
370
390
|
|
|
371
|
-
def get_value(self, name: str) -> Value:
|
|
391
|
+
def get_value(self, name: str) -> Value | Any:
|
|
372
392
|
binding = self.get_binding(name)
|
|
373
393
|
match binding:
|
|
374
394
|
case ValueBinding(value):
|
|
@@ -398,7 +418,7 @@ class Scope:
|
|
|
398
418
|
return
|
|
399
419
|
assert all(len(inc.outgoing) == 0 for inc in incoming)
|
|
400
420
|
sources = [context.scope for context in incoming]
|
|
401
|
-
keys =
|
|
421
|
+
keys = unique(key for source in sources for key in source.bindings)
|
|
402
422
|
for key in keys:
|
|
403
423
|
bindings = [source.get_binding(key) for source in sources]
|
|
404
424
|
if not all(isinstance(binding, ValueBinding) for binding in bindings):
|
|
@@ -413,8 +433,9 @@ class Scope:
|
|
|
413
433
|
target.scope.set_binding(key, ConflictBinding())
|
|
414
434
|
continue
|
|
415
435
|
common_type: type[Value] = types.pop()
|
|
416
|
-
|
|
417
|
-
target_value = common_type.
|
|
436
|
+
with using_ctx(target):
|
|
437
|
+
target_value = common_type._get_merge_target_(values)
|
|
438
|
+
if target_value is not NotImplemented:
|
|
418
439
|
for inc in incoming:
|
|
419
440
|
with using_ctx(inc):
|
|
420
441
|
target_value._set_(inc.scope.get_value(key))
|
|
@@ -447,3 +468,13 @@ def context_to_cfg(context: Context) -> BasicBlock:
|
|
|
447
468
|
blocks[current].outgoing.add(edge)
|
|
448
469
|
blocks[target].incoming.add(edge)
|
|
449
470
|
return blocks[context]
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def unique[T](iterable: Iterable[T]) -> list[T]:
|
|
474
|
+
result = []
|
|
475
|
+
seen = set()
|
|
476
|
+
for item in iterable:
|
|
477
|
+
if item not in seen:
|
|
478
|
+
seen.add(item)
|
|
479
|
+
result.append(item)
|
|
480
|
+
return result
|
sonolus/script/internal/impl.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from types import EllipsisType, FunctionType, MethodType, ModuleType, NoneType, NotImplementedType, UnionType
|
|
6
|
-
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, get_origin, overload
|
|
6
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, Union, get_origin, overload
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from sonolus.script.internal.value import Value
|
|
@@ -33,7 +33,7 @@ def meta_fn(fn=None):
|
|
|
33
33
|
return decorator(fn)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def validate_value(value:
|
|
36
|
+
def validate_value[T](value: T) -> Value | T:
|
|
37
37
|
result = try_validate_value(value)
|
|
38
38
|
if result is None:
|
|
39
39
|
raise TypeError(f"Unsupported value: {value!r}")
|
|
@@ -42,7 +42,7 @@ def validate_value(value: Any) -> Value:
|
|
|
42
42
|
|
|
43
43
|
def try_validate_value(value: Any) -> Value | None:
|
|
44
44
|
from sonolus.script.globals import _GlobalPlaceholder
|
|
45
|
-
from sonolus.script.internal.constant import BasicConstantValue
|
|
45
|
+
from sonolus.script.internal.constant import BasicConstantValue, TypingSpecialFormConstant
|
|
46
46
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
47
47
|
from sonolus.script.internal.generic import PartialGeneric
|
|
48
48
|
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
@@ -85,6 +85,12 @@ def try_validate_value(value: Any) -> Value | None:
|
|
|
85
85
|
| EllipsisType()
|
|
86
86
|
):
|
|
87
87
|
return BasicConstantValue.of(value)
|
|
88
|
+
case special_form if value in {
|
|
89
|
+
Literal,
|
|
90
|
+
Annotated,
|
|
91
|
+
Union,
|
|
92
|
+
}:
|
|
93
|
+
return TypingSpecialFormConstant.of(special_form)
|
|
88
94
|
case other_type if get_origin(value) in {Literal, Annotated, UnionType, tuple}:
|
|
89
95
|
return BasicConstantValue.of(other_type)
|
|
90
96
|
case _GlobalPlaceholder():
|
|
@@ -4,7 +4,14 @@ from typing import Annotated
|
|
|
4
4
|
_missing = object()
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def get_field_specifiers(
|
|
7
|
+
def get_field_specifiers(
|
|
8
|
+
cls,
|
|
9
|
+
*,
|
|
10
|
+
skip: frozenset[str] | set[str] = frozenset(),
|
|
11
|
+
globals=None, # noqa: A002
|
|
12
|
+
locals=None, # noqa: A002
|
|
13
|
+
eval_str=True,
|
|
14
|
+
):
|
|
8
15
|
"""Like inspect.get_annotations, but also turns class attributes into Annotated."""
|
|
9
16
|
results = inspect.get_annotations(cls, globals=globals, locals=locals, eval_str=eval_str)
|
|
10
17
|
for key, value in results.items():
|
|
@@ -33,8 +33,8 @@ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]
|
|
|
33
33
|
bound_args = signature.bind(*args)
|
|
34
34
|
bound_args.apply_defaults()
|
|
35
35
|
return native_call(op, *(Num._accept_(arg) for arg in bound_args.args))
|
|
36
|
-
return fn(*args)
|
|
36
|
+
return fn(*args) # type: ignore
|
|
37
37
|
|
|
38
38
|
return wrapper
|
|
39
39
|
|
|
40
|
-
return decorator
|
|
40
|
+
return decorator # type: ignore
|
sonolus/script/internal/range.py
CHANGED
|
@@ -2,6 +2,7 @@ from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
|
2
2
|
from sonolus.script.internal.context import ctx
|
|
3
3
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
4
4
|
from sonolus.script.iterator import SonolusIterator
|
|
5
|
+
from sonolus.script.maybe import Maybe, Nothing, Some
|
|
5
6
|
from sonolus.script.num import Num
|
|
6
7
|
from sonolus.script.record import Record
|
|
7
8
|
|
|
@@ -70,17 +71,13 @@ class RangeIterator(Record, SonolusIterator):
|
|
|
70
71
|
stop: int
|
|
71
72
|
step: int
|
|
72
73
|
|
|
73
|
-
def
|
|
74
|
-
if self.step > 0
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return self.value
|
|
81
|
-
|
|
82
|
-
def advance(self):
|
|
83
|
-
self.value += self.step
|
|
74
|
+
def next(self) -> Maybe[int]:
|
|
75
|
+
has_next = self.value < self.stop if self.step > 0 else self.value > self.stop
|
|
76
|
+
if has_next:
|
|
77
|
+
current = self.value
|
|
78
|
+
self.value += self.step
|
|
79
|
+
return Some(current)
|
|
80
|
+
return Nothing
|
|
84
81
|
|
|
85
82
|
|
|
86
83
|
@meta_fn
|
|
@@ -126,6 +126,6 @@ class SimulationContext:
|
|
|
126
126
|
SimulationContext._active_context = None
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
def sim_ctx() -> SimulationContext |
|
|
129
|
+
def sim_ctx() -> SimulationContext | Any:
|
|
130
130
|
"""Get the current simulation context, or None if not active."""
|
|
131
131
|
return SimulationContext._active_context
|
|
@@ -36,11 +36,11 @@ class TransientValue(Value):
|
|
|
36
36
|
def _get_(self) -> Self:
|
|
37
37
|
return self
|
|
38
38
|
|
|
39
|
-
def _set_(self, value:
|
|
39
|
+
def _set_(self, value: Any) -> None:
|
|
40
40
|
if value is not self:
|
|
41
41
|
raise TypeError(f"{type(self).__name__} is immutable")
|
|
42
42
|
|
|
43
|
-
def _copy_from_(self, value:
|
|
43
|
+
def _copy_from_(self, value: Any):
|
|
44
44
|
raise TypeError(f"{type(self).__name__} is immutable")
|
|
45
45
|
|
|
46
46
|
def _copy_(self) -> Self:
|