sonolus.py 0.1.9__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.

Files changed (175) hide show
  1. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/PKG-INFO +3 -2
  2. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/doc_stubs/builtins.pyi +22 -0
  3. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/concepts/resources.md +5 -1
  4. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/concepts/types.md +14 -0
  5. sonolus_py-0.2.0/docs/reference/sonolus.script.metadata.md +3 -0
  6. sonolus_py-0.2.0/docs/reference/sonolus.script.printing.md +3 -0
  7. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/mkdocs.yml +2 -1
  8. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/pyproject.toml +1 -1
  9. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/constant_evaluation.py +2 -2
  10. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/optimize.py +12 -4
  11. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/passes.py +2 -1
  12. sonolus_py-0.2.0/sonolus/backend/place.py +163 -0
  13. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/visitor.py +51 -3
  14. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/build/cli.py +60 -9
  15. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/build/collection.py +68 -26
  16. sonolus_py-0.2.0/sonolus/build/compile.py +145 -0
  17. sonolus_py-0.2.0/sonolus/build/engine.py +346 -0
  18. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/build/node.py +8 -1
  19. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/build/project.py +30 -11
  20. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/archetype.py +110 -26
  21. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/array.py +11 -0
  22. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/debug.py +2 -2
  23. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/effect.py +2 -2
  24. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/engine.py +123 -15
  25. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/builtin_impls.py +21 -2
  26. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/constant.py +5 -1
  27. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/context.py +30 -25
  28. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/math_impls.py +2 -1
  29. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/transient.py +4 -0
  30. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/value.py +6 -0
  31. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/interval.py +16 -0
  32. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/iterator.py +17 -0
  33. sonolus_py-0.2.0/sonolus/script/level.py +198 -0
  34. sonolus_py-0.2.0/sonolus/script/metadata.py +32 -0
  35. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/num.py +9 -0
  36. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/options.py +5 -3
  37. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/pointer.py +2 -0
  38. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/project.py +41 -5
  39. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/record.py +7 -2
  40. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/runtime.py +61 -10
  41. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/sprite.py +18 -1
  42. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/ui.py +7 -3
  43. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/values.py +8 -5
  44. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/vec.py +28 -0
  45. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_var_array.py +52 -0
  46. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/uv.lock +1 -1
  47. sonolus_py-0.1.9/docs/reference/sonolus.script.print.md +0 -3
  48. sonolus_py-0.1.9/sonolus/backend/place.py +0 -82
  49. sonolus_py-0.1.9/sonolus/build/compile.py +0 -87
  50. sonolus_py-0.1.9/sonolus/build/engine.py +0 -220
  51. sonolus_py-0.1.9/sonolus/script/level.py +0 -95
  52. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/.github/workflows/publish.yaml +0 -0
  53. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/.gitignore +0 -0
  54. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/.python-version +0 -0
  55. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/LICENSE +0 -0
  56. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/README.md +0 -0
  57. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/doc_stubs/__init__.py +0 -0
  58. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/doc_stubs/math.pyi +0 -0
  59. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/doc_stubs/num.pyi +0 -0
  60. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/doc_stubs/random.pyi +0 -0
  61. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/CNAME +0 -0
  62. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/concepts/builtins.md +0 -0
  63. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/concepts/cli.md +0 -0
  64. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/concepts/constructs.md +0 -0
  65. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/concepts/index.md +0 -0
  66. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/concepts/project.md +0 -0
  67. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/index.md +0 -0
  68. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/builtins.md +0 -0
  69. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/index.md +0 -0
  70. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/math.md +0 -0
  71. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/random.md +0 -0
  72. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.archetype.md +0 -0
  73. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.array.md +0 -0
  74. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.array_like.md +0 -0
  75. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.bucket.md +0 -0
  76. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.containers.md +0 -0
  77. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.debug.md +0 -0
  78. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.easing.md +0 -0
  79. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.effect.md +0 -0
  80. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.engine.md +0 -0
  81. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.globals.md +0 -0
  82. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.instruction.md +0 -0
  83. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.interval.md +0 -0
  84. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.iterator.md +0 -0
  85. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.level.md +0 -0
  86. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.num.md +0 -0
  87. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.options.md +0 -0
  88. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.particle.md +0 -0
  89. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.project.md +0 -0
  90. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.quad.md +0 -0
  91. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.record.md +0 -0
  92. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.runtime.md +0 -0
  93. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.sprite.md +0 -0
  94. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.text.md +0 -0
  95. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.timing.md +0 -0
  96. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.transform.md +0 -0
  97. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.ui.md +0 -0
  98. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.values.md +0 -0
  99. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/docs/reference/sonolus.script.vec.md +0 -0
  100. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/scripts/generate.py +0 -0
  101. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/scripts/runtimes/Engine/Tutorial/Blocks.json +0 -0
  102. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/scripts/runtimes/Functions.json +0 -0
  103. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/scripts/runtimes/Level/Play/Blocks.json +0 -0
  104. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/scripts/runtimes/Level/Preview/Blocks.json +0 -0
  105. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/scripts/runtimes/Level/Watch/Blocks.json +0 -0
  106. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/__init__.py +0 -0
  107. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/__init__.py +0 -0
  108. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/blocks.py +0 -0
  109. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/excepthook.py +0 -0
  110. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/finalize.py +0 -0
  111. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/interpret.py +0 -0
  112. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/ir.py +0 -0
  113. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/mode.py +0 -0
  114. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/node.py +0 -0
  115. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/ops.py +0 -0
  116. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/__init__.py +0 -0
  117. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/allocate.py +0 -0
  118. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/copy_coalesce.py +0 -0
  119. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/dead_code.py +0 -0
  120. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/dominance.py +0 -0
  121. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/flow.py +0 -0
  122. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/inlining.py +0 -0
  123. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/liveness.py +0 -0
  124. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/simplify.py +0 -0
  125. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/optimize/ssa.py +0 -0
  126. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/backend/utils.py +0 -0
  127. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/build/__init__.py +0 -0
  128. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/build/level.py +0 -0
  129. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/py.typed +0 -0
  130. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/__init__.py +0 -0
  131. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/array_like.py +0 -0
  132. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/bucket.py +0 -0
  133. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/containers.py +0 -0
  134. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/easing.py +0 -0
  135. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/globals.py +0 -0
  136. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/instruction.py +0 -0
  137. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/__init__.py +0 -0
  138. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/callbacks.py +0 -0
  139. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/descriptor.py +0 -0
  140. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/dict_impl.py +0 -0
  141. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/error.py +0 -0
  142. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/generic.py +0 -0
  143. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/impl.py +0 -0
  144. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/introspection.py +0 -0
  145. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/native.py +0 -0
  146. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/random.py +0 -0
  147. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/range.py +0 -0
  148. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/internal/tuple_impl.py +0 -0
  149. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/particle.py +0 -0
  150. /sonolus_py-0.1.9/sonolus/script/print.py → /sonolus_py-0.2.0/sonolus/script/printing.py +0 -0
  151. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/quad.py +0 -0
  152. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/text.py +0 -0
  153. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/timing.py +0 -0
  154. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/sonolus/script/transform.py +0 -0
  155. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/__init__.py +0 -0
  156. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/__init__.py +0 -0
  157. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/conftest.py +0 -0
  158. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_array.py +0 -0
  159. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_array_map.py +0 -0
  160. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_assert.py +0 -0
  161. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_dict.py +0 -0
  162. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_flow.py +0 -0
  163. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_functions.py +0 -0
  164. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_helpers.py +0 -0
  165. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_interval.py +0 -0
  166. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_match.py +0 -0
  167. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_num.py +0 -0
  168. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_operator.py +0 -0
  169. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_quad.py +0 -0
  170. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_random.py +0 -0
  171. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_range.py +0 -0
  172. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_record.py +0 -0
  173. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_transform.py +0 -0
  174. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_tuple.py +0 -0
  175. {sonolus_py-0.1.9 → sonolus_py-0.2.0}/tests/script/test_vec.py +0 -0
