sonolus.py 0.1.3__py3-none-any.whl → 0.1.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.

Files changed (90) hide show
  1. sonolus/backend/blocks.py +756 -756
  2. sonolus/backend/excepthook.py +37 -37
  3. sonolus/backend/finalize.py +77 -69
  4. sonolus/backend/interpret.py +7 -7
  5. sonolus/backend/ir.py +29 -3
  6. sonolus/backend/mode.py +24 -24
  7. sonolus/backend/node.py +40 -40
  8. sonolus/backend/ops.py +197 -197
  9. sonolus/backend/optimize/__init__.py +0 -0
  10. sonolus/backend/optimize/allocate.py +126 -0
  11. sonolus/backend/optimize/constant_evaluation.py +374 -0
  12. sonolus/backend/optimize/copy_coalesce.py +85 -0
  13. sonolus/backend/optimize/dead_code.py +185 -0
  14. sonolus/backend/optimize/dominance.py +96 -0
  15. sonolus/backend/{flow.py → optimize/flow.py} +122 -92
  16. sonolus/backend/optimize/inlining.py +137 -0
  17. sonolus/backend/optimize/liveness.py +177 -0
  18. sonolus/backend/optimize/optimize.py +44 -0
  19. sonolus/backend/optimize/passes.py +52 -0
  20. sonolus/backend/optimize/simplify.py +191 -0
  21. sonolus/backend/optimize/ssa.py +200 -0
  22. sonolus/backend/place.py +17 -25
  23. sonolus/backend/utils.py +58 -48
  24. sonolus/backend/visitor.py +1151 -882
  25. sonolus/build/cli.py +7 -1
  26. sonolus/build/compile.py +88 -90
  27. sonolus/build/engine.py +10 -5
  28. sonolus/build/level.py +24 -23
  29. sonolus/build/node.py +43 -43
  30. sonolus/script/archetype.py +438 -139
  31. sonolus/script/array.py +27 -10
  32. sonolus/script/array_like.py +297 -0
  33. sonolus/script/bucket.py +253 -191
  34. sonolus/script/containers.py +257 -51
  35. sonolus/script/debug.py +26 -10
  36. sonolus/script/easing.py +365 -0
  37. sonolus/script/effect.py +191 -131
  38. sonolus/script/engine.py +71 -4
  39. sonolus/script/globals.py +303 -269
  40. sonolus/script/instruction.py +205 -151
  41. sonolus/script/internal/__init__.py +5 -5
  42. sonolus/script/internal/builtin_impls.py +255 -144
  43. sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
  44. sonolus/script/internal/constant.py +139 -0
  45. sonolus/script/internal/context.py +26 -9
  46. sonolus/script/internal/descriptor.py +17 -17
  47. sonolus/script/internal/dict_impl.py +65 -0
  48. sonolus/script/internal/generic.py +6 -9
  49. sonolus/script/internal/impl.py +38 -13
  50. sonolus/script/internal/introspection.py +17 -14
  51. sonolus/script/internal/math_impls.py +121 -0
  52. sonolus/script/internal/native.py +40 -38
  53. sonolus/script/internal/random.py +67 -0
  54. sonolus/script/internal/range.py +81 -0
  55. sonolus/script/internal/transient.py +51 -0
  56. sonolus/script/internal/tuple_impl.py +113 -0
  57. sonolus/script/internal/value.py +3 -3
  58. sonolus/script/interval.py +338 -112
  59. sonolus/script/iterator.py +167 -214
  60. sonolus/script/level.py +24 -0
  61. sonolus/script/num.py +80 -48
  62. sonolus/script/options.py +257 -191
  63. sonolus/script/particle.py +190 -157
  64. sonolus/script/pointer.py +30 -30
  65. sonolus/script/print.py +102 -81
  66. sonolus/script/project.py +8 -0
  67. sonolus/script/quad.py +263 -0
  68. sonolus/script/record.py +47 -16
  69. sonolus/script/runtime.py +52 -1
  70. sonolus/script/sprite.py +418 -333
  71. sonolus/script/text.py +409 -407
  72. sonolus/script/timing.py +114 -42
  73. sonolus/script/transform.py +332 -48
  74. sonolus/script/ui.py +216 -160
  75. sonolus/script/values.py +6 -13
  76. sonolus/script/vec.py +196 -78
  77. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  78. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  79. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
  80. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
  81. sonolus/backend/allocate.py +0 -51
  82. sonolus/backend/optimize.py +0 -9
  83. sonolus/backend/passes.py +0 -6
  84. sonolus/backend/simplify.py +0 -30
  85. sonolus/script/comptime.py +0 -160
  86. sonolus/script/graphics.py +0 -150
  87. sonolus/script/math.py +0 -92
  88. sonolus/script/range.py +0 -58
  89. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  90. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
