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.
- sonolus/__init__.py +0 -0
- sonolus/backend/__init__.py +0 -0
- sonolus/backend/allocate.py +51 -0
- sonolus/backend/blocks.py +756 -0
- sonolus/backend/excepthook.py +37 -0
- sonolus/backend/finalize.py +69 -0
- sonolus/backend/flow.py +92 -0
- sonolus/backend/interpret.py +333 -0
- sonolus/backend/ir.py +89 -0
- sonolus/backend/mode.py +24 -0
- sonolus/backend/node.py +40 -0
- sonolus/backend/ops.py +197 -0
- sonolus/backend/optimize.py +9 -0
- sonolus/backend/passes.py +6 -0
- sonolus/backend/place.py +90 -0
- sonolus/backend/simplify.py +30 -0
- sonolus/backend/utils.py +48 -0
- sonolus/backend/visitor.py +880 -0
- sonolus/build/__init__.py +0 -0
- sonolus/build/cli.py +170 -0
- sonolus/build/collection.py +293 -0
- sonolus/build/compile.py +90 -0
- sonolus/build/defaults.py +32 -0
- sonolus/build/engine.py +149 -0
- sonolus/build/level.py +23 -0
- sonolus/build/node.py +43 -0
- sonolus/build/project.py +94 -0
- sonolus/py.typed +0 -0
- sonolus/script/__init__.py +0 -0
- sonolus/script/archetype.py +651 -0
- sonolus/script/array.py +241 -0
- sonolus/script/bucket.py +192 -0
- sonolus/script/callbacks.py +105 -0
- sonolus/script/comptime.py +146 -0
- sonolus/script/containers.py +247 -0
- sonolus/script/debug.py +70 -0
- sonolus/script/effect.py +132 -0
- sonolus/script/engine.py +101 -0
- sonolus/script/globals.py +234 -0
- sonolus/script/graphics.py +141 -0
- sonolus/script/icon.py +73 -0
- sonolus/script/internal/__init__.py +5 -0
- sonolus/script/internal/builtin_impls.py +144 -0
- sonolus/script/internal/context.py +365 -0
- sonolus/script/internal/descriptor.py +17 -0
- sonolus/script/internal/error.py +15 -0
- sonolus/script/internal/generic.py +197 -0
- sonolus/script/internal/impl.py +69 -0
- sonolus/script/internal/introspection.py +14 -0
- sonolus/script/internal/native.py +38 -0
- sonolus/script/internal/value.py +144 -0
- sonolus/script/interval.py +98 -0
- sonolus/script/iterator.py +211 -0
- sonolus/script/level.py +52 -0
- sonolus/script/math.py +92 -0
- sonolus/script/num.py +382 -0
- sonolus/script/options.py +194 -0
- sonolus/script/particle.py +158 -0
- sonolus/script/pointer.py +30 -0
- sonolus/script/project.py +17 -0
- sonolus/script/range.py +58 -0
- sonolus/script/record.py +293 -0
- sonolus/script/runtime.py +526 -0
- sonolus/script/sprite.py +332 -0
- sonolus/script/text.py +404 -0
- sonolus/script/timing.py +42 -0
- sonolus/script/transform.py +118 -0
- sonolus/script/ui.py +160 -0
- sonolus/script/values.py +43 -0
- sonolus/script/vec.py +48 -0
- sonolus_py-0.1.0.dist-info/METADATA +10 -0
- sonolus_py-0.1.0.dist-info/RECORD +75 -0
- sonolus_py-0.1.0.dist-info/WHEEL +4 -0
- sonolus_py-0.1.0.dist-info/entry_points.txt +2 -0
- sonolus_py-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
# ruff: noqa: N802
|
|
2
|
+
import ast
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from types import FunctionType, MethodType
|
|
8
|
+
from typing import Any, Never
|
|
9
|
+
|
|
10
|
+
from sonolus.backend.excepthook import install_excepthook
|
|
11
|
+
from sonolus.backend.utils import get_function, scan_writes
|
|
12
|
+
from sonolus.script.debug import assert_true
|
|
13
|
+
from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS
|
|
14
|
+
from sonolus.script.internal.context import Context, EmptyBinding, Scope, ValueBinding, ctx, set_ctx
|
|
15
|
+
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
16
|
+
from sonolus.script.internal.error import CompilationError
|
|
17
|
+
from sonolus.script.internal.impl import try_validate_value, validate_value
|
|
18
|
+
from sonolus.script.internal.value import Value
|
|
19
|
+
from sonolus.script.iterator import SonolusIterator
|
|
20
|
+
from sonolus.script.num import Num
|
|
21
|
+
|
|
22
|
+
_compiler_internal_ = True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def compile_and_call[**P, R](fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
26
|
+
if not ctx():
|
|
27
|
+
return fn(*args, **kwargs)
|
|
28
|
+
return validate_value(generate_fn_impl(fn)(*args, **kwargs))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate_fn_impl(fn: Callable):
|
|
32
|
+
install_excepthook()
|
|
33
|
+
match fn:
|
|
34
|
+
case Value() as value if value._is_py_():
|
|
35
|
+
return generate_fn_impl(value._as_py_())
|
|
36
|
+
case MethodType() as method:
|
|
37
|
+
return functools.partial(generate_fn_impl(method.__func__), method.__self__)
|
|
38
|
+
case FunctionType() as function:
|
|
39
|
+
if getattr(function, "_meta_fn_", False):
|
|
40
|
+
return function
|
|
41
|
+
return functools.partial(eval_fn, function)
|
|
42
|
+
case _:
|
|
43
|
+
if callable(fn) and isinstance(fn, Value):
|
|
44
|
+
return generate_fn_impl(fn.__call__)
|
|
45
|
+
elif fn is type:
|
|
46
|
+
return fn
|
|
47
|
+
elif callable(fn):
|
|
48
|
+
raise TypeError(f"Unsupported callable {fn!r}")
|
|
49
|
+
else:
|
|
50
|
+
raise TypeError(f"Not callable {fn!r}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def eval_fn(fn: Callable, /, *args, **kwargs):
|
|
54
|
+
source_file, node = get_function(fn)
|
|
55
|
+
bound_args = inspect.signature(fn).bind(*args, **kwargs)
|
|
56
|
+
bound_args.apply_defaults()
|
|
57
|
+
closurevars = inspect.getclosurevars(fn)
|
|
58
|
+
global_vars = {**closurevars.nonlocals, **closurevars.globals, **closurevars.builtins}
|
|
59
|
+
return Visitor(source_file, bound_args, global_vars).run(node)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
unary_ops = {
|
|
63
|
+
ast.Invert: "__invert__",
|
|
64
|
+
ast.UAdd: "__pos__",
|
|
65
|
+
ast.USub: "__neg__",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
bin_ops = {
|
|
69
|
+
ast.Add: "__add__",
|
|
70
|
+
ast.Sub: "__sub__",
|
|
71
|
+
ast.Mult: "__mul__",
|
|
72
|
+
ast.Div: "__truediv__",
|
|
73
|
+
ast.FloorDiv: "__floordiv__",
|
|
74
|
+
ast.Mod: "__mod__",
|
|
75
|
+
ast.Pow: "__pow__",
|
|
76
|
+
ast.LShift: "__lshift__",
|
|
77
|
+
ast.RShift: "__rshift__",
|
|
78
|
+
ast.BitOr: "__or__",
|
|
79
|
+
ast.BitAnd: "__and__",
|
|
80
|
+
ast.BitXor: "__xor__",
|
|
81
|
+
ast.MatMult: "__matmul__",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
rbin_ops = {
|
|
85
|
+
ast.Add: "__radd__",
|
|
86
|
+
ast.Sub: "__rsub__",
|
|
87
|
+
ast.Mult: "__rmul__",
|
|
88
|
+
ast.Div: "__rtruediv__",
|
|
89
|
+
ast.FloorDiv: "__rfloordiv__",
|
|
90
|
+
ast.Mod: "__rmod__",
|
|
91
|
+
ast.Pow: "__rpow__",
|
|
92
|
+
ast.LShift: "__rlshift__",
|
|
93
|
+
ast.RShift: "__rrshift__",
|
|
94
|
+
ast.BitOr: "__ror__",
|
|
95
|
+
ast.BitAnd: "__rand__",
|
|
96
|
+
ast.BitXor: "__rxor__",
|
|
97
|
+
ast.MatMult: "__rmatmul__",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
inplace_ops = {
|
|
101
|
+
ast.Add: "__iadd__",
|
|
102
|
+
ast.Sub: "__isub__",
|
|
103
|
+
ast.Mult: "__imul__",
|
|
104
|
+
ast.Div: "__itruediv__",
|
|
105
|
+
ast.FloorDiv: "__ifloordiv__",
|
|
106
|
+
ast.Mod: "__imod__",
|
|
107
|
+
ast.Pow: "__ipow__",
|
|
108
|
+
ast.LShift: "__ilshift__",
|
|
109
|
+
ast.RShift: "__irshift__",
|
|
110
|
+
ast.BitOr: "__ior__",
|
|
111
|
+
ast.BitXor: "__ixor__",
|
|
112
|
+
ast.BitAnd: "__iand__",
|
|
113
|
+
ast.MatMult: "__imatmul__",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
comp_ops = {
|
|
117
|
+
ast.Eq: "__eq__",
|
|
118
|
+
ast.NotEq: "__ne__",
|
|
119
|
+
ast.Lt: "__lt__",
|
|
120
|
+
ast.LtE: "__le__",
|
|
121
|
+
ast.Gt: "__gt__",
|
|
122
|
+
ast.GtE: "__ge__",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
rcomp_ops = {
|
|
126
|
+
ast.Eq: "__req__",
|
|
127
|
+
ast.NotEq: "__rne__",
|
|
128
|
+
ast.Lt: "__gt__",
|
|
129
|
+
ast.LtE: "__ge__",
|
|
130
|
+
ast.Gt: "__lt__",
|
|
131
|
+
ast.GtE: "__le__",
|
|
132
|
+
ast.In: "__contains__",
|
|
133
|
+
ast.NotIn: "__contains__",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class Visitor(ast.NodeVisitor):
|
|
138
|
+
source_file: str
|
|
139
|
+
globals: dict[str, Any]
|
|
140
|
+
bound_args: inspect.BoundArguments
|
|
141
|
+
used_names: dict[str, int]
|
|
142
|
+
return_ctxs: list[Context] # Contexts at return statements, which will branch to the exit
|
|
143
|
+
loop_head_ctxs: list[Context] # Contexts at loop heads, from outer to inner
|
|
144
|
+
break_ctxs: list[list[Context]] # Contexts at break statements, from outer to inner
|
|
145
|
+
|
|
146
|
+
def __init__(self, source_file: str, bound_args: inspect.BoundArguments, global_vars: dict[str, Any]):
|
|
147
|
+
self.source_file = source_file
|
|
148
|
+
self.globals = {}
|
|
149
|
+
for k, v in global_vars.items():
|
|
150
|
+
# Unfortunately, inspect.closurevars also includes attributes
|
|
151
|
+
if v is ctx:
|
|
152
|
+
raise ValueError("Unexpected use of ctx in non-meta function")
|
|
153
|
+
value = try_validate_value(BUILTIN_IMPLS.get(id(v), v))
|
|
154
|
+
if value is not None:
|
|
155
|
+
self.globals[k] = value
|
|
156
|
+
self.bound_args = bound_args
|
|
157
|
+
self.used_names = {}
|
|
158
|
+
self.return_ctxs = []
|
|
159
|
+
self.loop_head_ctxs = []
|
|
160
|
+
self.break_ctxs = []
|
|
161
|
+
|
|
162
|
+
def run(self, node):
|
|
163
|
+
before_ctx = ctx()
|
|
164
|
+
set_ctx(before_ctx.branch_with_scope(None, Scope()))
|
|
165
|
+
for name, value in self.bound_args.arguments.items():
|
|
166
|
+
ctx().scope.set_value(name, validate_value(value))
|
|
167
|
+
match node:
|
|
168
|
+
case ast.FunctionDef(body=body):
|
|
169
|
+
ctx().scope.set_value("$return", validate_value(None))
|
|
170
|
+
for stmt in body:
|
|
171
|
+
self.visit(stmt)
|
|
172
|
+
case _:
|
|
173
|
+
raise NotImplementedError("Unsupported syntax")
|
|
174
|
+
after_ctx = Context.meet([*self.return_ctxs, ctx()])
|
|
175
|
+
result_binding = after_ctx.scope.get_binding("$return")
|
|
176
|
+
if not isinstance(result_binding, ValueBinding):
|
|
177
|
+
raise ValueError("Function has conflicting return values")
|
|
178
|
+
set_ctx(after_ctx.branch_with_scope(None, before_ctx.scope.copy()))
|
|
179
|
+
return result_binding.value
|
|
180
|
+
|
|
181
|
+
def visit_FunctionDef(self, node):
|
|
182
|
+
raise NotImplementedError("Nested functions are not supported")
|
|
183
|
+
|
|
184
|
+
def visit_AsyncFunctionDef(self, node):
|
|
185
|
+
raise NotImplementedError("Async functions are not supported")
|
|
186
|
+
|
|
187
|
+
def visit_ClassDef(self, node):
|
|
188
|
+
raise NotImplementedError("Classes within functions are not supported")
|
|
189
|
+
|
|
190
|
+
def visit_Return(self, node):
|
|
191
|
+
value = self.visit(node.value) if node.value else validate_value(None)
|
|
192
|
+
ctx().scope.set_value("$return", value)
|
|
193
|
+
self.return_ctxs.append(ctx())
|
|
194
|
+
set_ctx(ctx().into_dead())
|
|
195
|
+
|
|
196
|
+
def visit_Delete(self, node):
|
|
197
|
+
raise NotImplementedError("Delete statements are not supported")
|
|
198
|
+
|
|
199
|
+
def visit_Assign(self, node):
|
|
200
|
+
value = self.visit(node.value)
|
|
201
|
+
for target in node.targets:
|
|
202
|
+
self.handle_assign(target, value)
|
|
203
|
+
|
|
204
|
+
def visit_TypeAlias(self, node):
|
|
205
|
+
raise NotImplementedError("Type aliases are not supported")
|
|
206
|
+
|
|
207
|
+
def visit_AugAssign(self, node):
|
|
208
|
+
lhs_value = self.visit(node.target)
|
|
209
|
+
rhs_value = self.visit(node.value)
|
|
210
|
+
inplace_fn_name = inplace_ops[type(node.op)]
|
|
211
|
+
regular_fn_name = bin_ops[type(node.op)]
|
|
212
|
+
right_fn_name = rbin_ops[type(node.op)]
|
|
213
|
+
if hasattr(lhs_value, inplace_fn_name):
|
|
214
|
+
result = self.handle_call(node, getattr(lhs_value, inplace_fn_name), rhs_value)
|
|
215
|
+
if not self.is_not_implemented(result):
|
|
216
|
+
if result is not lhs_value:
|
|
217
|
+
raise ValueError("Inplace operation must return the same object")
|
|
218
|
+
# Skip the actual assignment because the inplace operation already did the job, as an optimization
|
|
219
|
+
# There could be side effects of assignment, but that's atypical
|
|
220
|
+
return
|
|
221
|
+
if hasattr(lhs_value, regular_fn_name):
|
|
222
|
+
result = self.handle_call(node, getattr(lhs_value, regular_fn_name), rhs_value)
|
|
223
|
+
if not self.is_not_implemented(result):
|
|
224
|
+
self.handle_assign(node.target, result)
|
|
225
|
+
return
|
|
226
|
+
if hasattr(rhs_value, right_fn_name):
|
|
227
|
+
result = self.handle_call(node, getattr(rhs_value, right_fn_name), lhs_value)
|
|
228
|
+
if not self.is_not_implemented(result):
|
|
229
|
+
self.handle_assign(node.target, result)
|
|
230
|
+
return
|
|
231
|
+
raise NotImplementedError("Unsupported augmented assignment")
|
|
232
|
+
|
|
233
|
+
def visit_AnnAssign(self, node):
|
|
234
|
+
value = self.visit(node.value)
|
|
235
|
+
self.handle_assign(node.target, value)
|
|
236
|
+
|
|
237
|
+
def visit_For(self, node):
|
|
238
|
+
iterator = iter(self.visit(node.iter))
|
|
239
|
+
if not isinstance(iterator, SonolusIterator):
|
|
240
|
+
raise ValueError("Unsupported iterator")
|
|
241
|
+
writes = scan_writes(node)
|
|
242
|
+
header_ctx = ctx().prepare_loop_header(writes)
|
|
243
|
+
self.loop_head_ctxs.append(header_ctx)
|
|
244
|
+
self.break_ctxs.append([])
|
|
245
|
+
set_ctx(header_ctx)
|
|
246
|
+
has_next = self.ensure_boolean_num(self.handle_call(node, iterator.has_next))
|
|
247
|
+
ctx().test = has_next.ir()
|
|
248
|
+
body_ctx = ctx().branch(None)
|
|
249
|
+
else_ctx = ctx().branch(0)
|
|
250
|
+
|
|
251
|
+
set_ctx(body_ctx)
|
|
252
|
+
self.handle_assign(node.target, self.handle_call(node, iterator.next))
|
|
253
|
+
for stmt in node.body:
|
|
254
|
+
self.visit(stmt)
|
|
255
|
+
ctx().branch_to_loop_header(header_ctx)
|
|
256
|
+
|
|
257
|
+
set_ctx(else_ctx)
|
|
258
|
+
for stmt in node.orelse:
|
|
259
|
+
self.visit(stmt)
|
|
260
|
+
else_end_ctx = ctx()
|
|
261
|
+
|
|
262
|
+
break_ctxs = self.break_ctxs.pop()
|
|
263
|
+
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
264
|
+
set_ctx(after_ctx)
|
|
265
|
+
|
|
266
|
+
def visit_While(self, node):
|
|
267
|
+
writes = scan_writes(node)
|
|
268
|
+
header_ctx = ctx().prepare_loop_header(writes)
|
|
269
|
+
self.loop_head_ctxs.append(header_ctx)
|
|
270
|
+
self.break_ctxs.append([])
|
|
271
|
+
set_ctx(header_ctx)
|
|
272
|
+
test = self.ensure_boolean_num(self.visit(node.test))
|
|
273
|
+
ctx().test = test.ir()
|
|
274
|
+
body_ctx = ctx().branch(None)
|
|
275
|
+
else_ctx = ctx().branch(0)
|
|
276
|
+
|
|
277
|
+
set_ctx(body_ctx)
|
|
278
|
+
for stmt in node.body:
|
|
279
|
+
self.visit(stmt)
|
|
280
|
+
ctx().branch_to_loop_header(header_ctx)
|
|
281
|
+
|
|
282
|
+
set_ctx(else_ctx)
|
|
283
|
+
for stmt in node.orelse:
|
|
284
|
+
self.visit(stmt)
|
|
285
|
+
else_end_ctx = ctx()
|
|
286
|
+
|
|
287
|
+
break_ctxs = self.break_ctxs.pop()
|
|
288
|
+
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
289
|
+
set_ctx(after_ctx)
|
|
290
|
+
|
|
291
|
+
def visit_If(self, node):
|
|
292
|
+
test = self.ensure_boolean_num(self.visit(node.test))
|
|
293
|
+
|
|
294
|
+
if test._is_py_():
|
|
295
|
+
if test._as_py_():
|
|
296
|
+
for stmt in node.body:
|
|
297
|
+
self.visit(stmt)
|
|
298
|
+
else:
|
|
299
|
+
for stmt in node.orelse:
|
|
300
|
+
self.visit(stmt)
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
ctx_init = ctx()
|
|
304
|
+
ctx_init.test = test.ir()
|
|
305
|
+
true_ctx = ctx_init.branch(None)
|
|
306
|
+
false_ctx = ctx_init.branch(0)
|
|
307
|
+
|
|
308
|
+
set_ctx(true_ctx)
|
|
309
|
+
for stmt in node.body:
|
|
310
|
+
self.visit(stmt)
|
|
311
|
+
true_end_ctx = ctx()
|
|
312
|
+
|
|
313
|
+
set_ctx(false_ctx)
|
|
314
|
+
for stmt in node.orelse:
|
|
315
|
+
self.visit(stmt)
|
|
316
|
+
false_end_ctx = ctx()
|
|
317
|
+
|
|
318
|
+
set_ctx(Context.meet([true_end_ctx, false_end_ctx]))
|
|
319
|
+
|
|
320
|
+
def visit_With(self, node):
|
|
321
|
+
raise NotImplementedError("With statements are not supported")
|
|
322
|
+
|
|
323
|
+
def visit_AsyncWith(self, node):
|
|
324
|
+
raise NotImplementedError("Async with statements are not supported")
|
|
325
|
+
|
|
326
|
+
def visit_Match(self, node):
|
|
327
|
+
subject = self.visit(node.subject)
|
|
328
|
+
end_ctxs = []
|
|
329
|
+
for case in node.cases:
|
|
330
|
+
if not ctx().live:
|
|
331
|
+
break
|
|
332
|
+
true_ctx, false_ctx = self.handle_match_pattern(subject, case.pattern)
|
|
333
|
+
if not true_ctx.live:
|
|
334
|
+
set_ctx(false_ctx)
|
|
335
|
+
continue
|
|
336
|
+
set_ctx(true_ctx)
|
|
337
|
+
guard = self.ensure_boolean_num(self.visit(case.guard)) if case.guard else validate_value(True)
|
|
338
|
+
if guard._is_py_():
|
|
339
|
+
if guard._as_py_():
|
|
340
|
+
for stmt in case.body:
|
|
341
|
+
self.visit(stmt)
|
|
342
|
+
end_ctxs.append(ctx())
|
|
343
|
+
else:
|
|
344
|
+
end_ctxs.append(ctx())
|
|
345
|
+
else:
|
|
346
|
+
ctx().test = guard.ir()
|
|
347
|
+
true_ctx = ctx().branch(None)
|
|
348
|
+
false_ctx = ctx().branch(0)
|
|
349
|
+
set_ctx(true_ctx)
|
|
350
|
+
for stmt in case.body:
|
|
351
|
+
self.visit(stmt)
|
|
352
|
+
end_ctxs.append(ctx())
|
|
353
|
+
set_ctx(false_ctx)
|
|
354
|
+
if end_ctxs:
|
|
355
|
+
set_ctx(Context.meet(end_ctxs))
|
|
356
|
+
|
|
357
|
+
def handle_match_pattern(self, subject: Value, pattern: ast.pattern) -> tuple[Context, Context]:
|
|
358
|
+
match pattern:
|
|
359
|
+
case ast.MatchValue(value=value):
|
|
360
|
+
value = self.visit(value)
|
|
361
|
+
test = self.ensure_boolean_num(subject == value)
|
|
362
|
+
ctx_init = ctx()
|
|
363
|
+
ctx_init.test = test.ir()
|
|
364
|
+
true_ctx = ctx_init.branch(None)
|
|
365
|
+
false_ctx = ctx_init.branch(0)
|
|
366
|
+
return true_ctx, false_ctx
|
|
367
|
+
case ast.MatchSingleton(value=value):
|
|
368
|
+
match value:
|
|
369
|
+
case True:
|
|
370
|
+
test = self.ensure_boolean_num(subject)
|
|
371
|
+
case False:
|
|
372
|
+
test = self.ensure_boolean_num(subject).not_()
|
|
373
|
+
case None:
|
|
374
|
+
test = Num._accept_(subject._is_py_() and subject._as_py_() is None)
|
|
375
|
+
case _:
|
|
376
|
+
raise NotImplementedError("Unsupported match singleton")
|
|
377
|
+
ctx_init = ctx()
|
|
378
|
+
ctx_init.test = test.ir()
|
|
379
|
+
true_ctx = ctx_init.branch(None)
|
|
380
|
+
false_ctx = ctx_init.branch(0)
|
|
381
|
+
return true_ctx, false_ctx
|
|
382
|
+
case ast.MatchSequence():
|
|
383
|
+
raise NotImplementedError("Match sequences are not supported")
|
|
384
|
+
case ast.MatchMapping():
|
|
385
|
+
raise NotImplementedError("Match mappings are not supported")
|
|
386
|
+
case ast.MatchClass(cls=cls, patterns=patterns, kwd_attrs=kwd_attrs, kwd_patterns=kwd_patterns):
|
|
387
|
+
from sonolus.script.comptime import Comptime
|
|
388
|
+
from sonolus.script.internal.generic import validate_type_spec
|
|
389
|
+
|
|
390
|
+
cls = validate_type_spec(self.visit(cls))
|
|
391
|
+
if not isinstance(cls, type):
|
|
392
|
+
raise TypeError("Class is not a type")
|
|
393
|
+
if issubclass(cls, Comptime):
|
|
394
|
+
raise TypeError("Comptime is not supported in match patterns")
|
|
395
|
+
if not isinstance(subject, cls):
|
|
396
|
+
return ctx().into_dead(), ctx()
|
|
397
|
+
if patterns:
|
|
398
|
+
if not hasattr(cls, "__match_args__"):
|
|
399
|
+
raise TypeError("Class does not support match patterns")
|
|
400
|
+
if len(cls.__match_args__) < len(patterns):
|
|
401
|
+
raise ValueError("Too many match patterns")
|
|
402
|
+
# kwd_attrs can't be mixed with patterns on the syntax level,
|
|
403
|
+
# so we can just set it like this since it's empty
|
|
404
|
+
kwd_attrs = cls.__match_args__[: len(patterns)]
|
|
405
|
+
kwd_patterns = patterns
|
|
406
|
+
if kwd_attrs:
|
|
407
|
+
true_ctx = ctx()
|
|
408
|
+
false_ctxs = []
|
|
409
|
+
for attr, subpattern in zip(kwd_attrs, kwd_patterns, strict=False):
|
|
410
|
+
if not hasattr(subject, attr):
|
|
411
|
+
raise AttributeError(f"Object has no attribute {attr}")
|
|
412
|
+
value = self.handle_getattr(subpattern, subject, attr)
|
|
413
|
+
true_ctx, false_ctx = self.handle_match_pattern(value, subpattern)
|
|
414
|
+
false_ctxs.append(false_ctx)
|
|
415
|
+
set_ctx(true_ctx)
|
|
416
|
+
return true_ctx, Context.meet(false_ctxs)
|
|
417
|
+
return ctx(), ctx().into_dead()
|
|
418
|
+
case ast.MatchStar():
|
|
419
|
+
raise NotImplementedError("Match stars are not supported")
|
|
420
|
+
case ast.MatchAs(pattern=pattern, name=name):
|
|
421
|
+
if pattern:
|
|
422
|
+
true_ctx, false_ctx = self.handle_match_pattern(subject, pattern)
|
|
423
|
+
if name:
|
|
424
|
+
true_ctx.scope.set_value(name, subject)
|
|
425
|
+
return true_ctx, false_ctx
|
|
426
|
+
else:
|
|
427
|
+
if name:
|
|
428
|
+
ctx().scope.set_value(name, subject)
|
|
429
|
+
return ctx(), ctx().into_dead()
|
|
430
|
+
case ast.MatchOr():
|
|
431
|
+
true_ctxs = []
|
|
432
|
+
false_ctx = ctx()
|
|
433
|
+
assert pattern.patterns
|
|
434
|
+
for subpattern in pattern.patterns:
|
|
435
|
+
true_ctx, false_ctx = self.handle_match_pattern(subject, subpattern)
|
|
436
|
+
true_ctxs.append(true_ctx)
|
|
437
|
+
set_ctx(false_ctx)
|
|
438
|
+
return Context.meet(true_ctxs), false_ctx
|
|
439
|
+
|
|
440
|
+
def visit_Raise(self, node):
|
|
441
|
+
raise NotImplementedError("Raise statements are not supported")
|
|
442
|
+
|
|
443
|
+
def visit_Try(self, node):
|
|
444
|
+
raise NotImplementedError("Try statements are not supported")
|
|
445
|
+
|
|
446
|
+
def visit_TryStar(self, node):
|
|
447
|
+
raise NotImplementedError("Try* statements are not supported")
|
|
448
|
+
|
|
449
|
+
def visit_Assert(self, node):
|
|
450
|
+
self.handle_call(
|
|
451
|
+
node, assert_true, self.visit(node.test), self.visit(node.msg) if node.msg else validate_value(None)
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def visit_Import(self, node):
|
|
455
|
+
raise NotImplementedError("Import statements are not supported")
|
|
456
|
+
|
|
457
|
+
def visit_ImportFrom(self, node):
|
|
458
|
+
raise NotImplementedError("Import statements are not supported")
|
|
459
|
+
|
|
460
|
+
def visit_Global(self, node):
|
|
461
|
+
raise NotImplementedError("Global statements are not supported")
|
|
462
|
+
|
|
463
|
+
def visit_Nonlocal(self, node):
|
|
464
|
+
raise NotImplementedError("Nonlocal statements are not supported")
|
|
465
|
+
|
|
466
|
+
def visit_Expr(self, node):
|
|
467
|
+
return self.visit(node.value)
|
|
468
|
+
|
|
469
|
+
def visit_Pass(self, node):
|
|
470
|
+
pass
|
|
471
|
+
|
|
472
|
+
def visit_Break(self, node):
|
|
473
|
+
self.break_ctxs[-1].append(ctx())
|
|
474
|
+
set_ctx(ctx().into_dead())
|
|
475
|
+
|
|
476
|
+
def visit_Continue(self, node):
|
|
477
|
+
ctx().branch_to_loop_header(self.loop_head_ctxs[-1])
|
|
478
|
+
set_ctx(ctx().into_dead())
|
|
479
|
+
|
|
480
|
+
def visit_BoolOp(self, node) -> Value:
|
|
481
|
+
match node.op:
|
|
482
|
+
case ast.And():
|
|
483
|
+
handler = self.handle_and
|
|
484
|
+
case ast.Or():
|
|
485
|
+
handler = self.handle_or
|
|
486
|
+
case _:
|
|
487
|
+
raise NotImplementedError(f"Unsupported bool operator {node.op}")
|
|
488
|
+
|
|
489
|
+
if not node.values:
|
|
490
|
+
raise ValueError("Bool operator requires at least one operand")
|
|
491
|
+
if len(node.values) == 1:
|
|
492
|
+
return self.visit(node.values[0])
|
|
493
|
+
initial, *rest = node.values
|
|
494
|
+
return handler(self.visit(initial), ast.copy_location(ast.BoolOp(op=node.op, values=rest), node))
|
|
495
|
+
|
|
496
|
+
def visit_NamedExpr(self, node):
|
|
497
|
+
value = self.visit(node.value)
|
|
498
|
+
self.handle_assign(node.target, value)
|
|
499
|
+
return value
|
|
500
|
+
|
|
501
|
+
def visit_BinOp(self, node):
|
|
502
|
+
lhs = self.visit(node.left)
|
|
503
|
+
rhs = self.visit(node.right)
|
|
504
|
+
op = bin_ops[type(node.op)]
|
|
505
|
+
if hasattr(lhs, op):
|
|
506
|
+
result = self.handle_call(node, getattr(lhs, op), rhs)
|
|
507
|
+
if not self.is_not_implemented(result):
|
|
508
|
+
return result
|
|
509
|
+
if hasattr(rhs, rbin_ops[type(node.op)]):
|
|
510
|
+
result = self.handle_call(node, getattr(rhs, rbin_ops[type(node.op)]), lhs)
|
|
511
|
+
if not self.is_not_implemented(result):
|
|
512
|
+
return result
|
|
513
|
+
raise NotImplementedError(f"Unsupported operand types for binary operator {node.op}")
|
|
514
|
+
|
|
515
|
+
def visit_UnaryOp(self, node):
|
|
516
|
+
operand = self.visit(node.operand)
|
|
517
|
+
if isinstance(node.op, ast.Not):
|
|
518
|
+
return self.ensure_boolean_num(operand).not_()
|
|
519
|
+
op = unary_ops[type(node.op)]
|
|
520
|
+
if hasattr(operand, op):
|
|
521
|
+
return self.handle_call(node, getattr(operand, op))
|
|
522
|
+
raise NotImplementedError(f"Unsupported operand type for unary operator {node.op}")
|
|
523
|
+
|
|
524
|
+
def visit_Lambda(self, node):
|
|
525
|
+
raise NotImplementedError("Lambda functions are not supported")
|
|
526
|
+
|
|
527
|
+
def visit_IfExp(self, node):
|
|
528
|
+
test = self.ensure_boolean_num(self.visit(node.test))
|
|
529
|
+
|
|
530
|
+
if test._is_py_():
|
|
531
|
+
if test._as_py_():
|
|
532
|
+
return self.visit(node.body)
|
|
533
|
+
else:
|
|
534
|
+
return self.visit(node.orelse)
|
|
535
|
+
|
|
536
|
+
res_name = self.new_name("ifexp")
|
|
537
|
+
ctx_init = ctx()
|
|
538
|
+
ctx_init.test = test.ir()
|
|
539
|
+
|
|
540
|
+
set_ctx(ctx_init.branch(None))
|
|
541
|
+
true_value = self.visit(node.body)
|
|
542
|
+
ctx().scope.set_value(res_name, true_value)
|
|
543
|
+
ctx_true = ctx()
|
|
544
|
+
|
|
545
|
+
set_ctx(ctx_init.branch(0))
|
|
546
|
+
false_value = self.visit(node.orelse)
|
|
547
|
+
ctx().scope.set_value(res_name, false_value)
|
|
548
|
+
ctx_false = ctx()
|
|
549
|
+
|
|
550
|
+
set_ctx(Context.meet([ctx_true, ctx_false]))
|
|
551
|
+
return ctx().scope.get_value(res_name)
|
|
552
|
+
|
|
553
|
+
def visit_Dict(self, node):
|
|
554
|
+
return validate_value({self.visit(k): self.visit(v) for k, v in zip(node.keys, node.values, strict=True)})
|
|
555
|
+
|
|
556
|
+
def visit_Set(self, node):
|
|
557
|
+
raise NotImplementedError("Set literals are not supported")
|
|
558
|
+
|
|
559
|
+
def visit_ListComp(self, node):
|
|
560
|
+
raise NotImplementedError("List comprehensions are not supported")
|
|
561
|
+
|
|
562
|
+
def visit_SetComp(self, node):
|
|
563
|
+
raise NotImplementedError("Set comprehensions are not supported")
|
|
564
|
+
|
|
565
|
+
def visit_DictComp(self, node):
|
|
566
|
+
raise NotImplementedError("Dict comprehensions are not supported")
|
|
567
|
+
|
|
568
|
+
def visit_GeneratorExp(self, node):
|
|
569
|
+
raise NotImplementedError("Generator expressions are not supported")
|
|
570
|
+
|
|
571
|
+
def visit_Await(self, node):
|
|
572
|
+
raise NotImplementedError("Await expressions are not supported")
|
|
573
|
+
|
|
574
|
+
def visit_Yield(self, node):
|
|
575
|
+
raise NotImplementedError("Yield expressions are not supported")
|
|
576
|
+
|
|
577
|
+
def visit_YieldFrom(self, node):
|
|
578
|
+
raise NotImplementedError("Yield from expressions are not supported")
|
|
579
|
+
|
|
580
|
+
def visit_Compare(self, node):
|
|
581
|
+
result_name = self.new_name("compare")
|
|
582
|
+
ctx().scope.set_value(result_name, Num._accept_(0))
|
|
583
|
+
l_val = self.visit(node.left)
|
|
584
|
+
false_ctxs = []
|
|
585
|
+
for i, (op, rhs) in enumerate(zip(node.ops, node.comparators, strict=True)):
|
|
586
|
+
r_val = self.visit(rhs)
|
|
587
|
+
inverted = isinstance(op, ast.NotIn)
|
|
588
|
+
result = None
|
|
589
|
+
if isinstance(op, ast.Is | ast.IsNot):
|
|
590
|
+
if not (r_val._is_py_() and r_val._as_py_() is None):
|
|
591
|
+
raise TypeError("The right operand of 'is' must be None")
|
|
592
|
+
if isinstance(op, ast.Is):
|
|
593
|
+
result = Num._accept_(l_val._is_py_() and l_val._as_py_() is None)
|
|
594
|
+
else:
|
|
595
|
+
result = Num._accept_(not (l_val._is_py_() and l_val._as_py_() is None))
|
|
596
|
+
elif type(op) in comp_ops and hasattr(l_val, comp_ops[type(op)]):
|
|
597
|
+
result = self.handle_call(node, getattr(l_val, comp_ops[type(op)]), r_val)
|
|
598
|
+
if (
|
|
599
|
+
(result is None or self.is_not_implemented(result))
|
|
600
|
+
and type(op) in rcomp_ops
|
|
601
|
+
and hasattr(r_val, rcomp_ops[type(op)])
|
|
602
|
+
):
|
|
603
|
+
result = self.handle_call(node, getattr(r_val, rcomp_ops[type(op)]), l_val)
|
|
604
|
+
if result is None or self.is_not_implemented(result):
|
|
605
|
+
raise NotImplementedError(f"Unsupported comparison operator {op}")
|
|
606
|
+
result = self.ensure_boolean_num(result)
|
|
607
|
+
if inverted:
|
|
608
|
+
result = result.not_()
|
|
609
|
+
curr_ctx = ctx()
|
|
610
|
+
if i == len(node.ops) - 1:
|
|
611
|
+
curr_ctx.scope.set_value(result_name, result)
|
|
612
|
+
else:
|
|
613
|
+
curr_ctx.test = result.ir()
|
|
614
|
+
true_ctx = curr_ctx.branch(None)
|
|
615
|
+
false_ctx = curr_ctx.branch(0)
|
|
616
|
+
false_ctxs.append(false_ctx)
|
|
617
|
+
set_ctx(true_ctx)
|
|
618
|
+
l_val = r_val
|
|
619
|
+
last_ctx = ctx() # This is the result of the last comparison returning true
|
|
620
|
+
set_ctx(Context.meet([last_ctx, *false_ctxs]))
|
|
621
|
+
return ctx().scope.get_value(result_name)
|
|
622
|
+
|
|
623
|
+
def visit_Call(self, node):
|
|
624
|
+
fn = self.visit(node.func)
|
|
625
|
+
args = []
|
|
626
|
+
kwargs = {}
|
|
627
|
+
for arg in node.args:
|
|
628
|
+
if isinstance(arg, ast.Starred):
|
|
629
|
+
args.extend(self.handle_starred(self.visit(arg.value)))
|
|
630
|
+
else:
|
|
631
|
+
args.append(self.visit(arg))
|
|
632
|
+
for keyword in node.keywords:
|
|
633
|
+
if keyword.arg:
|
|
634
|
+
kwargs[keyword.arg] = self.visit(keyword.value)
|
|
635
|
+
else:
|
|
636
|
+
value = self.visit(keyword.value)
|
|
637
|
+
if value._is_py_() and isinstance(value._as_py_(), Mapping):
|
|
638
|
+
kwargs.update(value._as_py_())
|
|
639
|
+
else:
|
|
640
|
+
raise ValueError("Starred keyword arguments (**kwargs) must be dictionaries")
|
|
641
|
+
return self.handle_call(node, fn, *args, **kwargs)
|
|
642
|
+
|
|
643
|
+
def visit_FormattedValue(self, node):
|
|
644
|
+
raise NotImplementedError("F-strings are not supported")
|
|
645
|
+
|
|
646
|
+
def visit_JoinedStr(self, node):
|
|
647
|
+
raise NotImplementedError("F-strings are not supported")
|
|
648
|
+
|
|
649
|
+
def visit_Constant(self, node):
|
|
650
|
+
return validate_value(node.value)
|
|
651
|
+
|
|
652
|
+
def visit_Attribute(self, node):
|
|
653
|
+
return self.handle_getattr(node, self.visit(node.value), node.attr)
|
|
654
|
+
|
|
655
|
+
def visit_Subscript(self, node):
|
|
656
|
+
value = self.visit(node.value)
|
|
657
|
+
slice_value = self.visit(node.slice)
|
|
658
|
+
return self.handle_getitem(node, value, slice_value)
|
|
659
|
+
|
|
660
|
+
def visit_Starred(self, node):
|
|
661
|
+
raise NotImplementedError("Starred expressions are not supported")
|
|
662
|
+
|
|
663
|
+
def visit_Name(self, node):
|
|
664
|
+
if isinstance(ctx().scope.get_binding(node.id), EmptyBinding) and node.id in self.globals:
|
|
665
|
+
# globals can have false positives due to limitations of inspect.closurevars
|
|
666
|
+
# so we need to check that it's not defined as a local variable
|
|
667
|
+
return self.globals[node.id]
|
|
668
|
+
return ctx().scope.get_value(node.id)
|
|
669
|
+
|
|
670
|
+
def visit_List(self, node):
|
|
671
|
+
raise NotImplementedError("List literals are not supported")
|
|
672
|
+
|
|
673
|
+
def visit_Tuple(self, node):
|
|
674
|
+
values = []
|
|
675
|
+
for elt in node.elts:
|
|
676
|
+
if isinstance(elt, ast.Starred):
|
|
677
|
+
values.extend(self.handle_starred(self.visit(elt.value)))
|
|
678
|
+
else:
|
|
679
|
+
values.append(self.visit(elt))
|
|
680
|
+
return validate_value(tuple(values))
|
|
681
|
+
|
|
682
|
+
def visit_Slice(self, node):
|
|
683
|
+
raise NotImplementedError("Slices are not supported")
|
|
684
|
+
|
|
685
|
+
def handle_assign(self, target: ast.stmt | ast.expr, value: Value):
|
|
686
|
+
match target:
|
|
687
|
+
case ast.Name(id=name):
|
|
688
|
+
ctx().scope.set_value(name, value)
|
|
689
|
+
case ast.Attribute(value=attr_value, attr=attr):
|
|
690
|
+
attr_value = self.visit(attr_value)
|
|
691
|
+
self.handle_setattr(target, attr_value, attr, value)
|
|
692
|
+
case ast.Subscript(value=sub_value, slice=slice_expr):
|
|
693
|
+
sub_value = self.visit(sub_value)
|
|
694
|
+
slice_value = self.visit(slice_expr)
|
|
695
|
+
self.handle_setitem(target, sub_value, slice_value, value)
|
|
696
|
+
case ast.Tuple(elts=elts) | ast.List(elts=elts):
|
|
697
|
+
values = self.handle_starred(value)
|
|
698
|
+
if len(elts) != len(values):
|
|
699
|
+
raise ValueError("Unpacking assignment requires the same number of elements")
|
|
700
|
+
for elt, v in zip(elts, values, strict=False):
|
|
701
|
+
self.handle_assign(elt, validate_value(v))
|
|
702
|
+
case ast.Starred():
|
|
703
|
+
raise NotImplementedError("Starred assignment is not supported")
|
|
704
|
+
case _:
|
|
705
|
+
raise NotImplementedError("Unsupported assignment target")
|
|
706
|
+
|
|
707
|
+
def handle_and(self, l_val: Value, r_expr: ast.expr) -> Value:
|
|
708
|
+
ctx_init = ctx()
|
|
709
|
+
l_val = self.ensure_boolean_num(l_val)
|
|
710
|
+
ctx_init.test = l_val.ir()
|
|
711
|
+
res_name = self.new_name("and")
|
|
712
|
+
|
|
713
|
+
set_ctx(ctx_init.branch(None))
|
|
714
|
+
r_val = self.ensure_boolean_num(self.visit(r_expr))
|
|
715
|
+
ctx().scope.set_value(res_name, r_val)
|
|
716
|
+
ctx_true = ctx()
|
|
717
|
+
|
|
718
|
+
set_ctx(ctx_init.branch(0))
|
|
719
|
+
ctx().scope.set_value(res_name, Num._accept_(0))
|
|
720
|
+
ctx_false = ctx()
|
|
721
|
+
|
|
722
|
+
set_ctx(Context.meet([ctx_true, ctx_false]))
|
|
723
|
+
if l_val._is_py_() and r_val._is_py_():
|
|
724
|
+
return Num._accept_(l_val._as_py_() and r_val._as_py_())
|
|
725
|
+
return ctx().scope.get_value(res_name)
|
|
726
|
+
|
|
727
|
+
def handle_or(self, l_val: Value, r_expr: ast.expr) -> Value:
|
|
728
|
+
ctx_init = ctx()
|
|
729
|
+
l_val = self.ensure_boolean_num(l_val)
|
|
730
|
+
ctx_init.test = l_val.ir()
|
|
731
|
+
res_name = self.new_name("or")
|
|
732
|
+
|
|
733
|
+
set_ctx(ctx_init.branch(None))
|
|
734
|
+
ctx().scope.set_value(res_name, l_val)
|
|
735
|
+
ctx_true = ctx()
|
|
736
|
+
|
|
737
|
+
set_ctx(ctx_init.branch(0))
|
|
738
|
+
r_val = self.ensure_boolean_num(self.visit(r_expr))
|
|
739
|
+
ctx().scope.set_value(res_name, r_val)
|
|
740
|
+
ctx_false = ctx()
|
|
741
|
+
|
|
742
|
+
set_ctx(Context.meet([ctx_true, ctx_false]))
|
|
743
|
+
if l_val._is_py_() and r_val._is_py_():
|
|
744
|
+
return Num._accept_(l_val._as_py_() or r_val._as_py_())
|
|
745
|
+
return ctx().scope.get_value(res_name)
|
|
746
|
+
|
|
747
|
+
def generic_visit(self, node):
|
|
748
|
+
if isinstance(node, ast.stmt | ast.expr):
|
|
749
|
+
with self.reporting_errors_at_node(node):
|
|
750
|
+
raise NotImplementedError(f"Unsupported syntax: {type(node).__name__}")
|
|
751
|
+
raise NotImplementedError(f"Unsupported syntax: {type(node).__name__}")
|
|
752
|
+
|
|
753
|
+
def handle_getattr(self, node: ast.stmt | ast.expr, target: Value, key: str) -> Value:
|
|
754
|
+
with self.reporting_errors_at_node(node):
|
|
755
|
+
if target._is_py_():
|
|
756
|
+
target = target._as_py_()
|
|
757
|
+
descriptor = type(target).__dict__.get(key)
|
|
758
|
+
match descriptor:
|
|
759
|
+
case property(fget=getter):
|
|
760
|
+
return self.handle_call(node, getter, target)
|
|
761
|
+
case SonolusDescriptor() | FunctionType() | classmethod() | staticmethod() | None:
|
|
762
|
+
return validate_value(getattr(target, key))
|
|
763
|
+
case non_descriptor if not hasattr(non_descriptor, "__get__"):
|
|
764
|
+
return validate_value(getattr(target, key))
|
|
765
|
+
case _:
|
|
766
|
+
raise TypeError(f"Unsupported field or descriptor {key}")
|
|
767
|
+
|
|
768
|
+
def handle_setattr(self, node: ast.stmt | ast.expr, target: Value, key: str, value: Value):
|
|
769
|
+
with self.reporting_errors_at_node(node):
|
|
770
|
+
if target._is_py_():
|
|
771
|
+
target = target._as_py_()
|
|
772
|
+
descriptor = getattr(type(target), key, None)
|
|
773
|
+
match descriptor:
|
|
774
|
+
case property(fset=setter):
|
|
775
|
+
if setter is None:
|
|
776
|
+
raise AttributeError(f"Cannot set attribute {key} because property has no setter")
|
|
777
|
+
self.handle_call(node, setter, target, value)
|
|
778
|
+
case SonolusDescriptor():
|
|
779
|
+
setattr(target, key, value)
|
|
780
|
+
case _:
|
|
781
|
+
raise TypeError(f"Unsupported field or descriptor {key}")
|
|
782
|
+
|
|
783
|
+
def handle_call[**P, R](
|
|
784
|
+
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
785
|
+
) -> R:
|
|
786
|
+
"""Handles a call to the given callable."""
|
|
787
|
+
if (
|
|
788
|
+
isinstance(fn, Value)
|
|
789
|
+
and fn._is_py_()
|
|
790
|
+
and isinstance(fn._as_py_(), type)
|
|
791
|
+
and issubclass(fn._as_py_(), Value)
|
|
792
|
+
):
|
|
793
|
+
return validate_value(self.execute_at_node(node, fn._as_py_(), *args, **kwargs))
|
|
794
|
+
else:
|
|
795
|
+
return self.execute_at_node(node, lambda: validate_value(compile_and_call(fn, *args, **kwargs)))
|
|
796
|
+
|
|
797
|
+
def handle_getitem(self, node: ast.stmt | ast.expr, target: Value, key: Value) -> Value:
|
|
798
|
+
with self.reporting_errors_at_node(node):
|
|
799
|
+
if target._is_py_():
|
|
800
|
+
target = target._as_py_()
|
|
801
|
+
if key._is_py_():
|
|
802
|
+
return validate_value(target[key._as_py_()])
|
|
803
|
+
if isinstance(target, Value) and hasattr(target, "__getitem__"):
|
|
804
|
+
return self.handle_call(node, target.__getitem__, key)
|
|
805
|
+
raise TypeError(f"Cannot get items on {type(target).__name__}")
|
|
806
|
+
else:
|
|
807
|
+
if isinstance(target, Value) and hasattr(target, "__getitem__"):
|
|
808
|
+
return self.handle_call(node, target.__getitem__, key)
|
|
809
|
+
raise TypeError(f"Cannot get items on {type(target).__name__}")
|
|
810
|
+
|
|
811
|
+
def handle_setitem(self, node: ast.stmt | ast.expr, target: Value, key: Value, value: Value):
|
|
812
|
+
with self.reporting_errors_at_node(node):
|
|
813
|
+
if target._is_py_():
|
|
814
|
+
target = target._as_py_()
|
|
815
|
+
if key._is_py_():
|
|
816
|
+
target[key._as_py_()] = value._as_py_()
|
|
817
|
+
if isinstance(target, Value) and hasattr(target, "__setitem__"):
|
|
818
|
+
return self.handle_call(node, target.__setitem__, key, value)
|
|
819
|
+
raise TypeError(f"Cannot set items on {type(target).__name__}")
|
|
820
|
+
else:
|
|
821
|
+
if isinstance(target, Value) and hasattr(target, "__setitem__"):
|
|
822
|
+
return self.handle_call(node, target.__setitem__, key, value)
|
|
823
|
+
raise TypeError(f"Cannot set items on {type(target).__name__}")
|
|
824
|
+
|
|
825
|
+
def handle_starred(self, value: Value) -> tuple[Value, ...]:
|
|
826
|
+
if value._is_py_() and isinstance(value._as_py_(), tuple):
|
|
827
|
+
return value._as_py_()
|
|
828
|
+
raise ValueError("Unsupported starred expression")
|
|
829
|
+
|
|
830
|
+
def is_not_implemented(self, value):
|
|
831
|
+
value = validate_value(value)
|
|
832
|
+
return value._is_py_() and value._as_py_() is NotImplemented
|
|
833
|
+
|
|
834
|
+
def ensure_boolean_num(self, value) -> Num:
|
|
835
|
+
# This just checks the type for now, although we could support custom __bool__ implementations in the future
|
|
836
|
+
if not isinstance(value, Num):
|
|
837
|
+
raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
|
|
838
|
+
return value
|
|
839
|
+
|
|
840
|
+
def raise_exception_at_node(self, node: ast.stmt | ast.expr, cause: Exception) -> Never:
|
|
841
|
+
"""Throws a compilation error at the given node."""
|
|
842
|
+
|
|
843
|
+
def thrower() -> Never:
|
|
844
|
+
raise CompilationError(str(cause)) from cause
|
|
845
|
+
|
|
846
|
+
self.execute_at_node(node, thrower)
|
|
847
|
+
|
|
848
|
+
def execute_at_node[**P, R](
|
|
849
|
+
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
850
|
+
) -> R:
|
|
851
|
+
"""Executes the given function at the given node for a better traceback."""
|
|
852
|
+
expr = ast.Expression(
|
|
853
|
+
body=ast.Call(
|
|
854
|
+
func=ast.Name(id="fn", ctx=ast.Load()),
|
|
855
|
+
args=[ast.Starred(value=ast.Name(id="args", ctx=ast.Load()), ctx=ast.Load())],
|
|
856
|
+
keywords=[ast.keyword(value=ast.Name(id="kwargs", ctx=ast.Load()), arg=None)],
|
|
857
|
+
lineno=node.lineno,
|
|
858
|
+
col_offset=node.col_offset,
|
|
859
|
+
end_lineno=node.end_lineno,
|
|
860
|
+
end_col_offset=node.end_col_offset,
|
|
861
|
+
),
|
|
862
|
+
)
|
|
863
|
+
expr = ast.fix_missing_locations(expr)
|
|
864
|
+
return eval(
|
|
865
|
+
compile(expr, filename=self.source_file, mode="eval"),
|
|
866
|
+
{"fn": fn, "args": args, "kwargs": kwargs, "_filter_traceback_": True},
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
@contextmanager
|
|
870
|
+
def reporting_errors_at_node(self, node: ast.stmt | ast.expr):
|
|
871
|
+
try:
|
|
872
|
+
yield
|
|
873
|
+
except CompilationError as e:
|
|
874
|
+
raise e from None
|
|
875
|
+
except Exception as e:
|
|
876
|
+
self.raise_exception_at_node(node, e)
|
|
877
|
+
|
|
878
|
+
def new_name(self, name: str):
|
|
879
|
+
self.used_names[name] = self.used_names.get(name, 0) + 1
|
|
880
|
+
return f"${name}_{self.used_names[name]}"
|