@@ -1,7 +1,8 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.1.9
3
+ Version: 0.2.0
4
4
  Summary: Sonolus engine development in Python
5
+ License-File: LICENSE
5
6
  Requires-Python: >=3.12
6
7
  Description-Content-Type: text/markdown
7
8
 
@@ -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.
@@ -0,0 +1,3 @@
1
+ # sonolus.script.metadata
2
+
3
+ ::: sonolus.script.metadata
@@ -0,0 +1,3 @@
1
+ # sonolus.script.printing
2
+
3
+ ::: sonolus.script.printing
@@ -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.print.md
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sonolus.py"
3
- version = "0.1.9"
3
+ version = "0.2.0"
4
4
  description = "Sonolus engine development in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -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) == 2
319
- return math.log(args[0], args[1])
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])
@@ -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
- STANDARD_PASSES = [
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: list[CompilerPass]) -> BasicBlock:
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[Context] # Contexts at loop heads, from outer to inner
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):
@@ -338,6 +352,8 @@ class Visitor(ast.NodeVisitor):
338
352
  self.loop_head_ctxs.pop()
339
353
  self.break_ctxs.pop()
340
354
  for stmt in node.orelse:
355
+ if not ctx().live:
356
+ break
341
357
  self.visit(stmt)
342
358
  return
