sonolus.py 0.2.0__tar.gz → 0.2.1__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.

Files changed (174) hide show
  1. sonolus_py-0.2.1/.python-version +1 -0
  2. sonolus_py-0.2.1/.run/Python tests in tests.run.xml +21 -0
  3. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/PKG-INFO +2 -2
  4. sonolus_py-0.2.1/README.md +2 -0
  5. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/doc_stubs/builtins.pyi +3 -0
  6. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/concepts/constructs.md +18 -13
  7. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/concepts/resources.md +3 -2
  8. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/concepts/types.md +2 -1
  9. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/index.md +1 -1
  10. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/pyproject.toml +12 -11
  11. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/archetype.py +44 -6
  12. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/array.py +3 -3
  13. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/array_like.py +6 -2
  14. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/containers.py +166 -0
  15. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/debug.py +20 -2
  16. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/engine.py +2 -2
  17. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/constant.py +3 -3
  18. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/transient.py +3 -3
  19. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/value.py +27 -11
  20. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/interval.py +3 -14
  21. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/num.py +26 -15
  22. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/options.py +18 -3
  23. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/pointer.py +9 -1
  24. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/quad.py +55 -1
  25. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/record.py +3 -3
  26. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/runtime.py +17 -6
  27. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/text.py +9 -0
  28. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/ui.py +13 -4
  29. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/values.py +1 -1
  30. sonolus_py-0.2.1/tests/script/test_array_set.py +150 -0
  31. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_quad.py +21 -0
  32. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/uv.lock +1 -1
  33. sonolus_py-0.2.0/.python-version +0 -1
  34. sonolus_py-0.2.0/README.md +0 -2
  35. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/.github/workflows/publish.yaml +0 -0
  36. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/.gitignore +0 -0
  37. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/LICENSE +0 -0
  38. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/doc_stubs/__init__.py +0 -0
  39. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/doc_stubs/math.pyi +0 -0
  40. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/doc_stubs/num.pyi +0 -0
  41. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/doc_stubs/random.pyi +0 -0
  42. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/CNAME +0 -0
  43. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/concepts/builtins.md +0 -0
  44. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/concepts/cli.md +0 -0
  45. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/concepts/index.md +0 -0
  46. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/concepts/project.md +0 -0
  47. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/builtins.md +0 -0
  48. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/index.md +0 -0
  49. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/math.md +0 -0
  50. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/random.md +0 -0
  51. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.archetype.md +0 -0
  52. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.array.md +0 -0
  53. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.array_like.md +0 -0
  54. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.bucket.md +0 -0
  55. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.containers.md +0 -0
  56. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.debug.md +0 -0
  57. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.easing.md +0 -0
  58. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.effect.md +0 -0
  59. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.engine.md +0 -0
  60. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.globals.md +0 -0
  61. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.instruction.md +0 -0
  62. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.interval.md +0 -0
  63. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.iterator.md +0 -0
  64. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.level.md +0 -0
  65. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.metadata.md +0 -0
  66. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.num.md +0 -0
  67. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.options.md +0 -0
  68. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.particle.md +0 -0
  69. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.printing.md +0 -0
  70. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.project.md +0 -0
  71. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.quad.md +0 -0
  72. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.record.md +0 -0
  73. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.runtime.md +0 -0
  74. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.sprite.md +0 -0
  75. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.text.md +0 -0
  76. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.timing.md +0 -0
  77. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.transform.md +0 -0
  78. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.ui.md +0 -0
  79. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.values.md +0 -0
  80. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/docs/reference/sonolus.script.vec.md +0 -0
  81. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/mkdocs.yml +0 -0
  82. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/scripts/generate.py +0 -0
  83. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/scripts/runtimes/Engine/Tutorial/Blocks.json +0 -0
  84. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/scripts/runtimes/Functions.json +0 -0
  85. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/scripts/runtimes/Level/Play/Blocks.json +0 -0
  86. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/scripts/runtimes/Level/Preview/Blocks.json +0 -0
  87. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/scripts/runtimes/Level/Watch/Blocks.json +0 -0
  88. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/__init__.py +0 -0
  89. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/__init__.py +0 -0
  90. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/blocks.py +0 -0
  91. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/excepthook.py +0 -0
  92. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/finalize.py +0 -0
  93. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/interpret.py +0 -0
  94. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/ir.py +0 -0
  95. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/mode.py +0 -0
  96. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/node.py +0 -0
  97. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/ops.py +0 -0
  98. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/__init__.py +0 -0
  99. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/allocate.py +0 -0
  100. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/constant_evaluation.py +0 -0
  101. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/copy_coalesce.py +0 -0
  102. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/dead_code.py +0 -0
  103. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/dominance.py +0 -0
  104. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/flow.py +0 -0
  105. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/inlining.py +0 -0
  106. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/liveness.py +0 -0
  107. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/optimize.py +0 -0
  108. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/passes.py +0 -0
  109. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/simplify.py +0 -0
  110. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/optimize/ssa.py +0 -0
  111. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/place.py +0 -0
  112. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/utils.py +0 -0
  113. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/backend/visitor.py +0 -0
  114. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/__init__.py +0 -0
  115. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/cli.py +0 -0
  116. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/collection.py +0 -0
  117. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/compile.py +0 -0
  118. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/engine.py +0 -0
  119. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/level.py +0 -0
  120. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/node.py +0 -0
  121. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/build/project.py +0 -0
  122. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/py.typed +0 -0
  123. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/__init__.py +0 -0
  124. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/bucket.py +0 -0
  125. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/easing.py +0 -0
  126. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/effect.py +0 -0
  127. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/globals.py +0 -0
  128. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/instruction.py +0 -0
  129. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/__init__.py +0 -0
  130. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/builtin_impls.py +0 -0
  131. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/callbacks.py +0 -0
  132. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/context.py +0 -0
  133. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/descriptor.py +0 -0
  134. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/dict_impl.py +0 -0
  135. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/error.py +0 -0
  136. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/generic.py +0 -0
  137. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/impl.py +0 -0
  138. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/introspection.py +0 -0
  139. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/math_impls.py +0 -0
  140. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/native.py +0 -0
  141. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/random.py +0 -0
  142. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/range.py +0 -0
  143. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/internal/tuple_impl.py +0 -0
  144. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/iterator.py +0 -0
  145. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/level.py +0 -0
  146. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/metadata.py +0 -0
  147. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/particle.py +0 -0
  148. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/printing.py +0 -0
  149. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/project.py +0 -0
  150. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/sprite.py +0 -0
  151. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/timing.py +0 -0
  152. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/transform.py +0 -0
  153. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/sonolus/script/vec.py +0 -0
  154. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/__init__.py +0 -0
  155. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/__init__.py +0 -0
  156. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/conftest.py +0 -0
  157. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_array.py +0 -0
  158. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_array_map.py +0 -0
  159. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_assert.py +0 -0
  160. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_dict.py +0 -0
  161. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_flow.py +0 -0
  162. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_functions.py +0 -0
  163. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_helpers.py +0 -0
  164. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_interval.py +0 -0
  165. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_match.py +0 -0
  166. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_num.py +0 -0
  167. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_operator.py +0 -0
  168. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_random.py +0 -0
  169. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_range.py +0 -0
  170. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_record.py +0 -0
  171. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_transform.py +0 -0
  172. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_tuple.py +0 -0
  173. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_var_array.py +0 -0
  174. {sonolus_py-0.2.0 → sonolus_py-0.2.1}/tests/script/test_vec.py +0 -0
