sonolus.py 0.1.4__py3-none-any.whl → 0.1.6__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 (79) 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/cli.py +14 -3
  21. sonolus/build/compile.py +8 -8
  22. sonolus/build/engine.py +10 -5
  23. sonolus/build/project.py +30 -1
  24. sonolus/script/archetype.py +429 -138
  25. sonolus/script/array.py +25 -8
  26. sonolus/script/array_like.py +297 -0
  27. sonolus/script/bucket.py +73 -11
  28. sonolus/script/containers.py +234 -51
  29. sonolus/script/debug.py +8 -8
  30. sonolus/script/easing.py +147 -105
  31. sonolus/script/effect.py +60 -0
  32. sonolus/script/engine.py +71 -4
  33. sonolus/script/globals.py +66 -32
  34. sonolus/script/instruction.py +79 -25
  35. sonolus/script/internal/builtin_impls.py +138 -27
  36. sonolus/script/internal/constant.py +139 -0
  37. sonolus/script/internal/context.py +14 -5
  38. sonolus/script/internal/dict_impl.py +65 -0
  39. sonolus/script/internal/generic.py +6 -9
  40. sonolus/script/internal/impl.py +38 -13
  41. sonolus/script/internal/introspection.py +5 -2
  42. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  43. sonolus/script/internal/native.py +3 -3
  44. sonolus/script/internal/random.py +67 -0
  45. sonolus/script/internal/range.py +81 -0
  46. sonolus/script/internal/transient.py +51 -0
  47. sonolus/script/internal/tuple_impl.py +113 -0
  48. sonolus/script/interval.py +234 -16
  49. sonolus/script/iterator.py +120 -167
  50. sonolus/script/level.py +24 -0
  51. sonolus/script/num.py +79 -47
  52. sonolus/script/options.py +78 -12
  53. sonolus/script/particle.py +37 -4
  54. sonolus/script/pointer.py +4 -4
  55. sonolus/script/print.py +22 -1
  56. sonolus/script/project.py +59 -0
  57. sonolus/script/{graphics.py → quad.py} +75 -12
  58. sonolus/script/record.py +44 -13
  59. sonolus/script/runtime.py +50 -1
  60. sonolus/script/sprite.py +198 -115
  61. sonolus/script/text.py +2 -0
  62. sonolus/script/timing.py +72 -0
  63. sonolus/script/transform.py +296 -66
  64. sonolus/script/ui.py +134 -78
  65. sonolus/script/values.py +6 -13
  66. sonolus/script/vec.py +118 -3
  67. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/METADATA +1 -1
  68. sonolus_py-0.1.6.dist-info/RECORD +89 -0
  69. sonolus/backend/dead_code.py +0 -80
  70. sonolus/backend/optimize.py +0 -37
  71. sonolus/backend/simplify.py +0 -47
  72. sonolus/script/comptime.py +0 -160
  73. sonolus/script/random.py +0 -14
  74. sonolus/script/range.py +0 -58
  75. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  76. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/WHEEL +0 -0
  78. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/entry_points.txt +0 -0
  79. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,18 +1,39 @@
1
+ from abc import ABC
1
2
  from collections.abc import Iterable
2
3
  from typing import overload
3
4
 
5
+ from sonolus.script.array_like import ArrayLike
4
6
  from sonolus.script.internal.context import ctx
7
+ from sonolus.script.internal.dict_impl import DictImpl
5
8
  from sonolus.script.internal.impl import meta_fn, validate_value
6
- from sonolus.script.iterator import ArrayLike, Enumerator, SonolusIterator
7
- from sonolus.script.math import MATH_BUILTIN_IMPLS
8
- from sonolus.script.num import is_num
9
- from sonolus.script.range import Range
9
+ from sonolus.script.internal.math_impls import MATH_BUILTIN_IMPLS, _trunc
10
+ from sonolus.script.internal.random import RANDOM_BUILTIN_IMPLS
11
+ from sonolus.script.internal.range import Range
12
+ from sonolus.script.internal.tuple_impl import TupleImpl
13
+ from sonolus.script.internal.value import Value
14
+ from sonolus.script.iterator import (
15
+ SonolusIterator,
16
+ _EmptyIterator,
17
+ _Enumerator,
18
+ _FilteringIterator,
19
+ _MappingIterator,
20
+ _Zipper,
21
+ )
22
+ from sonolus.script.num import Num, _is_num
10
23
 
11
24
 
12
25
  @meta_fn
13
26
  def _isinstance(value, type_):
14
27
  value = validate_value(value)
15
28
  type_ = validate_value(type_)._as_py_()
