sonolus.py 0.1.9__py3-none-any.whl → 0.2.1__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/optimize/constant_evaluation.py +2 -2
- sonolus/backend/optimize/optimize.py +12 -4
- sonolus/backend/optimize/passes.py +2 -1
- sonolus/backend/place.py +95 -14
- sonolus/backend/visitor.py +51 -3
- sonolus/build/cli.py +60 -9
- sonolus/build/collection.py +68 -26
- sonolus/build/compile.py +85 -27
- sonolus/build/engine.py +166 -40
- sonolus/build/node.py +8 -1
- sonolus/build/project.py +30 -11
- sonolus/script/archetype.py +154 -32
- sonolus/script/array.py +14 -3
- sonolus/script/array_like.py +6 -2
- sonolus/script/containers.py +166 -0
- sonolus/script/debug.py +22 -4
- sonolus/script/effect.py +2 -2
- sonolus/script/engine.py +125 -17
- sonolus/script/internal/builtin_impls.py +21 -2
- sonolus/script/internal/constant.py +8 -4
- sonolus/script/internal/context.py +30 -25
- sonolus/script/internal/math_impls.py +2 -1
- sonolus/script/internal/transient.py +7 -3
- sonolus/script/internal/value.py +33 -11
- sonolus/script/interval.py +8 -3
- sonolus/script/iterator.py +17 -0
- sonolus/script/level.py +113 -10
- sonolus/script/metadata.py +32 -0
- sonolus/script/num.py +35 -15
- sonolus/script/options.py +23 -6
- sonolus/script/pointer.py +11 -1
- sonolus/script/project.py +41 -5
- sonolus/script/quad.py +55 -1
- sonolus/script/record.py +10 -5
- sonolus/script/runtime.py +78 -16
- sonolus/script/sprite.py +18 -1
- sonolus/script/text.py +9 -0
- sonolus/script/ui.py +20 -7
- sonolus/script/values.py +8 -5
- sonolus/script/vec.py +28 -0
- sonolus_py-0.2.1.dist-info/METADATA +10 -0
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.1.dist-info}/RECORD +46 -45
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.1.dist-info}/WHEEL +1 -1
- sonolus_py-0.1.9.dist-info/METADATA +0 -9
- /sonolus/script/{print.py → printing.py} +0 -0
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.1.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.1.dist-info}/licenses/LICENSE +0 -0
sonolus/script/engine.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from collections.abc import Callable
|
|
4
|
-
from
|
|
5
|
+
from os import PathLike
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Literal
|
|
5
8
|
|
|
6
|
-
from sonolus.build.collection import Asset
|
|
9
|
+
from sonolus.build.collection import Asset, load_asset
|
|
7
10
|
from sonolus.script.archetype import PlayArchetype, PreviewArchetype, WatchArchetype, _BaseArchetype
|
|
8
11
|
from sonolus.script.bucket import Buckets, EmptyBuckets
|
|
9
12
|
from sonolus.script.effect import Effects, EmptyEffects
|
|
@@ -13,12 +16,52 @@ from sonolus.script.instruction import (
|
|
|
13
16
|
TutorialInstructionIcons,
|
|
14
17
|
TutorialInstructions,
|
|
15
18
|
)
|
|
19
|
+
from sonolus.script.metadata import AnyText, Tag, as_localization_text
|
|
16
20
|
from sonolus.script.options import EmptyOptions, Options
|
|
17
21
|
from sonolus.script.particle import EmptyParticles, Particles
|
|
18
22
|
from sonolus.script.sprite import EmptySkin, Skin
|
|
19
23
|
from sonolus.script.ui import UiConfig
|
|
20
24
|
|
|
21
25
|
|
|
26
|
+
class ExportedEngine:
|
|
27
|
+
"""An exported Sonolus.py engine."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
item: dict,
|
|
33
|
+
thumbnail: bytes,
|
|
34
|
+
play_data: bytes,
|
|
35
|
+
watch_data: bytes,
|
|
36
|
+
preview_data: bytes,
|
|
37
|
+
tutorial_data: bytes,
|
|
38
|
+
rom: bytes | None = None,
|
|
39
|
+
configuration: bytes,
|
|
40
|
+
) -> None:
|
|
41
|
+
self.item = item
|
|
42
|
+
self.thumbnail = thumbnail
|
|
43
|
+
self.play_data = play_data
|
|
44
|
+
self.watch_data = watch_data
|
|
45
|
+
self.preview_data = preview_data
|
|
46
|
+
self.tutorial_data = tutorial_data
|
|
47
|
+
self.rom = rom
|
|
48
|
+
self.configuration = configuration
|
|
49
|
+
|
|
50
|
+
def write_to_dir(self, path: PathLike):
|
|
51
|
+
"""Write the exported engine to a directory."""
|
|
52
|
+
path = Path(path)
|
|
53
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
(path / "item.json").write_text(json.dumps(self.item, ensure_ascii=False), encoding="utf-8")
|
|
55
|
+
(path / "thumbnail").write_bytes(self.thumbnail)
|
|
56
|
+
(path / "playData").write_bytes(self.play_data)
|
|
57
|
+
(path / "watchData").write_bytes(self.watch_data)
|
|
58
|
+
(path / "previewData").write_bytes(self.preview_data)
|
|
59
|
+
(path / "tutorialData").write_bytes(self.tutorial_data)
|
|
60
|
+
if self.rom is not None:
|
|
61
|
+
(path / "rom").write_bytes(self.rom)
|
|
62
|
+
(path / "configuration").write_bytes(self.configuration)
|
|
63
|
+
|
|
64
|
+
|
|
22
65
|
class Engine:
|
|
23
66
|
"""A Sonolus.py engine.
|
|
24
67
|
|
|
@@ -33,34 +76,79 @@ class Engine:
|
|
|
33
76
|
particle: The default particle for the engine.
|
|
34
77
|
thumbnail: The thumbnail for the engine.
|
|
35
78
|
data: The engine's modes and configurations.
|
|
79
|
+
tags: The tags of the engine.
|
|
80
|
+
description: The description of the engine.
|
|
81
|
+
meta: Additional metadata of the engine.
|
|
36
82
|
"""
|
|
37
83
|
|
|
38
|
-
version =
|
|
84
|
+
version: Literal[13] = 13
|
|
39
85
|
|
|
40
86
|
def __init__(
|
|
41
87
|
self,
|
|
42
88
|
*,
|
|
43
89
|
name: str,
|
|
44
|
-
title:
|
|
45
|
-
subtitle:
|
|
46
|
-
author:
|
|
90
|
+
title: AnyText | None = None,
|
|
91
|
+
subtitle: AnyText = "Sonolus.py Engine",
|
|
92
|
+
author: AnyText = "Unknown",
|
|
47
93
|
skin: str | None = None,
|
|
48
94
|
background: str | None = None,
|
|
49
95
|
effect: str | None = None,
|
|
50
96
|
particle: str | None = None,
|
|
51
97
|
thumbnail: Asset | None = None,
|
|
52
98
|
data: EngineData,
|
|
99
|
+
tags: list[Tag] | None = None,
|
|
100
|
+
description: AnyText | None = None,
|
|
101
|
+
meta: Any = None,
|
|
53
102
|
) -> None:
|
|
54
103
|
self.name = name
|
|
55
|
-
self.title = title or name
|
|
56
|
-
self.subtitle = subtitle
|
|
57
|
-
self.author = author
|
|
104
|
+
self.title = as_localization_text(title or name)
|
|
105
|
+
self.subtitle = as_localization_text(subtitle)
|
|
106
|
+
self.author = as_localization_text(author)
|
|
58
107
|
self.skin = skin
|
|
59
108
|
self.background = background
|
|
60
109
|
self.effect = effect
|
|
61
110
|
self.particle = particle
|
|
62
111
|
self.thumbnail = thumbnail
|
|
63
112
|
self.data = data
|
|
113
|
+
self.tags = tags or []
|
|
114
|
+
self.description = as_localization_text(description) if description is not None else None
|
|
115
|
+
self.meta = meta
|
|
116
|
+
|
|
117
|
+
def export(self) -> ExportedEngine:
|
|
118
|
+
"""Export the engine in a sonolus-pack compatible format.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
An exported engine.
|
|
122
|
+
"""
|
|
123
|
+
from sonolus.build.engine import package_engine
|
|
124
|
+
from sonolus.build.project import BLANK_PNG
|
|
125
|
+
|
|
126
|
+
item = {
|
|
127
|
+
"version": self.version,
|
|
128
|
+
"title": self.title,
|
|
129
|
+
"subtitle": self.subtitle,
|
|
130
|
+
"author": self.author,
|
|
131
|
+
"tags": [tag.as_dict() for tag in self.tags],
|
|
132
|
+
"skin": self.skin,
|
|
133
|
+
"background": self.background,
|
|
134
|
+
"effect": self.effect,
|
|
135
|
+
"particle": self.particle,
|
|
136
|
+
}
|
|
137
|
+
packaged = package_engine(self.data)
|
|
138
|
+
if self.description is not None:
|
|
139
|
+
item["description"] = self.description
|
|
140
|
+
if self.meta is not None:
|
|
141
|
+
item["meta"] = self.meta
|
|
142
|
+
return ExportedEngine(
|
|
143
|
+
item=item,
|
|
144
|
+
thumbnail=load_asset(self.thumbnail) if self.thumbnail is not None else BLANK_PNG,
|
|
145
|
+
play_data=packaged.play_data,
|
|
146
|
+
watch_data=packaged.watch_data,
|
|
147
|
+
preview_data=packaged.preview_data,
|
|
148
|
+
tutorial_data=packaged.tutorial_data,
|
|
149
|
+
rom=packaged.rom,
|
|
150
|
+
configuration=packaged.configuration,
|
|
151
|
+
)
|
|
64
152
|
|
|
65
153
|
|
|
66
154
|
def default_callback() -> Any:
|
|
@@ -190,6 +278,30 @@ class TutorialMode:
|
|
|
190
278
|
self.update = update
|
|
191
279
|
|
|
192
280
|
|
|
281
|
+
def empty_play_mode() -> PlayMode:
|
|
282
|
+
"""Create an empty play mode."""
|
|
283
|
+
return PlayMode()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def empty_watch_mode() -> WatchMode:
|
|
287
|
+
"""Create an empty watch mode."""
|
|
288
|
+
return WatchMode(update_spawn=default_callback)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def empty_preview_mode() -> PreviewMode:
|
|
292
|
+
"""Create an empty preview mode."""
|
|
293
|
+
return PreviewMode()
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def empty_tutorial_mode() -> TutorialMode:
|
|
297
|
+
"""Create an empty tutorial mode."""
|
|
298
|
+
return TutorialMode(
|
|
299
|
+
preprocess=default_callback,
|
|
300
|
+
navigate=default_callback,
|
|
301
|
+
update=default_callback,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
193
305
|
class EngineData:
|
|
194
306
|
"""A Sonolus.py engine's modes and configurations.
|
|
195
307
|
|
|
@@ -214,11 +326,7 @@ class EngineData:
|
|
|
214
326
|
) -> None:
|
|
215
327
|
self.ui = ui or UiConfig()
|
|
216
328
|
self.options = options
|
|
217
|
-
self.play = play or
|
|
218
|
-
self.watch = watch or
|
|
219
|
-
self.preview = preview or
|
|
220
|
-
self.tutorial = tutorial or
|
|
221
|
-
preprocess=default_callback,
|
|
222
|
-
navigate=default_callback,
|
|
223
|
-
update=default_callback,
|
|
224
|
-
)
|
|
329
|
+
self.play = play or empty_play_mode()
|
|
330
|
+
self.watch = watch or empty_watch_mode()
|
|
331
|
+
self.preview = preview or empty_preview_mode()
|
|
332
|
+
self.tutorial = tutorial or empty_tutorial_mode()
|
|
@@ -52,9 +52,11 @@ def _enumerate(iterable, start=0):
|
|
|
52
52
|
from sonolus.backend.visitor import compile_and_call
|
|
53
53
|
|
|
54
54
|
iterable = validate_value(iterable)
|
|
55
|
-
if
|
|
55
|
+
if isinstance(iterable, TupleImpl):
|
|
56
|
+
return TupleImpl._accept_(tuple((start + i, value) for i, value in enumerate(iterable._as_py_(), start=start)))
|
|
57
|
+
elif not hasattr(iterable, "__iter__"):
|
|
56
58
|
raise TypeError(f"'{type(iterable).__name__}' object is not iterable")
|
|
57
|
-
|
|
59
|
+
elif isinstance(iterable, ArrayLike):
|
|
58
60
|
return compile_and_call(iterable._enumerate_, start)
|
|
59
61
|
else:
|
|
60
62
|
iterator = compile_and_call(iterable.__iter__)
|
|
@@ -232,10 +234,27 @@ _int._type_mapping_ = Num
|
|
|
232
234
|
_float._type_mapping_ = Num
|
|
233
235
|
_bool._type_mapping_ = Num
|
|
234
236
|
|
|
237
|
+
|
|
238
|
+
def _any(iterable):
|
|
239
|
+
for value in iterable: # noqa: SIM110
|
|
240
|
+
if value:
|
|
241
|
+
return True
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _all(iterable):
|
|
246
|
+
for value in iterable: # noqa: SIM110
|
|
247
|
+
if not value:
|
|
248
|
+
return False
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
|
|
235
252
|
# classmethod, property, staticmethod are supported as decorators, but not within functions
|
|
236
253
|
|
|
237
254
|
BUILTIN_IMPLS = {
|
|
238
255
|
id(abs): _abs,
|
|
256
|
+
id(all): _all,
|
|
257
|
+
id(any): _any,
|
|
239
258
|
id(bool): _bool,
|
|
240
259
|
id(callable): _callable,
|
|
241
260
|
id(enumerate): _enumerate,
|
|
@@ -3,7 +3,7 @@ from typing import Any, ClassVar, Self
|
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.place import BlockPlace
|
|
5
5
|
from sonolus.script.internal.impl import meta_fn
|
|
6
|
-
from sonolus.script.internal.value import Value
|
|
6
|
+
from sonolus.script.internal.value import DataValue, Value
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class _Missing:
|
|
@@ -94,10 +94,10 @@ class ConstantValue(Value):
|
|
|
94
94
|
return self.value()
|
|
95
95
|
|
|
96
96
|
@classmethod
|
|
97
|
-
def _from_list_(cls, values: Iterable[
|
|
97
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
98
98
|
return cls()
|
|
99
99
|
|
|
100
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
100
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
101
101
|
return []
|
|
102
102
|
|
|
103
103
|
@classmethod
|
|
@@ -107,7 +107,7 @@ class ConstantValue(Value):
|
|
|
107
107
|
def _get_(self) -> Self:
|
|
108
108
|
return self
|
|
109
109
|
|
|
110
|
-
def _set_(self, value: Any)
|
|
110
|
+
def _set_(self, value: Any):
|
|
111
111
|
if value is not self:
|
|
112
112
|
raise ValueError(f"{type(self).__name__} is immutable")
|
|
113
113
|
|
|
@@ -122,6 +122,10 @@ class ConstantValue(Value):
|
|
|
122
122
|
def _alloc_(cls) -> Self:
|
|
123
123
|
return cls()
|
|
124
124
|
|
|
125
|
+
@classmethod
|
|
126
|
+
def _zero_(cls) -> Self:
|
|
127
|
+
return cls()
|
|
128
|
+
|
|
125
129
|
@meta_fn
|
|
126
130
|
def __eq__(self, other):
|
|
127
131
|
return self is other
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
from contextvars import ContextVar
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from threading import Lock
|
|
6
7
|
from typing import Any, Self
|
|
7
8
|
|
|
8
9
|
from sonolus.backend.blocks import BlockData, PlayBlock
|
|
@@ -25,6 +26,7 @@ class GlobalContextState:
|
|
|
25
26
|
environment_mappings: dict[_GlobalInfo, int]
|
|
26
27
|
environment_offsets: dict[Block, int]
|
|
27
28
|
mode: Mode
|
|
29
|
+
lock: Lock
|
|
28
30
|
|
|
29
31
|
def __init__(self, mode: Mode, archetypes: dict[type, int] | None = None, rom: ReadOnlyMemory | None = None):
|
|
30
32
|
self.archetypes = archetypes or {}
|
|
@@ -33,6 +35,7 @@ class GlobalContextState:
|
|
|
33
35
|
self.environment_mappings = {}
|
|
34
36
|
self.environment_offsets = {}
|
|
35
37
|
self.mode = mode
|
|
38
|
+
self.lock = Lock()
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
class CallbackContextState:
|
|
@@ -88,10 +91,6 @@ class Context:
|
|
|
88
91
|
def used_names(self) -> dict[str, int]:
|
|
89
92
|
return self.callback_state.used_names
|
|
90
93
|
|
|
91
|
-
@property
|
|
92
|
-
def const_mappings(self) -> dict[Any, int]:
|
|
93
|
-
return self.global_state.const_mappings
|
|
94
|
-
|
|
95
94
|
def check_readable(self, place: BlockPlace):
|
|
96
95
|
if not self.callback:
|
|
97
96
|
return
|
|
@@ -204,22 +203,25 @@ class Context:
|
|
|
204
203
|
)
|
|
205
204
|
|
|
206
205
|
def map_constant(self, value: Any) -> int:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
with self.global_state.lock:
|
|
207
|
+
const_mappings = self.global_state.const_mappings
|
|
208
|
+
if value not in const_mappings:
|
|
209
|
+
const_mappings[value] = len(const_mappings)
|
|
210
|
+
return const_mappings[value]
|
|
210
211
|
|
|
211
212
|
def get_global_base(self, value: _GlobalInfo | _GlobalPlaceholder) -> BlockPlace:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if value
|
|
217
|
-
offset
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
213
|
+
with self.global_state.lock:
|
|
214
|
+
block = value.blocks.get(self.global_state.mode)
|
|
215
|
+
if block is None:
|
|
216
|
+
raise RuntimeError(f"Global {value.name} is not available in '{self.global_state.mode.name}' mode")
|
|
217
|
+
if value not in self.global_state.environment_mappings:
|
|
218
|
+
if value.offset is None:
|
|
219
|
+
offset = self.global_state.environment_offsets.get(block, 0)
|
|
220
|
+
self.global_state.environment_mappings[value] = offset
|
|
221
|
+
self.global_state.environment_offsets[block] = offset + value.size
|
|
222
|
+
else:
|
|
223
|
+
self.global_state.environment_mappings[value] = value.offset
|
|
224
|
+
return BlockPlace(block, self.global_state.environment_mappings[value])
|
|
223
225
|
|
|
224
226
|
@classmethod
|
|
225
227
|
def meet(cls, contexts: list[Context]) -> Context:
|
|
@@ -257,19 +259,22 @@ def using_ctx(value: Context | None):
|
|
|
257
259
|
class ReadOnlyMemory:
|
|
258
260
|
values: list[float]
|
|
259
261
|
indexes: dict[tuple[float, ...], int]
|
|
262
|
+
_lock: Lock
|
|
260
263
|
|
|
261
264
|
def __init__(self):
|
|
262
265
|
self.values = []
|
|
263
266
|
self.indexes = {}
|
|
267
|
+
self._lock = Lock()
|
|
264
268
|
|
|
265
269
|
def __getitem__(self, item: tuple[float, ...]) -> BlockPlace:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
with self._lock:
|
|
271
|
+
if item not in self.indexes:
|
|
272
|
+
index = len(self.values)
|
|
273
|
+
self.indexes[item] = index
|
|
274
|
+
self.values.extend(item)
|
|
275
|
+
else:
|
|
276
|
+
index = self.indexes[item]
|
|
277
|
+
return BlockPlace(self.block, index)
|
|
273
278
|
|
|
274
279
|
@property
|
|
275
280
|
def block(self) -> Block:
|
|
@@ -2,7 +2,7 @@ from collections.abc import Iterable
|
|
|
2
2
|
from typing import Any, Self
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.place import BlockPlace
|
|
5
|
-
from sonolus.script.internal.value import Value
|
|
5
|
+
from sonolus.script.internal.value import DataValue, Value
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TransientValue(Value):
|
|
@@ -23,10 +23,10 @@ class TransientValue(Value):
|
|
|
23
23
|
raise TypeError(f"{cls.__name__} cannot be dereferenced")
|
|
24
24
|
|
|
25
25
|
@classmethod
|
|
26
|
-
def _from_list_(cls, values: Iterable[
|
|
26
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
27
27
|
raise TypeError(f"{cls.__name__} cannot be constructed from list")
|
|
28
28
|
|
|
29
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
29
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
30
30
|
raise TypeError(f"{type(self).__name__} cannot be deconstructed to list")
|
|
31
31
|
|
|
32
32
|
@classmethod
|
|
@@ -49,3 +49,7 @@ class TransientValue(Value):
|
|
|
49
49
|
@classmethod
|
|
50
50
|
def _alloc_(cls) -> Self:
|
|
51
51
|
raise TypeError(f"{cls.__name__} is not allocatable")
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _zero_(cls) -> Self:
|
|
55
|
+
raise TypeError(f"{cls.__name__} does not have a zero value")
|
sonolus/script/internal/value.py
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from collections.abc import Iterable
|
|
2
|
+
from collections.abc import Callable, Iterable
|
|
3
3
|
from typing import Any, Self
|
|
4
4
|
|
|
5
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRStmt
|
|
5
6
|
from sonolus.backend.place import BlockPlace
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
class BackingValue:
|
|
10
|
+
def read(self) -> IRExpr:
|
|
11
|
+
raise NotImplementedError()
|
|
12
|
+
|
|
13
|
+
def write(self, value: IRExpr) -> IRStmt:
|
|
14
|
+
raise NotImplementedError()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
type DataValue = BlockPlace | BackingValue | float | int | bool
|
|
18
|
+
type BackingSource = Callable[[IRExpr], BackingValue]
|
|
19
|
+
|
|
20
|
+
|
|
8
21
|
class Value:
|
|
9
22
|
"""Base class for values."""
|
|
10
23
|
|
|
@@ -59,15 +72,20 @@ class Value:
|
|
|
59
72
|
"""
|
|
60
73
|
raise NotImplementedError
|
|
61
74
|
|
|
75
|
+
@classmethod
|
|
76
|
+
def _from_backing_source_(cls, source: BackingSource) -> Self:
|
|
77
|
+
"""Creates a value from a backing source."""
|
|
78
|
+
return cls._from_list_(source(IRConst(i)) for i in range(cls._size_()))
|
|
79
|
+
|
|
62
80
|
@classmethod
|
|
63
81
|
@abstractmethod
|
|
64
|
-
def _from_list_(cls, values: Iterable[
|
|
65
|
-
"""Creates a value from a list of
|
|
82
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
83
|
+
"""Creates a value from a list of data values."""
|
|
66
84
|
raise NotImplementedError
|
|
67
85
|
|
|
68
86
|
@abstractmethod
|
|
69
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
70
|
-
"""Converts this value to a list of
|
|
87
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
88
|
+
"""Converts this value to a list of data values."""
|
|
71
89
|
raise NotImplementedError
|
|
72
90
|
|
|
73
91
|
@classmethod
|
|
@@ -76,9 +94,7 @@ class Value:
|
|
|
76
94
|
"""Returns the keys to a flat representation of this value."""
|
|
77
95
|
raise NotImplementedError
|
|
78
96
|
|
|
79
|
-
def _to_flat_dict_(
|
|
80
|
-
self, prefix: str, level_refs: dict[Any, str] | None = None
|
|
81
|
-
) -> dict[str, float | str | BlockPlace]:
|
|
97
|
+
def _to_flat_dict_(self, prefix: str, level_refs: dict[Any, str] | None = None) -> dict[str, DataValue | str]:
|
|
82
98
|
"""Converts this value to a flat dictionary."""
|
|
83
99
|
return dict(zip(self._flat_keys_(prefix), self._to_list_(level_refs), strict=False))
|
|
84
100
|
|
|
@@ -96,12 +112,12 @@ class Value:
|
|
|
96
112
|
v: Num
|
|
97
113
|
|
|
98
114
|
a = 1
|
|
99
|
-
b = X(a) # (1)
|
|
100
|
-
c = b.v # (2)
|
|
115
|
+
b = X(a) # (1) _get_() is called on a
|
|
116
|
+
c = b.v # (2) _get_() is called on the value for v
|
|
101
117
|
|
|
102
118
|
# (1) prevents this from changing the value of a
|
|
103
119
|
# (2) prevents this from changing the value of c
|
|
104
|
-
# Thus, both calls to
|
|
120
|
+
# Thus, both calls to _get_() are necessary to ensure values behave immutably.
|
|
105
121
|
b.v = 2
|
|
106
122
|
```
|
|
107
123
|
"""
|
|
@@ -138,6 +154,12 @@ class Value:
|
|
|
138
154
|
"""Allocates a new value which may be uninitialized."""
|
|
139
155
|
raise NotImplementedError
|
|
140
156
|
|
|
157
|
+
@classmethod
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def _zero_(cls) -> Self:
|
|
160
|
+
"""Returns a zero-initialized value of this type."""
|
|
161
|
+
raise NotImplementedError
|
|
162
|
+
|
|
141
163
|
def __imatmul__(self, other):
|
|
142
164
|
self._copy_from_(other)
|
|
143
165
|
return self
|
sonolus/script/interval.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
|
-
from sonolus.script.debug import
|
|
4
|
+
from sonolus.script.debug import static_error
|
|
5
5
|
from sonolus.script.internal.native import native_function
|
|
6
6
|
from sonolus.script.num import Num
|
|
7
7
|
from sonolus.script.record import Record
|
|
@@ -19,6 +19,11 @@ class Interval(Record):
|
|
|
19
19
|
start: float
|
|
20
20
|
end: float
|
|
21
21
|
|
|
22
|
+
@classmethod
|
|
23
|
+
def zero(cls) -> Self:
|
|
24
|
+
"""Get an empty interval."""
|
|
25
|
+
return cls(0, 0)
|
|
26
|
+
|
|
22
27
|
@property
|
|
23
28
|
def length(self) -> float:
|
|
24
29
|
"""The length of the interval.
|
|
@@ -29,7 +34,7 @@ class Interval(Record):
|
|
|
29
34
|
|
|
30
35
|
@property
|
|
31
36
|
def is_empty(self) -> bool:
|
|
32
|
-
"""Whether the
|
|
37
|
+
"""Whether the has a start greater than its end."""
|
|
33
38
|
return self.start > self.end
|
|
34
39
|
|
|
35
40
|
@property
|
|
@@ -57,7 +62,7 @@ class Interval(Record):
|
|
|
57
62
|
case Num(value):
|
|
58
63
|
return self.start <= value <= self.end
|
|
59
64
|
case _:
|
|
60
|
-
|
|
65
|
+
static_error("Invalid type for interval check")
|
|
61
66
|
|
|
62
67
|
def __add__(self, other: float | int) -> Self:
|
|
63
68
|
"""Add a value to both ends of the interval.
|
sonolus/script/iterator.py
CHANGED
|
@@ -165,3 +165,20 @@ class _FilteringIterator[T, Fn](Record, SonolusIterator):
|
|
|
165
165
|
|
|
166
166
|
def advance(self):
|
|
167
167
|
self.iterator.advance()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class _ChainingIterator[T](Record, SonolusIterator):
|
|
171
|
+
iterator: T
|
|
172
|
+
|
|
173
|
+
def has_next(self) -> bool:
|
|
174
|
+
return self.iterator.has_next()
|
|
175
|
+
|
|
176
|
+
def get(self) -> Any:
|
|
177
|
+
return self.iterator.get().get()
|
|
178
|
+
|
|
179
|
+
def advance(self):
|
|
180
|
+
self.iterator.get().advance()
|
|
181
|
+
while not self.iterator.get().has_next():
|
|
182
|
+
self.iterator.advance()
|
|
183
|
+
if not self.iterator.has_next():
|
|
184
|
+
break
|