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.
- sonolus/backend/blocks.py +756 -756
- sonolus/backend/excepthook.py +37 -37
- sonolus/backend/finalize.py +77 -69
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +29 -3
- sonolus/backend/mode.py +24 -24
- sonolus/backend/node.py +40 -40
- sonolus/backend/ops.py +197 -197
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/optimize/allocate.py +126 -0
- sonolus/backend/optimize/constant_evaluation.py +374 -0
- sonolus/backend/optimize/copy_coalesce.py +85 -0
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/optimize/dominance.py +96 -0
- sonolus/backend/{flow.py → optimize/flow.py} +122 -92
- sonolus/backend/optimize/inlining.py +137 -0
- sonolus/backend/optimize/liveness.py +177 -0
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/optimize/passes.py +52 -0
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/optimize/ssa.py +200 -0
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +58 -48
- sonolus/backend/visitor.py +1151 -882
- sonolus/build/cli.py +7 -1
- sonolus/build/compile.py +88 -90
- sonolus/build/engine.py +10 -5
- sonolus/build/level.py +24 -23
- sonolus/build/node.py +43 -43
- sonolus/script/archetype.py +438 -139
- sonolus/script/array.py +27 -10
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +253 -191
- sonolus/script/containers.py +257 -51
- sonolus/script/debug.py +26 -10
- sonolus/script/easing.py +365 -0
- sonolus/script/effect.py +191 -131
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +303 -269
- sonolus/script/instruction.py +205 -151
- sonolus/script/internal/__init__.py +5 -5
- sonolus/script/internal/builtin_impls.py +255 -144
- sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +26 -9
- sonolus/script/internal/descriptor.py +17 -17
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +17 -14
- sonolus/script/internal/math_impls.py +121 -0
- sonolus/script/internal/native.py +40 -38
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/internal/value.py +3 -3
- sonolus/script/interval.py +338 -112
- sonolus/script/iterator.py +167 -214
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +80 -48
- sonolus/script/options.py +257 -191
- sonolus/script/particle.py +190 -157
- sonolus/script/pointer.py +30 -30
- sonolus/script/print.py +102 -81
- sonolus/script/project.py +8 -0
- sonolus/script/quad.py +263 -0
- sonolus/script/record.py +47 -16
- sonolus/script/runtime.py +52 -1
- sonolus/script/sprite.py +418 -333
- sonolus/script/text.py +409 -407
- sonolus/script/timing.py +114 -42
- sonolus/script/transform.py +332 -48
- sonolus/script/ui.py +216 -160
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +196 -78
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
- sonolus_py-0.1.5.dist-info/RECORD +89 -0
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
- sonolus/backend/allocate.py +0 -51
- sonolus/backend/optimize.py +0 -9
- sonolus/backend/passes.py +0 -6
- sonolus/backend/simplify.py +0 -30
- sonolus/script/comptime.py +0 -160
- sonolus/script/graphics.py +0 -150
- sonolus/script/math.py +0 -92
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.3.dist-info/RECORD +0 -75
- {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
|
|
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[
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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:
|
|
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
|
|
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
|
|
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.
|
|
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():
|
sonolus/script/internal/impl.py
CHANGED
|
@@ -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.
|
|
40
|
-
from sonolus.script.
|
|
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
|
|
53
|
-
return
|
|
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
|
|
73
|
+
return TupleImpl._accept_(value)
|
|
58
74
|
case dict():
|
|
59
|
-
return
|
|
60
|
-
case
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|