sonolus.py 0.3.2__tar.gz → 0.3.3__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.3.2 → sonolus_py-0.3.3}/PKG-INFO +1 -1
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/concepts/builtins.md +5 -1
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/concepts/types.md +5 -3
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/pyproject.toml +1 -1
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/finalize.py +16 -4
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/visitor.py +25 -9
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/archetype.py +25 -7
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/array.py +5 -3
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/array_like.py +25 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/containers.py +24 -4
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/builtin_impls.py +10 -5
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/context.py +5 -1
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/range.py +25 -2
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/tuple_impl.py +3 -1
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/interval.py +60 -2
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/quad.py +39 -3
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/record.py +2 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/runtime.py +28 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/stream.py +22 -13
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/transform.py +4 -4
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/vec.py +14 -2
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_array.py +56 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_array_map.py +96 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_array_set.py +17 -0
- sonolus_py-0.3.3/tests/script/test_interval.py +570 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_operator.py +104 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_range.py +73 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_tuple.py +70 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_var_array.py +98 -7
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/uv.lock +1 -1
- sonolus_py-0.3.2/tests/script/test_interval.py +0 -223
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/.github/workflows/publish.yaml +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/.gitignore +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/.python-version +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/.run/Python tests in tests.run.xml +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/LICENSE +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/README.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/doc_stubs/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/doc_stubs/builtins.pyi +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/doc_stubs/math.pyi +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/doc_stubs/num.pyi +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/doc_stubs/random.pyi +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/CNAME +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/concepts/cli.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/concepts/constructs.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/concepts/index.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/concepts/project.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/concepts/resources.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/index.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/builtins.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/index.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/math.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/random.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.archetype.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.array.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.array_like.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.bucket.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.containers.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.debug.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.easing.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.effect.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.engine.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.globals.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.instruction.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.interval.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.iterator.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.level.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.metadata.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.num.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.options.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.particle.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.printing.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.project.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.quad.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.record.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.runtime.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.sprite.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.stream.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.text.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.timing.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.transform.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.ui.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.values.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/docs/reference/sonolus.script.vec.md +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/mkdocs.yml +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/scripts/generate.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/scripts/runtimes/Engine/Tutorial/Blocks.json +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/scripts/runtimes/Functions.json +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/scripts/runtimes/Level/Play/Blocks.json +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/scripts/runtimes/Level/Preview/Blocks.json +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/scripts/runtimes/Level/Watch/Blocks.json +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/blocks.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/excepthook.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/interpret.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/ir.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/mode.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/node.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/ops.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/allocate.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/constant_evaluation.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/copy_coalesce.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/dead_code.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/dominance.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/flow.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/inlining.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/liveness.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/optimize.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/passes.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/simplify.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/optimize/ssa.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/place.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/backend/utils.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/cli.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/collection.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/compile.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/engine.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/level.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/node.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/build/project.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/py.typed +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/bucket.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/debug.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/easing.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/effect.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/engine.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/globals.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/instruction.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/callbacks.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/constant.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/descriptor.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/dict_impl.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/error.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/generic.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/impl.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/introspection.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/math_impls.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/native.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/random.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/simulation_context.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/transient.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/internal/value.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/iterator.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/level.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/metadata.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/num.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/options.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/particle.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/pointer.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/printing.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/project.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/sprite.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/text.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/timing.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/ui.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/sonolus/script/values.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/__init__.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/conftest.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_assert.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_dict.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_flow.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_functions.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_helpers.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_match.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_num.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_quad.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_random.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_record.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_transform.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_values.py +0 -0
- {sonolus_py-0.3.2 → sonolus_py-0.3.3}/tests/script/test_vec.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Sonolus.py comes with support for a number of built-in functions.
|
|
3
3
|
|
|
4
4
|
- `abs(x)`
|
|
5
|
-
- `bool(object)`
|
|
5
|
+
- `bool(object)`
|
|
6
6
|
- `callable(object)`
|
|
7
7
|
- `enumerate(iterable, start=0)`
|
|
8
8
|
- `filter(function, iterable)`
|
|
@@ -38,6 +38,10 @@ Sonolus.py also comes with support for some standard library modules.
|
|
|
38
38
|
- `ceil(x)`
|
|
39
39
|
- `trunc(x)`
|
|
40
40
|
- `log(x[, base])`
|
|
41
|
+
- `pi`
|
|
42
|
+
- `e`
|
|
43
|
+
- `tau`
|
|
44
|
+
- `inf`
|
|
41
45
|
|
|
42
46
|
### random
|
|
43
47
|
- `randrange(stop)`, `random.randrange(start, stop[, step])`
|
|
@@ -14,7 +14,7 @@ Sonolus.py will treat any of these types as `Num`, but it's recommended to use w
|
|
|
14
14
|
The Sonolus app uses 32-bit floating-point numbers for all numeric values, so precision may be lower compared to Python
|
|
15
15
|
when running on Sonolus.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
NaN and values outside the range of 32-bit floating-point numbers are not supported.
|
|
18
18
|
|
|
19
19
|
You can import `Num` from `sonolus.script.num`:
|
|
20
20
|
|
|
@@ -42,8 +42,10 @@ Nums support most of the standard Python operations:
|
|
|
42
42
|
Floating point precision may be lower when running on Sonolus compared to Python.
|
|
43
43
|
Care should be taken when performing precision-sensitive operations.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
As in regular Python, `0` is considered `False`, while any non-zero value is considered `True`.
|
|
46
|
+
|
|
47
|
+
Objects with an explicit `__bool__` method may also be used in `if`, `while`, `case ... if` expressions as well as with
|
|
48
|
+
the `not` operator. However, the operands of the `and` and `or` operators must be of type `Num`.
|
|
47
49
|
|
|
48
50
|
- Logical operators: `and`, `or`, `not`
|
|
49
51
|
- Ternary expressions: `... if <condition> else ...`
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from math import isfinite, isinf, isnan
|
|
2
|
+
|
|
1
3
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
2
4
|
from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
|
|
3
5
|
from sonolus.backend.ops import Op
|
|
@@ -54,10 +56,20 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
54
56
|
|
|
55
57
|
def ir_to_engine_node(stmt) -> EngineNode:
|
|
56
58
|
match stmt:
|
|
57
|
-
case int() | float():
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
case int(value) | float(value) | IRConst(value=int(value) | float(value)):
|
|
60
|
+
value = float(value)
|
|
61
|
+
if value.is_integer():
|
|
62
|
+
return ConstantNode(value=int(value))
|
|
63
|
+
elif isfinite(value):
|
|
64
|
+
return ConstantNode(value=value)
|
|
65
|
+
elif isinf(value):
|
|
66
|
+
# Read values from ROM
|
|
67
|
+
return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=1 if value > 0 else 2)])
|
|
68
|
+
elif isnan(value):
|
|
69
|
+
# Read value from ROM
|
|
70
|
+
return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=0)])
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Invalid constant value: {value}")
|
|
61
73
|
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
62
74
|
return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
|
|
63
75
|
case IRGet(place=place):
|
|
@@ -359,7 +359,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
359
359
|
self.loop_head_ctxs.append(header_ctx)
|
|
360
360
|
self.break_ctxs.append([])
|
|
361
361
|
set_ctx(header_ctx)
|
|
362
|
-
has_next = self.
|
|
362
|
+
has_next = self.convert_to_boolean_num(node, self.handle_call(node, iterator.has_next))
|
|
363
363
|
if has_next._is_py_() and not has_next._as_py_():
|
|
364
364
|
# The loop will never run, continue after evaluating the condition
|
|
365
365
|
self.loop_head_ctxs.pop()
|
|
@@ -400,7 +400,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
400
400
|
self.loop_head_ctxs.append(header_ctx)
|
|
401
401
|
self.break_ctxs.append([])
|
|
402
402
|
set_ctx(header_ctx)
|
|
403
|
-
test = self.
|
|
403
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
404
404
|
if test._is_py_():
|
|
405
405
|
if test._as_py_():
|
|
406
406
|
# The loop will run until a break / return
|
|
@@ -454,7 +454,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
454
454
|
set_ctx(after_ctx)
|
|
455
455
|
|
|
456
456
|
def visit_If(self, node):
|
|
457
|
-
test = self.
|
|
457
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
458
458
|
|
|
459
459
|
if test._is_py_():
|
|
460
460
|
if test._as_py_():
|
|
@@ -507,7 +507,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
507
507
|
set_ctx(false_ctx)
|
|
508
508
|
continue
|
|
509
509
|
set_ctx(true_ctx)
|
|
510
|
-
guard =
|
|
510
|
+
guard = (
|
|
511
|
+
self.convert_to_boolean_num(case.guard, self.visit(case.guard)) if case.guard else validate_value(True)
|
|
512
|
+
)
|
|
511
513
|
if guard._is_py_():
|
|
512
514
|
if guard._as_py_():
|
|
513
515
|
for stmt in case.body:
|
|
@@ -544,7 +546,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
544
546
|
match pattern:
|
|
545
547
|
case ast.MatchValue(value=value):
|
|
546
548
|
value = self.visit(value)
|
|
547
|
-
test = self.
|
|
549
|
+
test = self.convert_to_boolean_num(pattern, validate_value(subject == value))
|
|
548
550
|
if test._is_py_():
|
|
549
551
|
if test._as_py_():
|
|
550
552
|
return ctx(), ctx().into_dead()
|
|
@@ -574,7 +576,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
574
576
|
target_len = len(patterns)
|
|
575
577
|
if not (isinstance(subject, Sequence | TupleImpl)):
|
|
576
578
|
return ctx().into_dead(), ctx()
|
|
577
|
-
length_test = self.
|
|
579
|
+
length_test = self.convert_to_boolean_num(pattern, validate_value(_len(subject) == target_len))
|
|
578
580
|
ctx_init = ctx()
|
|
579
581
|
if not length_test._is_py_():
|
|
580
582
|
ctx_init.test = length_test.ir()
|
|
@@ -739,7 +741,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
739
741
|
def visit_UnaryOp(self, node):
|
|
740
742
|
operand = self.visit(node.operand)
|
|
741
743
|
if isinstance(node.op, ast.Not):
|
|
742
|
-
return self.
|
|
744
|
+
return self.convert_to_boolean_num(node, operand).not_()
|
|
743
745
|
op = unary_ops[type(node.op)]
|
|
744
746
|
if operand._is_py_():
|
|
745
747
|
operand_py = operand._as_py_()
|
|
@@ -768,7 +770,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
768
770
|
return validate_value(fn)
|
|
769
771
|
|
|
770
772
|
def visit_IfExp(self, node):
|
|
771
|
-
test = self.
|
|
773
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
772
774
|
|
|
773
775
|
if test._is_py_():
|
|
774
776
|
if test._as_py_():
|
|
@@ -1076,7 +1078,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
1076
1078
|
|
|
1077
1079
|
def handle_call[**P, R](
|
|
1078
1080
|
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
1079
|
-
) -> R:
|
|
1081
|
+
) -> R | Value:
|
|
1080
1082
|
"""Handles a call to the given callable."""
|
|
1081
1083
|
self.active_ctx = ctx()
|
|
1082
1084
|
if (
|
|
@@ -1127,6 +1129,20 @@ class Visitor(ast.NodeVisitor):
|
|
|
1127
1129
|
raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
|
|
1128
1130
|
return value
|
|
1129
1131
|
|
|
1132
|
+
def convert_to_boolean_num(self, node, value: Value) -> Num:
|
|
1133
|
+
if _is_num(value):
|
|
1134
|
+
return value
|
|
1135
|
+
if hasattr(type(value), "__bool__"):
|
|
1136
|
+
return self.ensure_boolean_num(self.handle_call(node, type(value).__bool__, validate_value(value)))
|
|
1137
|
+
if hasattr(type(value), "__len__"):
|
|
1138
|
+
length = self.handle_call(node, type(value).__len__, validate_value(value))
|
|
1139
|
+
if not _is_num(length):
|
|
1140
|
+
raise TypeError(f"Invalid type for __len__: {type(length).__name__}")
|
|
1141
|
+
if length._is_py_():
|
|
1142
|
+
return Num._accept_(length._as_py_() > 0)
|
|
1143
|
+
return length > Num._accept_(0)
|
|
1144
|
+
raise TypeError(f"Converting {type(value).__name__} to bool is not supported")
|
|
1145
|
+
|
|
1130
1146
|
def arguments_to_signature(self, arguments: ast.arguments) -> inspect.Signature:
|
|
1131
1147
|
parameters: list[inspect.Parameter] = []
|
|
1132
1148
|
pos_only_count = len(arguments.posonlyargs)
|
|
@@ -549,11 +549,19 @@ class _BaseArchetype:
|
|
|
549
549
|
metadata = _annotation_defaults.get(metadata, metadata)
|
|
550
550
|
if isinstance(metadata, _ArchetypeFieldInfo):
|
|
551
551
|
if field_info is not None:
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
552
|
+
if field_info.storage == metadata.storage and field_info.name is None:
|
|
553
|
+
field_info = metadata
|
|
554
|
+
elif field_info.storage == metadata.storage and (
|
|
555
|
+
metadata.name is None or field_info.name == metadata.name
|
|
556
|
+
):
|
|
557
|
+
pass
|
|
558
|
+
else:
|
|
559
|
+
raise TypeError(
|
|
560
|
+
f"Unexpected multiple field annotations for '{name}', "
|
|
561
|
+
f"expected exactly one of imported, exported, entity_memory, or shared_memory"
|
|
562
|
+
)
|
|
563
|
+
else:
|
|
564
|
+
field_info = metadata
|
|
557
565
|
if field_info is None:
|
|
558
566
|
raise TypeError(
|
|
559
567
|
f"Missing field annotation for '{name}', "
|
|
@@ -880,7 +888,7 @@ class WatchArchetype(_BaseArchetype):
|
|
|
880
888
|
case _ArchetypeSelfData():
|
|
881
889
|
return _deref(ctx().blocks.EntityInfo, 0, WatchEntityInfo)
|
|
882
890
|
case _ArchetypeReferenceData(index=index):
|
|
883
|
-
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(),
|
|
891
|
+
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
|
|
884
892
|
case _:
|
|
885
893
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
886
894
|
|
|
@@ -1106,10 +1114,20 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1106
1114
|
"""Return a new reference with the given archetype type."""
|
|
1107
1115
|
return EntityRef[archetype](index=self.index)
|
|
1108
1116
|
|
|
1117
|
+
@meta_fn
|
|
1109
1118
|
def get(self) -> A:
|
|
1110
1119
|
"""Get the entity."""
|
|
1120
|
+
if ref := getattr(self, "_ref_", None):
|
|
1121
|
+
return ref
|
|
1111
1122
|
return self.archetype().at(self.index)
|
|
1112
1123
|
|
|
1124
|
+
@meta_fn
|
|
1125
|
+
def get_as(self, archetype: type[_BaseArchetype]) -> _BaseArchetype:
|
|
1126
|
+
"""Get the entity as the given archetype type."""
|
|
1127
|
+
if getattr(archetype, "_ref_", None):
|
|
1128
|
+
raise TypeError("Using get_as in level data is not supported.")
|
|
1129
|
+
return self.with_archetype(archetype).get()
|
|
1130
|
+
|
|
1113
1131
|
def archetype_matches(self) -> bool:
|
|
1114
1132
|
"""Check if entity at the index is precisely of the archetype."""
|
|
1115
1133
|
return self.index >= 0 and self.archetype().is_at(self.index)
|
|
@@ -1117,7 +1135,7 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1117
1135
|
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
1118
1136
|
ref = getattr(self, "_ref_", None)
|
|
1119
1137
|
if ref is None:
|
|
1120
|
-
return
|
|
1138
|
+
return Num._accept_(self.index)._to_list_()
|
|
1121
1139
|
else:
|
|
1122
1140
|
if ref not in level_refs:
|
|
1123
1141
|
raise KeyError("Reference to entity not in level data")
|
|
@@ -5,7 +5,7 @@ from typing import Any, Self, final
|
|
|
5
5
|
|
|
6
6
|
from sonolus.backend.ir import IRConst, IRSet
|
|
7
7
|
from sonolus.backend.place import BlockPlace
|
|
8
|
-
from sonolus.script.array_like import ArrayLike
|
|
8
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
9
9
|
from sonolus.script.debug import assert_unreachable
|
|
10
10
|
from sonolus.script.internal.context import ctx
|
|
11
11
|
from sonolus.script.internal.error import InternalError
|
|
@@ -18,6 +18,7 @@ from sonolus.script.num import Num
|
|
|
18
18
|
class ArrayMeta(type):
|
|
19
19
|
@meta_fn
|
|
20
20
|
def __pos__[T](cls: type[T]) -> T:
|
|
21
|
+
"""Create a zero-initialized array instance."""
|
|
21
22
|
return cls._zero_()
|
|
22
23
|
|
|
23
24
|
|
|
@@ -186,7 +187,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
|
186
187
|
|
|
187
188
|
@meta_fn
|
|
188
189
|
def __getitem__(self, index: Num) -> T:
|
|
189
|
-
index: Num = Num._accept_(index)
|
|
190
|
+
index: Num = Num._accept_(get_positive_index(index, self.size()))
|
|
190
191
|
if index._is_py_() and 0 <= index._as_py_() < self.size():
|
|
191
192
|
const_index = index._as_py_()
|
|
192
193
|
if isinstance(const_index, float) and not const_index.is_integer():
|
|
@@ -230,7 +231,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
|
230
231
|
|
|
231
232
|
@meta_fn
|
|
232
233
|
def __setitem__(self, index: Num, value: T):
|
|
233
|
-
index: Num = Num._accept_(index)
|
|
234
|
+
index: Num = Num._accept_(get_positive_index(index, self.size()))
|
|
234
235
|
value = self.element_type()._accept_(value)
|
|
235
236
|
if ctx():
|
|
236
237
|
if isinstance(self._value, list):
|
|
@@ -303,6 +304,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
|
303
304
|
else:
|
|
304
305
|
return f"{type(self).__name__}({', '.join(repr(self[i]) for i in range(self.size()))})"
|
|
305
306
|
|
|
307
|
+
@meta_fn
|
|
306
308
|
def __pos__(self) -> Self:
|
|
307
309
|
"""Return a copy of the array."""
|
|
308
310
|
return self._copy_()
|
|
@@ -5,6 +5,8 @@ from abc import abstractmethod
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from sonolus.script.internal.context import ctx
|
|
9
|
+
from sonolus.script.internal.impl import meta_fn
|
|
8
10
|
from sonolus.script.iterator import SonolusIterator
|
|
9
11
|
from sonolus.script.num import Num
|
|
10
12
|
from sonolus.script.record import Record
|
|
@@ -302,3 +304,26 @@ class _ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
|
|
|
302
304
|
|
|
303
305
|
def advance(self):
|
|
304
306
|
self.i += 1
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@meta_fn
|
|
310
|
+
def get_positive_index(index: Num, length: Num) -> Num:
|
|
311
|
+
"""Get the positive index for the given index in the array of the given length.
|
|
312
|
+
|
|
313
|
+
This is used to convert negative indixes relative to the end of the array to positive indices.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
index: The index to convert.
|
|
317
|
+
length: The length of the array.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
The positive index.
|
|
321
|
+
"""
|
|
322
|
+
if not ctx():
|
|
323
|
+
return index if index >= 0 else index + length
|
|
324
|
+
index = Num._accept_(index)
|
|
325
|
+
length = Num._accept_(length)
|
|
326
|
+
if index._is_py_() and length._is_py_():
|
|
327
|
+
return Num._accept_(index._as_py_() + length._as_py_() if index._as_py_() < 0 else index._as_py_())
|
|
328
|
+
else:
|
|
329
|
+
return index + (index < 0) * length
|
|
@@ -2,10 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.visitor import compile_and_call
|
|
4
4
|
from sonolus.script.array import Array
|
|
5
|
-
from sonolus.script.array_like import ArrayLike
|
|
5
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
6
6
|
from sonolus.script.debug import error
|
|
7
7
|
from sonolus.script.internal.context import ctx
|
|
8
8
|
from sonolus.script.internal.impl import meta_fn
|
|
9
|
+
from sonolus.script.interval import clamp
|
|
9
10
|
from sonolus.script.iterator import SonolusIterator
|
|
10
11
|
from sonolus.script.num import Num
|
|
11
12
|
from sonolus.script.pointer import _deref
|
|
@@ -140,11 +141,11 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
140
141
|
assert p == Pair(5, 6) # The value of p has changed
|
|
141
142
|
```
|
|
142
143
|
"""
|
|
143
|
-
return self._array[item]
|
|
144
|
+
return self._array[get_positive_index(item, len(self))]
|
|
144
145
|
|
|
145
146
|
def __setitem__(self, key: int, value: T):
|
|
146
147
|
"""Update the element at the given index."""
|
|
147
|
-
self._array[key] = value
|
|
148
|
+
self._array[get_positive_index(key, len(self))] = value
|
|
148
149
|
|
|
149
150
|
def __delitem__(self, key: int):
|
|
150
151
|
"""Remove the element at the given index."""
|
|
@@ -190,6 +191,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
190
191
|
"""
|
|
191
192
|
if index is None:
|
|
192
193
|
index = self._size - 1
|
|
194
|
+
index = get_positive_index(index, len(self))
|
|
193
195
|
assert 0 <= index < self._size
|
|
194
196
|
value = copy(self._array[index])
|
|
195
197
|
self._size -= 1
|
|
@@ -207,7 +209,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
207
209
|
index: The index at which to insert the value. Must be in the range [0, size].
|
|
208
210
|
value: The value to insert.
|
|
209
211
|
"""
|
|
210
|
-
|
|
212
|
+
index = clamp(get_positive_index(index, len(self)), 0, self._size)
|
|
211
213
|
assert self._size < len(self._array)
|
|
212
214
|
self._size += 1
|
|
213
215
|
for i in range(self._size - 1, index, -1):
|
|
@@ -329,6 +331,7 @@ class ArrayPointer[T](Record, ArrayLike[T]):
|
|
|
329
331
|
|
|
330
332
|
@meta_fn
|
|
331
333
|
def _get_item(self, item: int) -> T:
|
|
334
|
+
item = get_positive_index(item, self.size)
|
|
332
335
|
if not ctx():
|
|
333
336
|
raise TypeError("ArrayPointer values cannot be accessed outside of a context")
|
|
334
337
|
return _deref(
|
|
@@ -536,6 +539,23 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
536
539
|
self._array[self._size] = _ArrayMapEntry(key, value)
|
|
537
540
|
self._size += 1
|
|
538
541
|
|
|
542
|
+
def __delitem__(self, key: K):
|
|
543
|
+
"""Remove the key-value pair associated with the given key.
|
|
544
|
+
|
|
545
|
+
Must be called with a key that is present in the map.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
key: The key to remove
|
|
549
|
+
"""
|
|
550
|
+
for i in range(self._size):
|
|
551
|
+
entry = self._array[i]
|
|
552
|
+
if entry.key == key:
|
|
553
|
+
self._size -= 1
|
|
554
|
+
if i < self._size:
|
|
555
|
+
self._array[i] = self._array[self._size]
|
|
556
|
+
return
|
|
557
|
+
error()
|
|
558
|
+
|
|
539
559
|
def __contains__(self, key: K) -> bool:
|
|
540
560
|
"""Return whether the given key is present in the map.
|
|
541
561
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
from typing import overload
|
|
3
3
|
|
|
4
|
+
from sonolus.script.array import Array
|
|
4
5
|
from sonolus.script.array_like import ArrayLike
|
|
5
6
|
from sonolus.script.internal.context import ctx
|
|
6
7
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
@@ -129,6 +130,8 @@ def _max(*args, key: callable = _identity):
|
|
|
129
130
|
(iterable,) = args
|
|
130
131
|
if isinstance(iterable, ArrayLike):
|
|
131
132
|
return compile_and_call(iterable._max_, key=key)
|
|
133
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
134
|
+
return compile_and_call(Array(*iterable.value)._max_, key=key)
|
|
132
135
|
else:
|
|
133
136
|
raise TypeError(f"Unsupported type: {type(iterable)} for max")
|
|
134
137
|
else:
|
|
@@ -169,6 +172,8 @@ def _min(*args, key: callable = _identity):
|
|
|
169
172
|
(iterable,) = args
|
|
170
173
|
if isinstance(iterable, ArrayLike):
|
|
171
174
|
return compile_and_call(iterable._min_, key=key)
|
|
175
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
176
|
+
return compile_and_call(Array(*iterable.value)._min_, key=key)
|
|
172
177
|
else:
|
|
173
178
|
raise TypeError(f"Unsupported type: {type(iterable)} for min")
|
|
174
179
|
else:
|
|
@@ -221,12 +226,12 @@ def _float(value=0.0):
|
|
|
221
226
|
return value
|
|
222
227
|
|
|
223
228
|
|
|
224
|
-
@meta_fn
|
|
225
229
|
def _bool(value=False):
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
# Relies on the compiler to perform the conversion in a boolean context
|
|
231
|
+
if value: # noqa: SIM103
|
|
232
|
+
return True
|
|
233
|
+
else:
|
|
234
|
+
return False
|
|
230
235
|
|
|
231
236
|
|
|
232
237
|
_int._type_mapping_ = Num
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from sonolus.script.array_like import ArrayLike
|
|
1
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
2
|
+
from sonolus.script.internal.context import ctx
|
|
3
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
2
4
|
from sonolus.script.iterator import SonolusIterator
|
|
3
5
|
from sonolus.script.num import Num
|
|
4
6
|
from sonolus.script.record import Record
|
|
@@ -36,7 +38,7 @@ class Range(Record, ArrayLike[Num]):
|
|
|
36
38
|
return (diff - self.step - 1) // -self.step
|
|
37
39
|
|
|
38
40
|
def __getitem__(self, index: Num) -> Num:
|
|
39
|
-
return self.start + index * self.step
|
|
41
|
+
return self.start + get_positive_index(index, len(self)) * self.step
|
|
40
42
|
|
|
41
43
|
def __setitem__(self, index: Num, value: Num):
|
|
42
44
|
raise TypeError("Range does not support item assignment")
|
|
@@ -79,3 +81,24 @@ class RangeIterator(Record, SonolusIterator):
|
|
|
79
81
|
|
|
80
82
|
def advance(self):
|
|
81
83
|
self.value += self.step
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@meta_fn
|
|
87
|
+
def range_or_tuple(start: Num, stop: Num | None = None, step: Num = 1) -> Range | tuple[Num, ...]:
|
|
88
|
+
if stop is None:
|
|
89
|
+
start, stop = 0, start
|
|
90
|
+
if not ctx():
|
|
91
|
+
return range(start, stop, step) # type: ignore
|
|
92
|
+
start = Num._accept_(start)
|
|
93
|
+
stop = Num._accept_(stop) if stop is not None else None
|
|
94
|
+
step = Num._accept_(step)
|
|
95
|
+
if start._is_py_() and stop._is_py_() and step._is_py_():
|
|
96
|
+
start_int = start._as_py_()
|
|
97
|
+
stop_int = stop._as_py_() if stop is not None else None
|
|
98
|
+
if stop_int is None:
|
|
99
|
+
start_int, stop_int = 0, start_int
|
|
100
|
+
step_int = step._as_py_()
|
|
101
|
+
if start_int % 1 != 0 or stop_int % 1 != 0 or step_int % 1 != 0:
|
|
102
|
+
raise TypeError("Range arguments must be integers")
|
|
103
|
+
return validate_value(tuple(range(int(start_int), int(stop_int), int(step_int)))) # type: ignore
|
|
104
|
+
return Range(start, stop, step)
|
|
@@ -21,8 +21,10 @@ class TupleImpl(TransientValue):
|
|
|
21
21
|
raise TypeError(f"Cannot index tuple with {item}")
|
|
22
22
|
if int(item) != item:
|
|
23
23
|
raise TypeError(f"Cannot index tuple with non-integer {item}")
|
|
24
|
-
if not (
|
|
24
|
+
if not (-len(self.value) <= item < len(self.value)):
|
|
25
25
|
raise IndexError(f"Tuple index out of range: {item}")
|
|
26
|
+
if item < 0:
|
|
27
|
+
item += len(self.value)
|
|
26
28
|
return self.value[int(item)]
|
|
27
29
|
|
|
28
30
|
@meta_fn
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
|
+
from sonolus.script.array_like import ArrayLike
|
|
4
5
|
from sonolus.script.debug import static_error
|
|
5
6
|
from sonolus.script.internal.native import native_function
|
|
7
|
+
from sonolus.script.internal.range import range_or_tuple
|
|
6
8
|
from sonolus.script.num import Num
|
|
7
9
|
from sonolus.script.record import Record
|
|
8
10
|
|
|
@@ -296,7 +298,7 @@ def unlerp_clamped(a: float, b: float, x: float, /) -> float:
|
|
|
296
298
|
|
|
297
299
|
@native_function(Op.Remap)
|
|
298
300
|
def remap(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
299
|
-
"""
|
|
301
|
+
"""Linearly remap a value from one interval to another.
|
|
300
302
|
|
|
301
303
|
Args:
|
|
302
304
|
a: The start of the input interval.
|
|
@@ -313,7 +315,7 @@ def remap(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
|
313
315
|
|
|
314
316
|
@native_function(Op.RemapClamped)
|
|
315
317
|
def remap_clamped(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
316
|
-
"""
|
|
318
|
+
"""Linearly remap a value from one interval to another, clamped to the output interval.
|
|
317
319
|
|
|
318
320
|
Args:
|
|
319
321
|
a: The start of the input interval.
|
|
@@ -341,3 +343,59 @@ def clamp(x: float, a: float, b: float, /) -> float:
|
|
|
341
343
|
The clamped value.
|
|
342
344
|
"""
|
|
343
345
|
return max(a, min(b, x))
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def interp(
|
|
349
|
+
xp: ArrayLike[float] | tuple[float, ...],
|
|
350
|
+
fp: ArrayLike[float] | tuple[float, ...],
|
|
351
|
+
x: float,
|
|
352
|
+
) -> float:
|
|
353
|
+
"""Linearly interpolate a value within a sequence of points.
|
|
354
|
+
|
|
355
|
+
The sequence must have at least 2 elements and be sorted in increasing order of x-coordinates.
|
|
356
|
+
For values of x outside the range of xp, the slope of the first or last segment is used to extrapolate.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
xp: The x-coordinates of the points in increasing order.
|
|
360
|
+
fp: The y-coordinates of the points.
|
|
361
|
+
x: The x-coordinate to interpolate.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
The interpolated value.
|
|
365
|
+
"""
|
|
366
|
+
assert len(xp) == len(fp)
|
|
367
|
+
assert len(xp) >= 2
|
|
368
|
+
for i in range_or_tuple(1, len(xp) - 1):
|
|
369
|
+
# At i == 1, x may be less than x[0], but since we're extrapolating, we use the first segment regardless.
|
|
370
|
+
if x <= xp[i]:
|
|
371
|
+
return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
|
|
372
|
+
# x > xp[-2] so we can just use the last segment regardless of whether x is in it or to the right of it.
|
|
373
|
+
return remap(xp[-2], xp[-1], fp[-2], fp[-1], x)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def interp_clamped(
|
|
377
|
+
xp: ArrayLike[float] | tuple[float, ...],
|
|
378
|
+
fp: ArrayLike[float] | tuple[float, ...],
|
|
379
|
+
x: float,
|
|
380
|
+
):
|
|
381
|
+
"""Linearly interpolate a value within a sequence of points.
|
|
382
|
+
|
|
383
|
+
The sequence must have at least 2 elements and be sorted in increasing order of x-coordinates.
|
|
384
|
+
For x-coordinates outside the range of the sequence, the respective endpoint of fp is returned.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
xp: The x-coordinates of the points in increasing order.
|
|
388
|
+
fp: The y-coordinates of the points.
|
|
389
|
+
x: The x-coordinate to interpolate.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
The interpolated value.
|
|
393
|
+
"""
|
|
394
|
+
assert len(xp) == len(fp)
|
|
395
|
+
assert len(xp) >= 2
|
|
396
|
+
if x <= xp[0]:
|
|
397
|
+
return fp[0]
|
|
398
|
+
for i in range_or_tuple(1, len(xp)):
|
|
399
|
+
if x <= xp[i]:
|
|
400
|
+
return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
|
|
401
|
+
return fp[-1]
|