sonolus.py 0.1.8__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sonolus.py might be problematic. Click here for more details.

Files changed (175) hide show
  1. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/PKG-INFO +3 -2
  2. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/builtins.pyi +22 -0
  3. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/resources.md +5 -1
  4. {sonolus_py-0.1.8 → 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.8 → sonolus_py-0.2.0}/mkdocs.yml +2 -1
  8. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/pyproject.toml +1 -1
  9. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/finalize.py +2 -1
  10. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/constant_evaluation.py +2 -2
  11. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/copy_coalesce.py +16 -7
  12. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/optimize.py +12 -4
  13. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/passes.py +2 -1
  14. sonolus_py-0.2.0/sonolus/backend/place.py +163 -0
  15. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/visitor.py +83 -12
  16. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/cli.py +60 -9
  17. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/collection.py +68 -26
  18. sonolus_py-0.2.0/sonolus/build/compile.py +145 -0
  19. sonolus_py-0.2.0/sonolus/build/engine.py +346 -0
  20. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/level.py +2 -1
  21. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/node.py +8 -1
  22. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/project.py +30 -11
  23. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/archetype.py +169 -51
  24. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/array.py +12 -1
  25. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/bucket.py +26 -8
  26. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/debug.py +2 -2
  27. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/effect.py +2 -2
  28. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/engine.py +123 -15
  29. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/builtin_impls.py +21 -2
  30. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/constant.py +6 -2
  31. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/context.py +30 -25
  32. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/introspection.py +8 -1
  33. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/math_impls.py +2 -1
  34. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/transient.py +5 -1
  35. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/value.py +10 -2
  36. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/interval.py +16 -0
  37. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/iterator.py +17 -0
  38. sonolus_py-0.2.0/sonolus/script/level.py +198 -0
  39. sonolus_py-0.2.0/sonolus/script/metadata.py +32 -0
  40. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/num.py +11 -2
  41. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/options.py +5 -3
  42. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/pointer.py +2 -0
  43. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/project.py +41 -5
  44. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/record.py +8 -3
  45. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/runtime.py +61 -10
  46. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/sprite.py +18 -1
  47. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/ui.py +7 -3
  48. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/values.py +8 -5
  49. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/vec.py +28 -0
  50. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_flow.py +16 -0
  51. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_interval.py +2 -2
  52. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_quad.py +17 -33
  53. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_var_array.py +52 -0
  54. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/uv.lock +1 -1
  55. sonolus_py-0.1.8/docs/reference/sonolus.script.print.md +0 -3
  56. sonolus_py-0.1.8/sonolus/backend/place.py +0 -82
  57. sonolus_py-0.1.8/sonolus/build/compile.py +0 -88
  58. sonolus_py-0.1.8/sonolus/build/engine.py +0 -220
  59. sonolus_py-0.1.8/sonolus/script/level.py +0 -76
  60. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/.github/workflows/publish.yaml +0 -0
  61. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/.gitignore +0 -0
  62. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/.python-version +0 -0
  63. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/LICENSE +0 -0
  64. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/README.md +0 -0
  65. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/__init__.py +0 -0
  66. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/math.pyi +0 -0
  67. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/num.pyi +0 -0
  68. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/doc_stubs/random.pyi +0 -0
  69. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/CNAME +0 -0
  70. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/builtins.md +0 -0
  71. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/cli.md +0 -0
  72. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/constructs.md +0 -0
  73. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/index.md +0 -0
  74. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/concepts/project.md +0 -0
  75. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/index.md +0 -0
  76. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/builtins.md +0 -0
  77. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/index.md +0 -0
  78. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/math.md +0 -0
  79. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/random.md +0 -0
  80. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.archetype.md +0 -0
  81. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.array.md +0 -0
  82. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.array_like.md +0 -0
  83. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.bucket.md +0 -0
  84. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.containers.md +0 -0
  85. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.debug.md +0 -0
  86. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.easing.md +0 -0
  87. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.effect.md +0 -0
  88. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.engine.md +0 -0
  89. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.globals.md +0 -0
  90. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.instruction.md +0 -0
  91. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.interval.md +0 -0
  92. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.iterator.md +0 -0
  93. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.level.md +0 -0
  94. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.num.md +0 -0
  95. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.options.md +0 -0
  96. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.particle.md +0 -0
  97. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.project.md +0 -0
  98. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.quad.md +0 -0
  99. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.record.md +0 -0
  100. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.runtime.md +0 -0
  101. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.sprite.md +0 -0
  102. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.text.md +0 -0
  103. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.timing.md +0 -0
  104. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.transform.md +0 -0
  105. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.ui.md +0 -0
  106. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.values.md +0 -0
  107. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/docs/reference/sonolus.script.vec.md +0 -0
  108. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/generate.py +0 -0
  109. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Engine/Tutorial/Blocks.json +0 -0
  110. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Functions.json +0 -0
  111. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Level/Play/Blocks.json +0 -0
  112. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Level/Preview/Blocks.json +0 -0
  113. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/scripts/runtimes/Level/Watch/Blocks.json +0 -0
  114. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/__init__.py +0 -0
  115. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/__init__.py +0 -0
  116. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/blocks.py +0 -0
  117. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/excepthook.py +0 -0
  118. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/interpret.py +0 -0
  119. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/ir.py +0 -0
  120. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/mode.py +0 -0
  121. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/node.py +0 -0
  122. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/ops.py +0 -0
  123. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/__init__.py +0 -0
  124. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/allocate.py +0 -0
  125. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/dead_code.py +0 -0
  126. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/dominance.py +0 -0
  127. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/flow.py +0 -0
  128. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/inlining.py +0 -0
  129. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/liveness.py +0 -0
  130. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/simplify.py +0 -0
  131. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/optimize/ssa.py +0 -0
  132. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/backend/utils.py +0 -0
  133. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/build/__init__.py +0 -0
  134. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/py.typed +0 -0
  135. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/__init__.py +0 -0
  136. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/array_like.py +0 -0
  137. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/containers.py +0 -0
  138. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/easing.py +0 -0
  139. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/globals.py +0 -0
  140. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/instruction.py +0 -0
  141. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/__init__.py +0 -0
  142. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/callbacks.py +0 -0
  143. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/descriptor.py +0 -0
  144. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/dict_impl.py +0 -0
  145. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/error.py +0 -0
  146. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/generic.py +0 -0
  147. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/impl.py +0 -0
  148. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/native.py +0 -0
  149. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/random.py +0 -0
  150. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/range.py +0 -0
  151. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/internal/tuple_impl.py +0 -0
  152. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/particle.py +0 -0
  153. /sonolus_py-0.1.8/sonolus/script/print.py → /sonolus_py-0.2.0/sonolus/script/printing.py +0 -0
  154. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/quad.py +0 -0
  155. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/text.py +0 -0
  156. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/timing.py +0 -0
  157. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/sonolus/script/transform.py +0 -0
  158. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/__init__.py +0 -0
  159. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/__init__.py +0 -0
  160. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/conftest.py +0 -0
  161. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_array.py +0 -0
  162. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_array_map.py +0 -0
  163. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_assert.py +0 -0
  164. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_dict.py +0 -0
  165. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_functions.py +0 -0
  166. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_helpers.py +0 -0
  167. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_match.py +0 -0
  168. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_num.py +0 -0
  169. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_operator.py +0 -0
  170. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_random.py +0 -0
  171. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_range.py +0 -0
  172. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_record.py +0 -0
  173. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_transform.py +0 -0
  174. {sonolus_py-0.1.8 → sonolus_py-0.2.0}/tests/script/test_tuple.py +0 -0
  175. {sonolus_py-0.1.8 → 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.8
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.8"
3
+ version = "0.2.0"
4
4
  description = "Sonolus engine development in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -42,6 +42,7 @@ def cfg_to_engine_node(entry: BasicBlock):
42
42
  for cond, target in targets.items():
43
43
  if cond is None:
44
44
  default = block_indexes[target]
45
+ continue
45
46
  args.append(ConstantNode(value=cond))
46
47
  args.append(ConstantNode(value=block_indexes[target]))
47
48
  args.append(ConstantNode(value=default))
@@ -55,7 +56,7 @@ def ir_to_engine_node(stmt) -> EngineNode:
55
56
  match stmt:
56
57
  case int() | float():
57
58
  return ConstantNode(value=float(stmt))
58
- case IRConst(value=value):
59
+ case IRConst(value=int(value) | float(value)):
59
60
  return ConstantNode(value=value)
60
61
  case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
61
62
  return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
@@ -315,8 +315,8 @@ class SparseConditionalConstantPropagation(CompilerPass):
315
315
  return 1
316
316
  return functools.reduce(operator.pow, args)
317
317
  case Op.Log:
318
- assert len(args) == 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])
@@ -39,20 +39,29 @@ class CopyCoalesce(CompilerPass):
39
39
  interference = self.get_interference(entry)