29
+ if type_ is dict:
30
+ return isinstance(value, DictImpl)
31
+ if type_ is tuple:
32
+ return isinstance(value, TupleImpl)
33
+ if type_ in {_int, _float, _bool}:
34
+ raise TypeError("Instance check against int, float, or bool is not supported, use Num instead")
35
+ if not (isinstance(type_, type) and issubclass(type_, Value | ABC)):
36
+ raise TypeError(f"Unsupported type: {type_} for isinstance")
16
37
  return validate_value(isinstance(value, type_))
17
38
 
18
39
 
@@ -34,12 +55,44 @@ def _enumerate(iterable, start=0):
34
55
  if not hasattr(iterable, "__iter__"):
35
56
  raise TypeError(f"'{type(iterable).__name__}' object is not iterable")
36
57
  if isinstance(iterable, ArrayLike):
37
- return compile_and_call(iterable.enumerate, start)
58
+ return compile_and_call(iterable._enumerate_, start)
38
59
  else:
39
60
  iterator = compile_and_call(iterable.__iter__)
40
61
  if not isinstance(iterator, SonolusIterator):
41
62
  raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
42
- return Enumerator(0, start, iterator)
63
+ return _Enumerator(0, start, iterator)
64
+
65
+
66
+ @meta_fn
67
+ def _reversed(iterable):
68
+ from sonolus.backend.visitor import compile_and_call
69
+
70
+ iterable = validate_value(iterable)
71
+ if not isinstance(iterable, ArrayLike):
72
+ raise TypeError(f"Unsupported type: {type(iterable)} for reversed")
73
+ return compile_and_call(iterable.__reversed__)
74
+
75
+
76
+ @meta_fn
77
+ def _zip(*iterables):
78
+ from sonolus.backend.visitor import compile_and_call
79
+ from sonolus.script.containers import Pair
80
+
81
+ if not iterables:
82
+ return _EmptyIterator()
83
+
84
+ iterables = [validate_value(iterable) for iterable in iterables]
85
+ if any(isinstance(iterable, TupleImpl) for iterable in iterables):
86
+ if not all(isinstance(iterable, TupleImpl) for iterable in iterables):
87
+ raise TypeError("Cannot mix tuples with other types in zip")
88
+ return TupleImpl._accept_(tuple(zip(*(iterable._as_py_() for iterable in iterables), strict=False)))
89
+ iterators = [compile_and_call(iterable.__iter__) for iterable in iterables]
90
+ if not all(isinstance(iterator, SonolusIterator) for iterator in iterators):
91
+ raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
92
+ v = iterators.pop()
93
+ while iterators:
94
+ v = Pair(iterators.pop(), v)
95
+ return _Zipper(v)
43
96
 
44
97
 
45
98
  @meta_fn
@@ -52,16 +105,20 @@ def _abs(value):
52
105
  return compile_and_call(value.__abs__)
53
106
 
54
107
 
108
+ def _identity(value):
109
+ return value
110
+
111
+
55
112
  @overload
56
- def _max[T](iterable: Iterable[T]) -> T: ...
113
+ def _max[T](iterable: Iterable[T], *, key: callable = ...) -> T: ...
57
114
 
58
115
 
59
116
  @overload
60
- def _max[T](a: T, b: T, *args: T) -> T: ...
117
+ def _max[T](a: T, b: T, *args: T, key: callable = ...) -> T: ...
61
118
 
62
119
 
63
120
  @meta_fn
64
- def _max(*args):
121
+ def _max(*args, key: callable = _identity):
65
122
  from sonolus.backend.visitor import compile_and_call
66
123
 
67
124
  args = tuple(validate_value(arg) for arg in args)
@@ -70,38 +127,38 @@ def _max(*args):
70
127
  elif len(args) == 1:
71
128
  (iterable,) = args
72
129
  if isinstance(iterable, ArrayLike):
73
- return iterable.max()
130
+ return compile_and_call(iterable._max_, key=key)
74
131
  else:
75
132
  raise TypeError(f"Unsupported type: {type(iterable)} for max")
76
133
  else:
77
- if not all(is_num(arg) for arg in args):
134
+ if not all(_is_num(arg) for arg in args):
78
135
  raise TypeError("Arguments to max must be numbers")
79
136
  if ctx():
80
- result = compile_and_call(_max2, args[0], args[1])
137
+ result = compile_and_call(_max2, args[0], args[1], key=key)
81
138
  for arg in args[2:]:
82
- result = compile_and_call(_max2, result, arg)
139
+ result = compile_and_call(_max2, result, arg, key=key)
83
140
  return result
84
141
  else:
85
142
  return max(arg._as_py_() for arg in args)
86
143
 
87
144
 
88
- def _max2(a, b):
89
- if a > b:
145
+ def _max2(a, b, key=_identity):
146
+ if key(a) > key(b):
90
147
  return a
91
148
  else:
92
149
  return b
93
150
 
94
151
 
95
152
  @overload
96
- def _min[T](iterable: Iterable[T]) -> T: ...
153
+ def _min[T](iterable: Iterable[T], *, key: callable = ...) -> T: ...
97
154
 
