sonolus.py 0.1.8__py3-none-any.whl → 0.2.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/backend/finalize.py +2 -1
- sonolus/backend/optimize/constant_evaluation.py +2 -2
- sonolus/backend/optimize/copy_coalesce.py +16 -7
- sonolus/backend/optimize/optimize.py +12 -4
- sonolus/backend/optimize/passes.py +2 -1
- sonolus/backend/place.py +95 -14
- sonolus/backend/visitor.py +83 -12
- sonolus/build/cli.py +60 -9
- sonolus/build/collection.py +68 -26
- sonolus/build/compile.py +87 -30
- sonolus/build/engine.py +166 -40
- sonolus/build/level.py +2 -1
- sonolus/build/node.py +8 -1
- sonolus/build/project.py +30 -11
- sonolus/script/archetype.py +169 -51
- sonolus/script/array.py +12 -1
- sonolus/script/bucket.py +26 -8
- sonolus/script/debug.py +2 -2
- sonolus/script/effect.py +2 -2
- sonolus/script/engine.py +123 -15
- sonolus/script/internal/builtin_impls.py +21 -2
- sonolus/script/internal/constant.py +6 -2
- sonolus/script/internal/context.py +30 -25
- sonolus/script/internal/introspection.py +8 -1
- sonolus/script/internal/math_impls.py +2 -1
- sonolus/script/internal/transient.py +5 -1
- sonolus/script/internal/value.py +10 -2
- sonolus/script/interval.py +16 -0
- sonolus/script/iterator.py +17 -0
- sonolus/script/level.py +130 -8
- sonolus/script/metadata.py +32 -0
- sonolus/script/num.py +11 -2
- sonolus/script/options.py +5 -3
- sonolus/script/pointer.py +2 -0
- sonolus/script/project.py +41 -5
- sonolus/script/record.py +8 -3
- sonolus/script/runtime.py +61 -10
- sonolus/script/sprite.py +18 -1
- sonolus/script/ui.py +7 -3
- sonolus/script/values.py +8 -5
- sonolus/script/vec.py +28 -0
- {sonolus_py-0.1.8.dist-info → sonolus_py-0.2.0.dist-info}/METADATA +3 -2
- sonolus_py-0.2.0.dist-info/RECORD +90 -0
- {sonolus_py-0.1.8.dist-info → sonolus_py-0.2.0.dist-info}/WHEEL +1 -1
- sonolus_py-0.1.8.dist-info/RECORD +0 -89
- /sonolus/script/{print.py → printing.py} +0 -0
- {sonolus_py-0.1.8.dist-info → sonolus_py-0.2.0.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.8.dist-info → sonolus_py-0.2.0.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/finalize.py
CHANGED
|
@@ -42,6 +42,7 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
42
42
|
for cond, target in targets.items():
|
|
43
43
|
if cond is None:
|
|
44
44
|
default = block_indexes[target]
|
|
45
|
+
continue
|
|
45
46
|
args.append(ConstantNode(value=cond))
|
|
46
47
|
args.append(ConstantNode(value=block_indexes[target]))
|
|
47
48
|
args.append(ConstantNode(value=default))
|
|
@@ -55,7 +56,7 @@ def ir_to_engine_node(stmt) -> EngineNode:
|
|
|
55
56
|
match stmt:
|
|
56
57
|
case int() | float():
|
|
57
58
|
return ConstantNode(value=float(stmt))
|
|
58
|
-
case IRConst(value=value):
|
|
59
|
+
case IRConst(value=int(value) | float(value)):
|
|
59
60
|
return ConstantNode(value=value)
|
|
60
61
|
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
61
62
|
return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
|
|
@@ -315,8 +315,8 @@ class SparseConditionalConstantPropagation(CompilerPass):
|
|
|
315
315
|
return 1
|
|
316
316
|
return functools.reduce(operator.pow, args)
|
|
317
317
|
case Op.Log:
|
|
318
|
-
assert len(args) ==
|
|
319
|
-
return math.log(args[0]
|
|
318
|
+
assert len(args) == 1
|
|
319
|
+
return math.log(args[0])
|
|
320
320
|
case Op.Ceil:
|
|
321
321
|
assert len(args) == 1
|
|
322
322
|
return math.ceil(args[0])
|
|
@@ -39,20 +39,29 @@ class CopyCoalesce(CompilerPass):
|
|
|
39
39
|
interference = self.get_interference(entry)
|
|
40
40
|
copies = self.get_copies(entry)
|
|
41
41
|
|
|
42
|
-
mapping = {}
|
|
42
|
+
mapping: dict[TempBlock, set[TempBlock]] = {}
|
|
43
43
|
|
|
44
44
|
for target, sources in copies.items():
|
|
45
45
|
for source in sources:
|
|
46
|
-
if source in mapping:
|
|
47
|
-
continue
|
|
48
46
|
if source in interference.get(target, set()):
|
|
49
47
|
continue
|
|
50
|
-
|
|
48
|
+
combined_mapping = mapping.get(target, {target}) | mapping.get(source, {source})
|
|
51
49
|
combined_interference = interference.get(target, set()) | interference.get(source, set())
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
for place in combined_mapping:
|
|
51
|
+
mapping[place] = combined_mapping
|
|
52
|
+
interference[place] = combined_interference
|
|
53
|
+
for place in combined_interference:
|
|
54
|
+
interference[place].update(combined_mapping)
|
|
55
|
+
|
|
56
|
+
canonical_mapping = {}
|
|
57
|
+
for place, group in mapping.items():
|
|
58
|
+
if place in canonical_mapping:
|
|
59
|
+
continue
|
|
60
|
+
canonical = min(group)
|
|
61
|
+
for member in group:
|
|
62
|
+
canonical_mapping[member] = canonical
|
|
54
63
|
|
|
55
|
-
return
|
|
64
|
+
return canonical_mapping
|
|
56
65
|
|
|
57
66
|
def get_interference(self, entry: BasicBlock) -> dict[TempBlock, set[TempBlock]]:
|
|
58
67
|
result = {}
|
|
@@ -12,13 +12,21 @@ from sonolus.backend.optimize.passes import run_passes
|
|
|
12
12
|
from sonolus.backend.optimize.simplify import CoalesceFlow, NormalizeSwitch, RewriteToSwitch
|
|
13
13
|
from sonolus.backend.optimize.ssa import FromSSA, ToSSA
|
|
14
14
|
|
|
15
|
-
MINIMAL_PASSES =
|
|
15
|
+
MINIMAL_PASSES = (
|
|
16
16
|
CoalesceFlow(),
|
|
17
17
|
UnreachableCodeElimination(),
|
|
18
18
|
AllocateBasic(),
|
|
19
|
-
|
|
19
|
+
)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
FAST_PASSES = (
|
|
22
|
+
CoalesceFlow(),
|
|
23
|
+
UnreachableCodeElimination(),
|
|
24
|
+
AdvancedDeadCodeElimination(),
|
|
25
|
+
CoalesceFlow(),
|
|
26
|
+
Allocate(),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
STANDARD_PASSES = (
|
|
22
30
|
CoalesceFlow(),
|
|
23
31
|
UnreachableCodeElimination(),
|
|
24
32
|
DeadCodeElimination(),
|
|
@@ -37,7 +45,7 @@ STANDARD_PASSES = [
|
|
|
37
45
|
CoalesceFlow(),
|
|
38
46
|
NormalizeSwitch(),
|
|
39
47
|
Allocate(),
|
|
40
|
-
|
|
48
|
+
)
|
|
41
49
|
|
|
42
50
|
|
|
43
51
|
def optimize_and_allocate(cfg: BasicBlock):
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from collections import deque
|
|
5
|
+
from collections.abc import Sequence
|
|
5
6
|
|
|
6
7
|
from sonolus.backend.optimize.flow import BasicBlock
|
|
7
8
|
|
|
@@ -35,7 +36,7 @@ class CompilerPass(ABC):
|
|
|
35
36
|
pass
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def run_passes(entry: BasicBlock, passes:
|
|
39
|
+
def run_passes(entry: BasicBlock, passes: Sequence[CompilerPass]) -> BasicBlock:
|
|
39
40
|
active_passes = set()
|
|
40
41
|
queue = deque(passes)
|
|
41
42
|
while queue:
|
sonolus/backend/place.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections.abc import Iterator
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Self
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.blocks import Block
|
|
5
5
|
|
|
@@ -8,9 +8,13 @@ type BlockValue = Block | int | TempBlock | Place
|
|
|
8
8
|
type IndexValue = int | Place
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class TempBlock
|
|
12
|
-
name
|
|
13
|
-
|
|
11
|
+
class TempBlock:
|
|
12
|
+
__slots__ = ("__hash", "name", "size")
|
|
13
|
+
|
|
14
|
+
def __init__(self, name: str, size: int = 1):
|
|
15
|
+
self.name = name
|
|
16
|
+
self.size = size
|
|
17
|
+
self.__hash = hash(name) # Precompute hash based on name alone
|
|
14
18
|
|
|
15
19
|
def __repr__(self):
|
|
16
20
|
return f"TempBlock(name={self.name!r}, size={self.size!r})"
|
|
@@ -28,14 +32,38 @@ class TempBlock(NamedTuple):
|
|
|
28
32
|
def __eq__(self, other):
|
|
29
33
|
return isinstance(other, TempBlock) and self.name == other.name and self.size == other.size
|
|
30
34
|
|
|
35
|
+
def __lt__(self, other):
|
|
36
|
+
if not isinstance(other, TempBlock):
|
|
37
|
+
return NotImplemented
|
|
38
|
+
return str(self) < str(other)
|
|
39
|
+
|
|
40
|
+
def __le__(self, other):
|
|
41
|
+
if not isinstance(other, TempBlock):
|
|
42
|
+
return NotImplemented
|
|
43
|
+
return str(self) <= str(other)
|
|
44
|
+
|
|
45
|
+
def __gt__(self, other):
|
|
46
|
+
if not isinstance(other, TempBlock):
|
|
47
|
+
return NotImplemented
|
|
48
|
+
return str(self) > str(other)
|
|
49
|
+
|
|
50
|
+
def __ge__(self, other):
|
|
51
|
+
if not isinstance(other, TempBlock):
|
|
52
|
+
return NotImplemented
|
|
53
|
+
return str(self) >= str(other)
|
|
54
|
+
|
|
31
55
|
def __hash__(self):
|
|
32
|
-
return
|
|
56
|
+
return self.__hash
|
|
33
57
|
|
|
34
58
|
|
|
35
|
-
class BlockPlace
|
|
36
|
-
block
|
|
37
|
-
|
|
38
|
-
offset: int = 0
|
|
59
|
+
class BlockPlace:
|
|
60
|
+
__slots__ = ("__hash", "block", "index", "offset")
|
|
61
|
+
|
|
62
|
+
def __init__(self, block: BlockValue, index: IndexValue = 0, offset: int = 0):
|
|
63
|
+
self.block = block
|
|
64
|
+
self.index = index
|
|
65
|
+
self.offset = offset
|
|
66
|
+
self.__hash = hash((block, index, offset))
|
|
39
67
|
|
|
40
68
|
def __repr__(self):
|
|
41
69
|
return f"BlockPlace(block={self.block!r}, index={self.index!r}, offset={self.offset!r})"
|
|
@@ -61,13 +89,42 @@ class BlockPlace(NamedTuple):
|
|
|
61
89
|
and self.offset == other.offset
|
|
62
90
|
)
|
|
63
91
|
|
|
92
|
+
def __lt__(self, other):
|
|
93
|
+
if not isinstance(other, BlockPlace):
|
|
94
|
+
return NotImplemented
|
|
95
|
+
return str(self) < str(other)
|
|
96
|
+
|
|
97
|
+
def __le__(self, other):
|
|
98
|
+
if not isinstance(other, BlockPlace):
|
|
99
|
+
return NotImplemented
|
|
100
|
+
return str(self) <= str(other)
|
|
101
|
+
|
|
102
|
+
def __gt__(self, other):
|
|
103
|
+
if not isinstance(other, BlockPlace):
|
|
104
|
+
return NotImplemented
|
|
105
|
+
return str(self) > str(other)
|
|
106
|
+
|
|
107
|
+
def __ge__(self, other):
|
|
108
|
+
if not isinstance(other, BlockPlace):
|
|
109
|
+
return NotImplemented
|
|
110
|
+
return str(self) >= str(other)
|
|
111
|
+
|
|
64
112
|
def __hash__(self):
|
|
65
|
-
return
|
|
113
|
+
return self.__hash
|
|
114
|
+
|
|
115
|
+
def __iter__(self):
|
|
116
|
+
yield self.block
|
|
117
|
+
yield self.index
|
|
118
|
+
yield self.offset
|
|
66
119
|
|
|
67
120
|
|
|
68
|
-
class SSAPlace
|
|
69
|
-
name
|
|
70
|
-
|
|
121
|
+
class SSAPlace:
|
|
122
|
+
__slots__ = ("__hash", "name", "num")
|
|
123
|
+
|
|
124
|
+
def __init__(self, name: str, num: int):
|
|
125
|
+
self.name = name
|
|
126
|
+
self.num = num
|
|
127
|
+
self.__hash = hash((name, num))
|
|
71
128
|
|
|
72
129
|
def __repr__(self):
|
|
73
130
|
return f"SSAPlace(name={self.name!r}, num={self.num!r})"
|
|
@@ -78,5 +135,29 @@ class SSAPlace(NamedTuple):
|
|
|
78
135
|
def __eq__(self, other):
|
|
79
136
|
return isinstance(other, SSAPlace) and self.name == other.name and self.num == other.num
|
|
80
137
|
|
|
138
|
+
def __lt__(self, other):
|
|
139
|
+
if not isinstance(other, SSAPlace):
|
|
140
|
+
return NotImplemented
|
|
141
|
+
return str(self) < str(other)
|
|
142
|
+
|
|
143
|
+
def __le__(self, other):
|
|
144
|
+
if not isinstance(other, SSAPlace):
|
|
145
|
+
return NotImplemented
|
|
146
|
+
return str(self) <= str(other)
|
|
147
|
+
|
|
148
|
+
def __gt__(self, other):
|
|
149
|
+
if not isinstance(other, SSAPlace):
|
|
150
|
+
return NotImplemented
|
|
151
|
+
return str(self) > str(other)
|
|
152
|
+
|
|
153
|
+
def __ge__(self, other):
|
|
154
|
+
if not isinstance(other, SSAPlace):
|
|
155
|
+
return NotImplemented
|
|
156
|
+
return str(self) >= str(other)
|
|
157
|
+
|
|
81
158
|
def __hash__(self):
|
|
82
|
-
return
|
|
159
|
+
return self.__hash
|
|
160
|
+
|
|
161
|
+
def __iter__(self):
|
|
162
|
+
yield self.name
|
|
163
|
+
yield self.num
|
sonolus/backend/visitor.py
CHANGED
|
@@ -172,7 +172,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
172
172
|
bound_args: inspect.BoundArguments
|
|
173
173
|
used_names: dict[str, int]
|
|
174
174
|
return_ctxs: list[Context] # Contexts at return statements, which will branch to the exit
|
|
175
|
-
loop_head_ctxs: list[
|
|
175
|
+
loop_head_ctxs: list[
|
|
176
|
+
Context | list[Context]
|
|
177
|
+
] # Contexts at loop heads, from outer to inner. Contains a list for unrolled (tuple) loops
|
|
176
178
|
break_ctxs: list[list[Context]] # Contexts at break statements, from outer to inner
|
|
177
179
|
active_ctx: Context | None # The active context for use in nested functions=
|
|
178
180
|
parent: Self | None # The parent visitor for use in nested functions
|
|
@@ -203,6 +205,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
203
205
|
case ast.FunctionDef(body=body):
|
|
204
206
|
ctx().scope.set_value("$return", validate_value(None))
|
|
205
207
|
for stmt in body:
|
|
208
|
+
if not ctx().live:
|
|
209
|
+
break
|
|
206
210
|
self.visit(stmt)
|
|
207
211
|
case ast.Lambda(body=body):
|
|
208
212
|
result = self.visit(body)
|
|
@@ -318,11 +322,21 @@ class Visitor(ast.NodeVisitor):
|
|
|
318
322
|
iterable = self.visit(node.iter)
|
|
319
323
|
if isinstance(iterable, TupleImpl):
|
|
320
324
|
# Unroll the loop
|
|
325
|
+
break_ctxs = []
|
|
321
326
|
for value in iterable.value:
|
|
322
327
|
set_ctx(ctx().branch(None))
|
|
328
|
+
self.loop_head_ctxs.append([])
|
|
329
|
+
self.break_ctxs.append([])
|
|
323
330
|
self.handle_assign(node.target, validate_value(value))
|
|
324
331
|
for stmt in node.body:
|
|
332
|
+
if not ctx().live:
|
|
333
|
+
break
|
|
325
334
|
self.visit(stmt)
|
|
335
|
+
continue_ctxs = [*self.loop_head_ctxs.pop(), ctx()]
|
|
336
|
+
break_ctxs.extend(self.break_ctxs.pop())
|
|
337
|
+
set_ctx(Context.meet(continue_ctxs))
|
|
338
|
+
if break_ctxs:
|
|
339
|
+
set_ctx(Context.meet([*break_ctxs, ctx()]))
|
|
326
340
|
return
|
|
327
341
|
iterator = self.handle_call(node, iterable.__iter__)
|
|
328
342
|
if not isinstance(iterator, SonolusIterator):
|
|
@@ -335,7 +349,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
335
349
|
has_next = self.ensure_boolean_num(self.handle_call(node, iterator.has_next))
|
|
336
350
|
if has_next._is_py_() and not has_next._as_py_():
|
|
337
351
|
# The loop will never run, continue after evaluating the condition
|
|
352
|
+
self.loop_head_ctxs.pop()
|
|
353
|
+
self.break_ctxs.pop()
|
|
338
354
|
for stmt in node.orelse:
|
|
355
|
+
if not ctx().live:
|
|
356
|
+
break
|
|
339
357
|
self.visit(stmt)
|
|
340
358
|
return
|
|
341
359
|
ctx().test = has_next.ir()
|
|
@@ -345,16 +363,21 @@ class Visitor(ast.NodeVisitor):
|
|
|
345
363
|
set_ctx(body_ctx)
|
|
346
364
|
self.handle_assign(node.target, self.handle_call(node, iterator.next))
|
|
347
365
|
for stmt in node.body:
|
|
366
|
+
if not ctx().live:
|
|
367
|
+
break
|
|
348
368
|
self.visit(stmt)
|
|
349
369
|
ctx().branch_to_loop_header(header_ctx)
|
|
350
370
|
|
|
371
|
+
self.loop_head_ctxs.pop()
|
|
372
|
+
break_ctxs = self.break_ctxs.pop()
|
|
373
|
+
|
|
351
374
|
set_ctx(else_ctx)
|
|
352
375
|
for stmt in node.orelse:
|
|
376
|
+
if not ctx().live:
|
|
377
|
+
break
|
|
353
378
|
self.visit(stmt)
|
|
354
379
|
else_end_ctx = ctx()
|
|
355
380
|
|
|
356
|
-
self.loop_head_ctxs.pop()
|
|
357
|
-
break_ctxs = self.break_ctxs.pop()
|
|
358
381
|
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
359
382
|
set_ctx(after_ctx)
|
|
360
383
|
|
|
@@ -365,27 +388,55 @@ class Visitor(ast.NodeVisitor):
|
|
|
365
388
|
self.break_ctxs.append([])
|
|
366
389
|
set_ctx(header_ctx)
|
|
367
390
|
test = self.ensure_boolean_num(self.visit(node.test))
|
|
368
|
-
if test._is_py_()
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
391
|
+
if test._is_py_():
|
|
392
|
+
if test._as_py_():
|
|
393
|
+
# The loop will run until a break / return
|
|
394
|
+
body_ctx = ctx().branch(None)
|
|
395
|
+
set_ctx(body_ctx)
|
|
396
|
+
for stmt in node.body:
|
|
397
|
+
if not ctx().live:
|
|
398
|
+
break
|
|
399
|
+
self.visit(stmt)
|
|
400
|
+
ctx().branch_to_loop_header(header_ctx)
|
|
401
|
+
|
|
402
|
+
self.loop_head_ctxs.pop()
|
|
403
|
+
break_ctxs = self.break_ctxs.pop()
|
|
404
|
+
|
|
405
|
+
# Skip the else block
|
|
406
|
+
|
|
407
|
+
after_ctx = Context.meet([ctx().into_dead(), *break_ctxs])
|
|
408
|
+
set_ctx(after_ctx)
|
|
409
|
+
return
|
|
410
|
+
else:
|
|
411
|
+
# The loop will never run, continue after evaluating the condition
|
|
412
|
+
self.loop_head_ctxs.pop()
|
|
413
|
+
self.break_ctxs.pop()
|
|
414
|
+
for stmt in node.orelse:
|
|
415
|
+
if not ctx().live:
|
|
416
|
+
break
|
|
417
|
+
self.visit(stmt)
|
|
418
|
+
return
|
|
373
419
|
ctx().test = test.ir()
|
|
374
420
|
body_ctx = ctx().branch(None)
|
|
375
421
|
else_ctx = ctx().branch(0)
|
|
376
422
|
|
|
377
423
|
set_ctx(body_ctx)
|
|
378
424
|
for stmt in node.body:
|
|
425
|
+
if not ctx().live:
|
|
426
|
+
break
|
|
379
427
|
self.visit(stmt)
|
|
380
428
|
ctx().branch_to_loop_header(header_ctx)
|
|
381
429
|
|
|
430
|
+
self.loop_head_ctxs.pop()
|
|
431
|
+
break_ctxs = self.break_ctxs.pop()
|
|
432
|
+
|
|
382
433
|
set_ctx(else_ctx)
|
|
383
434
|
for stmt in node.orelse:
|
|
435
|
+
if not ctx().live:
|
|
436
|
+
break
|
|
384
437
|
self.visit(stmt)
|
|
385
438
|
else_end_ctx = ctx()
|
|
386
439
|
|
|
387
|
-
self.loop_head_ctxs.pop()
|
|
388
|
-
break_ctxs = self.break_ctxs.pop()
|
|
389
440
|
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
390
441
|
set_ctx(after_ctx)
|
|
391
442
|
|
|
@@ -395,9 +446,13 @@ class Visitor(ast.NodeVisitor):
|
|
|
395
446
|
if test._is_py_():
|
|
396
447
|
if test._as_py_():
|
|
397
448
|
for stmt in node.body:
|
|
449
|
+
if not ctx().live:
|
|
450
|
+
break
|
|
398
451
|
self.visit(stmt)
|
|
399
452
|
else:
|
|
400
453
|
for stmt in node.orelse:
|
|
454
|
+
if not ctx().live:
|
|
455
|
+
break
|
|
401
456
|
self.visit(stmt)
|
|
402
457
|
return
|
|
403
458
|
|
|
@@ -408,11 +463,15 @@ class Visitor(ast.NodeVisitor):
|
|
|
408
463
|
|
|
409
464
|
set_ctx(true_ctx)
|
|
410
465
|
for stmt in node.body:
|
|
466
|
+
if not ctx().live:
|
|
467
|
+
break
|
|
411
468
|
self.visit(stmt)
|
|
412
469
|
true_end_ctx = ctx()
|
|
413
470
|
|
|
414
471
|
set_ctx(false_ctx)
|
|
415
472
|
for stmt in node.orelse:
|
|
473
|
+
if not ctx().live:
|
|
474
|
+
break
|
|
416
475
|
self.visit(stmt)
|
|
417
476
|
false_end_ctx = ctx()
|
|
418
477
|
|
|
@@ -439,6 +498,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
439
498
|
if guard._is_py_():
|
|
440
499
|
if guard._as_py_():
|
|
441
500
|
for stmt in case.body:
|
|
501
|
+
if not ctx().live:
|
|
502
|
+
break
|
|
442
503
|
self.visit(stmt)
|
|
443
504
|
end_ctxs.append(ctx())
|
|
444
505
|
else:
|
|
@@ -450,6 +511,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
450
511
|
guard_false_ctx = ctx().branch(0)
|
|
451
512
|
set_ctx(guard_true_ctx)
|
|
452
513
|
for stmt in case.body:
|
|
514
|
+
if not ctx().live:
|
|
515
|
+
break
|
|
453
516
|
self.visit(stmt)
|
|
454
517
|
end_ctxs.append(ctx())
|
|
455
518
|
false_ctx = Context.meet([false_ctx, guard_false_ctx])
|
|
@@ -610,7 +673,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
610
673
|
set_ctx(ctx().into_dead())
|
|
611
674
|
|
|
612
675
|
def visit_Continue(self, node):
|
|
613
|
-
|
|
676
|
+
loop_head = self.loop_head_ctxs[-1]
|
|
677
|
+
if isinstance(loop_head, list):
|
|
678
|
+
loop_head.append(ctx())
|
|
679
|
+
else:
|
|
680
|
+
ctx().branch_to_loop_header(loop_head)
|
|
614
681
|
set_ctx(ctx().into_dead())
|
|
615
682
|
|
|
616
683
|
def visit_BoolOp(self, node) -> Value:
|
|
@@ -959,7 +1026,11 @@ class Visitor(ast.NodeVisitor):
|
|
|
959
1026
|
if isinstance(target, ConstantValue):
|
|
960
1027
|
# Unwrap so we can access fields
|
|
961
1028
|
target = target._as_py_()
|
|
962
|
-
descriptor =
|
|
1029
|
+
descriptor = None
|
|
1030
|
+
for cls in type.mro(type(target)):
|
|
1031
|
+
descriptor = cls.__dict__.get(key, None)
|
|
1032
|
+
if descriptor is not None:
|
|
1033
|
+
break
|
|
963
1034
|
match descriptor:
|
|
964
1035
|
case property(fget=getter):
|
|
965
1036
|
return self.handle_call(node, getter, target)
|
sonolus/build/cli.py
CHANGED
|
@@ -10,10 +10,11 @@ import sys
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from time import perf_counter
|
|
12
12
|
|
|
13
|
+
from sonolus.backend.optimize.optimize import FAST_PASSES, MINIMAL_PASSES, STANDARD_PASSES
|
|
13
14
|
from sonolus.build.engine import package_engine
|
|
14
15
|
from sonolus.build.level import package_level_data
|
|
15
16
|
from sonolus.build.project import build_project_to_collection, get_project_schema
|
|
16
|
-
from sonolus.script.project import Project
|
|
17
|
+
from sonolus.script.project import BuildConfig, Project
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def find_default_module() -> str | None:
|
|
@@ -63,29 +64,29 @@ def import_project(module_path: str) -> Project | None:
|
|
|
63
64
|
return project
|
|
64
65
|
except Exception as e:
|
|
65
66
|
print(f"Error: Failed to import project: {e}")
|
|
66
|
-
|
|
67
|
+
raise e from None
|
|
67
68
|
|
|
68
69
|
|
|
69
|
-
def build_project(project: Project, build_dir: Path):
|
|
70
|
+
def build_project(project: Project, build_dir: Path, config: BuildConfig):
|
|
70
71
|
dist_dir = build_dir / "dist"
|
|
71
72
|
levels_dir = dist_dir / "levels"
|
|
72
73
|
shutil.rmtree(dist_dir, ignore_errors=True)
|
|
73
74
|
dist_dir.mkdir(parents=True, exist_ok=True)
|
|
74
75
|
levels_dir.mkdir(parents=True, exist_ok=True)
|
|
75
76
|
|
|
76
|
-
package_engine(project.engine.data).write(dist_dir / "engine")
|
|
77
|
+
package_engine(project.engine.data, config).write(dist_dir / "engine")
|
|
77
78
|
|
|
78
79
|
for level in project.levels:
|
|
79
80
|
level_path = levels_dir / level.name
|
|
80
81
|
level_path.write_bytes(package_level_data(level.data))
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
def build_collection(project: Project, build_dir: Path):
|
|
84
|
+
def build_collection(project: Project, build_dir: Path, config: BuildConfig):
|
|
84
85
|
site_dir = build_dir / "site"
|
|
85
86
|
shutil.rmtree(site_dir, ignore_errors=True)
|
|
86
87
|
site_dir.mkdir(parents=True, exist_ok=True)
|
|
87
88
|
|
|
88
|
-
collection = build_project_to_collection(project)
|
|
89
|
+
collection = build_project_to_collection(project, config)
|
|
89
90
|
collection.write(site_dir)
|
|
90
91
|
|
|
91
92
|
|
|
@@ -125,10 +126,55 @@ def run_server(base_dir: Path, port: int = 8000):
|
|
|
125
126
|
httpd.shutdown()
|
|
126
127
|
|
|
127
128
|
|
|
129
|
+
def get_config(args: argparse.Namespace) -> BuildConfig:
|
|
130
|
+
if hasattr(args, "optimize_minimal") and args.optimize_minimal:
|
|
131
|
+
optimization_passes = MINIMAL_PASSES
|
|
132
|
+
elif hasattr(args, "optimize_fast") and args.optimize_fast:
|
|
133
|
+
optimization_passes = FAST_PASSES
|
|
134
|
+
elif hasattr(args, "optimize_standard") and args.optimize_standard:
|
|
135
|
+
optimization_passes = STANDARD_PASSES
|
|
136
|
+
else:
|
|
137
|
+
optimization_passes = FAST_PASSES if args.command == "dev" else STANDARD_PASSES
|
|
138
|
+
|
|
139
|
+
if any(hasattr(args, attr) and getattr(args, attr) for attr in ["play", "watch", "preview", "tutorial"]):
|
|
140
|
+
build_play = hasattr(args, "play") and args.play
|
|
141
|
+
build_watch = hasattr(args, "watch") and args.watch
|
|
142
|
+
build_preview = hasattr(args, "preview") and args.preview
|
|
143
|
+
build_tutorial = hasattr(args, "tutorial") and args.tutorial
|
|
144
|
+
else:
|
|
145
|
+
build_play = build_watch = build_preview = build_tutorial = True
|
|
146
|
+
|
|
147
|
+
return BuildConfig(
|
|
148
|
+
passes=optimization_passes,
|
|
149
|
+
build_play=build_play,
|
|
150
|
+
build_watch=build_watch,
|
|
151
|
+
build_preview=build_preview,
|
|
152
|
+
build_tutorial=build_tutorial,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
128
156
|
def main():
|
|
129
157
|
parser = argparse.ArgumentParser(description="Sonolus project build and development tools")
|
|
130
158
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
131
159
|
|
|
160
|
+
def add_common_arguments(parser):
|
|
161
|
+
optimization_group = parser.add_mutually_exclusive_group()
|
|
162
|
+
optimization_group.add_argument(
|
|
163
|
+
"-o0", "--optimize-minimal", action="store_true", help="Use minimal optimization passes"
|
|
164
|
+
)
|
|
165
|
+
optimization_group.add_argument(
|
|
166
|
+
"-o1", "--optimize-fast", action="store_true", help="Use fast optimization passes"
|
|
167
|
+
)
|
|
168
|
+
optimization_group.add_argument(
|
|
169
|
+
"-o2", "--optimize-standard", action="store_true", help="Use standard optimization passes"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
build_components = parser.add_argument_group("build components")
|
|
173
|
+
build_components.add_argument("--play", action="store_true", help="Build play component")
|
|
174
|
+
build_components.add_argument("--watch", action="store_true", help="Build watch component")
|
|
175
|
+
build_components.add_argument("--preview", action="store_true", help="Build preview component")
|
|
176
|
+
build_components.add_argument("--tutorial", action="store_true", help="Build tutorial component")
|
|
177
|
+
|
|
132
178
|
build_parser = subparsers.add_parser("build")
|
|
133
179
|
build_parser.add_argument(
|
|
134
180
|
"module",
|
|
@@ -137,6 +183,7 @@ def main():
|
|
|
137
183
|
help="Module path (e.g., 'module.name'). If omitted, will auto-detect if only one module exists.",
|
|
138
184
|
)
|
|
139
185
|
build_parser.add_argument("--build-dir", type=str, default="./build")
|
|
186
|
+
add_common_arguments(build_parser)
|
|
140
187
|
|
|
141
188
|
dev_parser = subparsers.add_parser("dev")
|
|
142
189
|
dev_parser.add_argument(
|
|
@@ -147,6 +194,7 @@ def main():
|
|
|
147
194
|
)
|
|
148
195
|
dev_parser.add_argument("--build-dir", type=str, default="./build")
|
|
149
196
|
dev_parser.add_argument("--port", type=int, default=8000)
|
|
197
|
+
add_common_arguments(dev_parser)
|
|
150
198
|
|
|
151
199
|
schema_parser = subparsers.add_parser("schema")
|
|
152
200
|
schema_parser.add_argument(
|
|
@@ -161,7 +209,8 @@ def main():
|
|
|
161
209
|
if not args.module:
|
|
162
210
|
default_module = find_default_module()
|
|
163
211
|
if default_module:
|
|
164
|
-
|
|
212
|
+
if args.command != "schema":
|
|
213
|
+
print(f"Using auto-detected module: {default_module}")
|
|
165
214
|
args.module = default_module
|
|
166
215
|
else:
|
|
167
216
|
parser.error("Module argument is required when multiple or no modules are found")
|
|
@@ -173,13 +222,15 @@ def main():
|
|
|
173
222
|
if args.command == "build":
|
|
174
223
|
build_dir = Path(args.build_dir)
|
|
175
224
|
start_time = perf_counter()
|
|
176
|
-
|
|
225
|
+
config = get_config(args)
|
|
226
|
+
build_project(project, build_dir, config)
|
|
177
227
|
end_time = perf_counter()
|
|
178
228
|
print(f"Project built successfully to '{build_dir.resolve()}' in {end_time - start_time:.2f}s")
|
|
179
229
|
elif args.command == "dev":
|
|
180
230
|
build_dir = Path(args.build_dir)
|
|
181
231
|
start_time = perf_counter()
|
|
182
|
-
|
|
232
|
+
config = get_config(args)
|
|
233
|
+
build_collection(project, build_dir, config)
|
|
183
234
|
end_time = perf_counter()
|
|
184
235
|
print(f"Build finished in {end_time - start_time:.2f}s")
|
|
185
236
|
run_server(build_dir / "site", port=args.port)
|