40
40
  copies = self.get_copies(entry)
41
41
 
42
- mapping = {}
42
+ mapping: dict[TempBlock, set[TempBlock]] = {}
43
43
 
44
44
  for target, sources in copies.items():
45
45
  for source in sources:
46
- if source in mapping:
47
- continue
48
46
  if source in interference.get(target, set()):
49
47
  continue
50
- mapping[source] = mapping.get(target, target)
48
+ combined_mapping = mapping.get(target, {target}) | mapping.get(source, {source})
51
49
  combined_interference = interference.get(target, set()) | interference.get(source, set())
52
- interference[source] = combined_interference
53
- interference[target] = combined_interference
50
+ for place in combined_mapping:
51
+ mapping[place] = combined_mapping
52
+ interference[place] = combined_interference
53
+ for place in combined_interference:
54
+ interference[place].update(combined_mapping)
55
+
56
+ canonical_mapping = {}
57
+ for place, group in mapping.items():
58
+ if place in canonical_mapping:
59
+ continue
60
+ canonical = min(group)
61
+ for member in group:
62
+ canonical_mapping[member] = canonical
54
63
 
55
- return mapping
64
+ return canonical_mapping
56
65
 
57
66
  def get_interference(self, entry: BasicBlock) -> dict[TempBlock, set[TempBlock]]:
58
67
  result = {}
@@ -12,13 +12,21 @@ from sonolus.backend.optimize.passes import run_passes
12
12
  from sonolus.backend.optimize.simplify import CoalesceFlow, NormalizeSwitch, RewriteToSwitch
13
13
  from sonolus.backend.optimize.ssa import FromSSA, ToSSA
14
14
 
15
- MINIMAL_PASSES = [
15
+ MINIMAL_PASSES = (
16
16
  CoalesceFlow(),
17
17
  UnreachableCodeElimination(),
18
18
  AllocateBasic(),
19
- ]
19
+ )
20
20
 
21
- 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):
@@ -335,7 +349,11 @@ class Visitor(ast.NodeVisitor):
335
349
  has_next = self.ensure_boolean_num(self.handle_call(node, iterator.has_next))
336
350
  if has_next._is_py_() and not has_next._as_py_():
337
351
  # The loop will never run, continue after evaluating the condition
352
+ self.loop_head_ctxs.pop()
353
+ self.break_ctxs.pop()
338
354
  for stmt in node.orelse:
355
+ if not ctx().live:
356
+ break
339
357
  self.visit(stmt)
340
358
  return
341
359
  ctx().test = has_next.ir()
@@ -345,16 +363,21 @@ class Visitor(ast.NodeVisitor):
345
363
  set_ctx(body_ctx)
346
364
  self.handle_assign(node.target, self.handle_call(node, iterator.next))
347
365
  for stmt in node.body:
366
+ if not ctx().live:
367
+ break
348
368
  self.visit(stmt)
349
369
  ctx().branch_to_loop_header(header_ctx)
350
370
 
371
+ self.loop_head_ctxs.pop()
372
+ break_ctxs = self.break_ctxs.pop()
373
+
351
374
  set_ctx(else_ctx)
352
375
  for stmt in node.orelse:
376
+ if not ctx().live:
377
+ break
353
378
  self.visit(stmt)
354
379
  else_end_ctx = ctx()
355
380
 
356
- self.loop_head_ctxs.pop()
357
- break_ctxs = self.break_ctxs.pop()
358
381
  after_ctx = Context.meet([else_end_ctx, *break_ctxs])
359
382
  set_ctx(after_ctx)
360
383
 
@@ -365,27 +388,55 @@ class Visitor(ast.NodeVisitor):
365
388
  self.break_ctxs.append([])
366
389
  set_ctx(header_ctx)
367
390
  test = self.ensure_boolean_num(self.visit(node.test))
