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
@@ -0,0 +1,139 @@
1
+ from collections.abc import Iterable
2
+ from typing import Any, ClassVar, Self
3
+
4
+ from sonolus.backend.place import BlockPlace
5
+ from sonolus.script.internal.impl import meta_fn
6
+ from sonolus.script.internal.value import Value
7
+
8
+
9
+ class _Missing:
10
+ def __repr__(self) -> str:
11
+ return "MISSING"
12
+
13
+ def __bool__(self) -> bool:
14
+ return False
15
+
16
+
17
+ _MISSING = _Missing()
18
+
19
+
20
+ class ConstantValue(Value):
21
+ _parameterized_: ClassVar[dict[Any, type[Self]]] = {}
22
+ _value: ClassVar[Any] = _MISSING
23
+ instance: ClassVar[Self | _Missing] = _MISSING
24
+
25
+ def __new__(cls) -> Self:
26
+ if cls.value() is _MISSING:
27
+ raise TypeError(f"Class {cls.__name__} is not parameterized")
28
+ return cls.instance
29
+
30
+ @classmethod
31
+ def value(cls):
32
+ # We need this to avoid descriptors getting in the way
33
+ return cls._value[0] if cls._value is not _MISSING else _MISSING
34
+
35
+ @classmethod
36
+ def of(cls, value: Any) -> Self:
37
+ if value in cls._parameterized_:
38
+ return cls._parameterized_[value]()
39
+
40
+ parameterized = cls._get_parameterized(value)
41
+ cls._parameterized_[value] = parameterized
42
+ return parameterized()
43
+
44
+ @classmethod
45
+ def _get_parameterized(cls, parameter: Any) -> type[Self]:
46
+ class Parameterized(cls):
47
+ _value = (parameter,)
48
+
49
+ Parameterized.__name__ = f"{parameter}"
50
+ Parameterized.__qualname__ = f"{parameter}"
51
+ Parameterized.__module__ = cls.__module__
52
+ Parameterized.instance = object.__new__(Parameterized)
53
+ return Parameterized
54
+
55
+ @classmethod
56
+ def _is_concrete_(cls) -> bool:
57
+ return True
58
+
59
+ @classmethod
60
+ def _size_(cls) -> int:
61
+ return 0
62
+
63
+ @classmethod
64
+ def _is_value_type_(cls) -> bool:
65
+ return False
66
+
67
+ @classmethod
68
+ def _from_place_(cls, place: BlockPlace) -> Self:
69
+ if cls.value() is _MISSING:
70
+ raise TypeError(f"Class {cls.__name__} is not parameterized")
71
+ return cls()
72
+
73
+ @classmethod
74
+ def _accepts_(cls, value: Any) -> bool:
75
+ from sonolus.script.internal.impl import validate_value
76
+
77
+ # We rely on validate_value to create the correct instance
78
+ return isinstance(validate_value(value), cls)
79
+
80
+ @classmethod
81
+ def _accept_(cls, value: Any) -> Self:
82
+ from sonolus.script.internal.impl import validate_value
83
+
84
+ # We rely on validate_value to create the correct instance
85
+ value = validate_value(value)
86
+ if not isinstance(value, cls):
87
+ raise ValueError(f"Value {value} is not of type {cls}")
88
+ return value
89
+
90
+ def _is_py_(self) -> bool:
91
+ return True
92
+
93
+ def _as_py_(self) -> Any:
94
+ return self.value()
95
+
96
+ @classmethod
97
+ def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
98
+ return cls()
99
+
100
+ def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
101
+ return []
102
+
103
+ @classmethod
104
+ def _flat_keys_(cls, prefix: str) -> list[str]:
105
+ return []
106
+
107
+ def _get_(self) -> Self:
108
+ return self
109
+
110
+ def _set_(self, value: Any) -> Self:
111
+ if value is not self:
112
+ raise ValueError(f"{type(self).__name__} is immutable")
113
+
114
+ def _copy_from_(self, value: Self):
115
+ if value is not self:
116
+ raise ValueError(f"{type(self).__name__} is immutable")
117
+
118
+ def _copy_(self) -> Self:
119
+ return self
120
+
121
+ @classmethod
122
+ def _alloc_(cls) -> Self:
123
+ return cls()
124
+
125
+ @meta_fn
126
+ def __eq__(self, other):
127
+ return self is other
128
+
129
+ @meta_fn
130
+ def __ne__(self, other):
131
+ return self is not other
132
+
133
+ @meta_fn
134
+ def __hash__(self):
135
+ return hash(self.value())
136
+
137
+
138
+ class MiscConstantValue(ConstantValue):
139
+ """For constants without any special behavior."""
@@ -6,11 +6,11 @@ from dataclasses import dataclass
6
6
  from typing import Any, Self
