sonolus.py 0.1.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.

Files changed (75) hide show
  1. sonolus/__init__.py +0 -0
  2. sonolus/backend/__init__.py +0 -0
  3. sonolus/backend/allocate.py +51 -0
  4. sonolus/backend/blocks.py +756 -0
  5. sonolus/backend/excepthook.py +37 -0
  6. sonolus/backend/finalize.py +69 -0
  7. sonolus/backend/flow.py +92 -0
  8. sonolus/backend/interpret.py +333 -0
  9. sonolus/backend/ir.py +89 -0
  10. sonolus/backend/mode.py +24 -0
  11. sonolus/backend/node.py +40 -0
  12. sonolus/backend/ops.py +197 -0
  13. sonolus/backend/optimize.py +9 -0
  14. sonolus/backend/passes.py +6 -0
  15. sonolus/backend/place.py +90 -0
  16. sonolus/backend/simplify.py +30 -0
  17. sonolus/backend/utils.py +48 -0
  18. sonolus/backend/visitor.py +880 -0
  19. sonolus/build/__init__.py +0 -0
  20. sonolus/build/cli.py +170 -0
  21. sonolus/build/collection.py +293 -0
  22. sonolus/build/compile.py +90 -0
  23. sonolus/build/defaults.py +32 -0
  24. sonolus/build/engine.py +149 -0
  25. sonolus/build/level.py +23 -0
  26. sonolus/build/node.py +43 -0
  27. sonolus/build/project.py +94 -0
  28. sonolus/py.typed +0 -0
  29. sonolus/script/__init__.py +0 -0
  30. sonolus/script/archetype.py +651 -0
  31. sonolus/script/array.py +241 -0
  32. sonolus/script/bucket.py +192 -0
  33. sonolus/script/callbacks.py +105 -0
  34. sonolus/script/comptime.py +146 -0
  35. sonolus/script/containers.py +247 -0
  36. sonolus/script/debug.py +70 -0
  37. sonolus/script/effect.py +132 -0
  38. sonolus/script/engine.py +101 -0
  39. sonolus/script/globals.py +234 -0
  40. sonolus/script/graphics.py +141 -0
  41. sonolus/script/icon.py +73 -0
  42. sonolus/script/internal/__init__.py +5 -0
  43. sonolus/script/internal/builtin_impls.py +144 -0
  44. sonolus/script/internal/context.py +365 -0
  45. sonolus/script/internal/descriptor.py +17 -0
  46. sonolus/script/internal/error.py +15 -0
  47. sonolus/script/internal/generic.py +197 -0
  48. sonolus/script/internal/impl.py +69 -0
  49. sonolus/script/internal/introspection.py +14 -0
  50. sonolus/script/internal/native.py +38 -0
  51. sonolus/script/internal/value.py +144 -0
  52. sonolus/script/interval.py +98 -0
  53. sonolus/script/iterator.py +211 -0
  54. sonolus/script/level.py +52 -0
  55. sonolus/script/math.py +92 -0
  56. sonolus/script/num.py +382 -0
  57. sonolus/script/options.py +194 -0
  58. sonolus/script/particle.py +158 -0
  59. sonolus/script/pointer.py +30 -0
  60. sonolus/script/project.py +17 -0
  61. sonolus/script/range.py +58 -0
  62. sonolus/script/record.py +293 -0
  63. sonolus/script/runtime.py +526 -0
  64. sonolus/script/sprite.py +332 -0
  65. sonolus/script/text.py +404 -0
  66. sonolus/script/timing.py +42 -0
  67. sonolus/script/transform.py +118 -0
  68. sonolus/script/ui.py +160 -0
  69. sonolus/script/values.py +43 -0
  70. sonolus/script/vec.py +48 -0
  71. sonolus_py-0.1.0.dist-info/METADATA +10 -0
  72. sonolus_py-0.1.0.dist-info/RECORD +75 -0
  73. sonolus_py-0.1.0.dist-info/WHEEL +4 -0
  74. sonolus_py-0.1.0.dist-info/entry_points.txt +2 -0
  75. sonolus_py-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,38 @@
