sonolus.py 0.11.0__py3-none-any.whl → 0.12.5__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 +30 -32
- sonolus/backend/interpret.py +3 -3
- sonolus/backend/node.py +6 -28
- sonolus/backend/visitor.py +2 -0
- sonolus/build/cli.py +7 -0
- sonolus/build/compile.py +17 -3
- sonolus/build/dev_server.py +7 -1
- sonolus/build/node.py +6 -8
- sonolus/script/archetype.py +182 -44
- sonolus/script/containers.py +6 -5
- sonolus/script/debug.py +18 -2
- sonolus/script/engine.py +48 -0
- sonolus/script/internal/builtin_impls.py +82 -3
- sonolus/script/internal/context.py +46 -5
- sonolus/script/internal/generic.py +34 -7
- sonolus/script/internal/impl.py +6 -0
- sonolus/script/internal/introspection.py +10 -1
- sonolus/script/project.py +2 -0
- sonolus/script/record.py +34 -20
- sonolus/script/stream.py +28 -21
- sonolus/script/values.py +1 -6
- {sonolus_py-0.11.0.dist-info → sonolus_py-0.12.5.dist-info}/METADATA +2 -2
- {sonolus_py-0.11.0.dist-info → sonolus_py-0.12.5.dist-info}/RECORD +26 -26
- {sonolus_py-0.11.0.dist-info → sonolus_py-0.12.5.dist-info}/WHEEL +0 -0
- {sonolus_py-0.11.0.dist-info → sonolus_py-0.12.5.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.11.0.dist-info → sonolus_py-0.12.5.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/finalize.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from math import isfinite, isinf, isnan
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
4
|
-
from sonolus.backend.node import
|
|
4
|
+
from sonolus.backend.node import EngineNode, FunctionNode
|
|
5
5
|
from sonolus.backend.ops import Op
|
|
6
6
|
from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_reverse_postorder
|
|
7
7
|
from sonolus.backend.place import BlockPlace
|
|
@@ -18,18 +18,18 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
18
18
|
}
|
|
19
19
|
match outgoing:
|
|
20
20
|
case {**other} if not other:
|
|
21
|
-
statements.append(
|
|
21
|
+
statements.append(len(block_indexes))
|
|
22
22
|
case {None: target, **other} if not other:
|
|
23
|
-
statements.append(
|
|
23
|
+
statements.append(block_indexes[target])
|
|
24
24
|
case {0: f_branch, None: t_branch, **other} if not other:
|
|
25
25
|
statements.append(
|
|
26
26
|
FunctionNode(
|
|
27
27
|
func=Op.If,
|
|
28
|
-
args=
|
|
28
|
+
args=(
|
|
29
29
|
ir_to_engine_node(block.test),
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
block_indexes[t_branch],
|
|
31
|
+
block_indexes[f_branch],
|
|
32
|
+
),
|
|
33
33
|
)
|
|
34
34
|
)
|
|
35
35
|
case {None: default_branch, **other} if len(other) == 1:
|
|
@@ -37,11 +37,11 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
37
37
|
statements.append(
|
|
38
38
|
FunctionNode(
|
|
39
39
|
func=Op.If,
|
|
40
|
-
args=
|
|
40
|
+
args=(
|
|
41
41
|
ir_to_engine_node(IRPureInstr(Op.Equal, args=[block.test, IRConst(cond)])),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
block_indexes[cond_branch],
|
|
43
|
+
block_indexes[default_branch],
|
|
44
|
+
),
|
|
45
45
|
)
|
|
46
46
|
)
|
|
47
47
|
case dict() as targets:
|
|
@@ -49,23 +49,23 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
49
49
|
default = len(block_indexes)
|
|
50
50
|
conds = [cond for cond in targets if cond is not None]
|
|
51
51
|
if min(conds) == 0 and max(conds) == len(conds) - 1 and all(int(cond) == cond for cond in conds):
|
|
52
|
-
args.extend(
|
|
52
|
+
args.extend(block_indexes[targets[cond]] for cond in range(len(conds)))
|
|
53
53
|
if None in targets:
|
|
54
54
|
default = block_indexes[targets[None]]
|
|
55
|
-
args.append(
|
|
56
|
-
statements.append(FunctionNode(Op.SwitchIntegerWithDefault, args))
|
|
55
|
+
args.append(default)
|
|
56
|
+
statements.append(FunctionNode(Op.SwitchIntegerWithDefault, tuple(args)))
|
|
57
57
|
else:
|
|
58
58
|
for cond, target in targets.items():
|
|
59
59
|
if cond is None:
|
|
60
60
|
default = block_indexes[target]
|
|
61
61
|
continue
|
|
62
|
-
args.append(
|
|
63
|
-
args.append(
|
|
64
|
-
args.append(
|
|
65
|
-
statements.append(FunctionNode(Op.SwitchWithDefault, args))
|
|
66
|
-
block_statements.append(FunctionNode(Op.Execute, statements))
|
|
67
|
-
block_statements.append(
|
|
68
|
-
result = FunctionNode(Op.Block,
|
|
62
|
+
args.append(cond)
|
|
63
|
+
args.append(block_indexes[target])
|
|
64
|
+
args.append(default)
|
|
65
|
+
statements.append(FunctionNode(Op.SwitchWithDefault, tuple(args)))
|
|
66
|
+
block_statements.append(FunctionNode(Op.Execute, tuple(statements)))
|
|
67
|
+
block_statements.append(0)
|
|
68
|
+
result = FunctionNode(Op.Block, (FunctionNode(Op.JumpLoop, tuple(block_statements)),))
|
|
69
69
|
for block in block_indexes:
|
|
70
70
|
# Clean up without relying on gc
|
|
71
71
|
del block.incoming
|
|
@@ -81,32 +81,30 @@ def ir_to_engine_node(stmt) -> EngineNode:
|
|
|
81
81
|
case int(value) | float(value) | IRConst(value=int(value) | float(value)):
|
|
82
82
|
value = float(value)
|
|
83
83
|
if value.is_integer():
|
|
84
|
-
return
|
|
84
|
+
return int(value)
|
|
85
85
|
elif isfinite(value):
|
|
86
|
-
return
|
|
86
|
+
return value
|
|
87
87
|
elif isinf(value):
|
|
88
88
|
# Read values from ROM
|
|
89
|
-
return FunctionNode(Op.Get, args=
|
|
89
|
+
return FunctionNode(Op.Get, args=(3000, 1 if value > 0 else 2))
|
|
90
90
|
elif isnan(value):
|
|
91
91
|
# Read value from ROM
|
|
92
|
-
return FunctionNode(Op.Get, args=
|
|
92
|
+
return FunctionNode(Op.Get, args=(3000, 0))
|
|
93
93
|
else:
|
|
94
94
|
raise ValueError(f"Invalid constant value: {value}")
|
|
95
95
|
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
96
|
-
return FunctionNode(func=op, args=
|
|
96
|
+
return FunctionNode(func=op, args=tuple(ir_to_engine_node(arg) for arg in args))
|
|
97
97
|
case IRGet(place=place):
|
|
98
98
|
return ir_to_engine_node(place)
|
|
99
99
|
case BlockPlace() as place:
|
|
100
100
|
if place.offset == 0:
|
|
101
101
|
index = ir_to_engine_node(place.index)
|
|
102
102
|
elif place.index == 0:
|
|
103
|
-
index =
|
|
103
|
+
index = place.offset
|
|
104
104
|
else:
|
|
105
|
-
index = FunctionNode(
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
return FunctionNode(func=Op.Get, args=[ir_to_engine_node(place.block), index])
|
|
105
|
+
index = FunctionNode(func=Op.Add, args=(ir_to_engine_node(place.index), place.offset))
|
|
106
|
+
return FunctionNode(func=Op.Get, args=(ir_to_engine_node(place.block), index))
|
|
109
107
|
case IRSet(place=place, value=value):
|
|
110
|
-
return FunctionNode(func=Op.Set, args=
|
|
108
|
+
return FunctionNode(func=Op.Set, args=(*ir_to_engine_node(place).args, ir_to_engine_node(value)))
|
|
111
109
|
case _:
|
|
112
110
|
raise TypeError(f"Unsupported IR statement: {stmt}")
|
sonolus/backend/interpret.py
CHANGED
|
@@ -2,7 +2,7 @@ import math
|
|
|
2
2
|
import operator
|
|
3
3
|
import random
|
|
4
4
|
|
|
5
|
-
from sonolus.backend.node import
|
|
5
|
+
from sonolus.backend.node import EngineNode, FunctionNode
|
|
6
6
|
from sonolus.backend.ops import Op
|
|
7
7
|
|
|
8
8
|
|
|
@@ -24,8 +24,8 @@ class Interpreter:
|
|
|
24
24
|
self.log = []
|
|
25
25
|
|
|
26
26
|
def run(self, node: EngineNode) -> float:
|
|
27
|
-
if isinstance(node,
|
|
28
|
-
return node
|
|
27
|
+
if not isinstance(node, FunctionNode):
|
|
28
|
+
return node
|
|
29
29
|
func = node.func
|
|
30
30
|
args = node.args
|
|
31
31
|
match func:
|
sonolus/backend/node.py
CHANGED
|
@@ -1,40 +1,18 @@
|
|
|
1
1
|
import textwrap
|
|
2
|
-
from
|
|
2
|
+
from typing import NamedTuple
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.ops import Op
|
|
5
5
|
|
|
6
|
-
type EngineNode =
|
|
6
|
+
type EngineNode = int | float | FunctionNode
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
class ConstantNode:
|
|
11
|
-
value: float
|
|
12
|
-
_hash: int = field(init=False, repr=False)
|
|
13
|
-
|
|
14
|
-
def __post_init__(self):
|
|
15
|
-
self._hash = hash(self.value)
|
|
16
|
-
|
|
17
|
-
def __hash__(self):
|
|
18
|
-
return hash(self.value)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass(slots=True)
|
|
22
|
-
class FunctionNode:
|
|
9
|
+
class FunctionNode(NamedTuple):
|
|
23
10
|
func: Op
|
|
24
|
-
args:
|
|
25
|
-
_hash: int = field(init=False, repr=False)
|
|
26
|
-
|
|
27
|
-
def __post_init__(self):
|
|
28
|
-
self._hash = hash((self.func, tuple(self.args)))
|
|
29
|
-
|
|
30
|
-
def __hash__(self):
|
|
31
|
-
return self._hash
|
|
11
|
+
args: tuple[EngineNode, ...]
|
|
32
12
|
|
|
33
13
|
|
|
34
14
|
def format_engine_node(node: EngineNode) -> str:
|
|
35
|
-
if isinstance(node,
|
|
36
|
-
return str(node.value)
|
|
37
|
-
elif isinstance(node, FunctionNode):
|
|
15
|
+
if isinstance(node, FunctionNode):
|
|
38
16
|
match len(node.args):
|
|
39
17
|
case 0:
|
|
40
18
|
return f"{node.func.name}()"
|
|
@@ -45,4 +23,4 @@ def format_engine_node(node: EngineNode) -> str:
|
|
|
45
23
|
textwrap.indent('\n'.join(format_engine_node(arg) for arg in node.args), ' ')
|
|
46
24
|
}\n)"
|
|
47
25
|
else:
|
|
48
|
-
|
|
26
|
+
return str(node)
|
sonolus/backend/visitor.py
CHANGED
|
@@ -1308,6 +1308,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
1308
1308
|
raise NotImplementedError(f"Unsupported syntax: {type(node).__name__}")
|
|
1309
1309
|
|
|
1310
1310
|
def handle_getattr(self, node: ast.stmt | ast.expr, target: Value, key: str) -> Value:
|
|
1311
|
+
# If this is changed, remember to update the getattr impl too
|
|
1311
1312
|
with self.reporting_errors_at_node(node):
|
|
1312
1313
|
if isinstance(target, ConstantValue):
|
|
1313
1314
|
# Unwrap so we can access fields
|
|
@@ -1328,6 +1329,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
1328
1329
|
raise TypeError(f"Unsupported field or descriptor {key}")
|
|
1329
1330
|
|
|
1330
1331
|
def handle_setattr(self, node: ast.stmt | ast.expr, target: Value, key: str, value: Value):
|
|
1332
|
+
# If this is changed, remember to update the setattr impl too
|
|
1331
1333
|
with self.reporting_errors_at_node(node):
|
|
1332
1334
|
if target._is_py_():
|
|
1333
1335
|
target = target._as_py_()
|
sonolus/build/cli.py
CHANGED
|
@@ -130,6 +130,7 @@ def get_config(args: argparse.Namespace) -> BuildConfig:
|
|
|
130
130
|
build_preview=build_preview,
|
|
131
131
|
build_tutorial=build_tutorial,
|
|
132
132
|
runtime_checks=get_runtime_checks(args),
|
|
133
|
+
verbose=hasattr(args, "verbose") and args.verbose,
|
|
133
134
|
)
|
|
134
135
|
|
|
135
136
|
|
|
@@ -175,6 +176,8 @@ def main():
|
|
|
175
176
|
build_components.add_argument("--preview", action="store_true", help="Build preview component")
|
|
176
177
|
build_components.add_argument("--tutorial", action="store_true", help="Build tutorial component")
|
|
177
178
|
|
|
179
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
|
|
180
|
+
|
|
178
181
|
build_parser = subparsers.add_parser("build")
|
|
179
182
|
build_parser.add_argument(
|
|
180
183
|
"module",
|
|
@@ -265,5 +268,9 @@ def main():
|
|
|
265
268
|
end_time = perf_counter()
|
|
266
269
|
print(f"Project validation completed successfully in {end_time - start_time:.2f}s")
|
|
267
270
|
except CompilationError:
|
|
271
|
+
if args.verbose:
|
|
272
|
+
raise
|
|
268
273
|
exc_info = sys.exc_info()
|
|
269
274
|
print_simple_traceback(*exc_info)
|
|
275
|
+
print("\nFor more details, run with the --verbose (-v) flag.")
|
|
276
|
+
sys.exit(1)
|
sonolus/build/compile.py
CHANGED
|
@@ -32,9 +32,11 @@ class CompileCache:
|
|
|
32
32
|
self._cache = {}
|
|
33
33
|
self._lock = Lock()
|
|
34
34
|
self._event = Event()
|
|
35
|
+
self._accessed_hashes = set()
|
|
35
36
|
|
|
36
37
|
def get(self, entry_hash: int) -> EngineNode | None:
|
|
37
38
|
with self._lock:
|
|
39
|
+
self._accessed_hashes.add(entry_hash)
|
|
38
40
|
if entry_hash not in self._cache:
|
|
39
41
|
self._cache[entry_hash] = None # Mark as being compiled
|
|
40
42
|
return None
|
|
@@ -47,10 +49,21 @@ class CompileCache:
|
|
|
47
49
|
|
|
48
50
|
def set(self, entry_hash: int, node: EngineNode) -> None:
|
|
49
51
|
with self._lock:
|
|
52
|
+
self._accessed_hashes.add(entry_hash)
|
|
50
53
|
self._cache[entry_hash] = node
|
|
51
54
|
self._event.set()
|
|
52
55
|
self._event.clear()
|
|
53
56
|
|
|
57
|
+
def reset_accessed(self) -> None:
|
|
58
|
+
with self._lock:
|
|
59
|
+
self._accessed_hashes.clear()
|
|
60
|
+
|
|
61
|
+
def prune_unaccessed(self) -> None:
|
|
62
|
+
with self._lock:
|
|
63
|
+
unaccessed_hashes = set(self._cache.keys()) - self._accessed_hashes
|
|
64
|
+
for h in unaccessed_hashes:
|
|
65
|
+
del self._cache[h]
|
|
66
|
+
|
|
54
67
|
|
|
55
68
|
def compile_mode(
|
|
56
69
|
mode: Mode,
|
|
@@ -67,7 +80,7 @@ def compile_mode(
|
|
|
67
80
|
|
|
68
81
|
mode_state = ModeContextState(
|
|
69
82
|
mode,
|
|
70
|
-
|
|
83
|
+
archetypes,
|
|
71
84
|
)
|
|
72
85
|
nodes = OutputNodeGenerator()
|
|
73
86
|
results = {}
|
|
@@ -134,9 +147,10 @@ def compile_mode(
|
|
|
134
147
|
archetype_data["exports"] = [*archetype._exported_keys_]
|
|
135
148
|
|
|
136
149
|
callback_items = [
|
|
137
|
-
(cb_name, cb_info,
|
|
150
|
+
(cb_name, cb_info, archetype._callbacks_[cb_name])
|
|
138
151
|
for cb_name, cb_info in archetype._supported_callbacks_.items()
|
|
139
|
-
if
|
|
152
|
+
if cb_name in archetype._callbacks_
|
|
153
|
+
and archetype._callbacks_[cb_name] not in archetype._default_callbacks_
|
|
140
154
|
]
|
|
141
155
|
|
|
142
156
|
if thread_pool is not None:
|
sonolus/build/dev_server.py
CHANGED
|
@@ -119,6 +119,7 @@ class RebuildCommand:
|
|
|
119
119
|
print("Rebuilding...")
|
|
120
120
|
try:
|
|
121
121
|
start_time = perf_counter()
|
|
122
|
+
server_state.cache.reset_accessed()
|
|
122
123
|
server_state.project_state = ProjectContextState.from_build_config(server_state.config)
|
|
123
124
|
server_state.project = project_module.project
|
|
124
125
|
build_collection(
|
|
@@ -128,11 +129,16 @@ class RebuildCommand:
|
|
|
128
129
|
cache=server_state.cache,
|
|
129
130
|
project_state=server_state.project_state,
|
|
130
131
|
)
|
|
132
|
+
server_state.cache.prune_unaccessed()
|
|
131
133
|
end_time = perf_counter()
|
|
132
134
|
print(f"Rebuild completed in {end_time - start_time:.2f} seconds")
|
|
133
135
|
except CompilationError:
|
|
134
136
|
exc_info = sys.exc_info()
|
|
135
|
-
|
|
137
|
+
if server_state.config.verbose:
|
|
138
|
+
print(traceback.format_exc())
|
|
139
|
+
else:
|
|
140
|
+
print_simple_traceback(*exc_info)
|
|
141
|
+
print("\nFor more details, run with the --verbose (-v) flag.")
|
|
136
142
|
|
|
137
143
|
|
|
138
144
|
@dataclass
|
sonolus/build/node.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from threading import Lock
|
|
2
2
|
from typing import TypedDict
|
|
3
3
|
|
|
4
|
-
from sonolus.backend.node import
|
|
4
|
+
from sonolus.backend.node import EngineNode, FunctionNode
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ValueOutputNode(TypedDict):
|
|
@@ -32,19 +32,17 @@ class OutputNodeGenerator:
|
|
|
32
32
|
return self.indexes[node]
|
|
33
33
|
|
|
34
34
|
match node:
|
|
35
|
-
case ConstantNode(value):
|
|
36
|
-
index = len(self.nodes)
|
|
37
|
-
self.nodes.append({"value": value})
|
|
38
|
-
self.indexes[node] = index
|
|
39
|
-
return index
|
|
40
35
|
case FunctionNode(func, args):
|
|
41
36
|
arg_indexes = [self._add(arg) for arg in args]
|
|
42
37
|
index = len(self.nodes)
|
|
43
38
|
self.nodes.append({"func": func.value, "args": arg_indexes})
|
|
44
39
|
self.indexes[node] = index
|
|
45
40
|
return index
|
|
46
|
-
case
|
|
47
|
-
|
|
41
|
+
case constant:
|
|
42
|
+
index = len(self.nodes)
|
|
43
|
+
self.nodes.append({"value": constant})
|
|
44
|
+
self.indexes[node] = index
|
|
45
|
+
return index
|
|
48
46
|
|
|
49
47
|
def get(self):
|
|
50
48
|
return self.nodes
|