7
7
 
8
8
  from sonolus.backend.blocks import BlockData, PlayBlock
9
- from sonolus.backend.flow import BasicBlock, FlowEdge
10
9
  from sonolus.backend.ir import IRConst, IRStmt
11
10
  from sonolus.backend.mode import Mode
11
+ from sonolus.backend.optimize.flow import BasicBlock, FlowEdge
12
12
  from sonolus.backend.place import Block, BlockPlace, TempBlock
13
- from sonolus.script.globals import GlobalInfo, GlobalPlaceholder
13
+ from sonolus.script.globals import _GlobalInfo, _GlobalPlaceholder
14
14
  from sonolus.script.internal.value import Value
15
15
 
16
16
  _compiler_internal_ = True
@@ -22,7 +22,7 @@ class GlobalContextState:
22
22
  archetypes: dict[type, int]
23
23
  rom: ReadOnlyMemory
24
24
  const_mappings: dict[Any, int]
25
- environment_mappings: dict[GlobalInfo, int]
25
+ environment_mappings: dict[_GlobalInfo, int]
26
26
  environment_offsets: dict[Block, int]
27
27
  mode: Mode
28
28
 
@@ -60,6 +60,7 @@ class Context:
60
60
  callback_state: CallbackContextState,
61
61
  scope: Scope | None = None,
62
62
  live: bool = True,
63
+ foldable_constants: dict[TempBlock, list[float | None]] | None = None,
63
64
  ):
64
65
  self.global_state = global_state
65
66
  self.callback_state = callback_state
@@ -69,6 +70,7 @@ class Context:
69
70
  self.scope = scope if scope is not None else Scope()
70
71
  self.loop_variables = {}
71
72
  self.live = live
73
+ self.foldable_constants = foldable_constants if foldable_constants is not None else {}
72
74
 
73
75
  @property
74
76
  def rom(self) -> ReadOnlyMemory:
@@ -102,8 +104,14 @@ class Context:
102
104
  if isinstance(place.block, BlockData) and self.callback not in self.blocks(place.block).writable:
103
105
  raise RuntimeError(f"Block {place.block} is not writable in {self.callback}")
104
106
 
107
+ def add_statement(self, statement: IRStmt):
108
+ if not self.live:
109
+ return
110
+ self.statements.append(statement)
111
+
105
112
  def add_statements(self, *statements: IRStmt):
106
- self.statements.extend(statements)
113
+ for statement in statements:
114
+ self.add_statement(statement)
107
115
 
108
116
  def alloc(self, name: str | None = None, size: int = 1) -> BlockPlace:
109
117
  if size == 0:
@@ -124,6 +132,7 @@ class Context:
124
132
  callback_state=self.callback_state,
125
133
  scope=scope,
126
134
  live=self.live,
135
+ foldable_constants={**self.foldable_constants}, # We want this to persist even if the scope changes
127
136
  )
128
137
 
129
138
  def branch(self, condition: float | None):
@@ -164,7 +173,8 @@ class Context:
164
173
  header.scope.set_value(name, target_value)
165
174
  header.loop_variables[name] = target_value
166
175
  else:
167
- header.scope.set_binding(name, ConflictBinding())
176
+ header.scope.set_value(name, value)
177
+ header.loop_variables[name] = value
168
178
  return header
169
179
 
170
180
  def branch_to_loop_header(self, header: Self):
