sonolus.py 0.1.4__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 (77) hide show
  1. sonolus/backend/finalize.py +18 -10
  2. sonolus/backend/interpret.py +7 -7
  3. sonolus/backend/ir.py +24 -0
  4. sonolus/backend/optimize/__init__.py +0 -0
  5. sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
  6. sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
  7. sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
  8. sonolus/backend/optimize/dead_code.py +185 -0
  9. sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
  10. sonolus/backend/{flow.py → optimize/flow.py} +6 -5
  11. sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
  12. sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
  13. sonolus/backend/optimize/optimize.py +44 -0
  14. sonolus/backend/{passes.py → optimize/passes.py} +1 -1
  15. sonolus/backend/optimize/simplify.py +191 -0
  16. sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
  17. sonolus/backend/place.py +17 -25
  18. sonolus/backend/utils.py +10 -0
  19. sonolus/backend/visitor.py +360 -101
  20. sonolus/build/compile.py +8 -8
  21. sonolus/build/engine.py +10 -5
  22. sonolus/script/archetype.py +419 -137
  23. sonolus/script/array.py +25 -8
  24. sonolus/script/array_like.py +297 -0
  25. sonolus/script/bucket.py +73 -11
  26. sonolus/script/containers.py +234 -51
  27. sonolus/script/debug.py +8 -8
  28. sonolus/script/easing.py +147 -105
  29. sonolus/script/effect.py +60 -0
  30. sonolus/script/engine.py +71 -4
  31. sonolus/script/globals.py +66 -32
  32. sonolus/script/instruction.py +79 -25
  33. sonolus/script/internal/builtin_impls.py +138 -27
  34. sonolus/script/internal/constant.py +139 -0
  35. sonolus/script/internal/context.py +14 -5
  36. sonolus/script/internal/dict_impl.py +65 -0
  37. sonolus/script/internal/generic.py +6 -9
  38. sonolus/script/internal/impl.py +38 -13
  39. sonolus/script/internal/introspection.py +5 -2
  40. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  41. sonolus/script/internal/native.py +3 -3
  42. sonolus/script/internal/random.py +67 -0
  43. sonolus/script/internal/range.py +81 -0
  44. sonolus/script/internal/transient.py +51 -0
  45. sonolus/script/internal/tuple_impl.py +113 -0
  46. sonolus/script/interval.py +234 -16
  47. sonolus/script/iterator.py +120 -167
  48. sonolus/script/level.py +24 -0
  49. sonolus/script/num.py +79 -47
  50. sonolus/script/options.py +78 -12
  51. sonolus/script/particle.py +37 -4
  52. sonolus/script/pointer.py +4 -4
  53. sonolus/script/print.py +22 -1
  54. sonolus/script/project.py +8 -0
  55. sonolus/script/{graphics.py → quad.py} +75 -12
  56. sonolus/script/record.py +44 -13
  57. sonolus/script/runtime.py +50 -1
  58. sonolus/script/sprite.py +197 -112
  59. sonolus/script/text.py +2 -0
  60. sonolus/script/timing.py +72 -0
  61. sonolus/script/transform.py +296 -66
  62. sonolus/script/ui.py +134 -78
  63. sonolus/script/values.py +6 -13
  64. sonolus/script/vec.py +118 -3
  65. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  66. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  67. sonolus/backend/dead_code.py +0 -80
  68. sonolus/backend/optimize.py +0 -37
  69. sonolus/backend/simplify.py +0 -47
  70. sonolus/script/comptime.py +0 -160
  71. sonolus/script/random.py +0 -14
  72. sonolus/script/range.py +0 -58
  73. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  74. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  75. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +0 -0
  76. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -4,11 +4,14 @@ from typing import Annotated
4
4
  _missing = object()
5
5
 
6
6
 
7
- def get_field_specifiers(cls, *, globals=None, locals=None, eval_str=True): # noqa: A002
7
+ def get_field_specifiers(cls, *, skip: set[str] = frozenset(), globals=None, locals=None, eval_str=True): # noqa: A002
8
8
  """Like inspect.get_annotations, but also turns class attributes into Annotated."""
9
9
  results = inspect.get_annotations(cls, globals=globals, locals=locals, eval_str=eval_str)
10
10
  for key, value in results.items():
11
11
  class_value = getattr(cls, key, _missing)
12
- if class_value is not _missing:
12
+ if class_value is not _missing and key not in skip:
13
13
  results[key] = Annotated[value, class_value]
14
+ for key, value in cls.__dict__.items():
15
+ if key not in results and key not in skip and not key.startswith("__") and not callable(value):
16
+ raise ValueError(f"Missing annotation for {cls.__name__}.{key}")
14
17
  return results
@@ -5,67 +5,67 @@ from sonolus.script.internal.native import native_function
5
5
 
6
6
 
7
7
  @native_function(Op.Sin)
8
- def sin(x: float) -> float:
8
+ def _sin(x: float) -> float:
9
9
  return math.sin(x)
10
10
 
11
11
 
12
12
  @native_function(Op.Cos)
13
- def cos(x: float) -> float:
13
+ def _cos(x: float) -> float:
14
14
  return math.cos(x)
15
15
 
16
16
 
