sonolus.py 0.1.0__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/__init__.py +0 -0
- sonolus/backend/__init__.py +0 -0
- sonolus/backend/allocate.py +51 -0
- sonolus/backend/blocks.py +756 -0
- sonolus/backend/excepthook.py +37 -0
- sonolus/backend/finalize.py +69 -0
- sonolus/backend/flow.py +92 -0
- sonolus/backend/interpret.py +333 -0
- sonolus/backend/ir.py +89 -0
- sonolus/backend/mode.py +24 -0
- sonolus/backend/node.py +40 -0
- sonolus/backend/ops.py +197 -0
- sonolus/backend/optimize.py +9 -0
- sonolus/backend/passes.py +6 -0
- sonolus/backend/place.py +90 -0
- sonolus/backend/simplify.py +30 -0
- sonolus/backend/utils.py +48 -0
- sonolus/backend/visitor.py +880 -0
- sonolus/build/__init__.py +0 -0
- sonolus/build/cli.py +170 -0
- sonolus/build/collection.py +293 -0
- sonolus/build/compile.py +90 -0
- sonolus/build/defaults.py +32 -0
- sonolus/build/engine.py +149 -0
- sonolus/build/level.py +23 -0
- sonolus/build/node.py +43 -0
- sonolus/build/project.py +94 -0
- sonolus/py.typed +0 -0
- sonolus/script/__init__.py +0 -0
- sonolus/script/archetype.py +651 -0
- sonolus/script/array.py +241 -0
- sonolus/script/bucket.py +192 -0
- sonolus/script/callbacks.py +105 -0
- sonolus/script/comptime.py +146 -0
- sonolus/script/containers.py +247 -0
- sonolus/script/debug.py +70 -0
- sonolus/script/effect.py +132 -0
- sonolus/script/engine.py +101 -0
- sonolus/script/globals.py +234 -0
- sonolus/script/graphics.py +141 -0
- sonolus/script/icon.py +73 -0
- sonolus/script/internal/__init__.py +5 -0
- sonolus/script/internal/builtin_impls.py +144 -0
- sonolus/script/internal/context.py +365 -0
- sonolus/script/internal/descriptor.py +17 -0
- sonolus/script/internal/error.py +15 -0
- sonolus/script/internal/generic.py +197 -0
- sonolus/script/internal/impl.py +69 -0
- sonolus/script/internal/introspection.py +14 -0
- sonolus/script/internal/native.py +38 -0
- sonolus/script/internal/value.py +144 -0
- sonolus/script/interval.py +98 -0
- sonolus/script/iterator.py +211 -0
- sonolus/script/level.py +52 -0
- sonolus/script/math.py +92 -0
- sonolus/script/num.py +382 -0
- sonolus/script/options.py +194 -0
- sonolus/script/particle.py +158 -0
- sonolus/script/pointer.py +30 -0
- sonolus/script/project.py +17 -0
- sonolus/script/range.py +58 -0
- sonolus/script/record.py +293 -0
- sonolus/script/runtime.py +526 -0
- sonolus/script/sprite.py +332 -0
- sonolus/script/text.py +404 -0
- sonolus/script/timing.py +42 -0
- sonolus/script/transform.py +118 -0
- sonolus/script/ui.py +160 -0
- sonolus/script/values.py +43 -0
- sonolus/script/vec.py +48 -0
- sonolus_py-0.1.0.dist-info/METADATA +10 -0
- sonolus_py-0.1.0.dist-info/RECORD +75 -0
- sonolus_py-0.1.0.dist-info/WHEEL +4 -0
- sonolus_py-0.1.0.dist-info/entry_points.txt +2 -0
- sonolus_py-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +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
|
|
@@ -0,0 +1,69 @@
|
|
|
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}")
|
sonolus/backend/flow.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
from collections import deque
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
|
|
5
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRStmt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FlowEdge:
|
|
9
|
+
src: "BasicBlock"
|
|
10
|
+
dst: "BasicBlock"
|
|
11
|
+
cond: IRExpr | None
|
|
12
|
+
|
|
13
|
+
def __init__(self, src: "BasicBlock", dst: "BasicBlock", cond: IRExpr | None = None):
|
|
14
|
+
self.src = src
|
|
15
|
+
self.dst = dst
|
|
16
|
+
self.cond = cond
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BasicBlock:
|
|
20
|
+
statements: list[IRStmt]
|
|
21
|
+
test: IRExpr
|
|
22
|
+
incoming: set[FlowEdge]
|
|
23
|
+
outgoing: set[FlowEdge]
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
statements: list[IRStmt] | None = None,
|
|
28
|
+
test: IRExpr | None = None,
|
|
29
|
+
incoming: set[FlowEdge] | None = None,
|
|
30
|
+
outgoing: set[FlowEdge] | None = None,
|
|
31
|
+
):
|
|
32
|
+
self.statements = statements or []
|
|
33
|
+
self.test = test or IRConst(1)
|
|
34
|
+
self.incoming = incoming or set()
|
|
35
|
+
self.outgoing = outgoing or set()
|
|
36
|
+
|
|
37
|
+
def connect_to(self, other: "BasicBlock", cond: IRExpr | None = None):
|
|
38
|
+
edge = FlowEdge(self, other, cond)
|
|
39
|
+
self.outgoing.add(edge)
|
|
40
|
+
other.incoming.add(edge)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def traverse_cfg_preorder(block: BasicBlock) -> Iterator[BasicBlock]:
|
|
44
|
+
visited = set()
|
|
45
|
+
queue = deque([block])
|
|
46
|
+
while queue:
|
|
47
|
+
block = queue.popleft()
|
|
48
|
+
if block in visited:
|
|
49
|
+
continue
|
|
50
|
+
visited.add(block)
|
|
51
|
+
yield block
|
|
52
|
+
for edge in sorted(block.outgoing, key=lambda e: (e.cond is None, e.cond)):
|
|
53
|
+
queue.append(edge.dst)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cfg_to_mermaid(entry: BasicBlock):
|
|
57
|
+
def pre(s: str):
|
|
58
|
+
return "\"<pre style='text-align: left;'>" + s.replace("\n", "<br/>") + '</pre>"'
|
|
59
|
+
|
|
60
|
+
def fmt(nodes):
|
|
61
|
+
if nodes:
|
|
62
|
+
return "\n".join(str(n) for n in nodes)
|
|
63
|
+
else:
|
|
64
|
+
return "{}"
|
|
65
|
+
|
|
66
|
+
block_indexes = {block: i for i, block in enumerate(traverse_cfg_preorder(entry))}
|
|
67
|
+
|
|
68
|
+
lines = ["Entry([Entry]) --> 0"]
|
|
69
|
+
for block in traverse_cfg_preorder(entry):
|
|
70
|
+
index = block_indexes[block]
|
|
71
|
+
lines.append(f"{index}[{pre(fmt([f'#{index}', *block.statements]))}]")
|
|
72
|
+
|
|
73
|
+
outgoing = {edge.cond: edge.dst for edge in block.outgoing}
|
|
74
|
+
match outgoing:
|
|
75
|
+
case {**other} if not other:
|
|
76
|
+
lines.append(f"{index} --> Exit")
|
|
77
|
+
case {None: target, **other} if not other:
|
|
78
|
+
lines.append(f"{index} --> {block_indexes[target]}")
|
|
79
|
+
case {0: f_branch, None: t_branch, **other} if not other:
|
|
80
|
+
lines.append(f"{index}_{{{pre(fmt([block.test]))}}}")
|
|
81
|
+
lines.append(f"{index} --> {index}_")
|
|
82
|
+
lines.append(f"{index}_ --> |true| {block_indexes[t_branch]}")
|
|
83
|
+
lines.append(f"{index}_ --> |false| {block_indexes[f_branch]}")
|
|
84
|
+
case dict() as tgt:
|
|
85
|
+
lines.append(f"{index}_{{{{{pre(fmt([block.test]))}}}}}")
|
|
86
|
+
lines.append(f"{index} --> {index}_")
|
|
87
|
+
for cond, target in tgt.items():
|
|
88
|
+
lines.append(f"{index}_ --> |{pre(fmt([cond or "default"]))}| {block_indexes[target]}")
|
|
89
|
+
lines.append("Exit([Exit])")
|
|
90
|
+
|
|
91
|
+
body = textwrap.indent("\n".join(lines), " ")
|
|
92
|
+
return f"graph\n{body}"
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import operator
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from sonolus.backend.node import ConstantNode, EngineNode
|
|
6
|
+
from sonolus.backend.ops import Op
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BreakException(Exception): # noqa: N818
|
|
10
|
+
n: int
|
|
11
|
+
value: float
|
|
12
|
+
|
|
13
|
+
def __init__(self, n: int, value: float):
|
|
14
|
+
self.n = n
|
|
15
|
+
self.value = value
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Interpreter:
|
|
19
|
+
blocks: dict[int, list[int]]
|
|
20
|
+
log: list[float]
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.blocks = {}
|
|
24
|
+
self.log = []
|
|
25
|
+
|
|
26
|
+
def run(self, node: EngineNode) -> float:
|
|
27
|
+
if isinstance(node, ConstantNode):
|
|
28
|
+
return node.value
|
|
29
|
+
func = node.func
|
|
30
|
+
args = node.args
|
|
31
|
+
match func:
|
|
32
|
+
case Op.Execute:
|
|
33
|
+
result = 0.0
|
|
34
|
+
for arg in args:
|
|
35
|
+
result = self.run(arg)
|
|
36
|
+
return result
|
|
37
|
+
case Op.Execute0:
|
|
38
|
+
for arg in args:
|
|
39
|
+
self.run(arg)
|
|
40
|
+
return 0.0
|
|
41
|
+
case Op.If:
|
|
42
|
+
test, t_branch, f_branch = args
|
|
43
|
+
if self.run(test) != 0.0:
|
|
44
|
+
return self.run(t_branch)
|
|
45
|
+
else:
|
|
46
|
+
return self.run(f_branch)
|
|
47
|
+
case Op.Switch:
|
|
48
|
+
test, *branches = args
|
|
49
|
+
test_result = self.run(test)
|
|
50
|
+
for i in range(0, len(branches), 2):
|
|
51
|
+
case, branch = branches[i], branches[i + 1]
|
|
52
|
+
if test_result == self.run(case):
|
|
53
|
+
return self.run(branch)
|
|
54
|
+
return 0.0
|
|
55
|
+
case Op.SwitchWithDefault:
|
|
56
|
+
test, *branches, default = args
|
|
57
|
+
test_result = self.run(test)
|
|
58
|
+
for i in range(0, len(branches), 2):
|
|
59
|
+
case, branch = branches[i], branches[i + 1]
|
|
60
|
+
if test_result == self.run(case):
|
|
61
|
+
return self.run(branch)
|
|
62
|
+
return self.run(default)
|
|
63
|
+
case Op.SwitchInteger:
|
|
64
|
+
test, *branches = args
|
|
65
|
+
test_result = int(self.run(test))
|
|
66
|
+
if 0 <= test_result < len(branches):
|
|
67
|
+
return self.run(branches[test_result])
|
|
68
|
+
else:
|
|
69
|
+
return 0.0
|
|
70
|
+
case Op.SwitchIntegerWithDefault:
|
|
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])
|
|
75
|
+
else:
|
|
76
|
+
return self.run(default)
|
|
77
|
+
case Op.While:
|
|
78
|
+
test, body = args
|
|
79
|
+
while self.run(test) != 0.0:
|
|
80
|
+
self.run(body)
|
|
81
|
+
return 0.0
|
|
82
|
+
case Op.DoWhile:
|
|
83
|
+
body, test = args
|
|
84
|
+
while True:
|
|
85
|
+
self.run(body)
|
|
86
|
+
if self.run(test) == 0.0:
|
|
87
|
+
break
|
|
88
|
+
return 0.0
|
|
89
|
+
case Op.And:
|
|
90
|
+
result = 0.0
|
|
91
|
+
for arg in args:
|
|
92
|
+
result = self.run(arg)
|
|
93
|
+
if result == 0.0:
|
|
94
|
+
break
|
|
95
|
+
return result
|
|
96
|
+
case Op.Or:
|
|
97
|
+
result = 0.0
|
|
98
|
+
for arg in args:
|
|
99
|
+
result = self.run(arg)
|
|
100
|
+
if result != 0.0:
|
|
101
|
+
break
|
|
102
|
+
return result
|
|
103
|
+
case Op.JumpLoop:
|
|
104
|
+
index = 0
|
|
105
|
+
while 0 <= index < len(args):
|
|
106
|
+
if index == len(args) - 1:
|
|
107
|
+
return self.run(args[index])
|
|
108
|
+
index = int(self.run(args[index]))
|
|
109
|
+
return 0.0
|
|
110
|
+
case Op.Block:
|
|
111
|
+
try:
|
|
112
|
+
return self.run(args[0])
|
|
113
|
+
except BreakException as e:
|
|
114
|
+
if e.n > 1:
|
|
115
|
+
e.n -= 1
|
|
116
|
+
raise e from None
|
|
117
|
+
return e.value
|
|
118
|
+
case Op.Break:
|
|
119
|
+
raise BreakException(self.ensure_int(self.run(args[0])), self.run(args[1]))
|
|
120
|
+
|
|
121
|
+
case Op.Abs:
|
|
122
|
+
return abs(self.run(args[0]))
|
|
123
|
+
case Op.Add:
|
|
124
|
+
return self.reduce_args(args, operator.add)
|
|
125
|
+
case Op.Arccos:
|
|
126
|
+
return math.acos(self.run(args[0]))
|
|
127
|
+
case Op.Arcsin:
|
|
128
|
+
return math.asin(self.run(args[0]))
|
|
129
|
+
case Op.Arctan:
|
|
130
|
+
return math.atan(self.run(args[0]))
|
|
131
|
+
case Op.Arctan2:
|
|
132
|
+
return math.atan2(self.run(args[0]), self.run(args[1]))
|
|
133
|
+
case Op.Ceil:
|
|
134
|
+
return math.ceil(self.run(args[0]))
|
|
135
|
+
case Op.Clamp:
|
|
136
|
+
x, a, b = (self.run(arg) for arg in args)
|
|
137
|
+
return max(a, min(b, x))
|
|
138
|
+
case Op.Copy:
|
|
139
|
+
src_id, src_index, dst_id, dst_index, count = (self.ensure_int(self.run(arg)) for arg in args)
|
|
140
|
+
assert count >= 0, "Count must be non-negative"
|
|
141
|
+
values = [self.get(src_id, src_index + i) for i in range(count)]
|
|
142
|
+
for i, value in enumerate(values):
|
|
143
|
+
self.set(dst_id, dst_index + i, value)
|
|
144
|
+
return 0.0
|
|
145
|
+
case Op.Cos:
|
|
146
|
+
return math.cos(self.run(args[0]))
|
|
147
|
+
case Op.Cosh:
|
|
148
|
+
return math.cosh(self.run(args[0]))
|
|
149
|
+
case Op.DebugLog:
|
|
150
|
+
value = self.run(args[0])
|
|
151
|
+
self.log.append(value)
|
|
152
|
+
return 0.0
|
|
153
|
+
case Op.DebugPause:
|
|
154
|
+
return 0.0
|
|
155
|
+
case Op.Degree:
|
|
156
|
+
return math.degrees(self.run(args[0]))
|
|
157
|
+
case Op.Divide:
|
|
158
|
+
return self.reduce_args(args, operator.truediv)
|
|
159
|
+
case Op.Equal:
|
|
160
|
+
return 1.0 if self.run(args[0]) == self.run(args[1]) else 0.0
|
|
161
|
+
case Op.Floor:
|
|
162
|
+
return math.floor(self.run(args[0]))
|
|
163
|
+
case Op.Frac:
|
|
164
|
+
result = self.run(args[0]) % 1
|
|
165
|
+
return result if result >= 0 else result + 1
|
|
166
|
+
case Op.Get:
|
|
167
|
+
block, index = (self.ensure_int(self.run(arg)) for arg in args)
|
|
168
|
+
return self.get(block, index)
|
|
169
|
+
case Op.GetPointed:
|
|
170
|
+
block, index, offset = (self.ensure_int(self.run(arg)) for arg in args)
|
|
171
|
+
return self.get(self.get(block, index), self.get(block, index + 1) + offset)
|
|
172
|
+
case Op.GetShifted:
|
|
173
|
+
block, offset, index, stride = (self.ensure_int(self.run(arg)) for arg in args)
|
|
174
|
+
return self.get(block, offset + index * stride)
|
|
175
|
+
case Op.Greater:
|
|
176
|
+
return 1.0 if self.run(args[0]) > self.run(args[1]) else 0.0
|
|
177
|
+
case Op.GreaterOr:
|
|
178
|
+
return 1.0 if self.run(args[0]) >= self.run(args[1]) else 0.0
|
|
179
|
+
case Op.IncrementPost:
|
|
180
|
+
block, index = (self.ensure_int(self.run(arg)) for arg in args)
|
|
181
|
+
value = self.get(block, index)
|
|
182
|
+
self.set(block, index, value + 1)
|
|
183
|
+
return value
|
|
184
|
+
case Op.IncrementPostPointed:
|
|
185
|
+
block, index, offset = (self.ensure_int(self.run(arg)) for arg in args)
|
|
186
|
+
deref_block = self.get(block, index)
|
|
187
|
+
deref_index = self.get(block, index + 1) + offset
|
|
188
|
+
value = self.get(deref_block, deref_index)
|
|
189
|
+
self.set(deref_block, deref_index, value + 1)
|
|
190
|
+
return value
|
|
191
|
+
case Op.IncrementPostShifted:
|
|
192
|
+
block, offset, index, stride = (self.ensure_int(self.run(arg)) for arg in args)
|
|
193
|
+
value = self.get(block, offset + index * stride)
|
|
194
|
+
self.set(block, offset + index * stride, value + 1)
|
|
195
|
+
return value
|
|
196
|
+
case Op.IncrementPre:
|
|
197
|
+
block, index = (self.ensure_int(self.run(arg)) for arg in args)
|
|
198
|
+
value = self.get(block, index) + 1
|
|
199
|
+
self.set(block, index, value)
|
|
200
|
+
return value
|
|
201
|
+
case Op.IncrementPrePointed:
|
|
202
|
+
block, index, offset = (self.ensure_int(self.run(arg)) for arg in args)
|
|
203
|
+
deref_block = self.get(block, index)
|
|
204
|
+
deref_index = self.get(block, index + 1) + offset
|
|
205
|
+
value = self.get(deref_block, deref_index) + 1
|
|
206
|
+
self.set(deref_block, deref_index, value)
|
|
207
|
+
return value
|
|
208
|
+
case Op.IncrementPreShifted:
|
|
209
|
+
block, offset, index, stride = (self.ensure_int(self.run(arg)) for arg in args)
|
|
210
|
+
value = self.get(block, offset + index * stride) + 1
|
|
211
|
+
self.set(block, offset + index * stride, value)
|
|
212
|
+
return value
|
|
213
|
+
case Op.Lerp:
|
|
214
|
+
x, y, s = (self.run(arg) for arg in args)
|
|
215
|
+
return x + (y - x) * s
|
|
216
|
+
case Op.LerpClamped:
|
|
217
|
+
x, y, s = (self.run(arg) for arg in args)
|
|
218
|
+
return x + (y - x) * max(0, min(1, s))
|
|
219
|
+
case Op.Less:
|
|
220
|
+
return 1.0 if self.run(args[0]) < self.run(args[1]) else 0.0
|
|
221
|
+
case Op.LessOr:
|
|
222
|
+
return 1.0 if self.run(args[0]) <= self.run(args[1]) else 0.0
|
|
223
|
+
case Op.Log:
|
|
224
|
+
return math.log(self.run(args[0]))
|
|
225
|
+
case Op.Max:
|
|
226
|
+
return max(self.run(args[0]), self.run(args[1]))
|
|
227
|
+
case Op.Min:
|
|
228
|
+
return min(self.run(args[0]), self.run(args[1]))
|
|
229
|
+
case Op.Mod:
|
|
230
|
+
return self.reduce_args(args, operator.mod)
|
|
231
|
+
case Op.Multiply:
|
|
232
|
+
return self.reduce_args(args, operator.mul)
|
|
233
|
+
case Op.Negate:
|
|
234
|
+
return -self.run(args[0])
|
|
235
|
+
case Op.Not:
|
|
236
|
+
return 1.0 if self.run(args[0]) == 0.0 else 0.0
|
|
237
|
+
case Op.NotEqual:
|
|
238
|
+
return 1.0 if self.run(args[0]) != self.run(args[1]) else 0.0
|
|
239
|
+
case Op.Power:
|
|
240
|
+
return self.reduce_args(args, operator.pow)
|
|
241
|
+
case Op.Radian:
|
|
242
|
+
return math.radians(self.run(args[0]))
|
|
243
|
+
case Op.Random:
|
|
244
|
+
lo, hi = (self.run(arg) for arg in args)
|
|
245
|
+
return lo + (hi - lo) * random.random()
|
|
246
|
+
case Op.RandomInteger:
|
|
247
|
+
lo, hi = (self.ensure_int(self.run(arg)) for arg in args)
|
|
248
|
+
return random.randint(lo, hi)
|
|
249
|
+
case Op.Rem:
|
|
250
|
+
return self.reduce_args(args, math.remainder)
|
|
251
|
+
case Op.Remap:
|
|
252
|
+
from_min, from_max, to_min, to_max, value = (self.run(arg) for arg in args)
|
|
253
|
+
return to_min + (to_max - to_min) * (value - from_min) / (from_max - from_min)
|
|
254
|
+
case Op.RemapClamped:
|
|
255
|
+
from_min, from_max, to_min, to_max, value = (self.run(arg) for arg in args)
|
|
256
|
+
return to_min + (to_max - to_min) * max(0, min(1, (value - from_min) / (from_max - from_min)))
|
|
257
|
+
case Op.Round:
|
|
258
|
+
return round(self.run(args[0]))
|
|
259
|
+
case Op.Set:
|
|
260
|
+
block, index, value = (self.run(arg) for arg in args)
|
|
261
|
+
block, index = self.ensure_int(block), self.ensure_int(index)
|
|
262
|
+
return self.set(block, index, value)
|
|
263
|
+
case Op.SetPointed:
|
|
264
|
+
block, index, offset, value = (self.run(arg) for arg in args)
|
|
265
|
+
block, index, offset = self.ensure_int(block), self.ensure_int(index), self.ensure_int(offset)
|
|
266
|
+
return self.set(self.get(block, index), self.get(block, index + 1) + offset, value)
|
|
267
|
+
case Op.SetShifted:
|
|
268
|
+
block, offset, index, stride, value = (self.run(arg) for arg in args)
|
|
269
|
+
block, offset, index, stride = (
|
|
270
|
+
self.ensure_int(block),
|
|
271
|
+
self.ensure_int(offset),
|
|
272
|
+
self.ensure_int(index),
|
|
273
|
+
self.ensure_int(stride),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return self.set(block, offset + index * stride, value)
|
|
277
|
+
case Op.Sign:
|
|
278
|
+
return math.copysign(1, self.run(args[0]))
|
|
279
|
+
case Op.Sin:
|
|
280
|
+
return math.sin(self.run(args[0]))
|
|
281
|
+
case Op.Sinh:
|
|
282
|
+
return math.sinh(self.run(args[0]))
|
|
283
|
+
case Op.Subtract:
|
|
284
|
+
return self.reduce_args(args, operator.sub)
|
|
285
|
+
case Op.Tan:
|
|
286
|
+
return math.tan(self.run(args[0]))
|
|
287
|
+
case Op.Tanh:
|
|
288
|
+
return math.tanh(self.run(args[0]))
|
|
289
|
+
case Op.Trunc:
|
|
290
|
+
return math.trunc(self.run(args[0]))
|
|
291
|
+
case Op.Unlerp:
|
|
292
|
+
lo, hi, value = (self.run(arg) for arg in args)
|
|
293
|
+
return (value - lo) / (hi - lo)
|
|
294
|
+
case Op.UnlerpClamped:
|
|
295
|
+
lo, hi, value = (self.run(arg) for arg in args)
|
|
296
|
+
return max(0, min(1, (value - lo) / (hi - lo)))
|
|
297
|
+
case _:
|
|
298
|
+
raise NotImplementedError(f"Unsupported operation: {func}")
|
|
299
|
+
|
|
300
|
+
def reduce_args(self, args: list[EngineNode], operator) -> float:
|
|
301
|
+
if not args:
|
|
302
|
+
return 0.0
|
|
303
|
+
acc, *rest = (self.run(arg) for arg in args)
|
|
304
|
+
for arg in rest:
|
|
305
|
+
acc = operator(acc, arg)
|
|
306
|
+
return acc
|
|
307
|
+
|
|
308
|
+
def get(self, block: float, index: float) -> float:
|
|
309
|
+
block = self.ensure_int(block)
|
|
310
|
+
index = self.ensure_int(index)
|
|
311
|
+
assert index >= 0, "Index must be non-negative"
|
|
312
|
+
assert index <= 65535, "Index is too large"
|
|
313
|
+
if block not in self.blocks:
|
|
314
|
+
self.blocks[block] = []
|
|
315
|
+
if len(self.blocks[block]) <= index:
|
|
316
|
+
self.blocks[block] += [-1.0] * (index - len(self.blocks[block]) + 1)
|
|
317
|
+
return self.blocks[block][index]
|
|
318
|
+
|
|
319
|
+
def set(self, block: float, index: float, value: float):
|
|
320
|
+
block = self.ensure_int(block)
|
|
321
|
+
index = self.ensure_int(index)
|
|
322
|
+
assert index >= 0, "Index must be non-negative"
|
|
323
|
+
assert index <= 65535, "Index is too large"
|
|
324
|
+
if block not in self.blocks:
|
|
325
|
+
self.blocks[block] = []
|
|
326
|
+
if len(self.blocks[block]) <= index:
|
|
327
|
+
self.blocks[block] += [-1.0] * (index - len(self.blocks[block]) + 1)
|
|
328
|
+
self.blocks[block][index] = value
|
|
329
|
+
return value
|
|
330
|
+
|
|
331
|
+
def ensure_int(self, value: float) -> int:
|
|
332
|
+
assert value == int(value), "Value must be an integer"
|
|
333
|
+
return int(value)
|
sonolus/backend/ir.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from sonolus.backend.ops import Op
|
|
2
|
+
from sonolus.backend.place import BlockPlace, Place, SSAPlace
|
|
3
|
+
|
|
4
|
+
type IRExpr = IRConst | IRPureInstr | IRGet
|
|
5
|
+
type IRStmt = IRExpr | IRInstr | IRSet
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IRConst:
|
|
9
|
+
value: float
|
|
10
|
+
|
|
11
|
+
def __init__(self, value: float):
|
|
12
|
+
self.value = value
|
|
13
|
+
|
|
14
|
+
def __repr__(self):
|
|
15
|
+
return f"IRConst({self.value!r})"
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return f"{self.value}"
|
|
19
|
+
|
|
20
|
+
def __eq__(self, other):
|
|
21
|
+
return isinstance(other, IRConst) and self.value == other.value
|
|
22
|
+
|
|
23
|
+
def __hash__(self):
|
|
24
|
+
return hash(self.value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class IRPureInstr:
|
|
28
|
+
op: Op
|
|
29
|
+
args: list[IRExpr]
|
|
30
|
+
|
|
31
|
+
def __init__(self, op: Op, args: list[IRExpr]):
|
|
32
|
+
assert op.pure
|
|
33
|
+
self.op = op
|
|
34
|
+
self.args = args
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
return f"IRPureInstr({self.op!r}, {self.args!r})"
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return f"{self.op.name}({', '.join(map(str, self.args))})"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class IRInstr:
|
|
44
|
+
op: Op
|
|
45
|
+
args: list[IRExpr]
|
|
46
|
+
|
|
47
|
+
def __init__(self, op: Op, args: list[IRExpr]):
|
|
48
|
+
self.op = op
|
|
49
|
+
self.args = args
|
|
50
|
+
|
|
51
|
+
def __repr__(self):
|
|
52
|
+
return f"IRInstr({self.op!r}, {self.args!r})"
|
|
53
|
+
|
|
54
|
+
def __str__(self):
|
|
55
|
+
return f"{self.op.name}({', '.join(map(str, self.args))})"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class IRGet:
|
|
59
|
+
place: Place
|
|
60
|
+
|
|
61
|
+
def __init__(self, place: Place):
|
|
62
|
+
self.place = place
|
|
63
|
+
|
|
64
|
+
def __repr__(self):
|
|
65
|
+
return f"IRGet({self.place!r})"
|
|
66
|
+
|
|
67
|
+
def __str__(self):
|
|
68
|
+
return f"{self.place}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class IRSet:
|
|
72
|
+
place: Place
|
|
73
|
+
value: IRStmt
|
|
74
|
+
|
|
75
|
+
def __init__(self, place: Place, value: IRStmt):
|
|
76
|
+
self.place = place
|
|
77
|
+
self.value = value
|
|
78
|
+
|
|
79
|
+
def __repr__(self):
|
|
80
|
+
return f"IRSet({self.place!r}, {self.value!r})"
|
|
81
|
+
|
|
82
|
+
def __str__(self):
|
|
83
|
+
match self.place:
|
|
84
|
+
case BlockPlace():
|
|
85
|
+
return f"{self.place} <- {self.value}"
|
|
86
|
+
case SSAPlace():
|
|
87
|
+
return f"{self.place} := {self.value}"
|
|
88
|
+
case _:
|
|
89
|
+
raise TypeError(f"Invalid place: {self.place}")
|
sonolus/backend/mode.py
ADDED
|
@@ -0,0 +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)
|
sonolus/backend/node.py
ADDED
|
@@ -0,0 +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}")
|