@@ -174,16 +184,23 @@ class Context:
174
184
  self.outgoing[None] = header
175
185
  for name, target_value in header.loop_variables.items():
176
186
  with using_ctx(self):
177
- value = self.scope.get_value(name)
178
- value = type(target_value)._accept_(value)
179
- target_value._set_(value)
187
+ if type(target_value)._is_value_type_():
188
+ value = self.scope.get_value(name)
189
+ value = type(target_value)._accept_(value)
190
+ target_value._set_(value)
191
+ else:
192
+ value = self.scope.get_value(name)
193
+ if target_value is not value:
194
+ raise RuntimeError(
195
+ f"Variable '{name}' may have conflicting definitions between loop iterations"
196
+ )
180
197
 
181
198
  def map_constant(self, value: Any) -> int:
182
199
  if value not in self.const_mappings:
183
200
  self.const_mappings[value] = len(self.const_mappings)
184
201
  return self.const_mappings[value]
185
202
 
186
- def get_global_base(self, value: GlobalInfo | GlobalPlaceholder) -> BlockPlace:
203
+ def get_global_base(self, value: _GlobalInfo | _GlobalPlaceholder) -> BlockPlace:
187
204
  block = value.blocks.get(self.global_state.mode)
188
205
  if block is None:
189
206
  raise RuntimeError(f"Global {value.name} is not available in '{self.global_state.mode.name}' mode")
@@ -1,17 +1,17 @@
1
- from abc import abstractmethod
2
-
3
-
4
- class SonolusDescriptor:
5
- """Base class for Sonolus descriptors.
6
-
7
- The compiler checks if a descriptor is an instance of a subclass of this class,
8
- so it knows that it's a supported descriptor.
9
- """
10
-
11
- @abstractmethod
12
- def __get__(self, instance, owner):
13
- pass
14
-
15
- @abstractmethod
16
- def __set__(self, instance, value):
17
- pass
1
+ from abc import abstractmethod
2
+
3
+
4
+ class SonolusDescriptor:
5
+ """Base class for Sonolus descriptors.
6
+
7
+ The compiler checks if a descriptor is an instance of a subclass of this class,
8
+ so it knows that it's a supported descriptor.
9
+ """
10
+
11
+ @abstractmethod
12
+ def __get__(self, instance, owner):
13
+ pass
14
+
15
+ @abstractmethod
16
+ def __set__(self, instance, value):
17
+ pass
@@ -0,0 +1,65 @@
1
+ from typing import Any, Self
2
+
3
+ from sonolus.script.internal.impl import meta_fn, validate_value
4
+ from sonolus.script.internal.transient import TransientValue
5
+
6
+
7
+ class DictImpl(TransientValue):
8
+ def __init__(self, value: dict):
9
+ self.value = value
10
+
11
+ @meta_fn
12
+ def __getitem__(self, item):
13
+ item = validate_value(item)
14
+ if not item._is_py_():
15
+ raise TypeError("Key must be a compile-time constant")
16
+ item = item._as_py_()
17
+ if item not in self.value:
18
+ raise KeyError(item)
19
+ return self.value[item]
20
+
21
+ @meta_fn
22
+ def __contains__(self, item):
23
+ item = validate_value(item)
24
+ if not item._is_py_():
25
+ raise TypeError("Key must be a compile-time constant")
26
+ item = item._as_py_()
27
+ return item in self.value
28
+
29
+ @meta_fn
30
+ def __len__(self):
31
+ return len(self.value)
32
+
33
+ def __eq__(self, other):
34
+ raise TypeError("Comparing dicts is not supported")
35
+
36
+ __hash__ = None
37
+
38
+ @meta_fn
39
+ def __or__(self, other):
40
+ if not isinstance(other, DictImpl):
41
+ raise TypeError("Only dicts can be merged")
42
+ return DictImpl({**self.value, **other.value})
43
+
44
+ @classmethod
45
+ def _accepts_(cls, value: Any) -> bool:
46
+ return isinstance(value, cls | dict)
47
+
48
+ @classmethod
49
+ def _accept_(cls, value: Any) -> Self:
50
+ if not cls._accepts_(value):
51
+ raise TypeError(f"Cannot accept {value} as {cls.__name__}")
52
+ if isinstance(value, cls):
53
+ return value
54
+ else:
55
+ return cls({validate_value(k)._as_py_(): validate_value(v) for k, v in value.items()})
56
+
57
+ def _is_py_(self) -> bool:
58
+ return all(v._is_py_() for v in self.value.values())
59
+
60
+ def _as_py_(self) -> Any:
61
+ return {k: v._as_py_() for k, v in self.value.items()}
62
+
63
+
64
+ DictImpl.__name__ = "dict"
65
+ DictImpl.__qualname__ = "dict"
@@ -3,19 +3,19 @@ from __future__ import annotations
3
3
  from enum import Enum