@@ -1,37 +1,37 @@
1
- """Exception hook to filter out compiler internal frames from tracebacks."""
2
-
3
- import sys
4
- from types import TracebackType
5
-
6
-
7
- def should_filter_traceback(tb: TracebackType | None) -> bool:
8
- return tb is not None and (
9
- tb.tb_frame.f_globals.get("_filter_traceback_", False) or should_filter_traceback(tb.tb_next)
10
- )
11
-
12
-
13
- def is_compiler_internal(tb: TracebackType):
14
- return tb.tb_frame.f_locals.get("_compiler_internal_", False) or tb.tb_frame.f_globals.get(
15
- "_compiler_internal_", False
16
- )
17
-
18
-
19
- def filter_traceback(tb: TracebackType | None) -> TracebackType | None:
20
- if tb is None:
21
- return None
22
- if is_compiler_internal(tb):
23
- return filter_traceback(tb.tb_next)
24
- tb.tb_next = filter_traceback(tb.tb_next)
25
- return tb
26
-
27
-
28
- def excepthook(exc, value, tb):
29
- import traceback
30
-
31
- if should_filter_traceback(tb):
32
- tb = filter_traceback(tb)
33
- traceback.print_exception(exc, value, tb)
34
-
35
-
36
- def install_excepthook():
37
- sys.excepthook = excepthook
1
+ """Exception hook to filter out compiler internal frames from tracebacks."""
2
+
3
+ import sys
4
+ from types import TracebackType
5
+
6
+
7
+ def should_filter_traceback(tb: TracebackType | None) -> bool:
8
+ return tb is not None and (
9
+ tb.tb_frame.f_globals.get("_filter_traceback_", False) or should_filter_traceback(tb.tb_next)
10
+ )
11
+
12
+
13
+ def is_compiler_internal(tb: TracebackType):
14
+ return tb.tb_frame.f_locals.get("_compiler_internal_", False) or tb.tb_frame.f_globals.get(
15
+ "_compiler_internal_", False
16
+ )
17
+
18
+
19
+ def filter_traceback(tb: TracebackType | None) -> TracebackType | None:
20
+ if tb is None:
21
+ return None
22
+ if is_compiler_internal(tb):
23
+ return filter_traceback(tb.tb_next)
24
+ tb.tb_next = filter_traceback(tb.tb_next)
25
+ return tb
26
+
27
+
28
+ def excepthook(exc, value, tb):
29
+ import traceback
30
+
31
+ if should_filter_traceback(tb):
32
+ tb = filter_traceback(tb)
33
+ traceback.print_exception(exc, value, tb)
34
+
35
+
36
+ def install_excepthook():
37
+ sys.excepthook = excepthook
@@ -1,69 +1,77 @@
1
- from sonolus.backend.flow import BasicBlock, traverse_cfg_preorder
2
- from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
3
- from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
4
- from sonolus.backend.ops import Op
5
- from sonolus.backend.place import BlockPlace
6
-
7
-
8
- def cfg_to_engine_node(entry: BasicBlock):
9
- block_indexes = {block: i for i, block in enumerate(traverse_cfg_preorder(entry))}
10
- block_statements = []
11
- for block in block_indexes:
12
- statements = []
13
- statements.extend(ir_to_engine_node(stmt) for stmt in block.statements)
14
- outgoing = {edge.cond: edge.dst for edge in block.outgoing}
15
- match outgoing:
16
- case {**other} if not other:
17
- statements.append(ConstantNode(value=len(block_indexes)))
18
- case {None: target, **other} if not other:
19
- statements.append(ConstantNode(value=block_indexes[target]))
20
- case {0: f_branch, None: t_branch, **other} if not other:
21
- statements.append(
22
- FunctionNode(
23
- func=Op.If,
24
- args=[
25
- ir_to_engine_node(block.test),
26
- ConstantNode(value=block_indexes[t_branch]),
27
- ConstantNode(value=block_indexes[f_branch]),
28
- ],
29
- )
30
- )
31
- case dict() as tgt:
32
- args = [ir_to_engine_node(block.test)]
33
- default = len(block_indexes)
34
- for cond, target in tgt.items():
35
- if cond is None:
36
- default = block_indexes[target]
37
- args.append(ConstantNode(value=cond))
38
- args.append(ConstantNode(value=block_indexes[target]))
39
- args.append(ConstantNode(value=default))
40
- statements.append(FunctionNode(Op.SwitchWithDefault, args))
41
- block_statements.append(FunctionNode(Op.Execute, statements))
42
- block_statements.append(ConstantNode(value=0))
43
- return FunctionNode(Op.Block, [FunctionNode(Op.JumpLoop, block_statements)])
44
-
45
-
46
- def ir_to_engine_node(stmt) -> EngineNode:
47
- match stmt:
48
- case int() | float():
49
- return ConstantNode(value=float(stmt))
50
- case IRConst(value=value):
51
- return ConstantNode(value=value)
52
- case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
53
- return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
54
- case IRGet(place=place):
55
- return ir_to_engine_node(place)
56
- case BlockPlace() as place:
57
- if place.offset == 0:
58
- index = ir_to_engine_node(place.index)
59
- elif place.index == 0:
60
- index = ConstantNode(value=place.offset)
61
- else:
62
- index = FunctionNode(
63
- func=Op.Add, args=[ir_to_engine_node(place.index), ConstantNode(value=place.offset)]
64
- )
65
- return FunctionNode(func=Op.Get, args=[ir_to_engine_node(place.block), index])
66
- case IRSet(place=place, value=value):
67
- return FunctionNode(func=Op.Set, args=[*ir_to_engine_node(place).args, ir_to_engine_node(value)])
68
- case _:
69
- raise TypeError(f"Unsupported IR statement: {stmt}")
1
+ from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
2
+ from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
3
+ from sonolus.backend.ops import Op
4
+ from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_reverse_postorder
5
+ from sonolus.backend.place import BlockPlace
6
+
7
+
8
+ def cfg_to_engine_node(entry: BasicBlock):
9
+ block_indexes = {block: i for i, block in enumerate(traverse_cfg_reverse_postorder(entry))}
10
+ block_statements = []
11
+ for block in block_indexes:
12
+ statements = []
13
+ statements.extend(ir_to_engine_node(stmt) for stmt in block.statements)
14
+ outgoing = {edge.cond: edge.dst for edge in block.outgoing}
15
+ match outgoing:
16
+ case {**other} if not other:
17
+ statements.append(ConstantNode(value=len(block_indexes)))
18
+ case {None: target, **other} if not other:
19
+ statements.append(ConstantNode(value=block_indexes[target]))
20
+ case {0: f_branch, None: t_branch, **other} if not other:
21
+ statements.append(
22
+ FunctionNode(
23
+ func=Op.If,
24
+ args=[
25
+ ir_to_engine_node(block.test),
26
+ ConstantNode(value=block_indexes[t_branch]),
27
+ ConstantNode(value=block_indexes[f_branch]),
28
+ ],
29
+ )
30
+ )
31
+ case dict() as targets:
32
+ args = [ir_to_engine_node(block.test)]
33
+ default = len(block_indexes)
34
+ conds = [cond for cond in targets if cond is not None]
35
+ if min(conds) == 0 and max(conds) == len(conds) - 1 and all(int(cond) == cond for cond in conds):
36
+ args.extend(ConstantNode(value=block_indexes[targets[cond]]) for cond in range(len(conds)))
37
+ if None in targets:
38
+ default = block_indexes[targets[None]]
39
+ args.append(ConstantNode(value=default))
40
+ statements.append(FunctionNode(Op.SwitchIntegerWithDefault, args))
41
+ else:
42
+ for cond, target in targets.items():
43
+ if cond is None:
44
+ default = block_indexes[target]
45
+ args.append(ConstantNode(value=cond))
46
+ args.append(ConstantNode(value=block_indexes[target]))
47
+ args.append(ConstantNode(value=default))
48
+ statements.append(FunctionNode(Op.SwitchWithDefault, args))
49
+ block_statements.append(FunctionNode(Op.Execute, statements))
50
+ block_statements.append(ConstantNode(value=0))
51
+ return FunctionNode(Op.Block, [FunctionNode(Op.JumpLoop, block_statements)])
52
+
53
+
54
+ def ir_to_engine_node(stmt) -> EngineNode:
55
+ match stmt:
56
+ case int() | float():
57
+ return ConstantNode(value=float(stmt))
58
+ case IRConst(value=value):
59
+ return ConstantNode(value=value)
60
+ case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
61
+ return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
62
+ case IRGet(place=place):
63
+ return ir_to_engine_node(place)
64
+ case BlockPlace() as place:
65
+ if place.offset == 0:
66
+ index = ir_to_engine_node(place.index)
67
+ elif place.index == 0:
68
+ index = ConstantNode(value=place.offset)
69
+ else:
70
+ index = FunctionNode(
71
+ func=Op.Add, args=[ir_to_engine_node(place.index), ConstantNode(value=place.offset)]
72
+ )
73
+ return FunctionNode(func=Op.Get, args=[ir_to_engine_node(place.block), index])
74
+ case IRSet(place=place, value=value):
75
+ return FunctionNode(func=Op.Set, args=[*ir_to_engine_node(place).args, ir_to_engine_node(value)])
76
+ case _:
77
+ raise TypeError(f"Unsupported IR statement: {stmt}")
@@ -62,16 +62,16 @@ class Interpreter:
62
62
  return self.run(default)