1
+ import functools
2
+ import inspect
3
+ from collections.abc import Callable
4
+
5
+ from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
6
+ from sonolus.backend.ops import Op
7
+ from sonolus.script.internal.context import ctx
8
+ from sonolus.script.internal.impl import meta_fn, validate_value
9
+ from sonolus.script.num import Num
10
+
11
+
12
+ def native_call(op: Op, *args: Num) -> Num:
13
+ if not ctx():
14
+ raise RuntimeError("Unexpected native call")
15
+ args = tuple(validate_value(arg) for arg in args)
16
+ if not all(isinstance(arg, Num) for arg in args):
17
+ raise RuntimeError("All arguments must be of type Num")
18
+ result = ctx().alloc(size=1)
19
+ ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
20
+ return Num._from_place_(result)
21
+
22
+
23
+ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
24
+ def decorator(fn: Callable[P, Num]) -> Callable[P, Num]:
25
+ signature = inspect.signature(fn)
26
+
27
+ @functools.wraps(fn)
28
+ @meta_fn
29
+ def wrapper(*args: Num) -> Num:
30
+ if len(args) != len(signature.parameters):
31
+ raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
32
+ if ctx():
33
+ return native_call(op, *args)
34
+ return fn(*args)
35
+
36
+ return wrapper
37
+
38
+ return decorator
@@ -0,0 +1,144 @@
1
+ from abc import abstractmethod
2
+ from collections.abc import Iterable
3
+ from typing import Any, Self
4
+
5
+ from sonolus.backend.place import BlockPlace
6
+
7
+
8
+ class Value:
9
+ """Base class for values."""
10
+
11
+ @classmethod
12
+ def _is_concrete_(cls) -> bool:
13
+ """Returns whether this type is concrete (i.e. can be instantiated)."""
14
+ return False
15
+
16
+ @classmethod
17
+ @abstractmethod
18
+ def _size_(cls) -> int:
19
+ """Returns the size of this value."""
20
+ raise NotImplementedError
21
+
22
+ @classmethod
23
+ @abstractmethod
24
+ def _is_value_type_(cls) -> bool:
25
+ """Returns whether this is a value type.
26
+
27
+ If this is true, the value behaves immutably and _set_ is supported.
28
+ """
29
+ raise NotImplementedError
30
+
31
+ @classmethod
32
+ @abstractmethod
33
+ def _from_place_(cls, place: BlockPlace) -> Self:
34
+ """Creates a value from a place."""
35
+ raise NotImplementedError
36
+
37
+ @classmethod
38
+ @abstractmethod
39
+ def _accepts_(cls, value: Any) -> bool:
40
+ """Returns whether this value can accept the given value."""
41
+ raise NotImplementedError
42
+
43
+ @classmethod
44
+ @abstractmethod
45
+ def _accept_(cls, value: Any) -> Self:
46
+ """Accepts the given value."""
47
+ raise NotImplementedError
48
+
49
+ @abstractmethod
50
+ def _is_py_(self) -> bool:
51
+ """Returns whether this value is a valid Python value."""
52
+ raise NotImplementedError
53
+
54
+ @abstractmethod
55
+ def _as_py_(self) -> Any:
56
+ """Returns the Python equivalent of this value.
57
+
58
+ Will fail if _is_py_ returns false.
59
+ """
60
+ raise NotImplementedError
61
+
62
+ @classmethod
63
+ @abstractmethod
64
+ def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
65
+ """Creates a value from a list of floats."""
66
+ raise NotImplementedError
67
+
68
+ @abstractmethod
69
+ def _to_list_(self) -> list[float | BlockPlace]:
70
+ """Converts this value to a list of floats."""
71
+ raise NotImplementedError
72
+
73
+ @classmethod
74
+ @abstractmethod
75
+ def _flat_keys_(cls, prefix: str) -> list[str]:
76
+ """Returns the keys to a flat representation of this value."""
77
+ raise NotImplementedError
78
+
79
+ def _to_flat_dict_(self, prefix: str) -> dict[str, float | BlockPlace]:
80
+ """Converts this value to a flat dictionary."""
81
+ return dict(zip(self._flat_keys_(prefix), self._to_list_(), strict=False))
82
+
83
+ @abstractmethod
84
+ def _get_(self) -> Self:
85
+ """Implements access to the value.
86
+
87
+ This is used when accessing a value from a record or array (and when storing into a record).
88
+ For value (immutable) types, this makes a copy to preserve the appearance of immutability.
89
+ For mutable types, this returns self.
90
+
91
+ For instance:
92
+ ```
93
+ class X(Record):
94
+ v: Num
95
+
96
+ a = 1
97
+ b = X(a) # (1) _get() is called on a
98
+ c = b.v # (2) _get() is called on the value for v
99
+
100
+ # (1) prevents this from changing the value of a
101
+ # (2) prevents this from changing the value of c
102
+ # Thus, both calls to _get() are necessary to ensure values behave immutably.
103
+ b.v = 2
104
+ ```
105
+ """
106
+ raise NotImplementedError
107
+
108
+ @abstractmethod
109
+ def _set_(self, value: Self):
110
+ """Implements assignment (=).
111
+
112
+ This is only supported by value types.
113
+ This method must not change the active context like by branching.
114
+
115
+ In some places, = might instead call assign_, such as when setting the value of a record field
116
+ with a reference type.
117
+ """
118
+ raise NotImplementedError
119
+
120
+ @abstractmethod
121
+ def _copy_from_(self, value: Self):
122
+ """Implements copy assignment (@=).
123
+
124
+ This is only supported by mutable reference types.
125
+ """
126
+ raise NotImplementedError
127
+
128
+ @abstractmethod
129
+ def _copy_(self) -> Self:
130
+ """Returns a deep copy of this value."""
131
+ raise NotImplementedError
132
+
133
+ @classmethod
134
+ @abstractmethod
135
+ def _alloc_(cls) -> Self:
136
+ """Allocates a new value which may be uninitialized."""
137
+ raise NotImplementedError
138
+
139
+ def __imatmul__(self, other):
140
+ self._copy_from_(other)
141
+ return self
142
+
143
+
144
+ Value.__imatmul__._meta_fn_ = True
@@ -0,0 +1,98 @@
1
+ from typing import Self
2
+
3
+ from sonolus.backend.ops import Op
4
+ from sonolus.script.debug import error
5
+ from sonolus.script.internal.native import native_function
6
+ from sonolus.script.record import Record
7
+
8
+
9
+ class Interval(Record):
10
+ """A closed interval."""
11
+
12
+ start: float
13
+ end: float
14
+
15
+ @property
16
+ def length(self) -> float:
17
+ return self.end - self.start
18
+
19
+ @property
20
+ def is_empty(self) -> bool:
21
+ return self.start > self.end
22
+
23
+ @property
24
+ def mid(self) -> float:
25
+ return (self.start + self.end) / 2
26
+
27
+ @property
28
+ def tuple(self):
29
+ return self.start, self.end
30
+
31
+ def __contains__(self, item: Self | float | int) -> bool:
32
+ match item:
33
+ case Interval(start, end):
34
+ return self.start <= start and end <= self.end
35
+ case float() | int() as value:
36
+ return self.start <= value <= self.end
37
+ case _:
38
+ error("Invalid type for interval check")
39
+
40
+ def __add__(self, other: float | int) -> Self:
41
+ return Interval(self.start + other, self.end + other)
42
+
43
+ def __sub__(self, other: float | int) -> Self:
44
+ return Interval(self.start - other, self.end - other)
45
+
46
+ def __mul__(self, other: float | int) -> Self:
47
+ return Interval(self.start * other, self.end * other)
48
+
49
+ def __truediv__(self, other: float | int) -> Self:
50
+ return Interval(self.start / other, self.end / other)
51
+
52
+ def __floordiv__(self, other: float | int) -> Self:
53
+ return Interval(self.start // other, self.end // other)
54
+
55
+ def __and__(self, other: Self) -> Self:
56
+ return Interval(max(self.start, other.start), min(self.end, other.end))
57
+
58
+ def lerp(self, x: float, /) -> float:
59
+ return lerp(self.start, self.end, x)
60
+
61
+ def lerp_clamped(self, x: float, /) -> float:
62
+ return lerp_clamped(self.start, self.end, x)
63
+
64
+ def unlerp(self, x: float, /) -> float:
65
+ return unlerp(self.start, self.end, x)
66
+
67
+ def unlerp_clamped(self, x: float, /) -> float:
68
+ return unlerp_clamped(self.start, self.end, x)
69
+
70
+
71
+ @native_function(Op.Lerp)
72
+ def lerp(a, b, x, /):
73
+ return a + (b - a) * x
74
+
75
+
76
+ @native_function(Op.LerpClamped)
77
+ def lerp_clamped(a, b, x, /):
78
+ return a + (b - a) * max(0, min(1, x))
79
+
80
+
81
+ @native_function(Op.Unlerp)
82
+ def unlerp(a, b, x, /):
83
+ return (x - a) / (b - a)
84
+
85
+
86
+ @native_function(Op.UnlerpClamped)
87
+ def unlerp_clamped(a, b, x, /):
88
+ return max(0, min(1, (x - a) / (b - a)))
89
+
90
+
91
+ @native_function(Op.Remap)
92
+ def remap(a, b, c, d, x, /):
93
+ return c + (d - c) * (x - a) / (b - a)
94
+
95
+
96
+ @native_function(Op.RemapClamped)
97
+ def remap_clamped(a, b, c, d, x, /):
98
+ return c + (d - c) * max(0, min(1, (x - a) / (b - a)))
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+ from collections.abc import Collection, Iterator
5
+
6
+ from sonolus.script.num import Num
7
+ from sonolus.script.record import Record
8
+ from sonolus.script.values import copy
9
+
10
+
11
+ class SonolusIterator[T](Iterator[T]):
12
+ @abstractmethod
13
+ def has_next(self) -> bool:
14
+ raise NotImplementedError
15
+
16
+ @abstractmethod
17
+ def next(self) -> T:
18
+ raise NotImplementedError
19
+
20
+ def __next__(self) -> T:
21
+ if not self.has_next():
22
+ raise StopIteration
23
+ return self.next()
24
+
25
+
26
+ class ArrayLike[T](Collection):
27
+ @abstractmethod
28
+ def size(self) -> int:
29
+ pass
30
+
31
+ @abstractmethod
32
+ def __getitem__(self, index: Num) -> T:
33
+ pass
34
+
35
+ @abstractmethod
36
+ def __setitem__(self, index: Num, value: T):
37
+ pass
38
+
39
+ def __len__(self) -> int:
40
+ return self.size()
41
+
42
+ def __iter__(self) -> SonolusIterator[T]:
43
+ return ArrayIterator(0, self)
44
+
45
+ def __contains__(self, value: T) -> bool:
46
+ i = 0
47
+ while i < self.size():
48
+ if self[i] == value:
49
+ return True
50
+ i += 1
51
+ return False
52
+
53
+ def reversed(self) -> ArrayLike[T]:
54
+ return ArrayReverser(self)
55
+
56
+ def iter(self) -> SonolusIterator[T]:
57
+ return self.__iter__() # noqa: PLC2801
58
+
59
+ def enumerate(self, start: Num = 0) -> SonolusIterator[T]:
60
+ return ArrayEnumerator(0, start, self)
61
+
62
+ def index_of(self, value: T, start: Num = 0) -> Num:
63
+ i = start
64
+ while i < self.size():
65
+ if self[i] == value:
66
+ return i
67
+ i += 1
68
+ return -1
69
+
70
+ def last_index_of(self, value: T) -> Num:
71
+ i = self.size() - 1
72
+ while i >= 0:
73
+ if self[i] == value:
74
+ return i
75
+ i -= 1
76
+ return -1
77
+
78
+ def index_of_max(self) -> Num:
79
+ if self.size() == 0:
80
+ return -1
81
+ max_index = 0
82
+ i = 1
83
+ while i < self.size():
84
+ if self[i] > self[max_index]:
85
+ max_index = i
86
+ i += 1
87
+ return max_index
88
+
89
+ def index_of_min(self) -> Num:
90
+ if self.size() == 0:
91
+ return -1
92
+ min_index = 0
93
+ i = 1
94
+ while i < self.size():
95
+ if self[i] < self[min_index]:
96
+ min_index = i
97
+ i += 1
98
+ return min_index
99
+
100
+ def max(self) -> T:
101
+ return self[self.index_of_max()]
102
+
103
+ def min(self) -> T:
104
+ return self[self.index_of_min()]
105
+
106
+ def swap(self, i: Num, j: Num):
107
+ temp = copy(self[i])
108
+ self[i] = self[j]
109
+ self[j] = temp
110
+
111
+ def sort(self, *, reverse: bool = False):
112
+ if self.size() < 15:
113
+ _insertion_sort(self, 0, self.size(), reverse)
114
+ else:
115
+ _heap_sort(self, 0, self.size(), reverse)
116
+
117
+
118
+ def _insertion_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
119
+ i = start + 1
120
+ while i < end:
121
+ value = copy(array[i])
122
+ j = i - 1
123
+ while j >= start and (array[j] > value) != reverse:
124
+ array[j + 1] = array[j]
125
+ j -= 1
126
+ array[j + 1] = value
127
+ i += 1
128
+
129
+
130
+ def _heapify[T](array: ArrayLike[T], end: Num, index: Num, reverse: bool):
131
+ while True:
132
+ left = index * 2 + 1
133
+ right = left + 1
134
+ largest = index
135
+ if left < end and (array[left] > array[largest]) != reverse:
136
+ largest = left
137
+ if right < end and (array[right] > array[largest]) != reverse:
138
+ largest = right
139
+ if largest == index:
140
+ break
141
+ array.swap(index, largest)
142
+ index = largest
143
+
144
+
145
+ # Heap sort is simple to implement iteratively without dynamic memory allocation
146
+ def _heap_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
147
+ i = end // 2 - 1
148
+ while i >= start:
149
+ _heapify(array, end, i, reverse)
150
+ i -= 1
151
+ i = end - 1
152
+ while i > start:
153
+ array.swap(start, i)
154
+ _heapify(array, i, start, reverse)
155
+ i -= 1
156
+
157
+
158
+ class ArrayIterator[V: ArrayLike](Record, SonolusIterator):
159
+ i: int
160
+ array: V
161
+
162
+ def has_next(self) -> bool:
163
+ return self.i < self.array.size()
164
+
165
+ def next(self) -> V:
166
+ value = self.array[self.i]
167
+ self.i += 1
168
+ return value
169
+
170
+
171
+ class ArrayReverser[V: ArrayLike](Record, ArrayLike):
172
+ array: V
173
+
174
+ def size(self) -> int:
175
+ return self.array.size()
176
+
177
+ def __getitem__(self, index: Num) -> V:
178
+ return self.array[self.size() - 1 - index]
179
+
180
+ def __setitem__(self, index: Num, value: V):
181
+ self.array[self.size() - 1 - index] = value
182
+
183
+
184
+ class Enumerator[V: SonolusIterator](Record, SonolusIterator):
185
+ i: int
186
+ offset: int
187
+ iterator: V
188
+
189
+ def has_next(self) -> bool:
190
+ return self.iterator.has_next()
191
+
192
+ def next(self):
193
+ value = self.iterator.next()
194
+ index = self.i + self.offset
195
+ self.i += 1
196
+ return index, value
197
+
198
+
199
+ class ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
200
+ i: int
201
+ offset: int
202
+ array: V
203
+
204
+ def has_next(self) -> bool:
205
+ return self.i < self.array.size()
206
+
207
+ def next(self):
208
+ value = self.array[self.i]
209
+ index = self.i + self.offset
210
+ self.i += 1
211
+ return index, value
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from sonolus.build.collection import Asset
4
+ from sonolus.script.archetype import PlayArchetype, StandardImport
5
+
6
+
7
+ class Level:
8
+ version = 1
9
+
10
+ def __init__(
11
+ self,
12
+ *,
13
+ name: str,
14
+ title: str | None = None,
15
+ rating: int = 0,
16
+ artists: str = "Unknown",
17
+ author: str = "Unknown",
18
+ cover: Asset | None = None,
19
+ bgm: Asset | None = None,
20
+ data: LevelData,
21
+ ) -> None:
22
+ self.name = name
23
+ self.title = title or name
24
+ self.rating = rating
25
+ self.artists = artists
26
+ self.author = author
27
+ self.cover = cover
28
+ self.bgm = bgm
29
+ self.data = data
30
+
31
+
32
+ class LevelData:
33
+ bgm_offset: float
34
+ entities: list[PlayArchetype]
35
+
36
+ def __init__(self, bgm_offset: float, entities: list[PlayArchetype]) -> None:
37
+ self.bgm_offset = bgm_offset
38
+ self.entities = entities
39
+
40
+
41
+ class BpmChange(PlayArchetype):
42
+ name = "#BPM_CHANGE"
43
+
44
+ beat: StandardImport.Beat
45
+ bpm: StandardImport.Bpm
46
+
47
+
48
+ class TimescaleChange(PlayArchetype):
49
+ name = "#TIMESCALE_CHANGE"
50
+
51
+ beat: StandardImport.Beat
52
+ timescale: StandardImport.Timescale
sonolus/script/math.py ADDED
@@ -0,0 +1,92 @@
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
+ MATH_BUILTIN_IMPLS = {
78
+ id(math.sin): sin,
79
+ id(math.cos): cos,
80
+ id(math.tan): tan,
81
+ id(math.asin): asin,
82
+ id(math.acos): acos,
83
+ id(math.atan): atan,
84
+ id(math.atan2): atan2,
85
+ id(math.sinh): sinh,
86
+ id(math.cosh): cosh,
87
+ id(math.tanh): tanh,
88
+ id(math.floor): floor,
89
+ id(math.ceil): ceil,
90
+ id(math.trunc): trunc,
91
+ id(round): _round,
92
+ }