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
sonolus/backend/visitor.py
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
# ruff: noqa: N802
|
|
2
2
|
import ast
|
|
3
|
+
import builtins
|
|
3
4
|
import functools
|
|
4
5
|
import inspect
|
|
5
|
-
from collections.abc import Callable,
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from typing import Any, Never
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
|
+
from types import FunctionType, MethodType, MethodWrapperType
|
|
8
|
+
from typing import Any, Never, Self
|
|
9
9
|
|
|
10
10
|
from sonolus.backend.excepthook import install_excepthook
|
|
11
11
|
from sonolus.backend.utils import get_function, scan_writes
|
|
12
12
|
from sonolus.script.debug import assert_true
|
|
13
|
-
from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS
|
|
13
|
+
from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS, _bool, _float, _int, _len
|
|
14
|
+
from sonolus.script.internal.constant import ConstantValue
|
|
14
15
|
from sonolus.script.internal.context import Context, EmptyBinding, Scope, ValueBinding, ctx, set_ctx
|
|
15
16
|
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
16
17
|
from sonolus.script.internal.error import CompilationError
|
|
17
|
-
from sonolus.script.internal.impl import
|
|
18
|
+
from sonolus.script.internal.impl import validate_value
|
|
19
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
18
20
|
from sonolus.script.internal.value import Value
|
|
19
21
|
from sonolus.script.iterator import SonolusIterator
|
|
20
|
-
from sonolus.script.num import Num,
|
|
22
|
+
from sonolus.script.num import Num, _is_num
|
|
21
23
|
|
|
22
24
|
_compiler_internal_ = True
|
|
23
25
|
|
|
@@ -31,7 +33,7 @@ def compile_and_call[**P, R](fn: Callable[P, R], /, *args: P.args, **kwargs: P.k
|
|
|
31
33
|
def generate_fn_impl(fn: Callable):
|
|
32
34
|
install_excepthook()
|
|
33
35
|
match fn:
|
|
34
|
-
case
|
|
36
|
+
case ConstantValue() as value if value._is_py_():
|
|
35
37
|
return generate_fn_impl(value._as_py_())
|
|
36
38
|
case MethodType() as method:
|
|
37
39
|
return functools.partial(generate_fn_impl(method.__func__), method.__self__)
|
|
@@ -42,20 +44,21 @@ def generate_fn_impl(fn: Callable):
|
|
|
42
44
|
case _:
|
|
43
45
|
if callable(fn) and isinstance(fn, Value):
|
|
44
46
|
return generate_fn_impl(fn.__call__)
|
|
45
|
-
elif fn is type:
|
|
46
|
-
return fn
|
|
47
47
|
elif callable(fn):
|
|
48
48
|
raise TypeError(f"Unsupported callable {fn!r}")
|
|
49
49
|
else:
|
|
50
|
-
raise TypeError(f"
|
|
50
|
+
raise TypeError(f"'{type(fn).__name__}' object is not callable")
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def eval_fn(fn: Callable, /, *args, **kwargs):
|
|
54
54
|
source_file, node = get_function(fn)
|
|
55
55
|
bound_args = inspect.signature(fn).bind(*args, **kwargs)
|
|
56
56
|
bound_args.apply_defaults()
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
global_vars = {
|
|
58
|
+
**builtins.__dict__,
|
|
59
|
+
**fn.__globals__,
|
|
60
|
+
**inspect.getclosurevars(fn).nonlocals,
|
|
61
|
+
}
|
|
59
62
|
return Visitor(source_file, bound_args, global_vars).run(node)
|
|
60
63
|
|
|
61
64
|
|
|
@@ -123,16 +126,45 @@ comp_ops = {
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
rcomp_ops = {
|
|
126
|
-
ast.Eq: "
|
|
127
|
-
ast.NotEq: "
|
|
129
|
+
ast.Eq: "__eq__",
|
|
130
|
+
ast.NotEq: "__ne__",
|
|
128
131
|
ast.Lt: "__gt__",
|
|
129
132
|
ast.LtE: "__ge__",
|
|
130
133
|
ast.Gt: "__lt__",
|
|
131
134
|
ast.GtE: "__le__",
|
|
132
|
-
ast.In: "__contains__",
|
|
135
|
+
ast.In: "__contains__", # Only supported on the right side
|
|
133
136
|
ast.NotIn: "__contains__",
|
|
134
137
|
}
|
|
135
138
|
|
|
139
|
+
op_to_symbol = {
|
|
140
|
+
ast.Add: "+",
|
|
141
|
+
ast.Sub: "-",
|
|
142
|
+
ast.Mult: "*",
|
|
143
|
+
ast.Div: "/",
|
|
144
|
+
ast.FloorDiv: "//",
|
|
145
|
+
ast.Mod: "%",
|
|
146
|
+
ast.Pow: "**",
|
|
147
|
+
ast.Eq: "==",
|
|
148
|
+
ast.NotEq: "!=",
|
|
149
|
+
ast.Lt: "<",
|
|
150
|
+
ast.LtE: "<=",
|
|
151
|
+
ast.Gt: ">",
|
|
152
|
+
ast.GtE: ">=",
|
|
153
|
+
ast.And: "and",
|
|
154
|
+
ast.Or: "or",
|
|
155
|
+
ast.BitAnd: "&",
|
|
156
|
+
ast.BitOr: "|",
|
|
157
|
+
ast.BitXor: "^",
|
|
158
|
+
ast.LShift: "<<",
|
|
159
|
+
ast.RShift: ">>",
|
|
160
|
+
ast.USub: "-",
|
|
161
|
+
ast.UAdd: "+",
|
|
162
|
+
ast.Invert: "~",
|
|
163
|
+
ast.Not: "not",
|
|
164
|
+
ast.In: "in",
|
|
165
|
+
ast.NotIn: "not in",
|
|
166
|
+
}
|
|
167
|
+
|
|
136
168
|
|
|
137
169
|
class Visitor(ast.NodeVisitor):
|
|
138
170
|
source_file: str
|
|
@@ -142,22 +174,25 @@ class Visitor(ast.NodeVisitor):
|
|
|
142
174
|
return_ctxs: list[Context] # Contexts at return statements, which will branch to the exit
|
|
143
175
|
loop_head_ctxs: list[Context] # Contexts at loop heads, from outer to inner
|
|
144
176
|
break_ctxs: list[list[Context]] # Contexts at break statements, from outer to inner
|
|
145
|
-
|
|
146
|
-
|
|
177
|
+
active_ctx: Context | None # The active context for use in nested functions=
|
|
178
|
+
parent: Self | None # The parent visitor for use in nested functions
|
|
179
|
+
|
|
180
|
+
def __init__(
|
|
181
|
+
self,
|
|
182
|
+
source_file: str,
|
|
183
|
+
bound_args: inspect.BoundArguments,
|
|
184
|
+
global_vars: dict[str, Any],
|
|
185
|
+
parent: Self | None = None,
|
|
186
|
+
):
|
|
147
187
|
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
|
|
188
|
+
self.globals = global_vars
|
|
156
189
|
self.bound_args = bound_args
|
|
157
190
|
self.used_names = {}
|
|
158
191
|
self.return_ctxs = []
|
|
159
192
|
self.loop_head_ctxs = []
|
|
160
193
|
self.break_ctxs = []
|
|
194
|
+
self.active_ctx = None
|
|
195
|
+
self.parent = parent
|
|
161
196
|
|
|
162
197
|
def run(self, node):
|
|
163
198
|
before_ctx = ctx()
|
|
@@ -169,17 +204,49 @@ class Visitor(ast.NodeVisitor):
|
|
|
169
204
|
ctx().scope.set_value("$return", validate_value(None))
|
|
170
205
|
for stmt in body:
|
|
171
206
|
self.visit(stmt)
|
|
207
|
+
case ast.Lambda(body=body):
|
|
208
|
+
result = self.visit(body)
|
|
209
|
+
ctx().scope.set_value("$return", result)
|
|
172
210
|
case _:
|
|
173
211
|
raise NotImplementedError("Unsupported syntax")
|
|
174
212
|
after_ctx = Context.meet([*self.return_ctxs, ctx()])
|
|
213
|
+
self.active_ctx = after_ctx
|
|
175
214
|
result_binding = after_ctx.scope.get_binding("$return")
|
|
176
215
|
if not isinstance(result_binding, ValueBinding):
|
|
177
216
|
raise ValueError("Function has conflicting return values")
|
|
178
217
|
set_ctx(after_ctx.branch_with_scope(None, before_ctx.scope.copy()))
|
|
179
218
|
return result_binding.value
|
|
180
219
|
|
|
220
|
+
def visit(self, node):
|
|
221
|
+
"""Visit a node."""
|
|
222
|
+
# We want this here so this is filtered out of tracebacks
|
|
223
|
+
method = "visit_" + node.__class__.__name__
|
|
224
|
+
visitor = getattr(self, method, self.generic_visit)
|
|
225
|
+
with self.reporting_errors_at_node(node):
|
|
226
|
+
return visitor(node)
|
|
227
|
+
|
|
181
228
|
def visit_FunctionDef(self, node):
|
|
182
|
-
|
|
229
|
+
name = node.name
|
|
230
|
+
signature = self.arguments_to_signature(node.args)
|
|
231
|
+
|
|
232
|
+
def fn(*args, **kwargs):
|
|
233
|
+
bound = signature.bind(*args, **kwargs)
|
|
234
|
+
bound.apply_defaults()
|
|
235
|
+
return Visitor(
|
|
236
|
+
self.source_file,
|
|
237
|
+
bound,
|
|
238
|
+
self.globals,
|
|
239
|
+
self,
|
|
240
|
+
).run(node)
|
|
241
|
+
|
|
242
|
+
fn._meta_fn_ = True
|
|
243
|
+
fn.__name__ = name
|
|
244
|
+
fn.__qualname__ = name
|
|
245
|
+
|
|
246
|
+
for decorator in reversed(node.decorator_list):
|
|
247
|
+
fn = self.handle_call(decorator, self.visit(decorator), fn)
|
|
248
|
+
|
|
249
|
+
ctx().scope.set_value(name, validate_value(fn))
|
|
183
250
|
|
|
184
251
|
def visit_AsyncFunctionDef(self, node):
|
|
185
252
|
raise NotImplementedError("Async functions are not supported")
|
|
@@ -194,7 +261,16 @@ class Visitor(ast.NodeVisitor):
|
|
|
194
261
|
set_ctx(ctx().into_dead())
|
|
195
262
|
|
|
196
263
|
def visit_Delete(self, node):
|
|
197
|
-
|
|
264
|
+
for target in node.targets:
|
|
265
|
+
match target:
|
|
266
|
+
case ast.Name():
|
|
267
|
+
raise NotImplementedError("Deleting variables is not supported")
|
|
268
|
+
case ast.Subscript(value=value, slice=slice):
|
|
269
|
+
self.handle_delitem(target, self.visit(value), self.visit(slice))
|
|
270
|
+
case ast.Attribute():
|
|
271
|
+
raise NotImplementedError("Deleting attributes is not supported")
|
|
272
|
+
case _:
|
|
273
|
+
raise NotImplementedError("Unsupported delete target")
|
|
198
274
|
|
|
199
275
|
def visit_Assign(self, node):
|
|
200
276
|
value = self.visit(node.value)
|
|
@@ -215,27 +291,40 @@ class Visitor(ast.NodeVisitor):
|
|
|
215
291
|
if not self.is_not_implemented(result):
|
|
216
292
|
if result is not lhs_value:
|
|
217
293
|
raise ValueError("Inplace operation must return the same object")
|
|
218
|
-
|
|
219
|
-
# There could be side effects of assignment, but that's atypical
|
|
294
|
+
self.handle_assign(node.target, result)
|
|
220
295
|
return
|
|
221
296
|
if hasattr(lhs_value, regular_fn_name):
|
|
222
297
|
result = self.handle_call(node, getattr(lhs_value, regular_fn_name), rhs_value)
|
|
223
298
|
if not self.is_not_implemented(result):
|
|
224
299
|
self.handle_assign(node.target, result)
|
|
225
300
|
return
|
|
226
|
-
if hasattr(rhs_value, right_fn_name):
|
|
301
|
+
if hasattr(rhs_value, right_fn_name) and type(lhs_value) is not type(rhs_value):
|
|
227
302
|
result = self.handle_call(node, getattr(rhs_value, right_fn_name), lhs_value)
|
|
228
303
|
if not self.is_not_implemented(result):
|
|
229
304
|
self.handle_assign(node.target, result)
|
|
230
305
|
return
|
|
231
|
-
raise
|
|
306
|
+
raise TypeError(
|
|
307
|
+
f"unsupported operand type(s) for {op_to_symbol[type(node.op)]}=: "
|
|
308
|
+
f"'{type(lhs_value).__name__}' and '{type(rhs_value).__name__}'"
|
|
309
|
+
)
|
|
232
310
|
|
|
233
311
|
def visit_AnnAssign(self, node):
|
|
234
312
|
value = self.visit(node.value)
|
|
235
313
|
self.handle_assign(node.target, value)
|
|
236
314
|
|
|
237
315
|
def visit_For(self, node):
|
|
238
|
-
|
|
316
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
317
|
+
|
|
318
|
+
iterable = self.visit(node.iter)
|
|
319
|
+
if isinstance(iterable, TupleImpl):
|
|
320
|
+
# Unroll the loop
|
|
321
|
+
for value in iterable.value:
|
|
322
|
+
set_ctx(ctx().branch(None))
|
|
323
|
+
self.handle_assign(node.target, validate_value(value))
|
|
324
|
+
for stmt in node.body:
|
|
325
|
+
self.visit(stmt)
|
|
326
|
+
return
|
|
327
|
+
iterator = self.handle_call(node, iterable.__iter__)
|
|
239
328
|
if not isinstance(iterator, SonolusIterator):
|
|
240
329
|
raise ValueError("Unsupported iterator")
|
|
241
330
|
writes = scan_writes(node)
|
|
@@ -244,6 +333,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
244
333
|
self.break_ctxs.append([])
|
|
245
334
|
set_ctx(header_ctx)
|
|
246
335
|
has_next = self.ensure_boolean_num(self.handle_call(node, iterator.has_next))
|
|
336
|
+
if has_next._is_py_() and not has_next._as_py_():
|
|
337
|
+
# The loop will never run, continue after evaluating the condition
|
|
338
|
+
for stmt in node.orelse:
|
|
339
|
+
self.visit(stmt)
|
|
340
|
+
return
|
|
247
341
|
ctx().test = has_next.ir()
|
|
248
342
|
body_ctx = ctx().branch(None)
|
|
249
343
|
else_ctx = ctx().branch(0)
|
|
@@ -271,6 +365,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
271
365
|
self.break_ctxs.append([])
|
|
272
366
|
set_ctx(header_ctx)
|
|
273
367
|
test = self.ensure_boolean_num(self.visit(node.test))
|
|
368
|
+
if test._is_py_() and not test._as_py_():
|
|
369
|
+
# The loop will never run, continue after evaluating the condition
|
|
370
|
+
for stmt in node.orelse:
|
|
371
|
+
self.visit(stmt)
|
|
372
|
+
return
|
|
274
373
|
ctx().test = test.ir()
|
|
275
374
|
body_ctx = ctx().branch(None)
|
|
276
375
|
else_ctx = ctx().branch(0)
|
|
@@ -343,24 +442,38 @@ class Visitor(ast.NodeVisitor):
|
|
|
343
442
|
self.visit(stmt)
|
|
344
443
|
end_ctxs.append(ctx())
|
|
345
444
|
else:
|
|
346
|
-
|
|
445
|
+
# Merge failing before the guard and failing now at the guard (which we know is guaranteed to fail)
|
|
446
|
+
false_ctx = Context.meet([ctx(), false_ctx])
|
|
347
447
|
else:
|
|
348
448
|
ctx().test = guard.ir()
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
set_ctx(
|
|
449
|
+
guard_true_ctx = ctx().branch(None)
|
|
450
|
+
guard_false_ctx = ctx().branch(0)
|
|
451
|
+
set_ctx(guard_true_ctx)
|
|
352
452
|
for stmt in case.body:
|
|
353
453
|
self.visit(stmt)
|
|
354
454
|
end_ctxs.append(ctx())
|
|
455
|
+
false_ctx = Context.meet([false_ctx, guard_false_ctx])
|
|
355
456
|
set_ctx(false_ctx)
|
|
457
|
+
end_ctxs.append(ctx())
|
|
356
458
|
if end_ctxs:
|
|
357
459
|
set_ctx(Context.meet(end_ctxs))
|
|
358
460
|
|
|
359
461
|
def handle_match_pattern(self, subject: Value, pattern: ast.pattern) -> tuple[Context, Context]:
|
|
462
|
+
from sonolus.script.internal.generic import validate_type_spec
|
|
463
|
+
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
464
|
+
|
|
465
|
+
if not ctx().live:
|
|
466
|
+
return ctx().into_dead(), ctx()
|
|
467
|
+
|
|
360
468
|
match pattern:
|
|
361
469
|
case ast.MatchValue(value=value):
|
|
362
470
|
value = self.visit(value)
|
|
363
|
-
test = self.ensure_boolean_num(subject == value)
|
|
471
|
+
test = self.ensure_boolean_num(validate_value(subject == value))
|
|
472
|
+
if test._is_py_():
|
|
473
|
+
if test._as_py_():
|
|
474
|
+
return ctx(), ctx().into_dead()
|
|
475
|
+
else:
|
|
476
|
+
return ctx().into_dead(), ctx()
|
|
364
477
|
ctx_init = ctx()
|
|
365
478
|
ctx_init.test = test.ir()
|
|
366
479
|
true_ctx = ctx_init.branch(None)
|
|
@@ -369,11 +482,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
369
482
|
case ast.MatchSingleton(value=value):
|
|
370
483
|
match value:
|
|
371
484
|
case True:
|
|
372
|
-
|
|
485
|
+
raise NotImplementedError("Matching against True is not supported, use 1 instead")
|
|
373
486
|
case False:
|
|
374
|
-
|
|
487
|
+
raise NotImplementedError("Matching against False is not supported, use 0 instead")
|
|
375
488
|
case None:
|
|
376
|
-
test =
|
|
489
|
+
test = validate_value(subject._is_py_() and subject._as_py_() is None)
|
|
377
490
|
case _:
|
|
378
491
|
raise NotImplementedError("Unsupported match singleton")
|
|
379
492
|
ctx_init = ctx()
|
|
@@ -381,19 +494,39 @@ class Visitor(ast.NodeVisitor):
|
|
|
381
494
|
true_ctx = ctx_init.branch(None)
|
|
382
495
|
false_ctx = ctx_init.branch(0)
|
|
383
496
|
return true_ctx, false_ctx
|
|
384
|
-
case ast.MatchSequence():
|
|
385
|
-
|
|
497
|
+
case ast.MatchSequence(patterns=patterns):
|
|
498
|
+
target_len = len(patterns)
|
|
499
|
+
if not (isinstance(subject, Sequence | TupleImpl)):
|
|
500
|
+
return ctx().into_dead(), ctx()
|
|
501
|
+
length_test = self.ensure_boolean_num(validate_value(_len(subject) == target_len))
|
|
502
|
+
ctx_init = ctx()
|
|
503
|
+
if not length_test._is_py_():
|
|
504
|
+
ctx_init.test = length_test.ir()
|
|
505
|
+
true_ctx = ctx_init.branch(None)
|
|
506
|
+
false_ctxs = [ctx_init.branch(0)]
|
|
507
|
+
elif length_test._as_py_():
|
|
508
|
+
true_ctx = ctx_init
|
|
509
|
+
false_ctxs = []
|
|
510
|
+
else:
|
|
511
|
+
return ctx().into_dead(), ctx()
|
|
512
|
+
set_ctx(true_ctx)
|
|
513
|
+
for i, subpattern in enumerate(patterns):
|
|
514
|
+
if not ctx().live:
|
|
515
|
+
break
|
|
516
|
+
value = self.handle_getitem(subpattern, subject, validate_value(i))
|
|
517
|
+
true_ctx, false_ctx = self.handle_match_pattern(value, subpattern)
|
|
518
|
+
false_ctxs.append(false_ctx)
|
|
519
|
+
set_ctx(true_ctx)
|
|
520
|
+
return true_ctx, Context.meet(false_ctxs)
|
|
386
521
|
case ast.MatchMapping():
|
|
387
522
|
raise NotImplementedError("Match mappings are not supported")
|
|
388
523
|
case ast.MatchClass(cls=cls, patterns=patterns, kwd_attrs=kwd_attrs, kwd_patterns=kwd_patterns):
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
cls = validate_type_spec(
|
|
524
|
+
cls = self.visit(cls)
|
|
525
|
+
if cls._is_py_() and cls._as_py_() in {_int, _float, _bool}:
|
|
526
|
+
raise TypeError("Instance check against int, float, or bool is not supported, use Num instead")
|
|
527
|
+
cls = validate_type_spec(cls)
|
|
393
528
|
if not isinstance(cls, type):
|
|
394
529
|
raise TypeError("Class is not a type")
|
|
395
|
-
if issubclass(cls, Comptime):
|
|
396
|
-
raise TypeError("Comptime is not supported in match patterns")
|
|
397
530
|
if not isinstance(subject, cls):
|
|
398
531
|
return ctx().into_dead(), ctx()
|
|
399
532
|
if patterns:
|
|
@@ -431,13 +564,14 @@ class Visitor(ast.NodeVisitor):
|
|
|
431
564
|
return ctx(), ctx().into_dead()
|
|
432
565
|
case ast.MatchOr():
|
|
433
566
|
true_ctxs = []
|
|
434
|
-
false_ctx = ctx()
|
|
435
567
|
assert pattern.patterns
|
|
436
568
|
for subpattern in pattern.patterns:
|
|
569
|
+
if not ctx().live:
|
|
570
|
+
break
|
|
437
571
|
true_ctx, false_ctx = self.handle_match_pattern(subject, subpattern)
|
|
438
572
|
true_ctxs.append(true_ctx)
|
|
439
573
|
set_ctx(false_ctx)
|
|
440
|
-
return Context.meet(true_ctxs),
|
|
574
|
+
return Context.meet(true_ctxs), ctx()
|
|
441
575
|
|
|
442
576
|
def visit_Raise(self, node):
|
|
443
577
|
raise NotImplementedError("Raise statements are not supported")
|
|
@@ -486,7 +620,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
486
620
|
case ast.Or():
|
|
487
621
|
handler = self.handle_or
|
|
488
622
|
case _:
|
|
489
|
-
raise NotImplementedError(f"Unsupported bool operator {node.op}")
|
|
623
|
+
raise NotImplementedError(f"Unsupported bool operator {op_to_symbol[type(node.op)]}")
|
|
490
624
|
|
|
491
625
|
if not node.values:
|
|
492
626
|
raise ValueError("Bool operator requires at least one operand")
|
|
@@ -504,15 +638,23 @@ class Visitor(ast.NodeVisitor):
|
|
|
504
638
|
lhs = self.visit(node.left)
|
|
505
639
|
rhs = self.visit(node.right)
|
|
506
640
|
op = bin_ops[type(node.op)]
|
|
641
|
+
if lhs._is_py_() and rhs._is_py_():
|
|
642
|
+
lhs_py = lhs._as_py_()
|
|
643
|
+
rhs_py = rhs._as_py_()
|
|
644
|
+
if isinstance(lhs_py, type) and isinstance(rhs_py, type):
|
|
645
|
+
return validate_value(getattr(lhs_py, op)(rhs_py))
|
|
507
646
|
if hasattr(lhs, op):
|
|
508
647
|
result = self.handle_call(node, getattr(lhs, op), rhs)
|
|
509
648
|
if not self.is_not_implemented(result):
|
|
510
649
|
return result
|
|
511
|
-
if hasattr(rhs, rbin_ops[type(node.op)]):
|
|
650
|
+
if hasattr(rhs, rbin_ops[type(node.op)]) and type(lhs) is not type(rhs):
|
|
512
651
|
result = self.handle_call(node, getattr(rhs, rbin_ops[type(node.op)]), lhs)
|
|
513
652
|
if not self.is_not_implemented(result):
|
|
514
653
|
return result
|
|
515
|
-
raise
|
|
654
|
+
raise TypeError(
|
|
655
|
+
f"unsupported operand type(s) for {op_to_symbol[type(node.op)]}: "
|
|
656
|
+
f"'{type(lhs).__name__}' and '{type(rhs).__name__}'"
|
|
657
|
+
)
|
|
516
658
|
|
|
517
659
|
def visit_UnaryOp(self, node):
|
|
518
660
|
operand = self.visit(node.operand)
|
|
@@ -521,10 +663,25 @@ class Visitor(ast.NodeVisitor):
|
|
|
521
663
|
op = unary_ops[type(node.op)]
|
|
522
664
|
if hasattr(operand, op):
|
|
523
665
|
return self.handle_call(node, getattr(operand, op))
|
|
524
|
-
raise
|
|
666
|
+
raise TypeError(f"bad operand type for unary {op_to_symbol[type(node.op)]}: '{type(operand).__name__}'")
|
|
525
667
|
|
|
526
668
|
def visit_Lambda(self, node):
|
|
527
|
-
|
|
669
|
+
signature = self.arguments_to_signature(node.args)
|
|
670
|
+
|
|
671
|
+
def fn(*args, **kwargs):
|
|
672
|
+
bound = signature.bind(*args, **kwargs)
|
|
673
|
+
bound.apply_defaults()
|
|
674
|
+
return Visitor(
|
|
675
|
+
self.source_file,
|
|
676
|
+
bound,
|
|
677
|
+
self.globals,
|
|
678
|
+
self,
|
|
679
|
+
).run(node)
|
|
680
|
+
|
|
681
|
+
fn._meta_fn_ = True
|
|
682
|
+
fn.__name__ = "<lambda>"
|
|
683
|
+
|
|
684
|
+
return validate_value(fn)
|
|
528
685
|
|
|
529
686
|
def visit_IfExp(self, node):
|
|
530
687
|
test = self.ensure_boolean_num(self.visit(node.test))
|
|
@@ -579,6 +736,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
579
736
|
def visit_YieldFrom(self, node):
|
|
580
737
|
raise NotImplementedError("Yield from expressions are not supported")
|
|
581
738
|
|
|
739
|
+
def _has_real_method(self, obj: Value, method_name: str) -> bool:
|
|
740
|
+
return hasattr(obj, method_name) and not isinstance(getattr(obj, method_name), MethodWrapperType)
|
|
741
|
+
|
|
582
742
|
def visit_Compare(self, node):
|
|
583
743
|
result_name = self.new_name("compare")
|
|
584
744
|
ctx().scope.set_value(result_name, Num._accept_(0))
|
|
@@ -595,22 +755,37 @@ class Visitor(ast.NodeVisitor):
|
|
|
595
755
|
result = Num._accept_(l_val._is_py_() and l_val._as_py_() is None)
|
|
596
756
|
else:
|
|
597
757
|
result = Num._accept_(not (l_val._is_py_() and l_val._as_py_() is None))
|
|
598
|
-
elif type(op) in comp_ops and
|
|
758
|
+
elif type(op) in comp_ops and self._has_real_method(l_val, comp_ops[type(op)]):
|
|
599
759
|
result = self.handle_call(node, getattr(l_val, comp_ops[type(op)]), r_val)
|
|
600
760
|
if (
|
|
601
761
|
(result is None or self.is_not_implemented(result))
|
|
602
762
|
and type(op) in rcomp_ops
|
|
603
|
-
and
|
|
763
|
+
and self._has_real_method(r_val, rcomp_ops[type(op)])
|
|
604
764
|
):
|
|
605
765
|
result = self.handle_call(node, getattr(r_val, rcomp_ops[type(op)]), l_val)
|
|
606
766
|
if result is None or self.is_not_implemented(result):
|
|
607
|
-
|
|
767
|
+
if type(op) is ast.Eq:
|
|
768
|
+
result = Num._accept_(l_val is r_val)
|
|
769
|
+
elif type(op) is ast.NotEq:
|
|
770
|
+
result = Num._accept_(l_val is not r_val)
|
|
771
|
+
else:
|
|
772
|
+
raise TypeError(
|
|
773
|
+
f"'{op_to_symbol[type(op)]}' not supported between instances of '{type(l_val).__name__}' and "
|
|
774
|
+
f"'{type(r_val).__name__}'"
|
|
775
|
+
)
|
|
608
776
|
result = self.ensure_boolean_num(result)
|
|
609
777
|
if inverted:
|
|
610
778
|
result = result.not_()
|
|
611
779
|
curr_ctx = ctx()
|
|
612
780
|
if i == len(node.ops) - 1:
|
|
613
781
|
curr_ctx.scope.set_value(result_name, result)
|
|
782
|
+
elif result._is_py_():
|
|
783
|
+
if result._as_py_():
|
|
784
|
+
l_val = r_val
|
|
785
|
+
else:
|
|
786
|
+
false_ctxs.append(curr_ctx)
|
|
787
|
+
set_ctx(curr_ctx.into_dead())
|
|
788
|
+
break
|
|
614
789
|
else:
|
|
615
790
|
curr_ctx.test = result.ir()
|
|
616
791
|
true_ctx = curr_ctx.branch(None)
|
|
@@ -623,9 +798,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
623
798
|
return ctx().scope.get_value(result_name)
|
|
624
799
|
|
|
625
800
|
def visit_Call(self, node):
|
|
801
|
+
from sonolus.script.internal.dict_impl import DictImpl
|
|
802
|
+
|
|
626
803
|
fn = self.visit(node.func)
|
|
627
|
-
if fn is Num:
|
|
628
|
-
raise ValueError("Calling int/bool/float is not supported")
|
|
629
804
|
args = []
|
|
630
805
|
kwargs = {}
|
|
631
806
|
for arg in node.args:
|
|
@@ -638,8 +813,10 @@ class Visitor(ast.NodeVisitor):
|
|
|
638
813
|
kwargs[keyword.arg] = self.visit(keyword.value)
|
|
639
814
|
else:
|
|
640
815
|
value = self.visit(keyword.value)
|
|
641
|
-
if
|
|
642
|
-
|
|
816
|
+
if isinstance(value, DictImpl):
|
|
817
|
+
if not all(isinstance(k, str) for k in value.value):
|
|
818
|
+
raise ValueError("Keyword arguments must be strings")
|
|
819
|
+
kwargs.update(value.value)
|
|
643
820
|
else:
|
|
644
821
|
raise ValueError("Starred keyword arguments (**kwargs) must be dictionaries")
|
|
645
822
|
return self.handle_call(node, fn, *args, **kwargs)
|
|
@@ -665,11 +842,18 @@ class Visitor(ast.NodeVisitor):
|
|
|
665
842
|
raise NotImplementedError("Starred expressions are not supported")
|
|
666
843
|
|
|
667
844
|
def visit_Name(self, node):
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
845
|
+
self.active_ctx = ctx()
|
|
846
|
+
v = self
|
|
847
|
+
while v:
|
|
848
|
+
if not isinstance(v.active_ctx.scope.get_binding(node.id), EmptyBinding):
|
|
849
|
+
return v.active_ctx.scope.get_value(node.id)
|
|
850
|
+
v = v.parent
|
|
851
|
+
if node.id in self.globals:
|
|
852
|
+
value = self.globals[node.id]
|
|
853
|
+
if value is ctx:
|
|
854
|
+
raise ValueError("Unexpected use of ctx in non meta-function")
|
|
855
|
+
return validate_value(BUILTIN_IMPLS.get(id(value), value))
|
|
856
|
+
raise NameError(f"Name {node.id} is not defined")
|
|
673
857
|
|
|
674
858
|
def visit_List(self, node):
|
|
675
859
|
raise NotImplementedError("List literals are not supported")
|
|
@@ -712,8 +896,12 @@ class Visitor(ast.NodeVisitor):
|
|
|
712
896
|
ctx_init = ctx()
|
|
713
897
|
l_val = self.ensure_boolean_num(l_val)
|
|
714
898
|
|
|
715
|
-
if l_val._is_py_()
|
|
716
|
-
|
|
899
|
+
if l_val._is_py_():
|
|
900
|
+
if l_val._as_py_():
|
|
901
|
+
# The rhs is definitely evaluated, so we can return it directly
|
|
902
|
+
return self.ensure_boolean_num(self.visit(r_expr))
|
|
903
|
+
else:
|
|
904
|
+
return l_val
|
|
717
905
|
|
|
718
906
|
ctx_init.test = l_val.ir()
|
|
719
907
|
res_name = self.new_name("and")
|
|
@@ -736,8 +924,12 @@ class Visitor(ast.NodeVisitor):
|
|
|
736
924
|
ctx_init = ctx()
|
|
737
925
|
l_val = self.ensure_boolean_num(l_val)
|
|
738
926
|
|
|
739
|
-
if l_val._is_py_()
|
|
740
|
-
|
|
927
|
+
if l_val._is_py_():
|
|
928
|
+
if l_val._as_py_():
|
|
929
|
+
return l_val
|
|
930
|
+
else:
|
|
931
|
+
# The rhs is definitely evaluated, so we can return it directly
|
|
932
|
+
return self.ensure_boolean_num(self.visit(r_expr))
|
|
741
933
|
|
|
742
934
|
ctx_init.test = l_val.ir()
|
|
743
935
|
res_name = self.new_name("or")
|
|
@@ -764,7 +956,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
764
956
|
|
|
765
957
|
def handle_getattr(self, node: ast.stmt | ast.expr, target: Value, key: str) -> Value:
|
|
766
958
|
with self.reporting_errors_at_node(node):
|
|
767
|
-
if target
|
|
959
|
+
if isinstance(target, ConstantValue):
|
|
960
|
+
# Unwrap so we can access fields
|
|
768
961
|
target = target._as_py_()
|
|
769
962
|
descriptor = type(target).__dict__.get(key)
|
|
770
963
|
match descriptor:
|
|
@@ -796,6 +989,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
796
989
|
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
797
990
|
) -> R:
|
|
798
991
|
"""Handles a call to the given callable."""
|
|
992
|
+
self.active_ctx = ctx()
|
|
799
993
|
if (
|
|
800
994
|
isinstance(fn, Value)
|
|
801
995
|
and fn._is_py_()
|
|
@@ -808,13 +1002,10 @@ class Visitor(ast.NodeVisitor):
|
|
|
808
1002
|
|
|
809
1003
|
def handle_getitem(self, node: ast.stmt | ast.expr, target: Value, key: Value) -> Value:
|
|
810
1004
|
with self.reporting_errors_at_node(node):
|
|
811
|
-
if target._is_py_():
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
if isinstance(target, Value) and hasattr(target, "__getitem__"):
|
|
816
|
-
return self.handle_call(node, target.__getitem__, key)
|
|
817
|
-
raise TypeError(f"Cannot get items on {type(target).__name__}")
|
|
1005
|
+
if target._is_py_() and isinstance(target._as_py_(), type):
|
|
1006
|
+
if not key._is_py_():
|
|
1007
|
+
raise ValueError("Type parameters must be compile-time constants")
|
|
1008
|
+
return validate_value(target._as_py_()[key._as_py_()])
|
|
818
1009
|
else:
|
|
819
1010
|
if isinstance(target, Value) and hasattr(target, "__getitem__"):
|
|
820
1011
|
return self.handle_call(node, target.__getitem__, key)
|
|
@@ -822,21 +1013,19 @@ class Visitor(ast.NodeVisitor):
|
|
|
822
1013
|
|
|
823
1014
|
def handle_setitem(self, node: ast.stmt | ast.expr, target: Value, key: Value, value: Value):
|
|
824
1015
|
with self.reporting_errors_at_node(node):
|
|
825
|
-
if target
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
return self.handle_call(node, target.__setitem__, key, value)
|
|
835
|
-
raise TypeError(f"Cannot set items on {type(target).__name__}")
|
|
1016
|
+
if isinstance(target, Value) and hasattr(target, "__setitem__"):
|
|
1017
|
+
return self.handle_call(node, target.__setitem__, key, value)
|
|
1018
|
+
raise TypeError(f"Cannot set items on {type(target).__name__}")
|
|
1019
|
+
|
|
1020
|
+
def handle_delitem(self, node: ast.stmt | ast.expr, target: Value, key: Value):
|
|
1021
|
+
with self.reporting_errors_at_node(node):
|
|
1022
|
+
if isinstance(target, Value) and hasattr(target, "__delitem__"):
|
|
1023
|
+
return self.handle_call(node, target.__delitem__, key)
|
|
1024
|
+
raise TypeError(f"Cannot delete items on {type(target).__name__}")
|
|
836
1025
|
|
|
837
1026
|
def handle_starred(self, value: Value) -> tuple[Value, ...]:
|
|
838
|
-
if
|
|
839
|
-
return value.
|
|
1027
|
+
if isinstance(value, TupleImpl):
|
|
1028
|
+
return value.value
|
|
840
1029
|
raise ValueError("Unsupported starred expression")
|
|
841
1030
|
|
|
842
1031
|
def is_not_implemented(self, value):
|
|
@@ -845,10 +1034,66 @@ class Visitor(ast.NodeVisitor):
|
|
|
845
1034
|
|
|
846
1035
|
def ensure_boolean_num(self, value) -> Num:
|
|
847
1036
|
# This just checks the type for now, although we could support custom __bool__ implementations in the future
|
|
848
|
-
if not
|
|
1037
|
+
if not _is_num(value):
|
|
849
1038
|
raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
|
|
850
1039
|
return value
|
|
851
1040
|
|
|
1041
|
+
def arguments_to_signature(self, arguments: ast.arguments) -> inspect.Signature:
|
|
1042
|
+
parameters: list[inspect.Parameter] = []
|
|
1043
|
+
pos_only_count = len(arguments.posonlyargs)
|
|
1044
|
+
for i, arg in enumerate(arguments.posonlyargs):
|
|
1045
|
+
default_idx = i - pos_only_count + len(arguments.defaults)
|
|
1046
|
+
default = self.visit(arguments.defaults[default_idx]) if default_idx >= 0 else None
|
|
1047
|
+
param = inspect.Parameter(
|
|
1048
|
+
name=arg.arg,
|
|
1049
|
+
kind=inspect.Parameter.POSITIONAL_ONLY,
|
|
1050
|
+
default=default if default_idx >= 0 else inspect.Parameter.empty,
|
|
1051
|
+
annotation=inspect.Parameter.empty,
|
|
1052
|
+
)
|
|
1053
|
+
parameters.append(param)
|
|
1054
|
+
|
|
1055
|
+
pos_kw_count = len(arguments.args)
|
|
1056
|
+
for i, arg in enumerate(arguments.args):
|
|
1057
|
+
default_idx = i - pos_kw_count + len(arguments.defaults)
|
|
1058
|
+
default = self.visit(arguments.defaults[default_idx]) if default_idx >= 0 else None
|
|
1059
|
+
param = inspect.Parameter(
|
|
1060
|
+
name=arg.arg,
|
|
1061
|
+
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
1062
|
+
default=default if default_idx >= 0 else inspect.Parameter.empty,
|
|
1063
|
+
annotation=inspect.Parameter.empty,
|
|
1064
|
+
)
|
|
1065
|
+
parameters.append(param)
|
|
1066
|
+
|
|
1067
|
+
if arguments.vararg:
|
|
1068
|
+
param = inspect.Parameter(
|
|
1069
|
+
name=arguments.vararg.arg,
|
|
1070
|
+
kind=inspect.Parameter.VAR_POSITIONAL,
|
|
1071
|
+
default=inspect.Parameter.empty,
|
|
1072
|
+
annotation=inspect.Parameter.empty,
|
|
1073
|
+
)
|
|
1074
|
+
parameters.append(param)
|
|
1075
|
+
|
|
1076
|
+
for i, arg in enumerate(arguments.kwonlyargs):
|
|
1077
|
+
default = self.visit(arguments.kw_defaults[i]) if arguments.kw_defaults[i] is not None else None
|
|
1078
|
+
param = inspect.Parameter(
|
|
1079
|
+
name=arg.arg,
|
|
1080
|
+
kind=inspect.Parameter.KEYWORD_ONLY,
|
|
1081
|
+
default=default if default is not None else inspect.Parameter.empty,
|
|
1082
|
+
annotation=inspect.Parameter.empty,
|
|
1083
|
+
)
|
|
1084
|
+
parameters.append(param)
|
|
1085
|
+
|
|
1086
|
+
if arguments.kwarg:
|
|
1087
|
+
param = inspect.Parameter(
|
|
1088
|
+
name=arguments.kwarg.arg,
|
|
1089
|
+
kind=inspect.Parameter.VAR_KEYWORD,
|
|
1090
|
+
default=inspect.Parameter.empty,
|
|
1091
|
+
annotation=inspect.Parameter.empty,
|
|
1092
|
+
)
|
|
1093
|
+
parameters.append(param)
|
|
1094
|
+
|
|
1095
|
+
return inspect.Signature(parameters)
|
|
1096
|
+
|
|
852
1097
|
def raise_exception_at_node(self, node: ast.stmt | ast.expr, cause: Exception) -> Never:
|
|
853
1098
|
"""Throws a compilation error at the given node."""
|
|
854
1099
|
|
|
@@ -878,15 +1123,29 @@ class Visitor(ast.NodeVisitor):
|
|
|
878
1123
|
{"fn": fn, "args": args, "kwargs": kwargs, "_filter_traceback_": True},
|
|
879
1124
|
)
|
|
880
1125
|
|
|
881
|
-
@contextmanager
|
|
882
1126
|
def reporting_errors_at_node(self, node: ast.stmt | ast.expr):
|
|
883
|
-
|
|
884
|
-
yield
|
|
885
|
-
except CompilationError as e:
|
|
886
|
-
raise e from None
|
|
887
|
-
except Exception as e:
|
|
888
|
-
self.raise_exception_at_node(node, e)
|
|
1127
|
+
return ReportingErrorsAtNode(self, node)
|
|
889
1128
|
|
|
890
1129
|
def new_name(self, name: str):
|
|
891
1130
|
self.used_names[name] = self.used_names.get(name, 0) + 1
|
|
892
1131
|
return f"${name}_{self.used_names[name]}"
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
# Not using @contextmanager so it doesn't end up in tracebacks
|
|
1135
|
+
class ReportingErrorsAtNode:
|
|
1136
|
+
def __init__(self, compiler, node: ast.stmt | ast.expr):
|
|
1137
|
+
self.compiler = compiler
|
|
1138
|
+
self.node = node
|
|
1139
|
+
|
|
1140
|
+
def __enter__(self):
|
|
1141
|
+
return self
|
|
1142
|
+
|
|
1143
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
1144
|
+
if exc_type is None:
|
|
1145
|
+
return
|
|
1146
|
+
|
|
1147
|
+
if issubclass(exc_type, CompilationError):
|
|
1148
|
+
raise exc_value from exc_value.__cause__
|
|
1149
|
+
|
|
1150
|
+
if exc_value is not None:
|
|
1151
|
+
self.compiler.raise_exception_at_node(self.node, exc_value)
|