sltcore 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- sltcore-0.2.4/.github/workflows/publish_to_pypi.yml +43 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/.github/workflows/publish_to_testpypi.yml +1 -3
- {sltcore-0.2.2 → sltcore-0.2.4}/PKG-INFO +1 -1
- {sltcore-0.2.2 → sltcore-0.2.4}/pyproject.toml +1 -1
- {sltcore-0.2.2 → sltcore-0.2.4}/src/sltcore/types.py +103 -84
- {sltcore-0.2.2 → sltcore-0.2.4}/uv.lock +1 -1
- sltcore-0.2.2/.github/workflows/publish_to_pypi.yml +0 -25
- {sltcore-0.2.2 → sltcore-0.2.4}/.github/FUNDING.yml +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/.gitignore +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/.python-version +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/.vscode/launch.json +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/.vscode/settings.json +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/LICENSE +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/README.md +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/bump_major.ps1 +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/bump_minor.ps1 +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/bump_patch.ps1 +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/src/sltcore/__init__.py +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/src/sltcore/bits.py +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/tests/test_bits.py +0 -0
- {sltcore-0.2.2 → sltcore-0.2.4}/tests/test_types.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-and-publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Extract version from pyproject.toml
|
|
17
|
+
id: version
|
|
18
|
+
run: |
|
|
19
|
+
VERSION=$(grep -m1 '^version = "' pyproject.toml | sed -E 's/^version = "([^"]+)"$/\1/')
|
|
20
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
21
|
+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v3
|
|
25
|
+
|
|
26
|
+
- name: Build distribution
|
|
27
|
+
run: uv build
|
|
28
|
+
|
|
29
|
+
- name: Publish to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
31
|
+
with:
|
|
32
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
33
|
+
packages-dir: dist
|
|
34
|
+
|
|
35
|
+
- name: Create GitHub release and upload artifacts
|
|
36
|
+
uses: softprops/action-gh-release@v2
|
|
37
|
+
with:
|
|
38
|
+
tag_name: ${{ steps.version.outputs.tag }}
|
|
39
|
+
name: ${{ steps.version.outputs.tag }}
|
|
40
|
+
generate_release_notes: true
|
|
41
|
+
files: |
|
|
42
|
+
dist/*.tar.gz
|
|
43
|
+
dist/*.whl
|
|
@@ -2,8 +2,6 @@ name: Publish to TestPyPI
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_dispatch:
|
|
5
|
-
release:
|
|
6
|
-
types: [published]
|
|
7
5
|
|
|
8
6
|
jobs:
|
|
9
7
|
build-and-publish:
|
|
@@ -23,4 +21,4 @@ jobs:
|
|
|
23
21
|
with:
|
|
24
22
|
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
25
23
|
repository-url: https://test.pypi.org/legacy/
|
|
26
|
-
|
|
24
|
+
packages-dir: dist
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sltcore
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A toolkit for handling structured data layouts and bit-level operations.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fangface-hub/StructLayoutToolkitCore
|
|
6
6
|
Project-URL: Documentation, https://readthedocs.org
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
"""Type definitions for structured data layouts and bit-level operations."""
|
|
2
2
|
import math
|
|
3
|
+
import struct
|
|
3
4
|
from dataclasses import dataclass, field
|
|
5
|
+
from functools import total_ordering
|
|
4
6
|
|
|
5
7
|
|
|
8
|
+
def _has_struct_half():
|
|
9
|
+
"""Checks if the current Python environment supports
|
|
10
|
+
half-precision (16-bit) floats in the struct module."""
|
|
11
|
+
try:
|
|
12
|
+
struct.pack(">e", 1.0)
|
|
13
|
+
return True
|
|
14
|
+
except struct.error:
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
HAS_STRUCT_HALF = _has_struct_half() # ← 一度だけ実行
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@total_ordering
|
|
6
22
|
@dataclass(frozen=True)
|
|
7
23
|
class InfoSize:
|
|
8
24
|
"""Represents a size in bytes and bits."""
|
|
@@ -12,26 +28,43 @@ class InfoSize:
|
|
|
12
28
|
def __post_init__(self):
|
|
13
29
|
"""Ensures that the bit count is normalized to be less than 8,
|
|
14
30
|
and adjusts the byte count accordingly."""
|
|
15
|
-
extra_bytes
|
|
31
|
+
extra_bytes = self.bit >> 3
|
|
32
|
+
new_bits = self.bit & 7
|
|
16
33
|
object.__setattr__(self, "byte", self.byte + extra_bytes)
|
|
17
34
|
object.__setattr__(self, "bit", new_bits)
|
|
18
35
|
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
"""Returns a string representation of the size
|
|
38
|
+
in the format 'X bytes, Y bits'."""
|
|
39
|
+
return f"{self.byte} bytes, {self.bit} bits"
|
|
40
|
+
|
|
41
|
+
def __eq__(self, other: object) -> bool:
|
|
42
|
+
"""Checks equality between two InfoSize instances
|
|
43
|
+
based on their total size in bits."""
|
|
44
|
+
if not isinstance(other, InfoSize):
|
|
45
|
+
return NotImplemented
|
|
46
|
+
return self.bits == other.bits
|
|
47
|
+
|
|
48
|
+
def __lt__(self, other: "InfoSize") -> bool:
|
|
49
|
+
"""Compares two InfoSize instances based on their total size in bits."""
|
|
50
|
+
return self.bits < other.bits
|
|
51
|
+
|
|
19
52
|
@property
|
|
20
53
|
def bits(self) -> int:
|
|
21
54
|
"""Returns the total size in bits."""
|
|
22
|
-
return self.byte
|
|
55
|
+
return (self.byte << 3) | self.bit
|
|
23
56
|
|
|
24
57
|
@property
|
|
25
58
|
def bytes(self) -> int:
|
|
26
59
|
"""Returns the total size in bytes,
|
|
27
60
|
rounding up if there are leftover bits."""
|
|
28
|
-
return (self.bits + 7)
|
|
61
|
+
return (self.bits + 7) >> 3
|
|
29
62
|
|
|
30
63
|
@property
|
|
31
64
|
def hex_digits(self) -> int:
|
|
32
65
|
"""Returns the number of hexadecimal digits
|
|
33
66
|
needed to represent the size in bits."""
|
|
34
|
-
return self.bits
|
|
67
|
+
return self.bits >> 2
|
|
35
68
|
|
|
36
69
|
@property
|
|
37
70
|
def mask(self) -> int:
|
|
@@ -55,6 +88,7 @@ class InfoSize:
|
|
|
55
88
|
return InfoSize(0, total_bits)
|
|
56
89
|
|
|
57
90
|
|
|
91
|
+
@total_ordering
|
|
58
92
|
@dataclass(frozen=True)
|
|
59
93
|
class Info:
|
|
60
94
|
"""Represents a slice of bits extracted from a bytearray,
|
|
@@ -63,6 +97,28 @@ class Info:
|
|
|
63
97
|
info_size: InfoSize = field(default_factory=InfoSize,
|
|
64
98
|
metadata={"desc": "size information"})
|
|
65
99
|
|
|
100
|
+
def __str__(self) -> str:
|
|
101
|
+
"""Returns a string representation of the Info instance,
|
|
102
|
+
showing the raw value and its size."""
|
|
103
|
+
return f"Info(raw_value={self.raw_value}, info_size={self.info_size})"
|
|
104
|
+
|
|
105
|
+
def __eq__(self, other: object) -> bool:
|
|
106
|
+
"""Checks equality between two Info instances based on their raw values
|
|
107
|
+
and size information."""
|
|
108
|
+
if not isinstance(other, Info):
|
|
109
|
+
return NotImplemented
|
|
110
|
+
return (self.raw_value == other.raw_value
|
|
111
|
+
and self.info_size == other.info_size)
|
|
112
|
+
|
|
113
|
+
def __lt__(self, other: "Info") -> bool:
|
|
114
|
+
"""Compares two Info instances based on their raw values
|
|
115
|
+
and size information."""
|
|
116
|
+
if not isinstance(other, Info):
|
|
117
|
+
return NotImplemented
|
|
118
|
+
if self.info_size != other.info_size:
|
|
119
|
+
return self.info_size < other.info_size
|
|
120
|
+
return self.raw_value < other.raw_value
|
|
121
|
+
|
|
66
122
|
@classmethod
|
|
67
123
|
def from_int(cls, value: int, size: InfoSize):
|
|
68
124
|
"""Creates an Info instance from an integer value, ensuring that
|
|
@@ -136,65 +192,52 @@ def _float_from_bits(raw: int, bits: int) -> float:
|
|
|
136
192
|
to a Python float, based on the specified bit size.
|
|
137
193
|
Supports 16, 32, and 64 bits.
|
|
138
194
|
"""
|
|
139
|
-
if bits ==
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
f
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return (-1)**s * 0.0
|
|
148
|
-
return (-1)**s * (f / 2**10) * 2**(-14)
|
|
149
|
-
elif e == 31:
|
|
150
|
-
if f == 0:
|
|
151
|
-
return float('inf') if s == 0 else float('-inf')
|
|
152
|
-
return float('nan')
|
|
153
|
-
else:
|
|
154
|
-
return (-1)**s * (1 + f / 2**10) * 2**(e - 15)
|
|
155
|
-
elif bits == 32:
|
|
156
|
-
# IEEE754 single precision
|
|
157
|
-
# High-speed version without using struct
|
|
158
|
-
s = (raw >> 31) & 0x1
|
|
159
|
-
|
|
160
|
-
e = (raw >> 23) & 0xFF
|
|
161
|
-
f = raw & 0x7FFFFF
|
|
162
|
-
|
|
163
|
-
if e == 0:
|
|
164
|
-
return (-1)**s * (f / 2**23) * 2**(-126)
|
|
165
|
-
elif e == 255:
|
|
166
|
-
if f == 0:
|
|
167
|
-
return float('-inf') if s else float('inf')
|
|
168
|
-
return float('nan')
|
|
169
|
-
else:
|
|
170
|
-
return (-1)**s * (1 + f / 2**23) * 2**(e - 127)
|
|
171
|
-
|
|
172
|
-
elif bits == 64:
|
|
173
|
-
# IEEE754 double precision
|
|
174
|
-
s = (raw >> 63) & 0x1
|
|
175
|
-
e = (raw >> 52) & 0x7FF
|
|
176
|
-
f = raw & 0xFFFFFFFFFFFFF
|
|
177
|
-
|
|
178
|
-
if e == 0:
|
|
179
|
-
return (-1)**s * (f / 2**52) * 2**(-1022)
|
|
180
|
-
elif e == 2047:
|
|
181
|
-
if f == 0:
|
|
182
|
-
return float('-inf') if s else float('inf')
|
|
183
|
-
return float('nan')
|
|
184
|
-
else:
|
|
185
|
-
return (-1)**s * (1 + f / 2**52) * 2**(e - 1023)
|
|
186
|
-
|
|
187
|
-
else:
|
|
188
|
-
raise ValueError(''.join([
|
|
189
|
-
f"Unsupported bit size for float conversion: {bits}. ",
|
|
195
|
+
if bits == 64:
|
|
196
|
+
return struct.unpack('>d', struct.pack('>Q', raw))[0]
|
|
197
|
+
if bits == 32:
|
|
198
|
+
# Use struct to unpack the integer as a float
|
|
199
|
+
return struct.unpack('>f', struct.pack('>I', raw))[0]
|
|
200
|
+
if bits != 16:
|
|
201
|
+
raise ValueError("".join([
|
|
202
|
+
f"Unsupported float bit size: {bits}. ",
|
|
190
203
|
"Only 16, 32, and 64 are supported."
|
|
191
204
|
]))
|
|
205
|
+
if HAS_STRUCT_HALF:
|
|
206
|
+
# Use struct to unpack the integer as a half-precision float
|
|
207
|
+
return struct.unpack('>e', struct.pack('>H', raw))[0]
|
|
208
|
+
# IEEE754 half-precision (16bit) → float32
|
|
209
|
+
s = (raw >> 15) & 0x0001
|
|
210
|
+
e = (raw >> 10) & 0x001F
|
|
211
|
+
f = raw & 0x03FF
|
|
212
|
+
|
|
213
|
+
if e == 0:
|
|
214
|
+
if f == 0:
|
|
215
|
+
return (-1)**s * 0.0
|
|
216
|
+
return (-1)**s * (f / 2**10) * 2**(-14)
|
|
217
|
+
if e == 31:
|
|
218
|
+
if f == 0:
|
|
219
|
+
return float('inf') if s == 0 else float('-inf')
|
|
220
|
+
return float('nan')
|
|
221
|
+
return (-1)**s * (1 + f / 2**10) * 2**(e - 15)
|
|
192
222
|
|
|
193
223
|
|
|
194
224
|
def _float_to_bits(value: float, bits: int) -> int:
|
|
195
225
|
"""Converts a Python float to its raw integer representation
|
|
196
226
|
based on the specified bit size. Supports 16, 32, and 64 bits.
|
|
197
227
|
"""
|
|
228
|
+
if bits == 64:
|
|
229
|
+
return struct.unpack('>Q', struct.pack('>d', value))[0]
|
|
230
|
+
if bits == 32:
|
|
231
|
+
# Use struct to pack the float into bytes and then unpack as an integer
|
|
232
|
+
return struct.unpack('>I', struct.pack('>f', value))[0]
|
|
233
|
+
if bits != 16:
|
|
234
|
+
raise ValueError("".join([
|
|
235
|
+
f"Unsupported float bit size: {bits}. ",
|
|
236
|
+
"Only 16, 32, and 64 are supported."
|
|
237
|
+
]))
|
|
238
|
+
if HAS_STRUCT_HALF:
|
|
239
|
+
# Use struct to pack the float into bytes and then unpack as an integer
|
|
240
|
+
return struct.unpack('>H', struct.pack('>e', value))[0]
|
|
198
241
|
# Determine the sign and absolute value
|
|
199
242
|
# Use math.copysign to correctly detect negative zero
|
|
200
243
|
sign = 1 if math.copysign(1.0, value) < 0 else 0
|
|
@@ -206,40 +249,16 @@ def _float_to_bits(value: float, bits: int) -> int:
|
|
|
206
249
|
|
|
207
250
|
# NaN / Inf
|
|
208
251
|
if math.isnan(value):
|
|
209
|
-
|
|
210
|
-
return 0x7E00
|
|
211
|
-
if bits == 32:
|
|
212
|
-
return 0x7FC00000
|
|
213
|
-
if bits == 64:
|
|
214
|
-
return 0x7FF8000000000000
|
|
252
|
+
return 0x7E00
|
|
215
253
|
|
|
216
254
|
# Handle infinity
|
|
217
255
|
if math.isinf(value):
|
|
218
|
-
|
|
219
|
-
return (sign << 15) | 0x7C00
|
|
220
|
-
if bits == 32:
|
|
221
|
-
return (sign << 31) | 0x7F800000
|
|
222
|
-
if bits == 64:
|
|
223
|
-
return (sign << 63) | 0x7FF0000000000000
|
|
256
|
+
return (sign << 15) | 0x7C00
|
|
224
257
|
|
|
225
258
|
# Compute exponent and mantissa
|
|
226
259
|
e = int(math.floor(math.log(v, 2)))
|
|
227
260
|
mant = v / (2**e) - 1.0
|
|
228
261
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return (sign << 15) | (exp << 10) | frac
|
|
233
|
-
|
|
234
|
-
elif bits == 32:
|
|
235
|
-
exp = e + 127
|
|
236
|
-
frac = int(mant * (2**23))
|
|
237
|
-
return (sign << 31) | (exp << 23) | frac
|
|
238
|
-
|
|
239
|
-
elif bits == 64:
|
|
240
|
-
exp = e + 1023
|
|
241
|
-
frac = int(mant * (2**52))
|
|
242
|
-
return (sign << 63) | (exp << 52) | frac
|
|
243
|
-
|
|
244
|
-
else:
|
|
245
|
-
raise ValueError(f"Unsupported float bit size: {bits}")
|
|
262
|
+
exp = e + 15
|
|
263
|
+
frac = int(mant * (2**10))
|
|
264
|
+
return (sign << 15) | (exp << 10) | frac
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
name: Publish to PyPI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
release:
|
|
6
|
-
types: [published]
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
build-and-publish:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v4
|
|
14
|
-
|
|
15
|
-
- name: Install uv
|
|
16
|
-
uses: astral-sh/setup-uv@v3
|
|
17
|
-
|
|
18
|
-
- name: Build wheel
|
|
19
|
-
run: uv build
|
|
20
|
-
|
|
21
|
-
- name: Publish to PyPI
|
|
22
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
23
|
-
with:
|
|
24
|
-
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
25
|
-
packages_dir: dist
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|