98
155
 
99
156
  @overload
100
- def _min[T](a: T, b: T, *args: T) -> T: ...
157
+ def _min[T](a: T, b: T, *args: T, key: callable = ...) -> T: ...
101
158
 
102
159
 
103
160
  @meta_fn
104
- def _min(*args):
161
+ def _min(*args, key: callable = _identity):
105
162
  from sonolus.backend.visitor import compile_and_call
106
163
 
107
164
  args = tuple(validate_value(arg) for arg in args)
@@ -110,35 +167,89 @@ def _min(*args):
110
167
  elif len(args) == 1:
111
168
  (iterable,) = args
112
169
  if isinstance(iterable, ArrayLike):
113
- return iterable.min()
170
+ return compile_and_call(iterable._min_, key=key)
114
171
  else:
115
172
  raise TypeError(f"Unsupported type: {type(iterable)} for min")
116
173
  else:
117
- if not all(is_num(arg) for arg in args):
174
+ if not all(_is_num(arg) for arg in args):
118
175
  raise TypeError("Arguments to min must be numbers")
119
176
  if ctx():
120
- result = compile_and_call(_min2, args[0], args[1])
177
+ result = compile_and_call(_min2, args[0], args[1], key=key)
121
178
  for arg in args[2:]:
122
- result = compile_and_call(_min2, result, arg)
179
+ result = compile_and_call(_min2, result, arg, key=key)
123
180
  return result
124
181
  else:
125
182
  return min(arg._as_py_() for arg in args)
126
183
 
127
184
 
128
- def _min2(a, b):
129
- if a < b:
185
+ def _min2(a, b, key=_identity):
186
+ if key(a) < key(b):
130
187
  return a
131
188
  else:
132
189
  return b
133
190
 
134
191
 
192
+ @meta_fn
193
+ def _callable(value):
194
+ return callable(value)
195
+
196
+
197
+ def _map(fn, iterable, *iterables):
198
+ return _MappingIterator(lambda args: fn(*args), zip(iterable, *iterables)) # noqa: B905
199
+
200
+
201
+ def _filter(fn, iterable):
202
+ if fn is None:
203
+ fn = _identity
204
+ return _FilteringIterator(fn, iterable.__iter__()) # noqa: PLC2801
205
+
206
+
207
+ @meta_fn
208
+ def _int(value=0):
209
+ value = validate_value(value)
210
+ if not _is_num(value):
211
+ raise TypeError("Only numeric arguments to int() are supported")
212
+ return _trunc(value)
213
+
214
+
215
+ @meta_fn
216
+ def _float(value=0.0):
217
+ value = validate_value(value)
218
+ if not _is_num(value):
219
+ raise TypeError("Only numeric arguments to float() are supported")
220
+ return value
221
+
222
+
223
+ @meta_fn
224
+ def _bool(value=False):
225
+ value = validate_value(value)
226
+ if not _is_num(value):
227
+ raise TypeError("Only numeric arguments to bool() are supported")
228
+ return value != 0
229
+
230
+
231
+ _int._type_mapping_ = Num
232
+ _float._type_mapping_ = Num
233
+ _bool._type_mapping_ = Num
234
+
235
+ # classmethod, property, staticmethod are supported as decorators, but not within functions
236
+
135
237
  BUILTIN_IMPLS = {
238
+ id(abs): _abs,
239
+ id(bool): _bool,
240
+ id(callable): _callable,
241
+ id(enumerate): _enumerate,
242
+ id(filter): _filter,
243
+ id(float): _float,
244
+ id(int): _int,
136
245
  id(isinstance): _isinstance,
137
246
  id(len): _len,
138
- id(enumerate): _enumerate,
139
- id(abs): _abs,
247
+ id(map): _map,
140
248
  id(max): _max,
141
249
  id(min): _min,
142
250
  id(range): Range,
143
- **MATH_BUILTIN_IMPLS,
251
+ id(reversed): _reversed,
252
+ id(zip): _zip,
253
+ **MATH_BUILTIN_IMPLS, # Includes round
254
+ **RANDOM_BUILTIN_IMPLS,
144
255
  }
@@ -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):
@@ -191,7 +200,7 @@ class Context:
191
200
  self.const_mappings[value] = len(self.const_mappings)
192
201
  return self.const_mappings[value]
193
202
 
194
- def get_global_base(self, value: GlobalInfo | GlobalPlaceholder) -> BlockPlace:
203
+ def get_global_base(self, value: _GlobalInfo | _GlobalPlaceholder) -> BlockPlace:
195
204
  block = value.blocks.get(self.global_state.mode)
196
205
  if block is None:
197
206
  raise RuntimeError(f"Global {value.name} is not available in '{self.global_state.mode.name}' mode")
@@ -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