17
17
  @native_function(Op.Tan)
18
- def tan(x: float) -> float:
18
+ def _tan(x: float) -> float:
19
19
  return math.tan(x)
20
20
 
21
21
 
22
22
  @native_function(Op.Arcsin)
23
- def asin(x: float) -> float:
23
+ def _asin(x: float) -> float:
24
24
  return math.asin(x)
25
25
 
26
26
 
27
27
  @native_function(Op.Arccos)
28
- def acos(x: float) -> float:
28
+ def _acos(x: float) -> float:
29
29
  return math.acos(x)
30
30
 
31
31
 
32
32
  @native_function(Op.Arctan)
33
- def atan(x: float) -> float:
33
+ def _atan(x: float) -> float:
34
34
  return math.atan(x)
35
35
 
36
36
 
37
37
  @native_function(Op.Arctan2)
38
- def atan2(y: float, x: float) -> float:
38
+ def _atan2(y: float, x: float) -> float:
39
39
  return math.atan2(y, x)
40
40
 
41
41
 
42
42
  @native_function(Op.Sinh)
43
- def sinh(x: float) -> float:
43
+ def _sinh(x: float) -> float:
44
44
  return math.sinh(x)
45
45
 
46
46
 
47
47
  @native_function(Op.Cosh)
48
- def cosh(x: float) -> float:
48
+ def _cosh(x: float) -> float:
49
49
  return math.cosh(x)
50
50
 
51
51
 
52
52
  @native_function(Op.Tanh)
53
- def tanh(x: float) -> float:
53
+ def _tanh(x: float) -> float:
54
54
  return math.tanh(x)
55
55
 
56
56
 
57
57
  @native_function(Op.Floor)
58
- def floor(x: float) -> float:
58
+ def _floor(x: float) -> float:
59
59
  return math.floor(x)
60
60
 
61
61
 
62
62
  @native_function(Op.Ceil)
63
- def ceil(x: float) -> float:
63
+ def _ceil(x: float) -> float:
64
64
  return math.ceil(x)
65
65
 
66
66
 
67
67
  @native_function(Op.Trunc)
68
- def trunc(x: float) -> float:
68
+ def _trunc(x: float) -> float:
69
69
  return math.trunc(x)
70
70
 
71
71
 
@@ -90,7 +90,7 @@ def _ln(x: float) -> float:
90
90
  return math.log(x)
91
91
 
92
92
 
93
- def log(x: float, base: float | None = None) -> float:
93
+ def _log(x: float, base: float | None = None) -> float:
94
94
  if base is None:
95
95
  return _ln(x)
96
96
  return _ln(x) / _ln(base)
@@ -103,19 +103,19 @@ def _remainder(x: float, y: float) -> float:
103
103
 
104
104
 
105
105
  MATH_BUILTIN_IMPLS = {
106
- id(math.sin): sin,
107
- id(math.cos): cos,
108
- id(math.tan): tan,
109
- id(math.asin): asin,
110
- id(math.acos): acos,
111
- id(math.atan): atan,
112
- id(math.atan2): atan2,
113
- id(math.sinh): sinh,
114
- id(math.cosh): cosh,
115
- id(math.tanh): tanh,
116
- id(math.floor): floor,
117
- id(math.ceil): ceil,
118
- id(math.trunc): trunc,
106
+ id(math.sin): _sin,
107
+ id(math.cos): _cos,
108
+ id(math.tan): _tan,
109
+ id(math.asin): _asin,
110
+ id(math.acos): _acos,
111
+ id(math.atan): _atan,
112
+ id(math.atan2): _atan2,
113
+ id(math.sinh): _sinh,
114
+ id(math.cosh): _cosh,
115
+ id(math.tanh): _tanh,
116
+ id(math.floor): _floor,
117
+ id(math.ceil): _ceil,
118
+ id(math.trunc): _trunc,
119
119
  id(round): _round,
120
- id(math.log): log,
120
+ id(math.log): _log,
121
121
  }
@@ -6,14 +6,14 @@ from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
6
6
  from sonolus.backend.ops import Op
7
7
  from sonolus.script.internal.context import ctx
8
8
  from sonolus.script.internal.impl import meta_fn, validate_value
9
- from sonolus.script.num import Num, is_num
9
+ from sonolus.script.num import Num, _is_num
10
10
 
11
11
 
12
12
  def native_call(op: Op, *args: Num) -> Num:
13
13
  if not ctx():
14
14
  raise RuntimeError("Unexpected native call")
15
15
  args = tuple(validate_value(arg) for arg in args)
16
- if not all(is_num(arg) for arg in args):
16
+ if not all(_is_num(arg) for arg in args):
17
17
  raise RuntimeError("All arguments must be of type Num")
18
18
  result = ctx().alloc(size=1)
19
19
  ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
@@ -32,7 +32,7 @@ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]
32
32
  if ctx():
33
33
  bound_args = signature.bind(*args)
34
34
  bound_args.apply_defaults()
35
- return native_call(op, *bound_args.args)
35
+ return native_call(op, *(Num._accept_(arg) for arg in bound_args.args))
36
36
  return fn(*args)
37
37
 
38
38
  return wrapper
@@ -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"