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.

@@ -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 ConstantNode, EngineNode, FunctionNode
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(ConstantNode(value=len(block_indexes)))
21
+ statements.append(len(block_indexes))
22
22
  case {None: target, **other} if not other:
23
- statements.append(ConstantNode(value=block_indexes[target]))
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
- ConstantNode(value=block_indexes[t_branch]),
31
- ConstantNode(value=block_indexes[f_branch]),
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
- ConstantNode(value=block_indexes[cond_branch]),
43
- ConstantNode(value=block_indexes[default_branch]),
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(ConstantNode(value=block_indexes[targets[cond]]) for cond in range(len(conds)))
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(ConstantNode(value=default))
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(ConstantNode(value=cond))
63
- args.append(ConstantNode(value=block_indexes[target]))
64
- args.append(ConstantNode(value=default))
65
- statements.append(FunctionNode(Op.SwitchWithDefault, args))
66
- block_statements.append(FunctionNode(Op.Execute, statements))
67
- block_statements.append(ConstantNode(value=0))
68
- result = FunctionNode(Op.Block, [FunctionNode(Op.JumpLoop, block_statements)])
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 ConstantNode(value=int(value))
84
+ return int(value)
85
85
  elif isfinite(value):
86
- return ConstantNode(value=value)
86
+ return value
87
87
  elif isinf(value):
88
88
  # Read values from ROM
89
- return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=1 if value > 0 else 2)])
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=[ConstantNode(value=3000), ConstantNode(value=0)])
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=[ir_to_engine_node(arg) for arg in 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 = ConstantNode(value=place.offset)
103
+ index = place.offset
104
104
  else:
105
- index = FunctionNode(
106
- func=Op.Add, args=[ir_to_engine_node(place.index), ConstantNode(value=place.offset)]
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=[*ir_to_engine_node(place).args, ir_to_engine_node(value)])
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}")
@@ -2,7 +2,7 @@ import math
2
2
  import operator
3
3
  import random
4
4
 
5
- from sonolus.backend.node import ConstantNode, EngineNode
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, ConstantNode):
28
- return node.value
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 dataclasses import dataclass, field
2
+ from typing import NamedTuple
3
3
 
4
4
  from sonolus.backend.ops import Op
5
5
 
6
- type EngineNode = ConstantNode | FunctionNode
6
+ type EngineNode = int | float | FunctionNode
7
7
 
8
8
 
9
- @dataclass(slots=True)
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: 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)))
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, ConstantNode):
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
- raise ValueError(f"Invalid engine node: {node}")
26
+ return str(node)
@@ -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
- {a: i for i, a in enumerate(archetypes)} if archetypes is not None else None,
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, getattr(archetype, cb_name))
150
+ (cb_name, cb_info, archetype._callbacks_[cb_name])
138
151
  for cb_name, cb_info in archetype._supported_callbacks_.items()
139
- if getattr(archetype, cb_name) not in archetype._default_callbacks_
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:
@@ -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
- print_simple_traceback(*exc_info)
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 ConstantNode, EngineNode, FunctionNode
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
- raise ValueError("Invalid node")
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