sonolus.py 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl

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 (90) hide show
  1. sonolus/backend/blocks.py +756 -756
  2. sonolus/backend/excepthook.py +37 -37
  3. sonolus/backend/finalize.py +77 -69
  4. sonolus/backend/interpret.py +7 -7
  5. sonolus/backend/ir.py +29 -3
  6. sonolus/backend/mode.py +24 -24
  7. sonolus/backend/node.py +40 -40
  8. sonolus/backend/ops.py +197 -197
  9. sonolus/backend/optimize/__init__.py +0 -0
  10. sonolus/backend/optimize/allocate.py +126 -0
  11. sonolus/backend/optimize/constant_evaluation.py +374 -0
  12. sonolus/backend/optimize/copy_coalesce.py +85 -0
  13. sonolus/backend/optimize/dead_code.py +185 -0
  14. sonolus/backend/optimize/dominance.py +96 -0
  15. sonolus/backend/{flow.py → optimize/flow.py} +122 -92
  16. sonolus/backend/optimize/inlining.py +137 -0
  17. sonolus/backend/optimize/liveness.py +177 -0
  18. sonolus/backend/optimize/optimize.py +44 -0
  19. sonolus/backend/optimize/passes.py +52 -0
  20. sonolus/backend/optimize/simplify.py +191 -0
  21. sonolus/backend/optimize/ssa.py +200 -0
  22. sonolus/backend/place.py +17 -25
  23. sonolus/backend/utils.py +58 -48
  24. sonolus/backend/visitor.py +1151 -882
  25. sonolus/build/cli.py +7 -1
  26. sonolus/build/compile.py +88 -90
  27. sonolus/build/engine.py +10 -5
  28. sonolus/build/level.py +24 -23
  29. sonolus/build/node.py +43 -43
  30. sonolus/script/archetype.py +438 -139
  31. sonolus/script/array.py +27 -10
  32. sonolus/script/array_like.py +297 -0
  33. sonolus/script/bucket.py +253 -191
  34. sonolus/script/containers.py +257 -51
  35. sonolus/script/debug.py +26 -10
  36. sonolus/script/easing.py +365 -0
  37. sonolus/script/effect.py +191 -131
  38. sonolus/script/engine.py +71 -4
  39. sonolus/script/globals.py +303 -269
  40. sonolus/script/instruction.py +205 -151
  41. sonolus/script/internal/__init__.py +5 -5
  42. sonolus/script/internal/builtin_impls.py +255 -144
  43. sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
  44. sonolus/script/internal/constant.py +139 -0
  45. sonolus/script/internal/context.py +26 -9
  46. sonolus/script/internal/descriptor.py +17 -17
  47. sonolus/script/internal/dict_impl.py +65 -0
  48. sonolus/script/internal/generic.py +6 -9
  49. sonolus/script/internal/impl.py +38 -13
  50. sonolus/script/internal/introspection.py +17 -14
  51. sonolus/script/internal/math_impls.py +121 -0
  52. sonolus/script/internal/native.py +40 -38
  53. sonolus/script/internal/random.py +67 -0
  54. sonolus/script/internal/range.py +81 -0
  55. sonolus/script/internal/transient.py +51 -0
  56. sonolus/script/internal/tuple_impl.py +113 -0
  57. sonolus/script/internal/value.py +3 -3
  58. sonolus/script/interval.py +338 -112
  59. sonolus/script/iterator.py +167 -214
  60. sonolus/script/level.py +24 -0
  61. sonolus/script/num.py +80 -48
  62. sonolus/script/options.py +257 -191
  63. sonolus/script/particle.py +190 -157
  64. sonolus/script/pointer.py +30 -30
  65. sonolus/script/print.py +102 -81
  66. sonolus/script/project.py +8 -0
  67. sonolus/script/quad.py +263 -0
  68. sonolus/script/record.py +47 -16
  69. sonolus/script/runtime.py +52 -1
  70. sonolus/script/sprite.py +418 -333
  71. sonolus/script/text.py +409 -407
  72. sonolus/script/timing.py +114 -42
  73. sonolus/script/transform.py +332 -48
  74. sonolus/script/ui.py +216 -160
  75. sonolus/script/values.py +6 -13
  76. sonolus/script/vec.py +196 -78
  77. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  78. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  79. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
  80. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
  81. sonolus/backend/allocate.py +0 -51
  82. sonolus/backend/optimize.py +0 -9
  83. sonolus/backend/passes.py +0 -6
  84. sonolus/backend/simplify.py +0 -30
  85. sonolus/script/comptime.py +0 -160
  86. sonolus/script/graphics.py +0 -150
  87. sonolus/script/math.py +0 -92
  88. sonolus/script/range.py +0 -58
  89. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  90. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