4
4
  from typing import Annotated, Any, ClassVar, Literal, Self, TypeVar, get_origin
5
5
 
6
- from sonolus.script.internal.impl import meta_fn
6
+ from sonolus.script.internal.impl import meta_fn, validate_value
7
7
  from sonolus.script.internal.value import Value
8
8
 
9
9
  type AnyType = type[Value] | PartialGeneric | TypeVar
10
10
 
11
11
 
12
12
  def validate_type_arg(arg: Any) -> Any:
13
- from sonolus.script.internal.impl import validate_value
14
-
15
13
  arg = validate_value(arg)
16
14
  if not arg._is_py_():
17
15
  raise TypeError(f"Expected a compile-time constant type argument, got {arg}")
18
16
  result = arg._as_py_()
17
+ if hasattr(result, "_type_mapping_"):
18
+ return result._type_mapping_
19
19
  if get_origin(result) is Annotated:
20
20
  return result.__args__[0]
21
21
  if get_origin(result) is Literal:
@@ -69,7 +69,7 @@ class GenericValue(Value):
69
69
  raise TypeError(f"Missing type arguments for {self.__class__.__name__}")
70
70
 
71
71
  @classmethod
72
- def _validate__type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
72
+ def _validate_type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
73
73
  """Validate the type arguments and return them as a tuple.
74
74
 
75
75
  This may be called with PartialGeneric or TypeVar instances inside args.
@@ -84,7 +84,7 @@ class GenericValue(Value):
84
84
 
85
85
  @classmethod
86
86
  @meta_fn
87
- def _get_type_arg_(cls, var: TypeVar) -> Any:
87
+ def type_var_value(cls, var: TypeVar) -> Any:
88
88
  if isinstance(var, Value):
89
89
  var = var._as_py_()
90
90
  if cls._type_args_ is None:
@@ -93,12 +93,11 @@ class GenericValue(Value):
93
93
  return cls._type_vars_to_args_[var]
94
94
  raise TypeError(f"Missing type argument for {var}")
95
95
 
96
- @classmethod
97
96
  def __class_getitem__(cls, args: Any) -> type[Self]:
98
97
  if cls._type_args_ is not None:
99
98
  raise TypeError(f"Type {cls.__name__} is already parameterized")
100
99
  args = validate_type_args(args)
101
- args = cls._validate__type_args_(args)
100
+ args = cls._validate_type_args_(args)
102
101
  if contains_incomplete_type(args):
103
102
  return PartialGeneric(cls, args)
104
103
  if args not in cls._parameterized_:
@@ -168,8 +167,6 @@ def infer_and_validate_types(dst: Any, src: Any, results: dict[TypeVar, Any] | N
168
167
 
169
168
 
170
169
  def accept_and_infer_types(dst: Any, val: Any, results: dict[TypeVar, Any]) -> Value:
171
- from sonolus.script.internal.impl import validate_value
172
-
173
170
  val = validate_value(val)
174
171
  match dst:
175
172
  case TypeVar():
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
4
  from enum import Enum
5
- from types import FunctionType, MethodType, NoneType, NotImplementedType
5
+ from types import FunctionType, MethodType, ModuleType, NoneType, NotImplementedType, UnionType
6
6
  from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, get_origin, overload
7
7
 
8
8
  if TYPE_CHECKING:
@@ -18,6 +18,11 @@ def meta_fn[T: Callable]() -> Callable[[T], T]: ...
18
18
 
19
19
 
20
20
  def meta_fn(fn=None):
21
+ """Marks a function as a meta function to be called directly without the AST visitor.
22
+
23
+ This can also improve performance in some cases by avoiding the overhead of the AST visitor.
24
+ """
25
+
21
26
  # noinspection PyShadowingNames
22
27
  def decorator(fn):
23
28
  fn._meta_fn_ = True
@@ -36,12 +41,23 @@ def validate_value(value: Any) -> Value:
36
41
 
37
42
 
38
43
  def try_validate_value(value: Any) -> Value | None:
39
- from sonolus.script.comptime import Comptime
40
- from sonolus.script.globals import GlobalPlaceholder
44
+ from sonolus.script.globals import _GlobalPlaceholder
45
+ from sonolus.script.internal.constant import MiscConstantValue
46
+ from sonolus.script.internal.dict_impl import DictImpl
41
47
  from sonolus.script.internal.generic import PartialGeneric
48
+ from sonolus.script.internal.tuple_impl import TupleImpl
42
49
  from sonolus.script.internal.value import Value
43
50
  from sonolus.script.num import Num
44
51
 
52
+ try:
53
+ # Unfortunately this is called during import, so this may fail
54
+ from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS
55
+
56
+ if id(value) in BUILTIN_IMPLS:
57
+ return validate_value(BUILTIN_IMPLS[id(value)])
58
+ except ImportError:
59
+ pass
60
+
45
61
  match value:
46
62
  case Enum():
47
63
  return validate_value(value.value)
@@ -49,21 +65,30 @@ def try_validate_value(value: Any) -> Value | None:
49
65
  return value
50
66
  case type():
51
67
  if value in {int, float, bool}:
52
- return Comptime.accept_unchecked(Num)
53
- return Comptime.accept_unchecked(value)
68
+ return MiscConstantValue.of(Num)
69
+ return MiscConstantValue.of(value)
54
70
  case int() | float() | bool():
55
71
  return Num._accept_(value)
56
72
  case tuple():
57
- return Comptime.accept_unchecked(tuple(validate_value(v) for v in value))
73
+ return TupleImpl._accept_(value)
58
74
  case dict():
59
- return Comptime.accept_unchecked({validate_value(k)._as_py_(): validate_value(v) for k, v in value.items()})
60
- case PartialGeneric() | TypeVar() | FunctionType() | MethodType() | NotImplementedType() | str() | NoneType():
61
- return Comptime.accept_unchecked(value)
62
- case other_type if get_origin(value) in {Literal, Annotated}:
63
- return Comptime.accept_unchecked(other_type)
64
- case GlobalPlaceholder():
75
+ return DictImpl._accept_(value)
76
+ case (
77
+ PartialGeneric()
78
+ | TypeVar()
79
+ | FunctionType()
80
+ | MethodType()
81
+ | NotImplementedType()
82
+ | str()
83
+ | NoneType()
84
+ | ModuleType()
85
+ ):
86
+ return MiscConstantValue.of(value)
87
+ case other_type if get_origin(value) in {Literal, Annotated, UnionType, tuple}:
88
+ return MiscConstantValue.of(other_type)
89
+ case _GlobalPlaceholder():
65
90
  return value.get()
66
91
  case comptime_value if getattr(comptime_value, "_is_comptime_value_", False):
67
- return Comptime.accept_unchecked(comptime_value)
92
+ return MiscConstantValue.of(comptime_value)
68
93
  case _:
69
94
  return None
@@ -1,14 +1,17 @@
1
- import inspect
2
- from typing import Annotated
3
-
4
- _missing = object()
5
-
6
-
7
- def get_field_specifiers(cls, *, globals=None, locals=None, eval_str=True): # noqa: A002
8
- """Like inspect.get_annotations, but also turns class attributes into Annotated."""
9
- results = inspect.get_annotations(cls, globals=globals, locals=locals, eval_str=eval_str)
10
- for key, value in results.items():
11
- class_value = getattr(cls, key, _missing)
12
- if class_value is not _missing:
13
- results[key] = Annotated[value, class_value]
14
- return results
1
+ import inspect
2
+ from typing import Annotated
3
+
4
+ _missing = object()
5
+
6
+
7
+ def get_field_specifiers(cls, *, skip: set[str] = frozenset(), globals=None, locals=None, eval_str=True): # noqa: A002
8
+ """Like inspect.get_annotations, but also turns class attributes into Annotated."""
9
+ results = inspect.get_annotations(cls, globals=globals, locals=locals, eval_str=eval_str)
10
+ for key, value in results.items():
11
+ class_value = getattr(cls, key, _missing)
12
+ if class_value is not _missing and key not in skip:
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}")
17
+ return results
@@ -0,0 +1,121 @@
1
+ import math
2
+
3
+ from sonolus.backend.ops import Op
4
+ from sonolus.script.internal.native import native_function
5
+
6
+
7
+ @native_function(Op.Sin)
8
+ def _sin(x: float) -> float:
9
+ return math.sin(x)
10
+
11
+
12
+ @native_function(Op.Cos)
13
+ def _cos(x: float) -> float:
14
+ return math.cos(x)
15
+
16
+
17
+ @native_function(Op.Tan)
18
+ def _tan(x: float) -> float:
19
+ return math.tan(x)
20
+
21
+
22
+ @native_function(Op.Arcsin)
23
+ def _asin(x: float) -> float:
24
+ return math.asin(x)
25
+
26
+
27
+ @native_function(Op.Arccos)
28
+ def _acos(x: float) -> float:
29
+ return math.acos(x)
30
+
31
+
32
+ @native_function(Op.Arctan)
33
+ def _atan(x: float) -> float:
34
+ return math.atan(x)
35
+
36
+
37
+ @native_function(Op.Arctan2)
38
+ def _atan2(y: float, x: float) -> float:
39
+ return math.atan2(y, x)
40
+
41
+
42
+ @native_function(Op.Sinh)
43
+ def _sinh(x: float) -> float:
44
+ return math.sinh(x)
45
+
46
+
47
+ @native_function(Op.Cosh)
48
+ def _cosh(x: float) -> float:
49
+ return math.cosh(x)
50
+
51
+
52
+ @native_function(Op.Tanh)
53
+ def _tanh(x: float) -> float:
54
+ return math.tanh(x)
55
+
56
+
57
+ @native_function(Op.Floor)
58
+ def _floor(x: float) -> float:
59
+ return math.floor(x)
60
+
61
+
62
+ @native_function(Op.Ceil)
63
+ def _ceil(x: float) -> float:
64
+ return math.ceil(x)
65
+
66
+
67
+ @native_function(Op.Trunc)
68
+ def _trunc(x: float) -> float:
69
+ return math.trunc(x)
70
+
71
+
72
+ @native_function(Op.Round)
73
+ def __round(x: float) -> float:
74
+ return round(x)
75
+
76
+
77
+ def _round(x: float, n: int = 0) -> float:
78
+ if n == 0:
79
+ return __round(x)
80
+ return __round(x * 10**n) / 10**n
81
+
82
+
83
+ @native_function(Op.Frac)
84
+ def frac(x: float) -> float:
85
+ return x % 1
86
+
87
+
88
+ @native_function(Op.Log)
89
+ def _ln(x: float) -> float:
90
+ return math.log(x)
91
+
92
+
93
+ def _log(x: float, base: float | None = None) -> float:
94
+ if base is None:
95
+ return _ln(x)
96
+ return _ln(x) / _ln(base)
97
+
98
+
99
+ @native_function(Op.Rem)
100
+ def _remainder(x: float, y: float) -> float:
101
+ # This is different from math.remainder in Python's math package, which could be confusing
102
+ return math.copysign(abs(x) % abs(y), x)
103
+
104
+
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,
119
+ id(round): _round,
120
+ id(math.log): _log,
121
+ }