343
359
  ctx().test = has_next.ir()
@@ -347,6 +363,8 @@ class Visitor(ast.NodeVisitor):
347
363
  set_ctx(body_ctx)
348
364
  self.handle_assign(node.target, self.handle_call(node, iterator.next))
349
365
  for stmt in node.body:
366
+ if not ctx().live:
367
+ break
350
368
  self.visit(stmt)
351
369
  ctx().branch_to_loop_header(header_ctx)
352
370
 
@@ -355,6 +373,8 @@ class Visitor(ast.NodeVisitor):
355
373
 
356
374
  set_ctx(else_ctx)
357
375
  for stmt in node.orelse:
376
+ if not ctx().live:
377
+ break
358
378
  self.visit(stmt)
359
379
  else_end_ctx = ctx()
360
380
 
@@ -374,6 +394,8 @@ class Visitor(ast.NodeVisitor):
374
394
  body_ctx = ctx().branch(None)
375
395
  set_ctx(body_ctx)
376
396
  for stmt in node.body:
397
+ if not ctx().live:
398
+ break
377
399
  self.visit(stmt)
378
400
  ctx().branch_to_loop_header(header_ctx)
379
401
 
@@ -390,6 +412,8 @@ class Visitor(ast.NodeVisitor):
390
412
  self.loop_head_ctxs.pop()
391
413
  self.break_ctxs.pop()
392
414
  for stmt in node.orelse:
415
+ if not ctx().live:
416
+ break
393
417
  self.visit(stmt)
394
418
  return
395
419
  ctx().test = test.ir()
@@ -398,6 +422,8 @@ class Visitor(ast.NodeVisitor):
398
422
 
399
423
  set_ctx(body_ctx)
400
424
  for stmt in node.body:
425
+ if not ctx().live:
426
+ break
401
427
  self.visit(stmt)
402
428
  ctx().branch_to_loop_header(header_ctx)
403
429
 
@@ -406,6 +432,8 @@ class Visitor(ast.NodeVisitor):
406
432
 
407
433
  set_ctx(else_ctx)
408
434
  for stmt in node.orelse:
435
+ if not ctx().live:
436
+ break
409
437
  self.visit(stmt)
410
438
  else_end_ctx = ctx()
411
439
 
@@ -418,9 +446,13 @@ class Visitor(ast.NodeVisitor):
418
446
  if test._is_py_():
419
447
  if test._as_py_():
420
448
  for stmt in node.body:
449
+ if not ctx().live:
450
+ break
421
451
  self.visit(stmt)
422
452
  else:
423
453
  for stmt in node.orelse:
454
+ if not ctx().live:
455
+ break
424
456
  self.visit(stmt)
425
457
  return
426
458
 
@@ -431,11 +463,15 @@ class Visitor(ast.NodeVisitor):
431
463
 
432
464
  set_ctx(true_ctx)
433
465
  for stmt in node.body:
466
+ if not ctx().live:
467
+ break
434
468
  self.visit(stmt)
435
469
  true_end_ctx = ctx()
436
470
 
437
471
  set_ctx(false_ctx)
438
472
  for stmt in node.orelse:
473
+ if not ctx().live:
474
+ break
439
475
  self.visit(stmt)
440
476
  false_end_ctx = ctx()
441
477
 
@@ -462,6 +498,8 @@ class Visitor(ast.NodeVisitor):
462
498
  if guard._is_py_():
463
499
  if guard._as_py_():
464
500
  for stmt in case.body:
501
+ if not ctx().live:
502
+ break
465
503
  self.visit(stmt)
466
504
  end_ctxs.append(ctx())
467
505
  else:
@@ -473,6 +511,8 @@ class Visitor(ast.NodeVisitor):
473
511
  guard_false_ctx = ctx().branch(0)
474
512
  set_ctx(guard_true_ctx)
475
513
  for stmt in case.body:
514
+ if not ctx().live:
515
+ break
476
516
  self.visit(stmt)
477
517
  end_ctxs.append(ctx())
478
518
  false_ctx = Context.meet([false_ctx, guard_false_ctx])
@@ -633,7 +673,11 @@ class Visitor(ast.NodeVisitor):
633
673
  set_ctx(ctx().into_dead())
634
674
 
635
675
  def visit_Continue(self, node):
636
- ctx().branch_to_loop_header(self.loop_head_ctxs[-1])
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)
637
681
  set_ctx(ctx().into_dead())
638
682
 
639
683
  def visit_BoolOp(self, node) -> Value:
@@ -982,7 +1026,11 @@ class Visitor(ast.NodeVisitor):
982
1026
  if isinstance(target, ConstantValue):
983
1027
  # Unwrap so we can access fields
984
1028
  target = target._as_py_()
985
- descriptor = type(target).__dict__.get(key)
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
986
1034
  match descriptor:
987
1035
  case property(fget=getter):
988
1036
  return self.handle_call(node, getter, target)
@@ -10,10 +10,11 @@ import sys
10
10
  from pathlib import Path
11
11
  from time import perf_counter
12
12
 
13
+ from sonolus.backend.optimize.optimize import FAST_PASSES, MINIMAL_PASSES, STANDARD_PASSES
13
14
  from sonolus.build.engine import package_engine
14
15
  from sonolus.build.level import package_level_data
15
16
  from sonolus.build.project import build_project_to_collection, get_project_schema
16
- from sonolus.script.project import Project
17
+ from sonolus.script.project import BuildConfig, Project
17
18
 
18
19
 
19
20
  def find_default_module() -> str | None:
@@ -63,29 +64,29 @@ def import_project(module_path: str) -> Project | None:
63
64
  return project
64
65
  except Exception as e:
65
66
  print(f"Error: Failed to import project: {e}")
66
- return None
67
+ raise e from None
67
68
 
68
69
 
69
- def build_project(project: Project, build_dir: Path):
70
+ def build_project(project: Project, build_dir: Path, config: BuildConfig):
70
71
  dist_dir = build_dir / "dist"
71
72
  levels_dir = dist_dir / "levels"
72
73
  shutil.rmtree(dist_dir, ignore_errors=True)
73
74
  dist_dir.mkdir(parents=True, exist_ok=True)
74
75
  levels_dir.mkdir(parents=True, exist_ok=True)
75
76
 
76
- package_engine(project.engine.data).write(dist_dir / "engine")
77
+ package_engine(project.engine.data, config).write(dist_dir / "engine")
77
78
 
78
79
  for level in project.levels:
79
80
  level_path = levels_dir / level.name
80
81
  level_path.write_bytes(package_level_data(level.data))
81
82
 
82
83
 
83
- def build_collection(project: Project, build_dir: Path):
84
+ def build_collection(project: Project, build_dir: Path, config: BuildConfig):
84
85
  site_dir = build_dir / "site"
85
86
  shutil.rmtree(site_dir, ignore_errors=True)
86
87
  site_dir.mkdir(parents=True, exist_ok=True)
87
88
 
88
- collection = build_project_to_collection(project)
89
+ collection = build_project_to_collection(project, config)
89
90
  collection.write(site_dir)
90
91
 
91
92
 
@@ -125,10 +126,55 @@ def run_server(base_dir: Path, port: int = 8000):
125
126
  httpd.shutdown()
126
127
 
127
128
 