@@ -1,38 +1,40 @@
1
- import functools
2
- import inspect
3
- from collections.abc import Callable
4
-
5
- from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
6
- from sonolus.backend.ops import Op
7
- from sonolus.script.internal.context import ctx
8
- from sonolus.script.internal.impl import meta_fn, validate_value
9
- from sonolus.script.num import Num, is_num
10
-
11
-
12
- def native_call(op: Op, *args: Num) -> Num:
13
- if not ctx():
14
- raise RuntimeError("Unexpected native call")
15
- args = tuple(validate_value(arg) for arg in args)
16
- if not all(is_num(arg) for arg in args):
17
- raise RuntimeError("All arguments must be of type Num")
18
- result = ctx().alloc(size=1)
19
- ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
20
- return Num._from_place_(result)
21
-
22
-
23
- def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
24
- def decorator(fn: Callable[P, Num]) -> Callable[P, Num]:
25
- signature = inspect.signature(fn)
26
-
27
- @functools.wraps(fn)
28
- @meta_fn
29
- def wrapper(*args: Num) -> Num:
30
- if len(args) != len(signature.parameters):
31
- raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
32
- if ctx():
33
- return native_call(op, *args)
34
- return fn(*args)
35
-
36
- return wrapper
37
-
38
- return decorator
1
+ import functools
2
+ import inspect
3
+ from collections.abc import Callable
4
+
5
+ from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
6
+ from sonolus.backend.ops import Op
7
+ from sonolus.script.internal.context import ctx
8
+ from sonolus.script.internal.impl import meta_fn, validate_value
9
+ from sonolus.script.num import Num, _is_num
10
+
11
+
12
+ def native_call(op: Op, *args: Num) -> Num:
13
+ if not ctx():
14
+ raise RuntimeError("Unexpected native call")
15
+ args = tuple(validate_value(arg) for arg in args)
16
+ if not all(_is_num(arg) for arg in args):
17
+ raise RuntimeError("All arguments must be of type Num")
18
+ result = ctx().alloc(size=1)
19
+ ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
20
+ return Num._from_place_(result)
21
+
22
+
23
+ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
24
+ def decorator(fn: Callable[P, Num]) -> Callable[P, Num]:
25
+ signature = inspect.signature(fn)
26
+
27
+ @functools.wraps(fn)
28
+ @meta_fn
29
+ def wrapper(*args: Num) -> Num:
30
+ if len(args) < sum(1 for p in signature.parameters.values() if p.default == inspect.Parameter.empty):
31
+ raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
32
+ if ctx():
33
+ bound_args = signature.bind(*args)
34
+ bound_args.apply_defaults()
35
+ return native_call(op, *(Num._accept_(arg) for arg in bound_args.args))
36
+ return fn(*args)
37
+
38
+ return wrapper
39
+
40
+ return decorator
@@ -0,0 +1,67 @@
1
+ import random as pyrand
2
+ from collections.abc import MutableSequence, Sequence
3
+
4
+ from sonolus.backend.ops import Op
5
+ from sonolus.script.internal.native import native_function
6
+ from sonolus.script.values import copy
7
+
8
+
9
+ @native_function(Op.Random)
10
+ def _random_float(a: float, b: float) -> float:
11
+ """Returns a random float between a and b, inclusive."""
12
+ return pyrand.uniform(a, b)
13
+
14
+
15
+ @native_function(Op.RandomInteger)
16
+ def _random_integer(a: int, b: int) -> int:
17
+ """Returns a random integer between a (inclusive) and b (exclusive)."""
18
+ return pyrand.randrange(a, b)
19
+
20
+
21
+ def _randrange(start: int, stop: int | None = None, step: int = 1) -> int:
22
+ if stop is None:
23
+ stop = start
24
+ start = 0
25
+ range_len = (stop - start + step - 1) // step
26
+ return start + step * _random_integer(0, range_len)
27
+
28
+
29
+ def _randint(a: int, b: int) -> int:
30
+ return _random_integer(a, b + 1)
31
+
32
+
33
+ def _choice[T](seq: Sequence[T]) -> T:
34
+ return seq[_randrange(len(seq))]
35
+
36
+
37
+ def _swap(seq: MutableSequence, i: int, j: int) -> None:
38
+ temp = copy(seq[i])
39
+ seq[i] = seq[j]
40
+ seq[j] = temp
41
+
42
+
43
+ def _shuffle[T: MutableSequence](seq: T) -> None:
44
+ i = len(seq) - 1
45
+ while i > 0:
46
+ j = _randrange(i + 1)
47
+ _swap(seq, i, j)
48
+ i -= 1
49
+
50
+
51
+ def _random() -> float:
52
+ # The end needs to exclude 1, and Sonolus uses 32-bit floats.
53
+ return _random_float(0.0, 0.99999994)
54
+
55
+
56
+ def _uniform(a: float, b: float) -> float:
57
+ return _random_float(a, b)
58
+
59
+
60
+ RANDOM_BUILTIN_IMPLS = {
61
+ id(pyrand.randrange): _randrange,
62
+ id(pyrand.randint): _randint,
63
+ id(pyrand.choice): _choice,
64
+ id(pyrand.shuffle): _shuffle,
65
+ id(pyrand.random): _random,
66
+ id(pyrand.uniform): _uniform,
67
+ }
@@ -0,0 +1,81 @@
1
+ from sonolus.script.array_like import ArrayLike
2
+ from sonolus.script.iterator import SonolusIterator
3
+ from sonolus.script.num import Num
4
+ from sonolus.script.record import Record
5
+
6
+
7
+ class Range(Record, ArrayLike[Num]):
8
+ start: int
9
+ stop: int
10
+ step: int
11
+
12
+ def __new__(cls, start: Num, stop: Num | None = None, step: Num = 1):
13
+ if stop is None:
14
+ start, stop = 0, start
15
+ return super().__new__(cls, start, stop, step)
16
+
17
+ def __iter__(self) -> SonolusIterator:
18
+ return RangeIterator(self.start, self.stop, self.step)
19
+
20
+ def __contains__(self, item):
21
+ if self.step > 0:
22
+ return self.start <= item < self.stop and (item - self.start) % self.step == 0
23
+ else:
24
+ return self.stop < item <= self.start and (self.start - item) % -self.step == 0
25
+
26
+ def __len__(self) -> int:
27
+ if self.step > 0:
28
+ diff = self.stop - self.start
29
+ if diff <= 0:
30
+ return 0
31
+ return (diff + self.step - 1) // self.step
32
+ else:
33
+ diff = self.start - self.stop
34
+ if diff <= 0:
35
+ return 0
36
+ return (diff - self.step - 1) // -self.step
37
+
38
+ def __getitem__(self, index: Num) -> Num:
39
+ return self.start + index * self.step
40
+
41
+ def __setitem__(self, index: Num, value: Num):
42
+ raise TypeError("Range does not support item assignment")
43
+
44
+ @property
45
+ def last(self) -> Num:
46
+ return self[len(self) - 1]
47
+
48
+ def __eq__(self, other):
49
+ if not isinstance(other, Range):
50
+ return False
51
+ len_self = len(self)
52
+ len_other = len(other)
53
+ if len_self != len_other:
54
+ return False
55
+ if len_self == 0:
56
+ return True
57
+ return self.start == other.start and self.last == other.last
58
+
59
+ def __ne__(self, other):
60
+ return not self == other
61
+
62
+ def __hash__(self):
63
+ raise TypeError("Range is not hashable")
64
+
65
+
66
+ class RangeIterator(Record, SonolusIterator):
67
+ value: int
68
+ stop: int
69
+ step: int
70
+
71
+ def has_next(self) -> bool:
72
+ if self.step > 0:
73
+ return self.value < self.stop
74
+ else:
75
+ return self.value > self.stop
76
+
77
+ def get(self) -> int:
78
+ return self.value
79
+
80
+ def advance(self):
81
+ self.value += self.step
@@ -0,0 +1,51 @@
1
+ from collections.abc import Iterable
2
+ from typing import Any, Self
3
+
4
+ from sonolus.backend.place import BlockPlace
5
+ from sonolus.script.internal.value import Value
6
+
7
+
8
+ class TransientValue(Value):
9
+ @classmethod
10
+ def _is_concrete_(cls) -> bool:
11
+ return True
12
+
13
+ @classmethod
14
+ def _size_(cls) -> int:
15
+ return 0
16
+
17
+ @classmethod
18
+ def _is_value_type_(cls) -> bool:
19
+ return False
20
+
21
+ @classmethod
22
+ def _from_place_(cls, place: BlockPlace) -> Self:
23
+ raise TypeError(f"{cls.__name__} cannot be dereferenced")
24
+
25
+ @classmethod
26
+ def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
27
+ raise TypeError(f"{cls.__name__} cannot be constructed from list")
28
+
29
+ def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
30
+ raise TypeError(f"{type(self).__name__} cannot be deconstructed to list")
31
+
32
+ @classmethod
33
+ def _flat_keys_(cls, prefix: str) -> list[str]:
34
+ raise TypeError(f"{cls.__name__} cannot be flattened")
35
+
36
+ def _get_(self) -> Self:
37
+ return self
38
+
39
+ def _set_(self, value: Self) -> None:
40
+ if value is not self:
41
+ raise TypeError(f"{type(self).__name__} is immutable")
42
+
43
+ def _copy_from_(self, value: Self):
44
+ raise TypeError(f"{type(self).__name__} is immutable")
45
+
46
+ def _copy_(self) -> Self:
47
+ raise TypeError(f"{type(self).__name__} cannot be copied")
48
+
49
+ @classmethod
50
+ def _alloc_(cls) -> Self:
51
+ raise TypeError(f"{cls.__name__} is not allocatable")
@@ -0,0 +1,113 @@
1
+ # ruff: noqa: B905
2
+ from typing import Any, Self
3
+
4
+ from sonolus.script.internal.impl import meta_fn, validate_value
5
+ from sonolus.script.internal.transient import TransientValue
6
+
7
+
8
+ class TupleImpl(TransientValue):
9
+ value: tuple
10
+
11
+ def __init__(self, value: tuple):
12
+ self.value = value
13
+
14
+ @meta_fn
15
+ def __getitem__(self, item):
16
+ item = validate_value(item)
17
+ if not item._is_py_():
18
+ raise TypeError(f"Cannot index tuple with non compile-time constant {item}")
19
+ item = item._as_py_()
20
+ if not isinstance(item, int | float):
21
+ raise TypeError(f"Cannot index tuple with {item}")
22
+ if int(item) != item:
23
+ raise TypeError(f"Cannot index tuple with non-integer {item}")
24
+ if not (0 <= item < len(self.value)):
25
+ raise IndexError(f"Tuple index out of range: {item}")
26
+ return self.value[int(item)]
27
+
28
+ @meta_fn
29
+ def __len__(self):
30
+ return len(self.value)
31
+
32
+ def __eq__(self, other):
33
+ if not isinstance(other, tuple):
34
+ return False
35
+ if len(self) != len(other):
36
+ return False
37
+ for a, b in zip(self, other): # noqa: SIM110
38
+ if a != b:
39
+ return False
40
+ return True
41
+
42
+ def __ne__(self, other):
43
+ if not isinstance(other, tuple):
44
+ return True
45
+ if len(self) != len(other):
46
+ return True
47
+ for a, b in zip(self, other): # noqa: SIM110
48
+ if a != b:
49
+ return True
50
+ return False
51
+
52
+ def __lt__(self, other):
53
+ if not isinstance(other, tuple):
54
+ return NotImplemented
55
+ for a, b in zip(self.value, other.value):
56
+ if a != b:
57
+ return a < b
58
+ return len(self.value) < len(other.value)
59
+
60
+ def __le__(self, other):
61
+ if not isinstance(other, tuple):
62
+ return NotImplemented
63
+ for a, b in zip(self.value, other.value):
64
+ if a != b:
65
+ return a < b
66
+ return len(self.value) <= len(other.value)
67
+
68
+ def __gt__(self, other):
69
+ if not isinstance(other, tuple):
70
+ return NotImplemented
71
+ for a, b in zip(self.value, other.value):
72
+ if a != b:
73
+ return a > b
74
+ return len(self.value) > len(other.value)
75
+
76
+ def __ge__(self, other):
77
+ if not isinstance(other, tuple):
78
+ return NotImplemented
79
+ for a, b in zip(self.value, other.value):
80
+ if a != b:
81
+ return a > b
82
+ return len(self.value) >= len(other.value)
83
+
84
+ def __hash__(self):
85
+ return hash(self.value)
86
+
87
+ @meta_fn
88
+ def __add__(self, other) -> Self:
89
+ other = TupleImpl._accept_(other)
90
+ return TupleImpl._accept_(self.value + other.value)
91
+
92
+ @classmethod
93
+ def _accepts_(cls, value: Any) -> bool:
94
+ return isinstance(value, cls | tuple)
95
+
96
+ @classmethod
97
+ def _accept_(cls, value: Any) -> Self:
98
+ if not cls._accepts_(value):
99
+ raise TypeError(f"Cannot accept {value} as {cls.__name__}")
100
+ if isinstance(value, cls):
101
+ return value
102
+ else:
103
+ return cls(tuple(validate_value(item) for item in value))
104
+
105
+ def _is_py_(self) -> bool:
106
+ return all(item._is_py_() for item in self.value)
107
+
108
+ def _as_py_(self) -> tuple:
109
+ return tuple(item._as_py_() for item in self.value)
110
+
111
+
112
+ TupleImpl.__name__ = "tuple"
113
+ TupleImpl.__qualname__ = "tuple"
@@ -66,7 +66,7 @@ class Value:
66
66
  raise NotImplementedError
67
67
 
68
68
  @abstractmethod
69
- def _to_list_(self) -> list[float | BlockPlace]:
69
+ def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
70
70
  """Converts this value to a list of floats."""
71
71
  raise NotImplementedError
72
72
 
@@ -76,9 +76,9 @@ class Value:
76
76
  """Returns the keys to a flat representation of this value."""
77
77
  raise NotImplementedError
78
78
 
79
- def _to_flat_dict_(self, prefix: str) -> dict[str, float | BlockPlace]:
79
+ def _to_flat_dict_(self, prefix: str, level_refs: dict[Any, int] | None = None) -> dict[str, float | BlockPlace]:
80
80
  """Converts this value to a flat dictionary."""
81
- return dict(zip(self._flat_keys_(prefix), self._to_list_(), strict=False))
81
+ return dict(zip(self._flat_keys_(prefix), self._to_list_(level_refs), strict=False))
82
82
 
83
83
  @abstractmethod
84
84
  def _get_(self) -> Self: