sonolus.py 0.7.1__py3-none-any.whl → 0.9.0__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/build/engine.py CHANGED
@@ -9,7 +9,7 @@ from dataclasses import dataclass
9
9
  from pathlib import Path
10
10
 
11
11
  from sonolus.backend.mode import Mode
12
- from sonolus.build.compile import compile_mode
12
+ from sonolus.build.compile import CompileCache, compile_mode
13
13
  from sonolus.script.archetype import _BaseArchetype
14
14
  from sonolus.script.bucket import Buckets
15
15
  from sonolus.script.effect import Effects
@@ -68,7 +68,11 @@ def no_gil() -> bool:
68
68
  def package_engine(
69
69
  engine: EngineData,
70
70
  config: BuildConfig | None = None,
71
+ cache: CompileCache | None = None,
71
72
  ):
73
+ if cache is None:
74
+ cache = CompileCache()
75
+
72
76
  config = config or BuildConfig()
73
77
  rom = ReadOnlyMemory()
74
78
  configuration = build_engine_configuration(engine.options, engine.ui)
@@ -76,7 +80,8 @@ def package_engine(
76
80
  # process_cpu_count is available in Python 3.13+
77
81
  from os import process_cpu_count
78
82
 
79
- thread_pool = ThreadPoolExecutor(process_cpu_count() or 1)
83
+ # Need a worker for each mode (so +4) plus at least one to do work
84
+ thread_pool = ThreadPoolExecutor(4 + max(1, min(8, (process_cpu_count() or 1))))
80
85
  else:
81
86
  thread_pool = None
82
87
 
@@ -97,6 +102,7 @@ def package_engine(
97
102
  rom=rom,
98
103
  config=config,
99
104
  thread_pool=thread_pool,
105
+ cache=cache,
100
106
  ),
101
107
  "watch": thread_pool.submit(
102
108
  build_watch_mode,
@@ -109,6 +115,7 @@ def package_engine(
109
115
  update_spawn=watch_mode.update_spawn,
110
116
  config=config,
111
117
  thread_pool=thread_pool,
118
+ cache=cache,
112
119
  ),
113
120
  "preview": thread_pool.submit(
114
121
  build_preview_mode,
@@ -117,6 +124,7 @@ def package_engine(
117
124
  rom=rom,
118
125
  config=config,
119
126
  thread_pool=thread_pool,
127
+ cache=cache,
120
128
  ),
121
129
  "tutorial": thread_pool.submit(
122
130
  build_tutorial_mode,
@@ -131,6 +139,7 @@ def package_engine(
131
139
  rom=rom,
132
140
  config=config,
133
141
  thread_pool=thread_pool,
142
+ cache=cache,
134
143
  ),
135
144
  }
136
145
 
@@ -148,6 +157,7 @@ def package_engine(
148
157
  rom=rom,
149
158
  config=config,
150
159
  thread_pool=None,
160
+ cache=cache,
151
161
  )
152
162
  watch_data = build_watch_mode(
153
163
  archetypes=watch_mode.archetypes,
@@ -159,6 +169,7 @@ def package_engine(
159
169
  update_spawn=watch_mode.update_spawn,
160
170
  config=config,
161
171
  thread_pool=None,
172
+ cache=cache,
162
173
  )
163
174
  preview_data = build_preview_mode(
164
175
  archetypes=preview_mode.archetypes,
@@ -166,6 +177,7 @@ def package_engine(
166
177
  rom=rom,
167
178
  config=config,
168
179
  thread_pool=None,
180
+ cache=cache,
169
181
  )
170
182
  tutorial_data = build_tutorial_mode(
171
183
  skin=tutorial_mode.skin,
@@ -179,6 +191,7 @@ def package_engine(
179
191
  rom=rom,
180
192
  config=config,
181
193
  thread_pool=None,
194
+ cache=cache,
182
195
  )
183
196
 
184
197
  return PackagedEngine(
@@ -270,6 +283,7 @@ def build_play_mode(
270
283
  config: BuildConfig,
271
284
  thread_pool: Executor | None = None,
272
285
  validate_only: bool = False,
286
+ cache: CompileCache | None = None,
273
287
  ):
274
288
  return {
275
289
  **compile_mode(
@@ -280,6 +294,7 @@ def build_play_mode(
280
294
  passes=config.passes,
281
295
  thread_pool=thread_pool,
282
296
  validate_only=validate_only,
297
+ cache=cache,
283
298
  ),
284
299
  "skin": build_skin(skin),
285
300
  "effect": build_effects(effects),
@@ -299,6 +314,7 @@ def build_watch_mode(
299
314
  config: BuildConfig,
300
315
  thread_pool: Executor | None = None,
301
316
  validate_only: bool = False,
317
+ cache: CompileCache | None = None,
302
318
  ):
303
319
  return {
304
320
  **compile_mode(
@@ -309,6 +325,7 @@ def build_watch_mode(
309
325
  passes=config.passes,
310
326
  thread_pool=thread_pool,
311
327
  validate_only=validate_only,
328
+ cache=cache,
312
329
  ),
313
330
  "skin": build_skin(skin),
314
331
  "effect": build_effects(effects),
@@ -324,6 +341,7 @@ def build_preview_mode(
324
341
  config: BuildConfig,
325
342
  thread_pool: Executor | None = None,
326
343
  validate_only: bool = False,
344
+ cache: CompileCache | None = None,
327
345
  ):
328
346
  return {
329
347
  **compile_mode(
@@ -334,6 +352,7 @@ def build_preview_mode(
334
352
  passes=config.passes,
335
353
  thread_pool=thread_pool,
336
354
  validate_only=validate_only,
355
+ cache=cache,
337
356
  ),
338
357
  "skin": build_skin(skin),
339
358
  }
@@ -352,6 +371,7 @@ def build_tutorial_mode(
352
371
  config: BuildConfig,
353
372
  thread_pool: Executor | None = None,
354
373
  validate_only: bool = False,
374
+ cache: CompileCache | None = None,
355
375
  ):
356
376
  return {
357
377
  **compile_mode(
@@ -366,6 +386,7 @@ def build_tutorial_mode(
366
386
  passes=config.passes,
367
387
  thread_pool=thread_pool,
368
388
  validate_only=validate_only,
389
+ cache=cache,
369
390
  ),
370
391
  "skin": build_skin(skin),
371
392
  "effect": build_effects(effects),
sonolus/build/project.py CHANGED
@@ -3,6 +3,7 @@ from pathlib import Path
3
3
  from typing import cast
4
4
 
5
5
  from sonolus.build.collection import Asset, Collection, Srl
6
+ from sonolus.build.compile import CompileCache
6
7
  from sonolus.build.engine import package_engine, unpackage_data
7
8
  from sonolus.build.level import package_level_data
8
9
  from sonolus.script.engine import Engine
@@ -19,7 +20,9 @@ BLANK_AUDIO = (
19
20
  )
20
21
 
21
22
 
22
- def build_project_to_collection(project: Project, config: BuildConfig | None):
23
+ def build_project_to_collection(
24
+ project: Project, config: BuildConfig | None, cache: CompileCache | None = None
25
+ ) -> Collection:
23
26
  collection = load_resources_files_to_collection(project.resources)
24
27
  for src_engine, converter in project.converters.items():
25
28
  if src_engine is None:
@@ -31,7 +34,7 @@ def build_project_to_collection(project: Project, config: BuildConfig | None):
31
34
  if config.override_resource_level_engines:
32
35
  for level in collection.categories.get("levels", {}).values():
33
36
  level["item"]["engine"] = project.engine.name
34
- add_engine_to_collection(collection, project, project.engine, config)
37
+ add_engine_to_collection(collection, project, project.engine, config, cache=cache)
35
38
  for level in project.levels:
36
39
  add_level_to_collection(collection, project, level)
37
40
  collection.name = f"{project.engine.name}"
@@ -63,8 +66,14 @@ def apply_converter_to_collection(
63
66
  level["data"] = new_data_srl
64
67
 
65
68
 
66
- def add_engine_to_collection(collection: Collection, project: Project, engine: Engine, config: BuildConfig | None):
67
- packaged_engine = package_engine(engine.data, config)
69
+ def add_engine_to_collection(
70
+ collection: Collection,
71
+ project: Project,
72
+ engine: Engine,
73
+ config: BuildConfig | None,
74
+ cache: CompileCache | None = None,
75
+ ):
76
+ packaged_engine = package_engine(engine.data, config, cache=cache)
68
77
  item = {
69
78
  "name": engine.name,
70
79
  "version": engine.version,
sonolus/script/array.py CHANGED
@@ -11,7 +11,7 @@ from sonolus.script.debug import assert_unreachable
11
11
  from sonolus.script.internal.context import ctx
12
12
  from sonolus.script.internal.error import InternalError
13
13
  from sonolus.script.internal.generic import GenericValue
14
- from sonolus.script.internal.impl import meta_fn, validate_value
14
+ from sonolus.script.internal.impl import meta_fn, perf_meta_fn, validate_value
15
15
  from sonolus.script.internal.value import BackingSource, DataValue, Value
16
16
  from sonolus.script.num import Num
17
17
 
@@ -188,6 +188,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
188
188
  else:
189
189
  return cls._with_value([cls.element_type()._zero_() for _ in range(cls.size())])
190
190
 
191
+ @perf_meta_fn
191
192
  def __len__(self):
192
193
  return self.size()
193
194
 
sonolus/script/bucket.py CHANGED
@@ -7,7 +7,7 @@ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
7
7
  from sonolus.backend.mode import Mode
8
8
  from sonolus.backend.ops import Op
9
9
  from sonolus.script.internal.context import ctx
10
- from sonolus.script.internal.impl import meta_fn
10
+ from sonolus.script.internal.impl import meta_fn, perf_meta_fn
11
11
  from sonolus.script.internal.introspection import get_field_specifiers
12
12
  from sonolus.script.internal.native import native_function
13
13
  from sonolus.script.interval import Interval
@@ -72,20 +72,22 @@ class JudgmentWindow(Record):
72
72
  *self.good.tuple,
73
73
  )
74
74
 
75
+ @perf_meta_fn
75
76
  def __mul__(self, other: float | int) -> JudgmentWindow:
76
77
  """Multiply the intervals by a scalar."""
77
- return JudgmentWindow(
78
- self.perfect * other,
79
- self.great * other,
80
- self.good * other,
78
+ return JudgmentWindow._quick_construct(
79
+ perfect=self.perfect * other,
80
+ great=self.great * other,
81
+ good=self.good * other,
81
82
  )
82
83
 
84
+ @perf_meta_fn
83
85
  def __add__(self, other: float | int) -> JudgmentWindow:
84
86
  """Add a scalar to the intervals."""
85
- return JudgmentWindow(
86
- self.perfect + other,
87
- self.great + other,
88
- self.good + other,
87
+ return JudgmentWindow._quick_construct(
88
+ perfect=self.perfect + other,
89
+ great=self.great + other,
90
+ good=self.good + other,
89
91
  )
90
92
 
91
93
  @property
sonolus/script/debug.py CHANGED
@@ -68,15 +68,44 @@ def debug_pause():
68
68
  input("[DEBUG] Paused")
69
69
 
70
70
 
71
+ @meta_fn
71
72
  def assert_true(value: int | float | bool, message: str | None = None):
72
- message = message if message is not None else "Assertion failed"
73
- if not value:
74
- error(message)
73
+ if not ctx():
74
+ if not value:
75
+ raise AssertionError(message if message is not None else "Assertion failed")
76
+ return
77
+ value = Num._accept_(validate_value(value))
78
+ message = validate_value(message)
79
+ message = message._as_py_() or "Assertion failed"
80
+ if value._is_py_():
81
+ if value._as_py_():
82
+ return
83
+ else:
84
+ error(message)
85
+ else:
86
+ ctx().test = value.ir()
87
+ t_branch = ctx().branch(None)
88
+ f_branch = ctx().branch(0)
89
+ set_ctx(f_branch)
90
+ error(message) # type: ignore
91
+ set_ctx(t_branch)
75
92
 
76
93
 
77
94
  def assert_false(value: int | float | bool, message: str | None = None):
78
- message = message if message is not None else "Assertion failed"
79
- if value:
95
+ assert_true(not value, message)
96
+
97
+
98
+ def static_assert(value: int | float | bool, message: str | None = None):
99
+ message = message if message is not None else "Static assertion failed"
100
+ if not _is_static_true(value):
101
+ static_error(message)
102
+
103
+
104
+ def try_static_assert(value: int | float | bool, message: str | None = None):
105
+ message = message if message is not None else "Static assertion failed"
106
+ if _is_static_false(value):
107
+ static_error(message)
108
+ if not value:
80
109
  error(message)
81
110
 
82
111
 
@@ -140,3 +169,21 @@ def visualize_cfg(
140
169
 
141
170
  def simulation_context() -> SimulationContext:
142
171
  return SimulationContext()
172
+
173
+
174
+ @meta_fn
175
+ def _is_static_true(value: int | float | bool) -> bool:
176
+ if ctx() is None:
177
+ return bool(value)
178
+ else:
179
+ value = validate_value(value)
180
+ return value._is_py_() and value._as_py_()
181
+
182
+
183
+ @meta_fn
184
+ def _is_static_false(value: int | float | bool) -> bool:
185
+ if ctx() is None:
186
+ return not bool(value)
187
+ else:
188
+ value = validate_value(value)
189
+ return value._is_py_() and not value._as_py_()
sonolus/script/effect.py CHANGED
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Iterable
3
4
  from dataclasses import dataclass
4
5
  from typing import Annotated, Any, NewType, dataclass_transform, get_origin
5
6
 
6
7
  from sonolus.backend.ops import Op
8
+ from sonolus.script.array_like import ArrayLike
9
+ from sonolus.script.debug import static_error
7
10
  from sonolus.script.internal.introspection import get_field_specifiers
8
11
  from sonolus.script.internal.native import native_function
9
12
  from sonolus.script.record import Record
@@ -92,6 +95,29 @@ class ScheduledLoopedEffectHandle(Record):
92
95
  _stop_looped_scheduled(self.id, end_time)
93
96
 
94
97
 
98
+ class EffectGroup(Record, ArrayLike[Effect]):
99
+ """A group of effect clips.
100
+
101
+ Usage:
102
+ ```python
103
+ EffectGroup(start_id: int, size: int)
104
+ ```
105
+ """
106
+
107
+ start_id: int
108
+ size: int
109
+
110
+ def __len__(self) -> int:
111
+ return self.size
112
+
113
+ def __getitem__(self, index: int) -> Effect:
114
+ assert 0 <= index < self.size
115
+ return Effect(self.start_id + index)
116
+
117
+ def __setitem__(self, index: int, value: Effect) -> None:
118
+ static_error("EffectGroup is read-only")
119
+
120
+
95
121
  @native_function(Op.HasEffectClip)
96
122
  def _has_effect_clip(effect_id: int) -> bool:
97
123
  raise NotImplementedError
@@ -132,11 +158,21 @@ class EffectInfo:
132
158
  name: str
133
159
 
134
160
 
161
+ @dataclass
162
+ class EffectGroupInfo:
163
+ names: list[str]
164
+
165
+
135
166
  def effect(name: str) -> Any:
136
167
  """Define a sound effect clip with the given name."""
137
168
  return EffectInfo(name)
138
169
 
139
170
 
171
+ def effect_group(names: Iterable[str]) -> Any:
172
+ """Define an effect group with the given names."""
173
+ return EffectGroupInfo(list(names))
174
+
175
+
140
176
  type Effects = NewType("Effects", Any) # type: ignore
141
177
 
142
178
 
@@ -150,24 +186,45 @@ def effects[T](cls: type[T]) -> T | Effects:
150
186
  class Effects:
151
187
  miss: StandardEffect.MISS
152
188
  other: Effect = effect("other")
189
+ group_1: EffectGroup = effect_group(["one", "two", "three"])
190
+ group_2: EffectGroup = effect_group(f"name_{i}" for i in range(10))
153
191
  ```
154
192
  """
155
193
  if len(cls.__bases__) != 1:
156
194
  raise ValueError("Effects class must not inherit from any class (except object)")
157
195
  instance = cls()
158
196
  names = []
159
- for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
197
+ i = 0
198
+ for name, annotation in get_field_specifiers(cls).items():
160
199
  if get_origin(annotation) is not Annotated:
161
- raise TypeError(f"Invalid annotation for effects: {annotation}")
200
+ raise TypeError(f"Invalid annotation for effects: {annotation} on field {name}")
162
201
  annotation_type = annotation.__args__[0]
163
202
  annotation_values = annotation.__metadata__
164
- if annotation_type is not Effect:
165
- raise TypeError(f"Invalid annotation for effects: {annotation}, expected annotation of type Effect")
166
- if len(annotation_values) != 1 or not isinstance(annotation_values[0], EffectInfo):
167
- raise TypeError(f"Invalid annotation for effects: {annotation}, expected a single string annotation value")
168
- effect_name = annotation_values[0].name
169
- names.append(effect_name)
170
- setattr(instance, name, Effect(i))
203
+ if len(annotation_values) != 1:
204
+ raise TypeError(f"Invalid annotation for effects: {annotation} on field {name}, too many annotation values")
205
+ effect_info = annotation_values[0]
206
+ match effect_info:
207
+ case EffectInfo(name=effect_name):
208
+ if annotation_type is not Effect:
209
+ raise TypeError(f"Invalid annotation for effects: {annotation} on field {name}, expected Effect")
210
+ names.append(effect_name)
211
+ setattr(instance, name, Effect(i))
212
+ i += 1
213
+ case EffectGroupInfo(names=effect_names):
214
+ if annotation_type is not EffectGroup:
215
+ raise TypeError(
216
+ f"Invalid annotation for effects: {annotation} on field {name}, expected EffectGroup"
217
+ )
218
+ start_id = i
219
+ count = len(effect_names)
220
+ names.extend(effect_names)
221
+ setattr(instance, name, EffectGroup(start_id, count))
222
+ i += count
223
+ case _:
224
+ raise TypeError(
225
+ f"Invalid annotation for effects: {annotation} on field {name}, unknown effect info, "
226
+ f"expected an effect() or effect_group() specifier"
227
+ )
171
228
  instance._effects_ = names
172
229
  instance._is_comptime_value_ = True
173
230
  return instance
@@ -33,6 +33,10 @@ def meta_fn(fn=None):
33
33
  return decorator(fn)
34
34
 
35
35
 
36
+ # To indicate this was used for performance reasons rather than functionality
37
+ perf_meta_fn = meta_fn
38
+
39
+
36
40
  def validate_value[T](value: T) -> Value | T:
37
41
  result = try_validate_value(value)
38
42
  if result is None:
@@ -59,16 +63,16 @@ def try_validate_value(value: Any) -> Value | None:
59
63
  pass
60
64
 
61
65
  match value:
62
- case Enum():
63
- return validate_value(value.value)
64
66
  case Value():
65
67
  return value
68
+ case int() | float() | bool():
69
+ return Num._accept_(value)
70
+ case Enum():
71
+ return validate_value(value.value)
66
72
  case type():
67
73
  if value in {int, float, bool}:
68
74
  return BasicConstantValue.of(Num)
69
75
  return BasicConstantValue.of(value)
70
- case int() | float() | bool():
71
- return Num._accept_(value)
72
76
  case tuple():
73
77
  return TupleImpl._accept_(value)
74
78
  case dict():
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  from sonolus.backend.ops import Op
4
4
  from sonolus.script.array_like import ArrayLike
5
5
  from sonolus.script.debug import static_error
6
+ from sonolus.script.internal.builtin_impls import _max, _min
7
+ from sonolus.script.internal.impl import perf_meta_fn
6
8
  from sonolus.script.internal.native import native_function
7
9
  from sonolus.script.internal.range import range_or_tuple
8
10
  from sonolus.script.num import Num
@@ -66,6 +68,7 @@ class Interval(Record):
66
68
  case _:
67
69
  static_error("Invalid type for interval check")
68
70
 
71
+ @perf_meta_fn
69
72
  def __add__(self, other: float | int) -> Interval:
70
73
  """Add a value to both ends of the interval.
71
74
 
@@ -75,8 +78,9 @@ class Interval(Record):
75
78
  Returns:
76
79
  A new interval with the value added to both ends.
77
80
  """
78
- return Interval(self.start + other, self.end + other)
81
+ return Interval._quick_construct(start=self.start + other, end=self.end + other)
79
82
 
83
+ @perf_meta_fn
80
84
  def __sub__(self, other: float | int) -> Interval:
81
85
  """Subtract a value from both ends of the interval.
82
86
 
@@ -86,8 +90,9 @@ class Interval(Record):
86
90
  Returns:
87
91
  A new interval with the value subtracted from both ends.
88
92
  """
89
- return Interval(self.start - other, self.end - other)
93
+ return Interval._quick_construct(start=self.start - other, end=self.end - other)
90
94
 
95
+ @perf_meta_fn
91
96
  def __mul__(self, other: float | int) -> Interval:
92
97
  """Multiply both ends of the interval by a value.
93
98
 
@@ -97,8 +102,9 @@ class Interval(Record):
97
102
  Returns:
98
103
  A new interval with both ends multiplied by the value.
99
104
  """
100
- return Interval(self.start * other, self.end * other)
105
+ return Interval._quick_construct(start=self.start * other, end=self.end * other)
101
106
 
107
+ @perf_meta_fn
102
108
  def __truediv__(self, other: float | int) -> Interval:
103
109
  """Divide both ends of the interval by a value.
104
110
 
@@ -108,8 +114,9 @@ class Interval(Record):
108
114
  Returns:
109
115
  A new interval with both ends divided by the value.
110
116
  """
111
- return Interval(self.start / other, self.end / other)
117
+ return Interval._quick_construct(start=self.start / other, end=self.end / other)
112
118
 
119
+ @perf_meta_fn
113
120
  def __floordiv__(self, other: float | int) -> Interval:
114
121
  """Divide both ends of the interval by a value and floor the result.
115
122
 
@@ -119,7 +126,7 @@ class Interval(Record):
119
126
  Returns:
120
127
  A new interval with both ends divided by the value and floored.
121
128
  """
122
- return Interval(self.start // other, self.end // other)
129
+ return Interval._quick_construct(start=self.start // other, end=self.end // other)
123
130
 
124
131
  def __and__(self, other: Interval) -> Interval:
125
132
  """Get the intersection of two intervals.
@@ -222,14 +229,17 @@ def _num_lerp_clamped(a, b, x, /):
222
229
  return a + (b - a) * max(0, min(1, x))
223
230
 
224
231
 
232
+ @perf_meta_fn
225
233
  def _generic_lerp[T](a: T, b: T, x: float, /) -> T:
226
234
  return a + (b - a) * x # type: ignore
227
235
 
228
236
 
237
+ @perf_meta_fn
229
238
  def _generic_lerp_clamped[T](a: T, b: T, x: float, /) -> T:
230
- return a + (b - a) * max(0, min(1, x)) # type: ignore
239
+ return a + (b - a) * _max(0, _min(1, x)) # type: ignore
231
240
 
232
241
 
242
+ @perf_meta_fn
233
243
  def lerp[T](a: T, b: T, x: float, /) -> T:
234
244
  """Linearly interpolate between two values.
235
245
 
@@ -248,6 +258,7 @@ def lerp[T](a: T, b: T, x: float, /) -> T:
248
258
  return _generic_lerp(a, b, x)
249
259
 
250
260
 
261
+ @perf_meta_fn
251
262
  def lerp_clamped[T](a: T, b: T, x: float, /) -> T:
252
263
  """Linearly interpolate between two values, clamped to the interval.
253
264
 
sonolus/script/num.py CHANGED
@@ -27,6 +27,8 @@ def _is_num(value: Any) -> TypeGuard[Num]:
27
27
 
28
28
  @final
29
29
  class _Num(Value, metaclass=_NumMeta):
30
+ __slots__ = ("data",)
31
+
30
32
  # This works for ints, floats, and bools
31
33
  # Since we don't support complex numbers, real is equal to the original number
32
34
  __match_args__ = ("real",)
@@ -34,16 +36,10 @@ class _Num(Value, metaclass=_NumMeta):
34
36
  data: DataValue
35
37
 
36
38
  def __init__(self, data: DataValue | IRExpr):
37
- if isinstance(data, complex):
38
- raise TypeError("Cannot create a Num from a complex number")
39
39
  if isinstance(data, int):
40
40
  data = float(data)
41
41
  if isinstance(data, IRConst | IRPureInstr | IRGet):
42
42
  data = ExprBackingValue(data)
43
- if _is_num(data):
44
- raise InternalError("Cannot create a Num from a Num")
45
- if not isinstance(data, BlockPlace | BackingValue | float | int | bool):
46
- raise TypeError(f"Cannot create a Num from {type(data)}")
47
43
  self.data = data
48
44
 
49
45
  def __str__(self) -> str:
@@ -131,6 +127,8 @@ class _Num(Value, metaclass=_NumMeta):
131
127
  if isinstance(self.data.block, BlockData) and not ctx().is_writable(self.data):
132
128
  # This block is immutable in the current callback, so no need to copy it in case it changes.
133
129
  return Num(self.data)
130
+ if isinstance(self.data, int | float | bool):
131
+ return Num(self.data)
134
132
  place = ctx().alloc(size=1)
135
133
  ctx().add_statements(IRSet(place, self.ir()))
136
134
  return Num(place)
@@ -449,6 +447,12 @@ class _Num(Value, metaclass=_NumMeta):
449
447
  return 0
450
448
 
451
449
 
450
+ def _create_num_raw(i: int) -> Num:
451
+ result = object.__new__(_Num)
452
+ result.data = float(i)
453
+ return result
454
+
455
+
452
456
  if TYPE_CHECKING:
453
457
  from typing import Protocol
454
458