sonolus.py 0.1.8__tar.gz → 0.2.0__tar.gz
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_py-0.1.8 → sonolus_py-0.2.0}/PKG-INFO +3 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/builtins.pyi +22 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/resources.md +5 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/types.md +14 -0
- sonolus_py-0.2.0/docs/reference/sonolus.script.metadata.md +3 -0
- sonolus_py-0.2.0/docs/reference/sonolus.script.printing.md +3 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/mkdocs.yml +2 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/pyproject.toml +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/finalize.py +2 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/constant_evaluation.py +2 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/copy_coalesce.py +16 -7
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/optimize.py +12 -4
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/passes.py +2 -1
- sonolus_py-0.2.0/sonolus/backend/place.py +163 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/visitor.py +83 -12
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/cli.py +60 -9
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/collection.py +68 -26
- sonolus_py-0.2.0/sonolus/build/compile.py +145 -0
- sonolus_py-0.2.0/sonolus/build/engine.py +346 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/level.py +2 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/node.py +8 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/project.py +30 -11
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/archetype.py +169 -51
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/array.py +12 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/bucket.py +26 -8
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/debug.py +2 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/effect.py +2 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/engine.py +123 -15
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/builtin_impls.py +21 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/constant.py +6 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/context.py +30 -25
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/introspection.py +8 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/math_impls.py +2 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/transient.py +5 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/value.py +10 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/interval.py +16 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/iterator.py +17 -0
- sonolus_py-0.2.0/sonolus/script/level.py +198 -0
- sonolus_py-0.2.0/sonolus/script/metadata.py +32 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/num.py +11 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/options.py +5 -3
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/pointer.py +2 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/project.py +41 -5
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/record.py +8 -3
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/runtime.py +61 -10
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/sprite.py +18 -1
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/ui.py +7 -3
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/values.py +8 -5
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/vec.py +28 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_flow.py +16 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_interval.py +2 -2
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_quad.py +17 -33
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_var_array.py +52 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/uv.lock +1 -1
- sonolus_py-0.1.8/docs/reference/sonolus.script.print.md +0 -3
- sonolus_py-0.1.8/sonolus/backend/place.py +0 -82
- sonolus_py-0.1.8/sonolus/build/compile.py +0 -88
- sonolus_py-0.1.8/sonolus/build/engine.py +0 -220
- sonolus_py-0.1.8/sonolus/script/level.py +0 -76
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/.github/workflows/publish.yaml +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/.gitignore +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/.python-version +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/LICENSE +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/README.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/math.pyi +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/num.pyi +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/random.pyi +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/CNAME +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/builtins.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/cli.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/constructs.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/index.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/project.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/index.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/builtins.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/index.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/math.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/random.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.archetype.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.array.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.array_like.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.bucket.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.containers.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.debug.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.easing.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.effect.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.engine.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.globals.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.instruction.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.interval.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.iterator.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.level.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.num.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.options.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.particle.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.project.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.quad.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.record.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.runtime.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.sprite.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.text.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.timing.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.transform.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.ui.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.values.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.vec.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/generate.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Engine/Tutorial/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Functions.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Level/Play/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Level/Preview/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Level/Watch/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/blocks.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/excepthook.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/interpret.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/ir.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/mode.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/node.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/ops.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/allocate.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/dead_code.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/dominance.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/flow.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/inlining.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/liveness.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/simplify.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/ssa.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/utils.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/py.typed +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/array_like.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/containers.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/easing.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/globals.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/instruction.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/callbacks.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/descriptor.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/dict_impl.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/error.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/generic.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/impl.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/native.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/random.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/range.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/tuple_impl.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/particle.py +0 -0
- /sonolus_py-0.1.8/sonolus/script/print.py → /sonolus_py-0.2.0/sonolus/script/printing.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/quad.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/text.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/timing.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/transform.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/conftest.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_array.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_array_map.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_assert.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_dict.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_functions.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_helpers.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_match.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_num.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_operator.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_random.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_range.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_record.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_transform.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_tuple.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_vec.py +0 -0
|
@@ -9,6 +9,28 @@ from typing import (
|
|
|
9
9
|
overload,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
+
def all(iterable: Iterable[builtins.bool]) -> builtins.bool:
|
|
13
|
+
"""Return True if all elements of the iterable are true.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
iterable: The iterable to evaluate.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
True if all elements are true, False otherwise.
|
|
20
|
+
"""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def any(iterable: Iterable[builtins.bool]) -> builtins.bool:
|
|
24
|
+
"""Return True if any element of the iterable is true.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
iterable: The iterable to evaluate.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
True if any element is true, False otherwise.
|
|
31
|
+
"""
|
|
32
|
+
...
|
|
33
|
+
|
|
12
34
|
def abs(x: builtins.int | builtins.float) -> builtins.int | builtins.float:
|
|
13
35
|
"""Return the absolute value of a number.
|
|
14
36
|
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
Skins are defined with the `@skin` decorator:
|
|
5
5
|
|
|
6
6
|
```python
|
|
7
|
-
from sonolus.script.sprite import skin, StandardSprite, sprite, Sprite
|
|
7
|
+
from sonolus.script.sprite import skin, StandardSprite, sprite, Sprite, RenderMode
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@skin
|
|
11
11
|
class Skin:
|
|
12
|
+
render_mode: RenderMode = RenderMode.DEFAULT
|
|
13
|
+
|
|
12
14
|
note: StandardSprite.NOTE_HEAD_RED
|
|
13
15
|
other: Sprite = sprite("other")
|
|
14
16
|
```
|
|
@@ -17,6 +19,8 @@ Standard sprites are defined by annotating the field with the corresponding valu
|
|
|
17
19
|
|
|
18
20
|
Custom sprites are defined by annotating the field with `Sprite` and calling `skin_sprite` with the sprite name.
|
|
19
21
|
|
|
22
|
+
To set the render mode for the skin, set the `render_mode` field to the desired value from `RenderMode`.
|
|
23
|
+
|
|
20
24
|
## Sound Effects
|
|
21
25
|
Sound effects are defined with the `@effects` decorator:
|
|
22
26
|
|
|
@@ -242,6 +242,20 @@ assert not isinstance(a, Array[int, 2])
|
|
|
242
242
|
assert not isinstance(a, Array[Pair, 3])
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
+
### Enums
|
|
246
|
+
|
|
247
|
+
There is limited support for enums containing `Num` values. Methods on enums are not supported.
|
|
248
|
+
When used as a type, any enum class is treated as `Num` and no enforcement is done on the values.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
class MyEnum(IntEnum):
|
|
252
|
+
A = 1
|
|
253
|
+
B = 2
|
|
254
|
+
|
|
255
|
+
a = Array[MyEnum, 2](MyEnum.A, MyEnum.B)
|
|
256
|
+
b = Array[MyEnum, 2](1, 2)
|
|
257
|
+
```
|
|
258
|
+
|
|
245
259
|
## Record
|
|
246
260
|
|
|
247
261
|
`Record` is the base class for user-defined types in Sonolus.py. It functions similarly to dataclasses.
|
|
@@ -96,10 +96,11 @@ nav:
|
|
|
96
96
|
- reference/sonolus.script.interval.md
|
|
97
97
|
- reference/sonolus.script.iterator.md
|
|
98
98
|
- reference/sonolus.script.level.md
|
|
99
|
+
- reference/sonolus.script.metadata.md
|
|
99
100
|
- reference/sonolus.script.num.md
|
|
100
101
|
- reference/sonolus.script.options.md
|
|
101
102
|
- reference/sonolus.script.particle.md
|
|
102
|
-
- reference/sonolus.script.
|
|
103
|
+
- reference/sonolus.script.printing.md
|
|
103
104
|
- reference/sonolus.script.project.md
|
|
104
105
|
- reference/sonolus.script.quad.md
|
|
105
106
|
- reference/sonolus.script.record.md
|
|
@@ -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:
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from typing import Self
|
|
3
|
+
|
|
4
|
+
from sonolus.backend.blocks import Block
|
|
5
|
+
|
|
6
|
+
type Place = BlockPlace | SSAPlace
|
|
7
|
+
type BlockValue = Block | int | TempBlock | Place
|
|
8
|
+
type IndexValue = int | Place
|
|
9
|
+
|
|
10
|
+
|
|
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
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return f"TempBlock(name={self.name!r}, size={self.size!r})"
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
return f"{self.name}"
|
|
24
|
+
|
|
25
|
+
def __getitem__(self, item) -> "BlockPlace":
|
|
26
|
+
return BlockPlace(self, item)
|
|
27
|
+
|
|
28
|
+
def __iter__(self) -> "Iterator[BlockPlace]":
|
|
29
|
+
for i in range(self.size):
|
|
30
|
+
yield self[i]
|
|
31
|
+
|
|
32
|
+
def __eq__(self, other):
|
|
33
|
+
return isinstance(other, TempBlock) and self.name == other.name and self.size == other.size
|
|
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
|
+
|
|
55
|
+
def __hash__(self):
|
|
56
|
+
return self.__hash
|
|
57
|
+
|
|
58
|
+
|
|
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))
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
return f"BlockPlace(block={self.block!r}, index={self.index!r}, offset={self.offset!r})"
|
|
70
|
+
|
|
71
|
+
def __str__(self):
|
|
72
|
+
if isinstance(self.block, TempBlock) and self.block.size == 1 and self.index == 0 and self.offset == 0:
|
|
73
|
+
return f"{self.block}"
|
|
74
|
+
elif isinstance(self.index, int):
|
|
75
|
+
return f"{self.block}[{self.index + self.offset}]"
|
|
76
|
+
elif self.offset == 0:
|
|
77
|
+
return f"{self.block}[{self.index}]"
|
|
78
|
+
else:
|
|
79
|
+
return f"{self.block}[{self.index} + {self.offset}]"
|
|
80
|
+
|
|
81
|
+
def add_offset(self, offset: int) -> Self:
|
|
82
|
+
return BlockPlace(self.block, self.index, self.offset + offset)
|
|
83
|
+
|
|
84
|
+
def __eq__(self, other):
|
|
85
|
+
return (
|
|
86
|
+
isinstance(other, BlockPlace)
|
|
87
|
+
and self.block == other.block
|
|
88
|
+
and self.index == other.index
|
|
89
|
+
and self.offset == other.offset
|
|
90
|
+
)
|
|
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
|
+
|
|
112
|
+
def __hash__(self):
|
|
113
|
+
return self.__hash
|
|
114
|
+
|
|
115
|
+
def __iter__(self):
|
|
116
|
+
yield self.block
|
|
117
|
+
yield self.index
|
|
118
|
+
yield self.offset
|
|
119
|
+
|
|
120
|
+
|
|
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))
|
|
128
|
+
|
|
129
|
+
def __repr__(self):
|
|
130
|
+
return f"SSAPlace(name={self.name!r}, num={self.num!r})"
|
|
131
|
+
|
|
132
|
+
def __str__(self):
|
|
133
|
+
return f"{self.name}.{self.num}"
|
|
134
|
+
|
|
135
|
+
def __eq__(self, other):
|
|
136
|
+
return isinstance(other, SSAPlace) and self.name == other.name and self.num == other.num
|
|
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
|
+
|
|
158
|
+
def __hash__(self):
|
|
159
|
+
return self.__hash
|
|
160
|
+
|
|
161
|
+
def __iter__(self):
|
|
162
|
+
yield self.name
|
|
163
|
+
yield self.num
|
|
@@ -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)
|