@@ -0,0 +1 @@
1
+ 3.13t
@@ -0,0 +1,21 @@
1
+ <component name="ProjectRunConfigurationManager">
2
+ <configuration default="false" name="Python tests in tests" type="tests" factoryName="Autodetect" nameIsGenerated="true">
3
+ <module name="sonolus" />
4
+ <option name="ENV_FILES" value="" />
5
+ <option name="INTERPRETER_OPTIONS" value="" />
6
+ <option name="PARENT_ENVS" value="true" />
7
+ <envs>
8
+ <env name="PYTHON_JIT" value="1" />
9
+ </envs>
10
+ <option name="SDK_HOME" value="" />
11
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
12
+ <option name="IS_MODULE_SDK" value="true" />
13
+ <option name="ADD_CONTENT_ROOTS" value="true" />
14
+ <option name="ADD_SOURCE_ROOTS" value="true" />
15
+ <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
16
+ <option name="_new_additionalArguments" value="&quot;-n 32 --cov sonolus --cov tests --cov-report xml&quot;" />
17
+ <option name="_new_target" value="&quot;$PROJECT_DIR$/tests&quot;" />
18
+ <option name="_new_targetType" value="&quot;PATH&quot;" />
19
+ <method v="2" />
20
+ </configuration>
21
+ </component>
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Sonolus engine development in Python
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Description-Content-Type: text/markdown
8
8
 