368
- if test._is_py_() and not test._as_py_():
369
- # The loop will never run, continue after evaluating the condition
370
- for stmt in node.orelse:
371
- self.visit(stmt)
372
- return
391
+ if test._is_py_():
392
+ if test._as_py_():
393
+ # The loop will run until a break / return
394
+ body_ctx = ctx().branch(None)
395
+ set_ctx(body_ctx)
396
+ for stmt in node.body:
397
+ if not ctx().live:
398
+ break
399
+ self.visit(stmt)
400
+ ctx().branch_to_loop_header(header_ctx)
401
+
402
+ self.loop_head_ctxs.pop()
403
+ break_ctxs = self.break_ctxs.pop()
404
+
405
+ # Skip the else block
406
+
407
+ after_ctx = Context.meet([ctx().into_dead(), *break_ctxs])
408
+ set_ctx(after_ctx)
409
+ return
410
+ else:
411
+ # The loop will never run, continue after evaluating the condition
412
+ self.loop_head_ctxs.pop()
413
+ self.break_ctxs.pop()
414
+ for stmt in node.orelse:
415
+ if not ctx().live:
416
+ break
417
+ self.visit(stmt)
418
+ return
373
419
  ctx().test = test.ir()
374
420
  body_ctx = ctx().branch(None)
375
421
  else_ctx = ctx().branch(0)
376
422
 
377
423
  set_ctx(body_ctx)
378
424
  for stmt in node.body:
425
+ if not ctx().live:
426
+ break
379
427
  self.visit(stmt)
380
428
  ctx().branch_to_loop_header(header_ctx)
381
429
 
430
+ self.loop_head_ctxs.pop()
431
+ break_ctxs = self.break_ctxs.pop()
432
+
382
433
  set_ctx(else_ctx)
383
434
  for stmt in node.orelse:
435
+ if not ctx().live:
436
+ break
384
437
  self.visit(stmt)
385
438
  else_end_ctx = ctx()
386
439
 
387
- self.loop_head_ctxs.pop()
388
- break_ctxs = self.break_ctxs.pop()
389
440
  after_ctx = Context.meet([else_end_ctx, *break_ctxs])
390
441
  set_ctx(after_ctx)
391
442
 
@@ -395,9 +446,13 @@ class Visitor(ast.NodeVisitor):
395
446
  if test._is_py_():
396
447
  if test._as_py_():
397
448
  for stmt in node.body:
449
+ if not ctx().live:
450
+ break
398
451
  self.visit(stmt)
399
452
  else:
400
453
  for stmt in node.orelse:
454
+ if not ctx().live:
455
+ break
401
456
  self.visit(stmt)
402
457
  return
403
458
 
@@ -408,11 +463,15 @@ class Visitor(ast.NodeVisitor):
408
463
 
409
464
  set_ctx(true_ctx)
410
465
  for stmt in node.body:
466
+ if not ctx().live:
467
+ break
411
468
  self.visit(stmt)
412
469
  true_end_ctx = ctx()
413
470
 
414
471
  set_ctx(false_ctx)
415
472
  for stmt in node.orelse:
473
+ if not ctx().live:
474
+ break
416
475
  self.visit(stmt)
417
476
  false_end_ctx = ctx()
418
477
 
@@ -439,6 +498,8 @@ class Visitor(ast.NodeVisitor):
439
498
  if guard._is_py_():
440
499
  if guard._as_py_():
441
500
  for stmt in case.body:
501
+ if not ctx().live:
502
+ break
442
503
  self.visit(stmt)
443
504
  end_ctxs.append(ctx())
444
505
  else:
@@ -450,6 +511,8 @@ class Visitor(ast.NodeVisitor):
450
511
  guard_false_ctx = ctx().branch(0)
451
512
  set_ctx(guard_true_ctx)
452
513
  for stmt in case.body:
514
+ if not ctx().live:
515
+ break
453
516
  self.visit(stmt)
454
517
  end_ctxs.append(ctx())
455
518
  false_ctx = Context.meet([false_ctx, guard_false_ctx])
@@ -610,7 +673,11 @@ class Visitor(ast.NodeVisitor):
610
673
  set_ctx(ctx().into_dead())
611
674
 
612
675
  def visit_Continue(self, node):
613
- 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)
614
681
  set_ctx(ctx().into_dead())
615
682
 
616
683
  def visit_BoolOp(self, node) -> Value:
@@ -959,7 +1026,11 @@ class Visitor(ast.NodeVisitor):
959
1026
  if isinstance(target, ConstantValue):
960
1027
  # Unwrap so we can access fields
961
1028
  target = target._as_py_()
962
- descriptor = 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
963
1034
  match descriptor:
964
1035
  case property(fget=getter):
965
1036
  return self.handle_call(node, getter, target)