sonolus.py 0.3.1__py3-none-any.whl → 0.3.3__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 +16 -4
- sonolus/backend/node.py +13 -5
- sonolus/backend/optimize/allocate.py +41 -4
- sonolus/backend/optimize/flow.py +24 -7
- sonolus/backend/optimize/optimize.py +2 -9
- sonolus/backend/utils.py +6 -1
- sonolus/backend/visitor.py +72 -23
- sonolus/build/cli.py +6 -1
- sonolus/build/engine.py +1 -1
- sonolus/script/archetype.py +52 -24
- sonolus/script/array.py +20 -8
- sonolus/script/array_like.py +30 -3
- sonolus/script/containers.py +27 -7
- sonolus/script/debug.py +66 -8
- sonolus/script/globals.py +17 -0
- sonolus/script/internal/builtin_impls.py +12 -8
- sonolus/script/internal/context.py +55 -1
- sonolus/script/internal/range.py +25 -2
- sonolus/script/internal/simulation_context.py +131 -0
- sonolus/script/internal/tuple_impl.py +18 -11
- sonolus/script/interval.py +60 -2
- sonolus/script/iterator.py +3 -2
- sonolus/script/num.py +11 -2
- sonolus/script/options.py +24 -1
- sonolus/script/quad.py +41 -3
- sonolus/script/record.py +24 -3
- sonolus/script/runtime.py +411 -0
- sonolus/script/stream.py +133 -16
- sonolus/script/transform.py +291 -2
- sonolus/script/values.py +9 -3
- sonolus/script/vec.py +14 -2
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/METADATA +1 -1
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/RECORD +36 -35
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/finalize.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from math import isfinite, isinf, isnan
|
|
2
|
+
|
|
1
3
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
2
4
|
from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
|
|
3
5
|
from sonolus.backend.ops import Op
|
|
@@ -54,10 +56,20 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
54
56
|
|
|
55
57
|
def ir_to_engine_node(stmt) -> EngineNode:
|
|
56
58
|
match stmt:
|
|
57
|
-
case int() | float():
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
case int(value) | float(value) | IRConst(value=int(value) | float(value)):
|
|
60
|
+
value = float(value)
|
|
61
|
+
if value.is_integer():
|
|
62
|
+
return ConstantNode(value=int(value))
|
|
63
|
+
elif isfinite(value):
|
|
64
|
+
return ConstantNode(value=value)
|
|
65
|
+
elif isinf(value):
|
|
66
|
+
# Read values from ROM
|
|
67
|
+
return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=1 if value > 0 else 2)])
|
|
68
|
+
elif isnan(value):
|
|
69
|
+
# Read value from ROM
|
|
70
|
+
return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=0)])
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Invalid constant value: {value}")
|
|
61
73
|
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
62
74
|
return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
|
|
63
75
|
case IRGet(place=place):
|
sonolus/backend/node.py
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import textwrap
|
|
2
|
-
from dataclasses import dataclass
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.ops import Op
|
|
5
5
|
|
|
6
6
|
type EngineNode = ConstantNode | FunctionNode
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@dataclass
|
|
9
|
+
@dataclass(slots=True)
|
|
10
10
|
class ConstantNode:
|
|
11
11
|
value: float
|
|
12
|
+
_hash: int = field(init=False, repr=False)
|
|
13
|
+
|
|
14
|
+
def __post_init__(self):
|
|
15
|
+
self._hash = hash(self.value)
|
|
12
16
|
|
|
13
17
|
def __hash__(self):
|
|
14
18
|
return hash(self.value)
|
|
15
19
|
|
|
16
20
|
|
|
17
|
-
@dataclass
|
|
21
|
+
@dataclass(slots=True)
|
|
18
22
|
class FunctionNode:
|
|
19
23
|
func: Op
|
|
20
24
|
args: list[EngineNode]
|
|
25
|
+
_hash: int = field(init=False, repr=False)
|
|
26
|
+
|
|
27
|
+
def __post_init__(self):
|
|
28
|
+
self._hash = hash((self.func, tuple(self.args)))
|
|
21
29
|
|
|
22
30
|
def __hash__(self):
|
|
23
|
-
return
|
|
31
|
+
return self._hash
|
|
24
32
|
|
|
25
33
|
|
|
26
34
|
def format_engine_node(node: EngineNode) -> str:
|
|
@@ -34,7 +42,7 @@ def format_engine_node(node: EngineNode) -> str:
|
|
|
34
42
|
return f"{node.func.name}({format_engine_node(node.args[0])})"
|
|
35
43
|
case _:
|
|
36
44
|
return f"{node.func.name}(\n{
|
|
37
|
-
textwrap.indent(
|
|
45
|
+
textwrap.indent('\n'.join(format_engine_node(arg) for arg in node.args), ' ')
|
|
38
46
|
}\n)"
|
|
39
47
|
else:
|
|
40
48
|
raise ValueError(f"Invalid engine node: {node}")
|
|
@@ -65,7 +65,8 @@ class Allocate(CompilerPass):
|
|
|
65
65
|
def run(self, entry: BasicBlock):
|
|
66
66
|
mapping = self.get_mapping(entry)
|
|
67
67
|
for block in traverse_cfg_preorder(entry):
|
|
68
|
-
|
|
68
|
+
updated_statements = [self.update_stmt(statement, mapping) for statement in block.statements]
|
|
69
|
+
block.statements = [stmt for stmt in updated_statements if stmt is not None]
|
|
69
70
|
block.test = self.update_stmt(block.test, mapping)
|
|
70
71
|
return entry
|
|
71
72
|
|
|
@@ -80,7 +81,19 @@ class Allocate(CompilerPass):
|
|
|
80
81
|
case IRGet(place=place):
|
|
81
82
|
return IRGet(place=self.update_stmt(place, mapping))
|
|
82
83
|
case IRSet(place=place, value=value):
|
|
83
|
-
|
|
84
|
+
# Do some dead code elimination here which is pretty much free since we already have liveness analysis,
|
|
85
|
+
# and prevents an error from the dead block place being missing from the mapping.
|
|
86
|
+
live = get_live(stmt)
|
|
87
|
+
is_live = not (
|
|
88
|
+
(isinstance(place, BlockPlace) and isinstance(place.block, TempBlock) and place.block not in live)
|
|
89
|
+
or (isinstance(value, IRGet) and place == value.place)
|
|
90
|
+
)
|
|
91
|
+
if is_live:
|
|
92
|
+
return IRSet(place=self.update_stmt(place, mapping), value=self.update_stmt(value, mapping))
|
|
93
|
+
elif isinstance(value, IRInstr) and value.op.side_effects:
|
|
94
|
+
return self.update_stmt(value, mapping)
|
|
95
|
+
else:
|
|
96
|
+
return None
|
|
84
97
|
case BlockPlace(block=block, index=index, offset=offset):
|
|
85
98
|
if isinstance(block, TempBlock):
|
|
86
99
|
if block.size == 0:
|
|
@@ -119,8 +132,32 @@ class Allocate(CompilerPass):
|
|
|
119
132
|
def get_interference(self, entry: BasicBlock) -> dict[TempBlock, set[TempBlock]]:
|
|
120
133
|
result = {}
|
|
121
134
|
for block in traverse_cfg_preorder(entry):
|
|
122
|
-
for stmt in
|
|
135
|
+
for stmt in block.statements:
|
|
136
|
+
if not isinstance(stmt, IRSet):
|
|
137
|
+
continue
|
|
123
138
|
live = {p for p in get_live(stmt) if isinstance(p, TempBlock) and p.size > 0}
|
|
124
139
|
for place in live:
|
|
125
|
-
|
|
140
|
+
if place not in result:
|
|
141
|
+
result[place] = set(live)
|
|
142
|
+
else:
|
|
143
|
+
result[place].update(live)
|
|
126
144
|
return result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class AllocateFast(Allocate):
|
|
148
|
+
"""A bit faster than Allocate but a bit less optimal."""
|
|
149
|
+
|
|
150
|
+
def get_mapping(self, entry: BasicBlock) -> dict[TempBlock, int]:
|
|
151
|
+
interference = self.get_interference(entry)
|
|
152
|
+
offsets: dict[TempBlock, int] = dict.fromkeys(interference, 0)
|
|
153
|
+
end_offsets: dict[TempBlock, int] = dict.fromkeys(interference, 0)
|
|
154
|
+
|
|
155
|
+
for block, others in interference.items():
|
|
156
|
+
size = block.size
|
|
157
|
+
offset = max((end_offsets[other] for other in others), default=0)
|
|
158
|
+
if offset + size > TEMP_SIZE:
|
|
159
|
+
raise ValueError("Temporary memory limit exceeded")
|
|
160
|
+
offsets[block] = offset
|
|
161
|
+
end_offsets[block] = offset + size
|
|
162
|
+
|
|
163
|
+
return offsets
|
sonolus/backend/optimize/flow.py
CHANGED
|
@@ -91,12 +91,29 @@ def cfg_to_mermaid(entry: BasicBlock):
|
|
|
91
91
|
|
|
92
92
|
lines = ["Entry([Entry]) --> 0"]
|
|
93
93
|
for block, index in block_indexes.items():
|
|
94
|
-
lines.append(
|
|
95
|
-
f"{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
lines.append(
|
|
95
|
+
f"{index}[{
|
|
96
|
+
pre(
|
|
97
|
+
fmt(
|
|
98
|
+
[
|
|
99
|
+
f'#{index}',
|
|
100
|
+
*(
|
|
101
|
+
f'{dst} := phi({
|
|
102
|
+
", ".join(
|
|
103
|
+
f"{block_indexes.get(src_block, '<dead>')}: {src_place}"
|
|
104
|
+
for src_block, src_place in sorted(
|
|
105
|
+
phis.items(), key=lambda x: block_indexes.get(x[0])
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
})'
|
|
109
|
+
for dst, phis in block.phis.items()
|
|
110
|
+
),
|
|
111
|
+
*block.statements,
|
|
112
|
+
]
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
}]"
|
|
116
|
+
)
|
|
100
117
|
|
|
101
118
|
outgoing = {edge.cond: edge.dst for edge in block.outgoing}
|
|
102
119
|
match outgoing:
|
|
@@ -114,7 +131,7 @@ def cfg_to_mermaid(entry: BasicBlock):
|
|
|
114
131
|
lines.append(f"{index} --> {index}_")
|
|
115
132
|
for cond, target in tgt.items():
|
|
116
133
|
lines.append(
|
|
117
|
-
f"{index}_ --> |{pre(fmt([cond if cond is not None else
|
|
134
|
+
f"{index}_ --> |{pre(fmt([cond if cond is not None else 'default']))}| {block_indexes[target]}"
|
|
118
135
|
)
|
|
119
136
|
lines.append("Exit([Exit])")
|
|
120
137
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from sonolus.backend.optimize.allocate import Allocate, AllocateBasic
|
|
1
|
+
from sonolus.backend.optimize.allocate import Allocate, AllocateBasic, AllocateFast
|
|
2
2
|
from sonolus.backend.optimize.constant_evaluation import SparseConditionalConstantPropagation
|
|
3
3
|
from sonolus.backend.optimize.copy_coalesce import CopyCoalesce
|
|
4
4
|
from sonolus.backend.optimize.dead_code import (
|
|
@@ -6,9 +6,7 @@ from sonolus.backend.optimize.dead_code import (
|
|
|
6
6
|
DeadCodeElimination,
|
|
7
7
|
UnreachableCodeElimination,
|
|
8
8
|
)
|
|
9
|
-
from sonolus.backend.optimize.flow import BasicBlock
|
|
10
9
|
from sonolus.backend.optimize.inlining import InlineVars
|
|
11
|
-
from sonolus.backend.optimize.passes import run_passes
|
|
12
10
|
from sonolus.backend.optimize.simplify import CoalesceFlow, NormalizeSwitch, RewriteToSwitch
|
|
13
11
|
from sonolus.backend.optimize.ssa import FromSSA, ToSSA
|
|
14
12
|
|
|
@@ -21,9 +19,8 @@ MINIMAL_PASSES = (
|
|
|
21
19
|
FAST_PASSES = (
|
|
22
20
|
CoalesceFlow(),
|
|
23
21
|
UnreachableCodeElimination(),
|
|
24
|
-
|
|
22
|
+
AllocateFast(), # Does dead code elimination too, so no need for a separate pass
|
|
25
23
|
CoalesceFlow(),
|
|
26
|
-
Allocate(),
|
|
27
24
|
)
|
|
28
25
|
|
|
29
26
|
STANDARD_PASSES = (
|
|
@@ -46,7 +43,3 @@ STANDARD_PASSES = (
|
|
|
46
43
|
NormalizeSwitch(),
|
|
47
44
|
Allocate(),
|
|
48
45
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def optimize_and_allocate(cfg: BasicBlock):
|
|
52
|
-
return run_passes(cfg, STANDARD_PASSES)
|
sonolus/backend/utils.py
CHANGED
|
@@ -11,10 +11,15 @@ def get_function(fn: Callable) -> tuple[str, ast.FunctionDef]:
|
|
|
11
11
|
# This preserves both line number and column number in the returned node
|
|
12
12
|
source_file = inspect.getsourcefile(fn)
|
|
13
13
|
_, start_line = inspect.getsourcelines(fn)
|
|
14
|
-
base_tree =
|
|
14
|
+
base_tree = get_tree_from_file(source_file)
|
|
15
15
|
return source_file, find_function(base_tree, start_line)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
@cache
|
|
19
|
+
def get_tree_from_file(file: str | Path) -> ast.Module:
|
|
20
|
+
return ast.parse(Path(file).read_text(encoding="utf-8"))
|
|
21
|
+
|
|
22
|
+
|
|
18
23
|
class FindFunction(ast.NodeVisitor):
|
|
19
24
|
def __init__(self, line):
|
|
20
25
|
self.line = line
|
sonolus/backend/visitor.py
CHANGED
|
@@ -4,6 +4,7 @@ import builtins
|
|
|
4
4
|
import functools
|
|
5
5
|
import inspect
|
|
6
6
|
from collections.abc import Callable, Sequence
|
|
7
|
+
from inspect import ismethod
|
|
7
8
|
from types import FunctionType, MethodType, MethodWrapperType
|
|
8
9
|
from typing import Any, Never, Self
|
|
9
10
|
|
|
@@ -54,10 +55,22 @@ def eval_fn(fn: Callable, /, *args, **kwargs):
|
|
|
54
55
|
source_file, node = get_function(fn)
|
|
55
56
|
bound_args = inspect.signature(fn).bind(*args, **kwargs)
|
|
56
57
|
bound_args.apply_defaults()
|
|
58
|
+
if ismethod(fn):
|
|
59
|
+
code = fn.__func__.__code__
|
|
60
|
+
closure = fn.__func__.__closure__
|
|
61
|
+
else:
|
|
62
|
+
code = fn.__code__
|
|
63
|
+
closure = fn.__closure__
|
|
64
|
+
if closure is None:
|
|
65
|
+
nonlocal_vars = {}
|
|
66
|
+
else:
|
|
67
|
+
nonlocal_vars = {
|
|
68
|
+
var: cell.cell_contents for var, cell in zip(code.co_freevars, closure, strict=True) if cell is not None
|
|
69
|
+
}
|
|
57
70
|
global_vars = {
|
|
58
71
|
**builtins.__dict__,
|
|
59
72
|
**fn.__globals__,
|
|
60
|
-
**
|
|
73
|
+
**nonlocal_vars,
|
|
61
74
|
}
|
|
62
75
|
return Visitor(source_file, bound_args, global_vars).run(node)
|
|
63
76
|
|
|
@@ -346,7 +359,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
346
359
|
self.loop_head_ctxs.append(header_ctx)
|
|
347
360
|
self.break_ctxs.append([])
|
|
348
361
|
set_ctx(header_ctx)
|
|
349
|
-
has_next = self.
|
|
362
|
+
has_next = self.convert_to_boolean_num(node, self.handle_call(node, iterator.has_next))
|
|
350
363
|
if has_next._is_py_() and not has_next._as_py_():
|
|
351
364
|
# The loop will never run, continue after evaluating the condition
|
|
352
365
|
self.loop_head_ctxs.pop()
|
|
@@ -387,7 +400,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
387
400
|
self.loop_head_ctxs.append(header_ctx)
|
|
388
401
|
self.break_ctxs.append([])
|
|
389
402
|
set_ctx(header_ctx)
|
|
390
|
-
test = self.
|
|
403
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
391
404
|
if test._is_py_():
|
|
392
405
|
if test._as_py_():
|
|
393
406
|
# The loop will run until a break / return
|
|
@@ -441,7 +454,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
441
454
|
set_ctx(after_ctx)
|
|
442
455
|
|
|
443
456
|
def visit_If(self, node):
|
|
444
|
-
test = self.
|
|
457
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
445
458
|
|
|
446
459
|
if test._is_py_():
|
|
447
460
|
if test._as_py_():
|
|
@@ -494,7 +507,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
494
507
|
set_ctx(false_ctx)
|
|
495
508
|
continue
|
|
496
509
|
set_ctx(true_ctx)
|
|
497
|
-
guard =
|
|
510
|
+
guard = (
|
|
511
|
+
self.convert_to_boolean_num(case.guard, self.visit(case.guard)) if case.guard else validate_value(True)
|
|
512
|
+
)
|
|
498
513
|
if guard._is_py_():
|
|
499
514
|
if guard._as_py_():
|
|
500
515
|
for stmt in case.body:
|
|
@@ -531,7 +546,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
531
546
|
match pattern:
|
|
532
547
|
case ast.MatchValue(value=value):
|
|
533
548
|
value = self.visit(value)
|
|
534
|
-
test = self.
|
|
549
|
+
test = self.convert_to_boolean_num(pattern, validate_value(subject == value))
|
|
535
550
|
if test._is_py_():
|
|
536
551
|
if test._as_py_():
|
|
537
552
|
return ctx(), ctx().into_dead()
|
|
@@ -561,7 +576,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
561
576
|
target_len = len(patterns)
|
|
562
577
|
if not (isinstance(subject, Sequence | TupleImpl)):
|
|
563
578
|
return ctx().into_dead(), ctx()
|
|
564
|
-
length_test = self.
|
|
579
|
+
length_test = self.convert_to_boolean_num(pattern, validate_value(_len(subject) == target_len))
|
|
565
580
|
ctx_init = ctx()
|
|
566
581
|
if not length_test._is_py_():
|
|
567
582
|
ctx_init.test = length_test.ir()
|
|
@@ -726,8 +741,12 @@ class Visitor(ast.NodeVisitor):
|
|
|
726
741
|
def visit_UnaryOp(self, node):
|
|
727
742
|
operand = self.visit(node.operand)
|
|
728
743
|
if isinstance(node.op, ast.Not):
|
|
729
|
-
return self.
|
|
744
|
+
return self.convert_to_boolean_num(node, operand).not_()
|
|
730
745
|
op = unary_ops[type(node.op)]
|
|
746
|
+
if operand._is_py_():
|
|
747
|
+
operand_py = operand._as_py_()
|
|
748
|
+
if isinstance(operand_py, type) and hasattr(type(operand_py), op):
|
|
749
|
+
return self.handle_call(node, getattr(type(operand_py), op), operand_py)
|
|
731
750
|
if hasattr(operand, op):
|
|
732
751
|
return self.handle_call(node, getattr(operand, op))
|
|
733
752
|
raise TypeError(f"bad operand type for unary {op_to_symbol[type(node.op)]}: '{type(operand).__name__}'")
|
|
@@ -751,7 +770,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
751
770
|
return validate_value(fn)
|
|
752
771
|
|
|
753
772
|
def visit_IfExp(self, node):
|
|
754
|
-
test = self.
|
|
773
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
755
774
|
|
|
756
775
|
if test._is_py_():
|
|
757
776
|
if test._as_py_():
|
|
@@ -831,10 +850,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
831
850
|
):
|
|
832
851
|
result = self.handle_call(node, getattr(r_val, rcomp_ops[type(op)]), l_val)
|
|
833
852
|
if result is None or self.is_not_implemented(result):
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
853
|
+
# Can't defer to the default object.__eq__ or similar since reference equality (is) is not reliable
|
|
854
|
+
if type(op) is ast.Eq and type(l_val) is not type(r_val):
|
|
855
|
+
return Num._accept_(False)
|
|
856
|
+
elif type(op) is ast.NotEq and type(l_val) is not type(r_val):
|
|
857
|
+
return Num._accept_(True)
|
|
838
858
|
else:
|
|
839
859
|
raise TypeError(
|
|
840
860
|
f"'{op_to_symbol[type(op)]}' not supported between instances of '{type(l_val).__name__}' and "
|
|
@@ -1058,7 +1078,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
1058
1078
|
|
|
1059
1079
|
def handle_call[**P, R](
|
|
1060
1080
|
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
1061
|
-
) -> R:
|
|
1081
|
+
) -> R | Value:
|
|
1062
1082
|
"""Handles a call to the given callable."""
|
|
1063
1083
|
self.active_ctx = ctx()
|
|
1064
1084
|
if (
|
|
@@ -1109,6 +1129,20 @@ class Visitor(ast.NodeVisitor):
|
|
|
1109
1129
|
raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
|
|
1110
1130
|
return value
|
|
1111
1131
|
|
|
1132
|
+
def convert_to_boolean_num(self, node, value: Value) -> Num:
|
|
1133
|
+
if _is_num(value):
|
|
1134
|
+
return value
|
|
1135
|
+
if hasattr(type(value), "__bool__"):
|
|
1136
|
+
return self.ensure_boolean_num(self.handle_call(node, type(value).__bool__, validate_value(value)))
|
|
1137
|
+
if hasattr(type(value), "__len__"):
|
|
1138
|
+
length = self.handle_call(node, type(value).__len__, validate_value(value))
|
|
1139
|
+
if not _is_num(length):
|
|
1140
|
+
raise TypeError(f"Invalid type for __len__: {type(length).__name__}")
|
|
1141
|
+
if length._is_py_():
|
|
1142
|
+
return Num._accept_(length._as_py_() > 0)
|
|
1143
|
+
return length > Num._accept_(0)
|
|
1144
|
+
raise TypeError(f"Converting {type(value).__name__} to bool is not supported")
|
|
1145
|
+
|
|
1112
1146
|
def arguments_to_signature(self, arguments: ast.arguments) -> inspect.Signature:
|
|
1113
1147
|
parameters: list[inspect.Parameter] = []
|
|
1114
1148
|
pos_only_count = len(arguments.posonlyargs)
|
|
@@ -1177,18 +1211,33 @@ class Visitor(ast.NodeVisitor):
|
|
|
1177
1211
|
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
1178
1212
|
) -> R:
|
|
1179
1213
|
"""Executes the given function at the given node for a better traceback."""
|
|
1214
|
+
location_args = {
|
|
1215
|
+
"lineno": node.lineno,
|
|
1216
|
+
"col_offset": node.col_offset,
|
|
1217
|
+
"end_lineno": node.end_lineno,
|
|
1218
|
+
"end_col_offset": node.end_col_offset,
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1180
1221
|
expr = ast.Expression(
|
|
1181
1222
|
body=ast.Call(
|
|
1182
|
-
func=ast.Name(id="fn", ctx=ast.Load()),
|
|
1183
|
-
args=[
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1223
|
+
func=ast.Name(id="fn", ctx=ast.Load(), **location_args),
|
|
1224
|
+
args=[
|
|
1225
|
+
ast.Starred(
|
|
1226
|
+
value=ast.Name(id="args", ctx=ast.Load(), **location_args),
|
|
1227
|
+
ctx=ast.Load(),
|
|
1228
|
+
**location_args,
|
|
1229
|
+
)
|
|
1230
|
+
],
|
|
1231
|
+
keywords=[
|
|
1232
|
+
ast.keyword(
|
|
1233
|
+
value=ast.Name(id="kwargs", ctx=ast.Load(), **location_args),
|
|
1234
|
+
arg=None,
|
|
1235
|
+
**location_args,
|
|
1236
|
+
)
|
|
1237
|
+
],
|
|
1238
|
+
**location_args,
|
|
1239
|
+
)
|
|
1190
1240
|
)
|
|
1191
|
-
expr = ast.fix_missing_locations(expr)
|
|
1192
1241
|
return eval(
|
|
1193
1242
|
compile(expr, filename=self.source_file, mode="eval"),
|
|
1194
1243
|
{"fn": fn, "args": args, "kwargs": kwargs, "_filter_traceback_": True},
|
sonolus/build/cli.py
CHANGED
|
@@ -11,7 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from time import perf_counter
|
|
12
12
|
|
|
13
13
|
from sonolus.backend.optimize.optimize import FAST_PASSES, MINIMAL_PASSES, STANDARD_PASSES
|
|
14
|
-
from sonolus.build.engine import package_engine
|
|
14
|
+
from sonolus.build.engine import no_gil, package_engine
|
|
15
15
|
from sonolus.build.level import package_level_data
|
|
16
16
|
from sonolus.build.project import build_project_to_collection, get_project_schema
|
|
17
17
|
from sonolus.script.project import BuildConfig, Project
|
|
@@ -215,6 +215,11 @@ def main():
|
|
|
215
215
|
else:
|
|
216
216
|
parser.error("Module argument is required when multiple or no modules are found")
|
|
217
217
|
|
|
218
|
+
if no_gil():
|
|
219
|
+
print("Multithreading is enabled")
|
|
220
|
+
if hasattr(sys, "_jit") and sys._jit.is_enabled():
|
|
221
|
+
print("Python JIT is enabled")
|
|
222
|
+
|
|
218
223
|
project = import_project(args.module)
|
|
219
224
|
if project is None:
|
|
220
225
|
sys.exit(1)
|
sonolus/build/engine.py
CHANGED
|
@@ -32,7 +32,7 @@ from sonolus.script.project import BuildConfig
|
|
|
32
32
|
from sonolus.script.sprite import Skin
|
|
33
33
|
from sonolus.script.ui import UiConfig
|
|
34
34
|
|
|
35
|
-
type JsonValue =
|
|
35
|
+
type JsonValue = bool | int | float | str | list[JsonValue] | dict[str, JsonValue] | None
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@dataclass
|