9
9
  # Sonolus.py
10
- Sonolus engine development in Python.
10
+ Sonolus engine development in Python. See [docs](https://sonolus.py.qwewqa.xyz) for more information.
@@ -0,0 +1,2 @@
1
+ # Sonolus.py
2
+ Sonolus engine development in Python. See [docs](https://sonolus.py.qwewqa.xyz) for more information.
@@ -148,6 +148,9 @@ def len(s: object) -> builtins.int:
148
148
  def map[T, S](function: Callable[[T], S], iterable: Iterable[T]) -> Iterator[S]:
149
149
  """Apply a function to every item of an iterable and return an iterator.
150
150
 
151
+ Unlike the standard Python map function, it is possible that the function may be called more than once on the
152
+ same item.
153
+
151
154
  Args:
152
155
  function: The function to apply.
153
156
  iterable: The iterable to process.
@@ -18,6 +18,8 @@ Most standard Python constructs are supported in Sonolus.py.
18
18
 
19
19
  ## Overview
20
20
 
21
+ The following constructs are supported in Sonolus.py:
22
+
21
23
  - Expressions:
22
24
  - Literals:
23
25
  - Numbers (excluding complex numbers): `0`, `1`, `1.0`, `1e3`, `0x1`, `0b1`, `0o1`
@@ -80,18 +82,20 @@ Some expressions can be evaluated at compile time:
80
82
  - Comparison: for compile time constant operands: `a == b`, `a != b`, `a > b`, `a < b`, `a >= b`, `a <= b`, ...
81
83
  - Variables assigned to compile time constants: `a = 1`, `b = a + 1`, ...
82
84
 
83
- The compiler can make additional inferences when compile time constants are used in certain contexts like control flow:
85
+ Some values like array sizes must be compile-time constants.
86
+
87
+ The compiler will eliminate branches known to be unreachable at compile time:
84
88
 
85
89
  ```python
86
- x = ...
90
+ def f(a):
91
+ if isinstance(a, Num):
92
+ debug_log(a)
93
+ else:
94
+ debug_log(a.x + a.y)
87
95
 
88
- # This code works because the compiler knows which branch will be taken based on the type of 'x'
89
- if isinstance(x, Num):
90
- debug_log(x)
91
- elif isinstance(x, Vec2):
92
- debug_log(x.x + x.y)
93
- else:
94
- debug_log(-1)
96
+ # This works because `isinstance` is evaluated at compile time and only the first (if) branch is reachable.
97
+ # The second (else) branch is eliminated, so we don't get an error that a does not have 'x' and 'y' attributes.
98
+ f(123)
95
99
  ```
96
100
 
97
101
  ## Variables
@@ -385,7 +389,8 @@ def g(a):
385
389
  ```
386
390
 
387
391
  Function returns follow the same rules as variable access. If a function returns a non-num value, it most only
388
- return that value. There is no restriction of a function only returns a num.
392
+ return that value. If the function always returns a num, it may have any number of returns. Similarly, if a function
393
+ always returns None (`return None` or just `return`), it may have any number of returns.
389
394
 
390
395
  The following are allowed:
391
396
 
@@ -439,7 +444,7 @@ def k():
439
444
  return Vec2(1, 2)
440
445
  ```
441
446
 
442
- Most functions returning a non-num value should have a single `return` statement at the end.
447
+ Outside of functions returning `None` or a num, most functions should have a single `return` statement at the end.
443
448
 
444
449
  ### Classes
445
450
 
@@ -475,8 +480,8 @@ Imports are supported at the module level, but not within functions.
475
480
 
476
481
  ### assert
477
482
 
478
- Assertions are supported. Since error handling is not supported, assertion failures will terminate the current
479
- callback when running in the Sonolus app.
483
+ Assertions are supported. Assertion failures cannot be handled and will terminate the current
484
+ callback when running in the Sonolus app. In debug mode, the game will also pause to indicate the error.
480
485
 
481
486
  ```python
482
487
  assert a > 0, 'a must be positive'
@@ -153,6 +153,7 @@ class Options:
153
153
  ```
154
154
 
155
155
  There are three types of options available:
156
+
156
157
  1. `slider_option`: A slider control for numeric values
157
158
  2. `toggle_option`: A toggle switch for boolean values
158
159
  3. `select_option`: A dropdown menu for selecting from predefined values
@@ -236,8 +237,8 @@ ui_config = UiConfig(
236
237
  ease=EaseType.NONE,
237
238
  ),
238
239
  ),
239
- judgment_error_style=UiJudgmentErrorStyle.NONE,
240
- judgment_error_placement=UiJudgmentErrorPlacement.BOTH,
240
+ judgment_error_style=UiJudgmentErrorStyle.LATE,
241
+ judgment_error_placement=UiJudgmentErrorPlacement.TOP,
241
242
  judgment_error_min=0.0,
242
243
  )
243
244
  ```
@@ -124,7 +124,8 @@ Array # The Generic Array type
124
124
  Array[int, 3] # A concrete Array type
125
125
  ```
126
126
 
127
- The element type of an array must be concrete (not generic) and the size must be a non-negative integer:
127
+ The element type of an array must be concrete (not generic) and the size must be a non-negative compile-time
128
+ constant integer:
128
129
 
129
130
  ```python
130
131
  # Ok
@@ -14,7 +14,7 @@ Sonolus.py is available on PyPI and can be installed using a package manager lik
14
14
  ```
15
15
 
16
16
  ## Getting Started
17
- ...
17
+ Coming soon!
18
18
 
19
19
  ## Documentation
20
20
  See [Concepts](concepts/index.md) for an overview of usage details.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sonolus.py"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Sonolus engine development in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -9,16 +9,7 @@ requires-python = ">=3.12"
9
9
  sonolus-py = "sonolus.build.cli:main"
10
10
 
11
11
  [tool.uv]
12
- dev-dependencies = [
13
- "hypothesis>=6.115.3",
14
- "pre-commit>=4.0.1",
15
- "pytest-xdist>=3.6.1",
16
- "pytest>=8.3.3",
17
- "ruff>=0.6.9",
18
- "pytest-cov>=6.0.0",
19
- "tox>=4.23.2",
20
- "tox-uv>=1.16.0",
21
- ]
12
+ default-groups = ["dev", "docs"]
22
13
 
23
14
  [tool.ruff]
24
15
  line-length = 120
@@ -37,6 +28,16 @@ requires = ["hatchling"]
37
28
  build-backend = "hatchling.build"
38
29
 
39
30
  [dependency-groups]
31
+ dev = [
32
+ "hypothesis>=6.115.3",
33
+ "pre-commit>=4.0.1",
34
+ "pytest-xdist>=3.6.1",
35
+ "pytest>=8.3.3",
36
+ "ruff>=0.6.9",
37
+ "pytest-cov>=6.0.0",
38
+ "tox>=4.23.2",
39
+ "tox-uv>=1.16.0",
40
+ ]
40
41
  docs = [
41
42
  "mkdocs-material>=9.5.45",
42
43
  "mkdocs>=1.6.1",
@@ -8,10 +8,9 @@ from enum import Enum, StrEnum
8
8
  from types import FunctionType
9
9
  from typing import Annotated, Any, ClassVar, Self, TypedDict, get_origin
10
10
 
11
- from sonolus.backend.ir import IRConst, IRInstr
11
+ from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr, IRStmt
12
12
  from sonolus.backend.mode import Mode
13
13
  from sonolus.backend.ops import Op
14
- from sonolus.backend.place import BlockPlace
15
14
  from sonolus.script.bucket import Bucket, Judgment
16
15
  from sonolus.script.internal.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
17
16
  from sonolus.script.internal.context import ctx
@@ -20,9 +19,9 @@ from sonolus.script.internal.generic import validate_concrete_type
20
19
  from sonolus.script.internal.impl import meta_fn, validate_value
21
20
  from sonolus.script.internal.introspection import get_field_specifiers
22
21
  from sonolus.script.internal.native import native_call
23
- from sonolus.script.internal.value import Value
22
+ from sonolus.script.internal.value import BackingValue, DataValue, Value
24
23
  from sonolus.script.num import Num
25
- from sonolus.script.pointer import _deref
24
+ from sonolus.script.pointer import _backing_deref, _deref
26
25
  from sonolus.script.record import Record
27
26
  from sonolus.script.values import zeros
28
27
 
@@ -44,6 +43,17 @@ class _ArchetypeFieldInfo:
44
43
  storage: _StorageType
45
44
 
46
45
 
46
+ class _ExportBackingValue(BackingValue):
47
+ def __init__(self, index: IRExpr):
48
+ self.index = index
49
+
50
+ def read(self) -> IRExpr:
51
+ raise NotImplementedError("Exported fields are write-only")
52
+
53
+ def write(self, value: IRExpr) -> IRStmt:
54
+ return IRInstr(Op.ExportValue, [self.index, value])
55
+
56
+
47
57
  class _ArchetypeField(SonolusDescriptor):
48
58
  def __init__(self, name: str, data_name: str, storage: _StorageType, offset: int, type_: type[Value]):
49
59
  self.name = name
@@ -70,7 +80,20 @@ class _ArchetypeField(SonolusDescriptor):
70
80
  case _ArchetypeLevelData(values=values):
71
81
  result = values[self.name]
72
82
  case _StorageType.EXPORTED:
73
- raise RuntimeError("Exported fields are write-only")
83
+ match instance._data_:
84
+ case _ArchetypeSelfData():
85
+
86
+ def backing_source(i: IRExpr):
87
+ return _ExportBackingValue(IRPureInstr(Op.Add, [i, IRConst(self.offset)]))
88
+
89
+ result = _backing_deref(
90
+ backing_source,
91
+ self.type,
92
+ )
93
+ case _ArchetypeReferenceData():
94
+ raise RuntimeError("Exported fields of other entities are not accessible")
95
+ case _ArchetypeLevelData():
96
+ raise RuntimeError("Exported fields are not available in level data")
74
97
  case _StorageType.MEMORY:
75
98
  match instance._data_:
76
99
  case _ArchetypeSelfData():
@@ -584,6 +607,7 @@ class _BaseArchetype:
584
607
  cls._spawn_signature_ = inspect.Signature(
585
608
  [inspect.Parameter(name, inspect.Parameter.POSITIONAL_OR_KEYWORD) for name in cls._memory_fields_]
586
609
  )
610
+ cls._post_init_fields()
587
611
 
588
612
  @property
589
613
  @abstractmethod
@@ -609,6 +633,10 @@ class _BaseArchetype:
609
633
  case _:
610
634
  raise RuntimeError("Invalid entity data")
611
635
 
636
+ @classmethod
637
+ def _post_init_fields(cls):
638
+ pass
639
+
612
640
 
613
641
  class PlayArchetype(_BaseArchetype):
614
642
  """Base class for play mode archetypes.
@@ -885,6 +913,11 @@ class WatchArchetype(_BaseArchetype):
885
913
  case _:
886
914
  raise RuntimeError("Result is only accessible from the entity itself")
887
915
 
916
+ @classmethod
917
+ def _post_init_fields(cls):
918
+ if cls._exported_fields_:
919
+ raise RuntimeError("Watch archetypes cannot have exported fields")
920
+
888
921
 
889
922
  class PreviewArchetype(_BaseArchetype):
890
923
  """Base class for preview mode archetypes.
@@ -934,6 +967,11 @@ class PreviewArchetype(_BaseArchetype):
934
967
  """The index of this entity."""
935
968
  return self._info.index
936
969
 
970
+ @classmethod
971
+ def _post_init_fields(cls):
972
+ if cls._exported_fields_:
973
+ raise RuntimeError("Preview archetypes cannot have exported fields")
974
+
937
975
 
938
976
  @meta_fn
939
977
  def entity_info_at(index: Num) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
@@ -1066,7 +1104,7 @@ class EntityRef[A: _BaseArchetype](Record):
1066
1104
  """Check if entity at the index is precisely of the archetype."""
1067
1105
  return self.index >= 0 and self.archetype().is_at(self.index)
1068
1106
 
1069
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
1107
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
1070
1108
  ref = getattr(self, "_ref_", None)
1071
1109
  if ref is None:
1072
1110
  return [self.index]
@@ -12,7 +12,7 @@ from sonolus.script.internal.context import ctx
12
12
  from sonolus.script.internal.error import InternalError
13
13
  from sonolus.script.internal.generic import GenericValue
14
14
  from sonolus.script.internal.impl import meta_fn, validate_value
15
- from sonolus.script.internal.value import Value
15
+ from sonolus.script.internal.value import DataValue, Value
16
16
  from sonolus.script.num import Num
17
17
 
18
18
 
@@ -108,11 +108,11 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
108
108
  return self
109
109
 
110
110
  @classmethod
111
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
111
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
112
112
  iterator = iter(values)
113
113
  return cls(*(cls.element_type()._from_list_(iterator) for _ in range(cls.size())))
114
114
 
115
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
115
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
116
116
  match self._value:
117
117
  case list():
118
118
  return [entry for value in self._value for entry in value._to_list_(level_refs)]
@@ -158,10 +158,14 @@ class ArrayLike[T](Sequence, ABC):
158
158
  return min_index
159
159
 
160
160
  def _max_(self, key: Callable[T, Any] | None = None) -> T:
161
- return self[self.index_of_max(key=key)]
161
+ index = self.index_of_max(key=key)
162
+ assert index != -1
163
+ return self[index]
162
164
 
163
165
  def _min_(self, key: Callable[T, Any] | None = None) -> T:
164
- return self[self.index_of_min(key=key)]
166
+ index = self.index_of_min(key=key)
167
+ assert index != -1
168
+ return self[index]
165
169
 
166
170
  def swap(self, i: Num, j: Num, /):
167
171
  """Swap the values at the given indices.
@@ -1,13 +1,44 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from sonolus.backend.visitor import compile_and_call
3
4
  from sonolus.script.array import Array
4
5
  from sonolus.script.array_like import ArrayLike
5
6
  from sonolus.script.debug import error
7
+ from sonolus.script.internal.context import ctx
8
+ from sonolus.script.internal.impl import meta_fn
6
9
  from sonolus.script.iterator import SonolusIterator
10
+ from sonolus.script.num import Num
11
+ from sonolus.script.pointer import _deref
7
12
  from sonolus.script.record import Record
8
13
  from sonolus.script.values import alloc, copy
9
14
 
10
15
 
16
+ class Box[T](Record):
17
+ """A box that contains a value.
18
+
19
+ This can be helpful for generic code that can handle both Num and non-Num types.
20
+
21
+ Usage:
22
+ ```python
23
+ Box[T](value: T)
24
+ ```
25
+
26
+ Examples:
27
+ ```python
28
+ box = Box(1)
29
+ box = Box[int](2)
30
+
31
+ x: T = ...
32
+ y: T = ...
33
+ box = Box(x)
34
+ box.value = y # Works regardless of whether x is a Num or not
35
+ ```
36
+ """
37
+
38
+ value: T
39
+ """The value contained in the box."""
40
+
41
+
11
42
  class Pair[T, U](Record):
12
43
  """A generic pair of values.
13
44
 
@@ -129,6 +160,17 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
129
160
  self._array[self._size] = value
130
161
  self._size += 1
131
162
 
163
+ def append_unchecked(self, value: T):
164
+ """Append the given value to the end of the array without checking the capacity.
165
+
166
+ Use with caution as this may cause hard to debug issues if the array is full.
167
+
168
+ Args:
169
+ value: The value to append.
170
+ """
171
+ self._array[self._size] = value
172
+ self._size += 1
173
+
132
174
  def extend(self, values: ArrayLike[T]):
133
175
  """Appends copies of the values in the given array to the end of the array.
134
176
 
@@ -258,6 +300,130 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
258
300
  raise TypeError("unhashable type: 'VarArray'")
259
301
 
260
302
 
303
+ class ArrayPointer[T](Record, ArrayLike[T]):
304
+ """An array defined by a size and pointer to the first element.
305
+
306
+ This is intended to be created internally and improper use may result in hard to debug issues.
307
+
308
+ Usage:
309
+ ```python
310
+ ArrayPointer[T](size: int, block: int, offset: int)
311
+ ```
312
+ """
313
+
314
+ size: int
315
+ block: int
316
+ offset: int
317
+
318
+ def __len__(self) -> int:
319
+ """Return the number of elements in the array."""
320
+ return self.size
321
+
322
+ @classmethod
323
+ def element_type(cls) -> type[T]:
324
+ """Return the type of the elements in the array."""
325
+ return cls.type_var_value(T)
326
+
327
+ def _check_index(self, index: int):
328
+ assert 0 <= index < self.size
329
+
330
+ @meta_fn
331
+ def _get_item(self, item: int) -> T:
332
+ if not ctx():
333
+ raise TypeError("ArrayPointer values cannot be accessed outside of a context")
334
+ return _deref(
335
+ self.block,
336
+ self.offset + Num._accept_(item) * Num._accept_(self.element_type()._size_()),
337
+ self.element_type(),
338
+ )
339
+
340
+ @meta_fn
341
+ def __getitem__(self, item: int) -> T:
342
+ compile_and_call(self._check_index, item)
343
+ return self._get_item(item)._get_()
344
+
345
+ @meta_fn
346
+ def __setitem__(self, key: int, value: T):
347
+ compile_and_call(self._check_index, key)
348
+ dst = self._get_item(key)
349
+ if self.element_type()._is_value_type_():
350
+ dst._set_(value)
351
+ else:
352
+ dst._copy_from__(value)
353
+
354
+
355
+ class ArraySet[T, Capacity](Record):
356
+ """A set implemented as an array with a fixed maximum capacity.
357
+
358
+ Usage:
359
+ ```python
360
+ ArraySet[T, Capacity].new() # Create a new empty set
361
+ ```
362
+
363
+ Examples:
364
+ ```python
365
+ s = ArraySet[int, 10].new()
366
+ s.add(1)
367
+ s.add(2)
368
+ assert 1 in s
369
+ assert 3 not in s
370
+ s.remove(1)
371
+ assert 1 not in s
372
+ ```
373
+ """
374
+
375
+ _values: VarArray[T, Capacity]
376
+
377
+ @classmethod
378
+ def new(cls):
379
+ """Create a new empty set."""
380
+ element_type = cls.type_var_value(T)
381
+ capacity = cls.type_var_value(Capacity)
382
+ return cls(VarArray[element_type, capacity].new())
383
+
384
+ def __len__(self):
385
+ """Return the number of elements in the set."""
386
+ return len(self._values)
387
+
388
+ def __contains__(self, value):
389
+ """Return whether the given value is present in the set."""
390
+ return value in self._values
391
+
392
+ def __iter__(self):
393
+ """Return an iterator over the values in the set."""
394
+ return self._values.__iter__()
395
+
396
+ def add(self, value: T) -> bool:
397
+ """Add a copy of the given value to the set.
398
+
399
+ This has no effect and returns False if the value is already present or if the set is full.
400
+
401
+ Args:
402
+ value: The value to add.
403
+
404
+ Returns:
405
+ True if the value was added, False otherwise.
406
+ """
407
+ return self._values.set_add(value)
408
+
409
+ def remove(self, value: T) -> bool:
410
+ """Remove the given value from the set.
411
+
412
+ This has no effect and returns False if the value is not present.
413
+
414
+ Args:
415
+ value: The value to remove.
416
+
417
+ Returns:
418
+ True if the value was removed, False otherwise.
419
+ """
420
+ return self._values.set_remove(value)
421
+
422
+ def clear(self):
423
+ """Clear the set, removing all elements."""
424
+ self._values.clear()
425
+
426
+
261
427
  class _ArrayMapEntry[K, V](Record):
262
428
  key: K
263
429
  value: V
@@ -16,8 +16,13 @@ debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
16
16
 
17
17
 
18
18
  @meta_fn
19
- def error(message: str | None = None) -> None:
20
- message = message._as_py_() if message is not None else "Error"
19
+ def error(message: str | None = None) -> Never:
20
+ """Raise an error.
21
+
22
+ This function is used to raise an error during runtime.
23
+ When this happens, the game will pause in debug mode. The current callback will also immediately return 0.
24
+ """
25
+ message = validate_value(message)._as_py_() if message is not None else "Error"
21
26
  if not isinstance(message, str):
22
27
  raise ValueError("Expected a string")
23
28
  if ctx():
@@ -28,6 +33,19 @@ def error(message: str | None = None) -> None:
28
33
  raise RuntimeError(message)
29
34
 
30
35
 
36
+ @meta_fn
37
+ def static_error(message: str | None = None) -> Never:
38
+ """Raise a static error.
39
+
40
+ This function is used to raise an error during compile-time if the compiler cannot guarantee that
41
+ this function will not be called during runtime.
42
+ """
43
+ message = validate_value(message)._as_py_() if message is not None else "Error"
44
+ if not isinstance(message, str):
45
+ raise ValueError("Expected a string")
46
+ raise RuntimeError(message)
47
+
48
+
31
49
  @meta_fn
32
50
  def debug_log(value: Num):
33
51
  """Log a value in debug mode."""
@@ -4,7 +4,7 @@ import json
4
4
  from collections.abc import Callable
5
5
  from os import PathLike
6
6
  from pathlib import Path
7
- from typing import Any
7
+ from typing import Any, Literal
8
8
 
9
9
  from sonolus.build.collection import Asset, load_asset
10
10
  from sonolus.script.archetype import PlayArchetype, PreviewArchetype, WatchArchetype, _BaseArchetype
@@ -81,7 +81,7 @@ class Engine:
81
81
  meta: Additional metadata of the engine.
82
82
  """
83
83
 
84
- version = 12
84
+ version: Literal[13] = 13
85
85
 
86
86
  def __init__(
87
87
  self,
@@ -3,7 +3,7 @@ from typing import Any, ClassVar, Self
3
3
 
4
4
  from sonolus.backend.place import BlockPlace
5
5
  from sonolus.script.internal.impl import meta_fn
6
- from sonolus.script.internal.value import Value
6
+ from sonolus.script.internal.value import DataValue, Value
7
7
 
8
8
 
9
9
  class _Missing:
@@ -94,10 +94,10 @@ class ConstantValue(Value):
94
94
  return self.value()
95
95
 
96
96
  @classmethod
97
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
97
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
98
98
  return cls()
99
99
 
100
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
100
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
101
101
  return []
102
102
 
103
103
  @classmethod
@@ -2,7 +2,7 @@ from collections.abc import Iterable
2
2
  from typing import Any, Self
3
3
 
4
4
  from sonolus.backend.place import BlockPlace
5
- from sonolus.script.internal.value import Value
5
+ from sonolus.script.internal.value import DataValue, Value
6
6
 
7
7
 
8
8
  class TransientValue(Value):
@@ -23,10 +23,10 @@ class TransientValue(Value):
23
23
  raise TypeError(f"{cls.__name__} cannot be dereferenced")
24
24
 
25
25
  @classmethod
26
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
26
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
27
27
  raise TypeError(f"{cls.__name__} cannot be constructed from list")
28
28
 
29
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
29
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
30
30
  raise TypeError(f"{type(self).__name__} cannot be deconstructed to list")
31
31
 
32
32
  @classmethod