UTF-8000 1.0.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.
UTF8000/UTF8000Byte.py ADDED
@@ -0,0 +1,97 @@
1
+ # size_t counting num of bytes; this is the only machine limitation
2
+ # big bittian ordered (128, 64, 32, 16, 8, 4, 2, 1)
3
+
4
+ ZERO = 0b00000000
5
+
6
+ ASCII_PREFIX = 0b00000000
7
+ ASCII_PREFIX_MASK = 0b10000000
8
+ ASCII_CONTENT_MASK = 0b01111111
9
+
10
+ CONTINUATION_PREFIX = 0b10000000
11
+ CONTINUATION_PREFIX_MASK = 0b11000000
12
+ CONTINUATION_CONTENT_MASK = 0b00111111
13
+ CONTINUATION_FILLED = CONTINUATION_PREFIX + 0b00111111
14
+
15
+ def byte_is_ascii(c: int) -> bool:
16
+ return c & ASCII_PREFIX_MASK == ASCII_PREFIX
17
+
18
+ def byte_is_continuation(c: int) -> bool:
19
+ return c & CONTINUATION_PREFIX_MASK == CONTINUATION_PREFIX
20
+
21
+ def byte_idx_0(c: int) -> int:
22
+ return next((
23
+ idx
24
+ for idx, bit in
25
+ enumerate(c & (1 << (8 - 1 - t)) for t in range(8))
26
+ if not bit
27
+ ), 8)
28
+
29
+ def byte_continuation_content_idx_0(c: int) -> int:
30
+ # could be made part of _byte_idx_0 but whatever
31
+ return next((
32
+ idx
33
+ for idx, bit in
34
+ enumerate(c & (1 << (6 - 1 - t)) for t in range(6))
35
+ if not bit
36
+ ), 6)
37
+
38
+ class UTF8000Byte:
39
+ def __init__(self, c: int, *,
40
+ is_start_byte: bool,
41
+ is_continuation_byte: bool,
42
+ is_content_byte: bool,
43
+ ) -> None:
44
+ self.c = c
45
+ self.is_start_byte = is_start_byte
46
+ self.is_continuation_byte = is_continuation_byte
47
+ self.is_content_byte = is_content_byte
48
+
49
+ def __str__(self) -> str:
50
+ return f"0b{self.c:08b}"
51
+
52
+ def debug_str(self) -> str:
53
+ if self.is_continuation_byte:
54
+ n_continuation_prefix_bits = 2
55
+ else:
56
+ n_continuation_prefix_bits = 0
57
+
58
+ if self.is_content_byte:
59
+ n_content_bits = self.n_content_bits
60
+ else:
61
+ n_content_bits = 0
62
+
63
+ RESET = "\x1b[0m"
64
+ RED = "\x1b[31m"
65
+ GREEN = "\x1b[32m"
66
+ BLUE = "\x1b[34m"
67
+ s = str(self)
68
+ binary_prefix = s[:2]
69
+ continuation_prefix = GREEN + s[2:2+n_continuation_prefix_bits] + RESET
70
+ start_bits = RED + s[2+n_continuation_prefix_bits:10 - n_content_bits] + RESET
71
+ content_bits = BLUE + s[10 - n_content_bits:] + RESET
72
+
73
+ return binary_prefix + continuation_prefix + start_bits + content_bits
74
+
75
+ @property
76
+ def is_ascii(self) -> bool:
77
+ return byte_is_ascii(self.c)
78
+
79
+ @property
80
+ def n_content_bits(self) -> int:
81
+ if not self.is_content_byte:
82
+ raise ValueError
83
+
84
+ if not self.is_continuation_byte:
85
+ idx_0 = byte_idx_0(self.c)
86
+ return (8 - 1 - idx_0)
87
+ elif not self.is_start_byte:
88
+ return 6
89
+ else:
90
+ idx_0_content = byte_continuation_content_idx_0(self.c)
91
+ return (6 - 1 - idx_0_content)
92
+
93
+ @property
94
+ def content(self) -> int:
95
+ content_mask = (1 << self.n_content_bits) - 1
96
+
97
+ return self.c & content_mask
@@ -0,0 +1,121 @@
1
+ from typing import Generator
2
+
3
+ from .UTF8000Byte import UTF8000Byte, byte_is_continuation, byte_idx_0, byte_continuation_content_idx_0
4
+ from .UTF8000Int import UTF8000Int
5
+
6
+ class UTF8000IncrementalDecoder:
7
+ def __init__(self) -> None:
8
+ self._results: list[UTF8000Int] = []
9
+ self._bytes_buffer: bytes = b""
10
+
11
+ self._generator = self._utf_8000_parse_forever()
12
+ self._wakeup()
13
+
14
+ def __iter__(self) -> Generator[UTF8000Int, None, None]:
15
+ # queue
16
+ while self._results:
17
+ yield self._results.pop(0)
18
+
19
+ def feed(self, utf_8000_bytes: bytes) -> None:
20
+ self._bytes_buffer += utf_8000_bytes
21
+ self._wakeup()
22
+
23
+ def _wakeup(self) -> None:
24
+ self._generator.send(None)
25
+
26
+ def _await_bytes(self, n_bytes: int) -> Generator[None, None, bytes]:
27
+ while len(self._bytes_buffer) < n_bytes:
28
+ yield
29
+
30
+ ret = self._bytes_buffer[:n_bytes]
31
+ self._bytes_buffer = self._bytes_buffer[n_bytes:]
32
+
33
+ return ret
34
+
35
+ def _await_byte(self) -> Generator[None, None, int]:
36
+ return (yield from self._await_bytes(1))[0]
37
+
38
+ def _await_continuation_byte(self) -> Generator[None, None, int]:
39
+ ret = yield from self._await_byte()
40
+ if not byte_is_continuation(ret):
41
+ self._on_error_invalid_continuation_byte()
42
+ return ret
43
+
44
+ def _on_error(self, error_message: str) -> UTF8000Byte:
45
+ raise ValueError(error_message)
46
+
47
+ def _on_error_invalid_start_byte(self) -> None:
48
+ # XXX TODO 101: read all following continuation bytes to skip them
49
+ self._on_error("Invalid start byte: continuation byte detected")
50
+
51
+ def _on_error_invalid_continuation_byte(self) -> None:
52
+ # XXX TODO 101: should we un-pop this (start) byte so further decodings can continue, and return a replacement character?
53
+ self._on_error("Not a continuation byte prefix")
54
+
55
+ def _on_error_overlong(self) -> None:
56
+ # XXX TODO 101: read all following continuation bytes to skip them
57
+ self._on_error("Overlong encoding")
58
+
59
+ def _utf_8000_parse_single(self) -> Generator[None, None, UTF8000Int]:
60
+ parsed_bytes: list[UTF8000Byte] = []
61
+
62
+ start_byte = yield from self._await_byte()
63
+ idx_0 = byte_idx_0(start_byte)
64
+
65
+ if idx_0 == 1:
66
+ self._on_error_invalid_start_byte()
67
+ elif idx_0 == 0:
68
+ n_bytes_expected = 1
69
+ else:
70
+ n_bytes_expected = idx_0
71
+ # when idx_0 == 8, this is 8 *so far!*
72
+
73
+ is_content_byte = idx_0 < 7
74
+ parsed_bytes.append(UTF8000Byte(start_byte, is_start_byte = True, is_continuation_byte = False, is_content_byte = is_content_byte))
75
+
76
+ # multiple start bytes, the power of UTF-8000!
77
+ if idx_0 == 8:
78
+ while True:
79
+ start_byte = yield from self._await_continuation_byte()
80
+ idx_0_content = byte_continuation_content_idx_0(start_byte)
81
+ n_bytes_expected += idx_0_content
82
+
83
+ is_content_byte = idx_0_content < 5
84
+ parsed_bytes.append(UTF8000Byte(start_byte, is_start_byte = True, is_continuation_byte = True, is_content_byte = is_content_byte))
85
+
86
+ if idx_0_content != 6:
87
+ break
88
+
89
+ # overlong checking for non-ASCII
90
+ if idx_0 != 0:
91
+ if idx_0 == 2:
92
+ # special case: we're only checking for activity in the first *4* content bits gained from 1-byte (ASCII) to a 2-byte sequence,
93
+ # whereas every other n-byte sequence checks the first *5* content bits, since 5 bits are gained for every additional continuation byte
94
+ anti_overlong_check_mask_start = 0b00011110
95
+ anti_overlong_check_mask_continuation = 0b00000000 # unused
96
+ else:
97
+ n_bits_overlong_check_continuation = divmod(n_bytes_expected - 2, 6)[1]
98
+ n_bits_overlong_check_start = 5 - n_bits_overlong_check_continuation
99
+ # lower bits of start byte contents
100
+ anti_overlong_check_mask_start = (1 << n_bits_overlong_check_start) - 1
101
+ # upper bits of continuation byte contents
102
+ anti_overlong_check_mask_continuation = ((1 << n_bits_overlong_check_continuation) - 1) << (6 - n_bits_overlong_check_continuation)
103
+
104
+ continuation_byte = yield from self._await_continuation_byte()
105
+
106
+ # at this point `start_byte` is the last start byte
107
+ if not (start_byte & anti_overlong_check_mask_start or continuation_byte & anti_overlong_check_mask_continuation):
108
+ self._on_error_overlong()
109
+
110
+ parsed_bytes.append(UTF8000Byte(continuation_byte, is_start_byte = False, is_continuation_byte = True, is_content_byte = True))
111
+
112
+ # the rest of the continuation bytes
113
+ while len(parsed_bytes) < n_bytes_expected:
114
+ continuation_byte = yield from self._await_continuation_byte()
115
+ parsed_bytes.append(UTF8000Byte(continuation_byte, is_start_byte = False, is_continuation_byte = True, is_content_byte = True))
116
+
117
+ return UTF8000Int(parsed_bytes)
118
+
119
+ def _utf_8000_parse_forever(self) -> Generator[None, None, None]:
120
+ while True:
121
+ self._results.append((yield from self._utf_8000_parse_single()))
UTF8000/UTF8000Int.py ADDED
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from .UTF8000Byte import UTF8000Byte
4
+
5
+ class UTF8000Int:
6
+ def __init__(self, utf_8000_bytes: list[UTF8000Byte]) -> None:
7
+ # No validation is done; we're assuming these come from `UTF8000IncrementalDecoder`
8
+ self.utf_8000_bytes = utf_8000_bytes
9
+
10
+ def __str__(self) -> str:
11
+ return " ".join(str(b) for b in self.utf_8000_bytes)
12
+
13
+ def debug_str(self) -> str:
14
+ return " ".join(b.debug_str() for b in self.utf_8000_bytes)
15
+
16
+ def __int__(self) -> int:
17
+ ret = 0
18
+ content_bytes = (b for b in self.utf_8000_bytes if b.is_content_byte)
19
+ for content_byte in content_bytes:
20
+ ret <<= content_byte.n_content_bits
21
+ ret += content_byte.content
22
+ return ret
23
+
24
+ @property
25
+ def n_bytes(self) -> int:
26
+ return len(self.utf_8000_bytes)
27
+
28
+ @property
29
+ def n_bits_capacity(self) -> int:
30
+ if self.n_bytes == 1:
31
+ return 7
32
+ else:
33
+ return 1 + 5 * self.n_bytes
34
+
35
+ @property
36
+ def n_bits_used(self) -> int:
37
+ content_bytes = (b for b in self.utf_8000_bytes if b.is_content_byte)
38
+ raise NotImplementedError
39
+
40
+ @classmethod
41
+ def blank(cls, n_bytes: int):
42
+ raise NotImplementedError
43
+ if n_bytes < 1:
44
+ raise ValueError
45
+
46
+ if n_bytes == 1:
47
+ segments = bytearray([_ZERO])
48
+
49
+ elif n_bytes < 8:
50
+ # lol whatever this is hardcoded
51
+ segments = bytearray(
52
+ [_ZERO + (((1 << n_bytes) - 1) << (8 - n_bytes))] +
53
+ [_CONTINUATION_PREFIX for _ in range(n_bytes - 1)]
54
+ )
55
+
56
+ else:
57
+ start_byte = [0b11111111]
58
+ # cont bytes
59
+ n_filled, n_bits_partial = divmod(n_bytes - 8, 6)
60
+ filled = [_CONTINUATION_FILLED for _ in range(n_filled)]
61
+ partial = [_CONTINUATION_PREFIX + (((1 << n_bits_partial) - 1) << (6 - n_bits_partial))]
62
+ empty = [
63
+ 0b10000000
64
+ for _ in range(
65
+ n_bytes - (len(start_byte) + len(filled) + len(partial))
66
+ )
67
+ ]
68
+
69
+ segments = bytearray(start_byte + filled + partial + empty)
70
+
71
+ return cls(segments)
72
+
73
+ def promote(self) -> UTF8000Int:
74
+ # reserve another continuation byte, adjust start byte(s), move start byte(s) content to new continuation byte, and pad new prefix with 0s if +ve or 1s if -ve
75
+ raise NotImplementedError
76
+
77
+ def __add__(self, rhs: UTF8000Int) -> UTF8000Int:
78
+ raise NotImplementedError
UTF8000/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """UTF-8000: Unlimited UTF-8!"""
2
+
3
+ __version__ = "1.0.0"
4
+
5
+ from .UTF8000Byte import UTF8000Byte
6
+ from .UTF8000Int import UTF8000Int
7
+ from .UTF8000IncrementalDecoder import UTF8000IncrementalDecoder