63
63
  case Op.SwitchInteger:
64
64
  test, *branches = args
65
- test_result = int(self.run(test))
66
- if 0 <= test_result < len(branches):
65
+ test_result = self.run(test)
66
+ if 0 <= test_result < len(branches) and int(test_result) == test_result:
67
67
  return self.run(branches[test_result])
68
68
  else:
69
69
  return 0.0
70
70
  case Op.SwitchIntegerWithDefault:
71
71
  test, *branches, default = args
72
- test_result = int(self.run(test))
73
- if 0 <= test_result < len(branches):
74
- return self.run(branches[test_result])
72
+ test_result = self.run(test)
73
+ if 0 <= test_result < len(branches) and int(test_result) == test_result:
74
+ return self.run(branches[int(test_result)])
75
75
  else:
76
76
  return self.run(default)
77
77
  case Op.While:
@@ -242,10 +242,10 @@ class Interpreter:
242
242
  return math.radians(self.run(args[0]))
243
243
  case Op.Random:
244
244
  lo, hi = (self.run(arg) for arg in args)
245
- return lo + (hi - lo) * random.random()
245
+ return random.uniform(lo, hi)
246
246
  case Op.RandomInteger:
247
247
  lo, hi = (self.ensure_int(self.run(arg)) for arg in args)