129
+ def get_config(args: argparse.Namespace) -> BuildConfig:
130
+ if hasattr(args, "optimize_minimal") and args.optimize_minimal:
131
+ optimization_passes = MINIMAL_PASSES
132
+ elif hasattr(args, "optimize_fast") and args.optimize_fast:
133
+ optimization_passes = FAST_PASSES
134
+ elif hasattr(args, "optimize_standard") and args.optimize_standard:
135
+ optimization_passes = STANDARD_PASSES
136
+ else:
137
+ optimization_passes = FAST_PASSES if args.command == "dev" else STANDARD_PASSES
138
+
139
+ if any(hasattr(args, attr) and getattr(args, attr) for attr in ["play", "watch", "preview", "tutorial"]):
140
+ build_play = hasattr(args, "play") and args.play
141
+ build_watch = hasattr(args, "watch") and args.watch
142
+ build_preview = hasattr(args, "preview") and args.preview
143
+ build_tutorial = hasattr(args, "tutorial") and args.tutorial
144
+ else:
145
+ build_play = build_watch = build_preview = build_tutorial = True
146
+
147
+ return BuildConfig(
148
+ passes=optimization_passes,
149
+ build_play=build_play,
150
+ build_watch=build_watch,
151
+ build_preview=build_preview,
152
+ build_tutorial=build_tutorial,
153
+ )
154
+
155
+
128
156
  def main():
129
157
  parser = argparse.ArgumentParser(description="Sonolus project build and development tools")
130
158
  subparsers = parser.add_subparsers(dest="command", required=True)
131
159
 
160
+ def add_common_arguments(parser):
161
+ optimization_group = parser.add_mutually_exclusive_group()
162
+ optimization_group.add_argument(
163
+ "-o0", "--optimize-minimal", action="store_true", help="Use minimal optimization passes"
164
+ )
165
+ optimization_group.add_argument(
166
+ "-o1", "--optimize-fast", action="store_true", help="Use fast optimization passes"
167
+ )
168
+ optimization_group.add_argument(
169
+ "-o2", "--optimize-standard", action="store_true", help="Use standard optimization passes"
170
+ )
171
+
172
+ build_components = parser.add_argument_group("build components")
173
+ build_components.add_argument("--play", action="store_true", help="Build play component")
174
+ build_components.add_argument("--watch", action="store_true", help="Build watch component")
175
+ build_components.add_argument("--preview", action="store_true", help="Build preview component")
176
+ build_components.add_argument("--tutorial", action="store_true", help="Build tutorial component")
177
+
132
178
  build_parser = subparsers.add_parser("build")
133
179
  build_parser.add_argument(
134
180
  "module",
@@ -137,6 +183,7 @@ def main():
137
183
  help="Module path (e.g., 'module.name'). If omitted, will auto-detect if only one module exists.",
138
184
  )
139
185
  build_parser.add_argument("--build-dir", type=str, default="./build")
186
+ add_common_arguments(build_parser)
140
187
 
141
188
  dev_parser = subparsers.add_parser("dev")
142
189
  dev_parser.add_argument(
@@ -147,6 +194,7 @@ def main():
147
194
  )
148
195
  dev_parser.add_argument("--build-dir", type=str, default="./build")
149
196
  dev_parser.add_argument("--port", type=int, default=8000)
197
+ add_common_arguments(dev_parser)
150
198
 
151
199
  schema_parser = subparsers.add_parser("schema")
152
200
  schema_parser.add_argument(
@@ -161,7 +209,8 @@ def main():
161
209
  if not args.module:
162
210
  default_module = find_default_module()
163
211
  if default_module:
164
- print(f"Using auto-detected module: {default_module}")
212
+ if args.command != "schema":
213
+ print(f"Using auto-detected module: {default_module}")
165
214
  args.module = default_module
166
215
  else:
167
216
  parser.error("Module argument is required when multiple or no modules are found")
@@ -173,13 +222,15 @@ def main():
173
222
  if args.command == "build":
174
223
  build_dir = Path(args.build_dir)
175
224
  start_time = perf_counter()
176
- build_project(project, build_dir)
225
+ config = get_config(args)
226
+ build_project(project, build_dir, config)
177
227
  end_time = perf_counter()
178
228
  print(f"Project built successfully to '{build_dir.resolve()}' in {end_time - start_time:.2f}s")
179
229
  elif args.command == "dev":
180
230
  build_dir = Path(args.build_dir)
181
231
  start_time = perf_counter()
182
- build_collection(project, build_dir)
232
+ config = get_config(args)
233
+ build_collection(project, build_dir, config)
183
234
  end_time = perf_counter()
184
235
  print(f"Build finished in {end_time - start_time:.2f}s")
185
236
  run_server(build_dir / "site", port=args.port)