248
- return random.randint(lo, hi)
248
+ return random.randrange(lo, hi)
249
249
  case Op.Rem:
250
250
  return self.reduce_args(args, math.remainder)
251
251
  case Op.Remap:
sonolus/backend/ir.py CHANGED
@@ -6,9 +6,11 @@ type IRStmt = IRExpr | IRInstr | IRSet
6
6
 
7
7
 
8
8
  class IRConst:
9
- value: float
9
+ value: float | int
10
10
 
11
11
  def __init__(self, value: float):
12
+ if isinstance(value, bool):
13
+ value = int(value)
12
14
  self.value = value
13
15
 
14
16
  def __repr__(self):
@@ -39,6 +41,12 @@ class IRPureInstr:
39
41
  def __str__(self):
40
42
  return f"{self.op.name}({', '.join(map(str, self.args))})"
41
43
 
44
+ def __eq__(self, other):
45
+ return isinstance(other, IRPureInstr) and self.op == other.op and self.args == other.args
46
+
47
+ def __hash__(self):
48
+ return hash((self.op, tuple(self.args)))
49
+
42
50
 
43
51
  class IRInstr:
44
52
  op: Op
@@ -54,6 +62,12 @@ class IRInstr:
54
62
  def __str__(self):
55
63
  return f"{self.op.name}({', '.join(map(str, self.args))})"
56
64
 
65
+ def __eq__(self, other):
66
+ return isinstance(other, IRInstr) and self.op == other.op and self.args == other.args
67
+
68
+ def __hash__(self):
69
+ return hash((self.op, tuple(self.args)))
70
+
57
71
 
58
72
  class IRGet:
59
73
  place: Place
@@ -67,12 +81,18 @@ class IRGet:
67
81
  def __str__(self):
68
82
  return f"{self.place}"
69
83
 
84
+ def __eq__(self, other):
85
+ return isinstance(other, IRGet) and self.place == other.place
86
+
87
+ def __hash__(self):
88
+ return hash(self.place)
89
+
70
90
 
71
91
  class IRSet:
72
92
  place: Place
73
- value: IRStmt
93
+ value: IRExpr | IRInstr
74
94
 
75
- def __init__(self, place: Place, value: IRStmt):
95
+ def __init__(self, place: Place, value: IRExpr | IRInstr):
76
96
  self.place = place
77
97
  self.value = value
78
98
 
@@ -87,3 +107,9 @@ class IRSet:
87
107
  return f"{self.place} := {self.value}"
88
108
  case _:
89
109
  raise TypeError(f"Invalid place: {self.place}")
110
+
111
+ def __eq__(self, other):
112
+ return isinstance(other, IRSet) and self.place == other.place and self.value == other.value
113
+
114
+ def __hash__(self):
115
+ return hash((self.place, self.value))
sonolus/backend/mode.py CHANGED
@@ -1,24 +1,24 @@
1
- from enum import Enum
2
- from functools import cached_property
3
-
4
- from sonolus.backend.blocks import Block, PlayBlock, PreviewBlock, TutorialBlock, WatchBlock
5
-
6
-
7
- class Mode(Enum):
8
- blocks: type[Block]
9
-
10
- PLAY = (PlayBlock,)
11
- WATCH = (WatchBlock,)
12
- PREVIEW = (PreviewBlock,)
13
- TUTORIAL = (TutorialBlock,)
14
-
15
- def __init__(self, blocks: type[Block]):
16
- self.blocks = blocks
17
-
18
- @cached_property
19
- def callbacks(self) -> frozenset[str]:
20
- cbs = set()
21
- for block in self.blocks:
22
- cbs.update(block.readable)
23
- cbs.update(block.writable)
24
- return frozenset(cbs)
1
+ from enum import Enum
2
+ from functools import cached_property
3
+
4
+ from sonolus.backend.blocks import Block, PlayBlock, PreviewBlock, TutorialBlock, WatchBlock
5
+
6
+
7
+ class Mode(Enum):
8
+ blocks: type[Block]
9
+
10
+ PLAY = (PlayBlock,)
11
+ WATCH = (WatchBlock,)
12
+ PREVIEW = (PreviewBlock,)
13
+ TUTORIAL = (TutorialBlock,)
14
+
15
+ def __init__(self, blocks: type[Block]):
16
+ self.blocks = blocks
17
+
18
+ @cached_property
19
+ def callbacks(self) -> frozenset[str]:
20
+ cbs = set()
21
+ for block in self.blocks:
22
+ cbs.update(block.readable)
23
+ cbs.update(block.writable)
24
+ return frozenset(cbs)
sonolus/backend/node.py CHANGED
@@ -1,40 +1,40 @@
1
- import textwrap
2
- from dataclasses import dataclass
3
-
4
- from sonolus.backend.ops import Op
5
-
6
- type EngineNode = ConstantNode | FunctionNode
7
-
8
-
9
- @dataclass
10
- class ConstantNode:
11
- value: float
12
-
13
- def __hash__(self):
14
- return hash(self.value)
15
-
16
-
17
- @dataclass
18
- class FunctionNode:
19
- func: Op
20
- args: list[EngineNode]
21
-
22
- def __hash__(self):
23
- return hash((self.func, tuple(self.args)))
24
-
25
-
26
- def format_engine_node(node: EngineNode) -> str:
27
- if isinstance(node, ConstantNode):
28
- return str(node.value)
29
- elif isinstance(node, FunctionNode):
30
- match len(node.args):
31
- case 0:
32
- return f"{node.func.name}()"
33
- case 1:
34
- return f"{node.func.name}({format_engine_node(node.args[0])})"
35
- case _:
36
- return f"{node.func.name}(\n{
37
- textwrap.indent("\n".join(format_engine_node(arg) for arg in node.args), " ")
38
- }\n)"
39
- else:
40
- raise ValueError(f"Invalid engine node: {node}")
1
+ import textwrap
2
+ from dataclasses import dataclass
3
+
4
+ from sonolus.backend.ops import Op
5
+
6
+ type EngineNode = ConstantNode | FunctionNode
7
+
8
+
9
+ @dataclass
10
+ class ConstantNode:
11
+ value: float
12
+
13
+ def __hash__(self):
14
+ return hash(self.value)
15
+
16
+
17
+ @dataclass
18
+ class FunctionNode:
19
+ func: Op
20
+ args: list[EngineNode]
21
+
22
+ def __hash__(self):
23
+ return hash((self.func, tuple(self.args)))
24
+
25
+
26
+ def format_engine_node(node: EngineNode) -> str:
27
+ if isinstance(node, ConstantNode):
28
+ return str(node.value)
29
+ elif isinstance(node, FunctionNode):
30
+ match len(node.args):
31
+ case 0:
32
+ return f"{node.func.name}()"
33
+ case 1:
34
+ return f"{node.func.name}({format_engine_node(node.args[0])})"
35
+ case _:
36
+ return f"{node.func.name}(\n{
37
+ textwrap.indent("\n".join(format_engine_node(arg) for arg in node.args), " ")
38
+ }\n)"
39
+ else:
40
+ raise ValueError(f"Invalid engine node: {node}")