501 lines
16 KiB
Python
501 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from math import log2, ceil
|
|
import string
|
|
from typing import Any, Optional, Sequence, Self
|
|
from re import compile as re_compile, Pattern as REPattern, IGNORECASE as RE_IGNORECASE
|
|
|
|
class ErrorsManager:
|
|
|
|
ALPHABET:tuple[str] = tuple("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")
|
|
|
|
ERRORS_MESSAGES:tuple[str] = (
|
|
"invalid_alphabet",
|
|
"invalid_base",
|
|
"invalid_alphabet_type",
|
|
"too_short_alphabet",
|
|
"repeated_characters_in_alphabet",
|
|
"too_long_alphabet",
|
|
"base_lower_than_2",
|
|
"base_greater_than_128",
|
|
"base_greater_than_alphabet"
|
|
)
|
|
|
|
RE_KEY:REPattern = re_compile(r"^[a-z_][a-z0-9_]*$", RE_IGNORECASE)
|
|
|
|
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
|
|
|
|
self.__error:int = 0
|
|
self.__alphabet:tuple[str] = tuple()
|
|
self.__dictionary:dict[str, int] = {}
|
|
self.__base:int = 0
|
|
self.__power:int = 0
|
|
|
|
self.set_alphabet(
|
|
self.get("alphabet", inputs, self.ALPHABET),
|
|
self.get("base", inputs, 64)
|
|
)
|
|
|
|
def set_alphabet(self:Self, alphabet:Optional[str|Sequence[str]] = None, base:int = 64) -> int:
|
|
|
|
original_length:int
|
|
i:int
|
|
character:str
|
|
|
|
self.__error = 0
|
|
|
|
if alphabet is None:
|
|
self.__alphabet = self.ALPHABET
|
|
elif self.is_string(alphabet) or self.is_array(alphabet):
|
|
self.__alphabet = tuple(alphabet)
|
|
else:
|
|
self.__error |= 1 << 2
|
|
self.__alphabet = self.ALPHABET
|
|
|
|
original_length = len(self.__alphabet)
|
|
|
|
self.__alphabet = tuple(self.unique(self.__alphabet))
|
|
|
|
if len(self.__alphabet) < 2:
|
|
self.__error |= 1 << 3
|
|
self.__alphabet = self.ALPHABET
|
|
if len(self.__alphabet) != original_length:
|
|
self.__error |= 1 << 4
|
|
if len(self.__alphabet) > 128:
|
|
self.__error |= 1 << 5
|
|
|
|
self.__error |= (
|
|
1 << 0 if base < 2 else
|
|
1 << 1 if base > 128 else
|
|
1 << 2 if base >= len(self.__alphabet) else
|
|
0) << 6
|
|
|
|
if not self.__error >> 6:
|
|
self.__alphabet = self.__alphabet[0:base]
|
|
|
|
self.__power = int(log2(len(self.__alphabet[0:128])))
|
|
self.__base = 2 ** self.__power
|
|
self.__alphabet = self.__alphabet[0:self.__base]
|
|
|
|
for i, character in enumerate(self.__alphabet):
|
|
self.__dictionary[character] = i
|
|
|
|
return self.__error
|
|
|
|
def get_alphabet(self:Self) -> str:
|
|
return "".join(self.__alphabet)
|
|
|
|
def to_array(self:Self, code:str|Sequence[int]|int) -> list[int]:
|
|
if self.is_string(code):
|
|
return [self.__dictionary[character] for character in code]
|
|
if self.is_array(code):
|
|
return list(code)
|
|
if self.is_integer(code):
|
|
|
|
hexas:list[int] = []
|
|
|
|
while code:
|
|
hexas.append(code % self.__base)
|
|
code //= self.__base
|
|
|
|
return hexas or [0]
|
|
return []
|
|
|
|
def to_string(self:Self, code:list[int]|str|Sequence[int]|int) -> str:
|
|
if self.is_string(code):
|
|
return code
|
|
if self.is_array(code):
|
|
return "".join(self.__alphabet[i] for i in code)
|
|
if self.is_integer(code):
|
|
|
|
hexas:str = ""
|
|
|
|
while code:
|
|
hexas += self.__alphabet[code % self.__base]
|
|
code //= self.__base
|
|
|
|
return hexas or self.__alphabet[0]
|
|
return ""
|
|
|
|
def to_integer(self:Self, code:str|Sequence[int]|int) -> int:
|
|
if self.is_string(code):
|
|
return sum(self.__dictionary[character] * (self.__base ** i) for i, character in enumerate(code))
|
|
if self.is_array(code):
|
|
return sum(i * (self.__base ** j) for j, i in enumerate(code))
|
|
if self.is_integer(code):
|
|
return code
|
|
return 0
|
|
|
|
def to_string_binary(self:Self, code:str|Sequence[int]|int) -> str:
|
|
if self.is_string(code):
|
|
return "".join(("0000000" + "{0:b}".format(self.__dictionary[character]))[-self.__power:] for character in code[::-1])
|
|
if self.is_array(code):
|
|
return "".join(("0000000" + "{0:b}".format(hexa))[-self.__power:] for hexa in code[::-1])
|
|
if self.is_integer(code):
|
|
|
|
binary:str = "{0:b}".format(code)
|
|
module:int = len(binary) % self.__power
|
|
|
|
return ("0000000"[:self.__power - module] if module else "") + binary
|
|
return ""
|
|
|
|
def get_bits(self:Self, code:str|Sequence[int]|int) -> int:
|
|
if self.is_string(code):
|
|
return (len(code) - 1) * self.__power + ceil(log2(self.__dictionary[code[-1]] + 1))
|
|
if self.is_array(code):
|
|
return (len(code) - 1) * self.__power + ceil(log2(code[-1] + 1))
|
|
if self.is_integer(code):
|
|
return code.bit_length()
|
|
return 0
|
|
|
|
def get_from_bits(self:Self, code:str|Sequence[int]|int, _from:int, bits:int) -> tuple[int, int]:
|
|
|
|
if _from < 0:
|
|
_from = self.get_bits(code) + _from
|
|
if _from < 0:
|
|
_from = 0
|
|
if bits < 0:
|
|
_from += bits
|
|
bits = -bits
|
|
if _from < 0:
|
|
bits += _from
|
|
_from = 0
|
|
|
|
return _from, bits
|
|
|
|
def clean(self:Self, code:str|Sequence[int]|int) -> str|Sequence[int]|int|None:
|
|
if self.is_string(code):
|
|
|
|
l:int = len(code)
|
|
|
|
while l > 0 and code[l - 1] != self.__alphabet[0]:
|
|
l -= 1
|
|
|
|
return code[:l] or self.__alphabet[0]
|
|
if self.is_array(code):
|
|
|
|
l:int = len(code)
|
|
|
|
while l > 0 and code[l - 1] == 0:
|
|
l -= 1
|
|
|
|
return code[:l] or [0]
|
|
if self.is_integer(code):
|
|
return code
|
|
return None
|
|
|
|
def get_range(self:Self, code:str|Sequence[int]|int, _from:int, bits:int) -> str|Sequence[int]|int|None:
|
|
if self.is_string(code):
|
|
return self.to_string(self.get_range(self.to_array(code), _from, bits))
|
|
if self.is_array(code):
|
|
|
|
hexas:list[int]
|
|
|
|
_from, bits = self.get_from_bits(code, _from, bits)
|
|
|
|
if not bits:
|
|
bits = self.get_bits(code) - _from
|
|
if bits <= 0:
|
|
return [0]
|
|
|
|
hexas = list(code)
|
|
|
|
if _from > 0:
|
|
|
|
shift:int = _from % self.__power
|
|
mask:int = (self.__base) - 1
|
|
|
|
hexas = hexas[_from // self.__power:]
|
|
if shift and len(hexas):
|
|
|
|
hexa:int
|
|
i:int
|
|
|
|
for i, hexa in enumerate(hexas[:-1]):
|
|
hexas[i] = ((hexa >> shift) | (hexas[i + 1] << self.__power - shift)) & mask
|
|
hexas[-1] >>= shift
|
|
|
|
if bits > 0:
|
|
|
|
shift:int = bits % self.__power
|
|
|
|
hexas = hexas[0:int(ceil(bits / self.__power))]
|
|
if shift and len(hexas):
|
|
hexas[len(hexas) - 1] &= (1 << shift) - 1
|
|
|
|
return self.clean(hexas)
|
|
if self.is_integer(code):
|
|
|
|
_from, bits = self.get_from_bits(code, _from, bits)
|
|
|
|
if not bits:
|
|
bits = self.get_bits(code) - _from
|
|
if bits <= 0 or _from >= 31:
|
|
return 0
|
|
|
|
if _from + bits > 31:
|
|
bits = 31 - _from
|
|
|
|
return (code >> _from) & ((1 << bits) - 1)
|
|
return None
|
|
|
|
def process(self:Self, code:str|Sequence[int]|int, messages:Sequence[str], blocks:dict[int, int]) -> list[tuple[int, str]]:
|
|
if self.is_string(code) or self.is_integer(code):
|
|
return self.process(self.to_array(code), messages, blocks)
|
|
if self.is_array(code):
|
|
|
|
response:list[tuple[int, str]] = []
|
|
k:int = 0
|
|
i:int
|
|
l:int = len(messages)
|
|
i:int = 0
|
|
m:int = len(code) * self.__power
|
|
|
|
if blocks is None:
|
|
blocks = {}
|
|
|
|
while i < m:
|
|
if i in blocks and blocks[i]:
|
|
|
|
logarithm:int = int(ceil(log2(blocks[i])))
|
|
_k:int = self.to_integer(self.get_range(code, i, logarithm if logarithm else (logarithm := 0)))
|
|
|
|
if _k:
|
|
_k += k
|
|
response.append((_k, messages[k] if k < l else "error_message_" + str(_k)))
|
|
|
|
k += blocks[i]
|
|
i += logarithm
|
|
|
|
else:
|
|
if code[i // self.__power] & (1 << (i % self.__power)):
|
|
response.append((k, messages[k] if k < l else "error_message_" + str(k)))
|
|
k += 1
|
|
i += 1
|
|
|
|
return response
|
|
return []
|
|
|
|
def bitwise(self:Self, code:str|Sequence[int]|int, bits:int) -> str|Sequence[int]|int|None:
|
|
if self.is_string(code):
|
|
return self.to_string(self.bitwise(self.to_array(code), bits))
|
|
if self.is_array(code):
|
|
if not len(code) or not bits:
|
|
return self.clean(code)
|
|
|
|
shift:int = abs(bits) % self.__power
|
|
mask:int = self.__base - 1
|
|
hexas:list[int] = list(code)
|
|
|
|
if bits < 0:
|
|
|
|
hexas = hexas[-bits // self.__power:]
|
|
|
|
if shift and len(hexas):
|
|
|
|
hexa:int
|
|
i:int
|
|
|
|
for i, hexa in enumerate(hexas[:-1]):
|
|
hexas[i] = ((hexa >> shift) | (hexas[i + 1] << self.__power - shift)) & mask
|
|
hexas[-1] >>= shift
|
|
|
|
else:
|
|
|
|
if shift:
|
|
|
|
last_hexa:int = hexas[-1] << shift
|
|
|
|
for i, hexa in reversed(list(enumerate(hexas))[1:]):
|
|
hexas[i] = ((hexas[i] << shift) | (hexas[i - 1] >> self.__power - shift)) & mask
|
|
hexas[0] = (hexas[0] << shift) & mask
|
|
|
|
if last_hexa >= self.__base:
|
|
hexas.append(last_hexa >> self.__power)
|
|
|
|
hexas = [0] * (bits // self.__power) + hexas
|
|
|
|
return self.clean(hexas)
|
|
if self.is_integer(code):
|
|
return (
|
|
code << bits if bits > 0 else
|
|
code >> -bits if bits < 0 else
|
|
code)
|
|
return None
|
|
|
|
def reset(self:Self, code:str|Sequence[int]|int, _from:int, bits:int = 0, reversed:bool = False) -> str|Sequence[int]|int|None:
|
|
if self.is_string(code):
|
|
return self.to_string(self.reset(self.to_array(code), _from, bits, reversed))
|
|
if self.is_array(code):
|
|
|
|
hexas:list[int] = list(code)
|
|
hexa_from:int
|
|
hexa_to:int
|
|
l:int
|
|
from_mask:int
|
|
to_mask:int
|
|
|
|
_from, bits = self.get_from_bits(code, _from, bits)
|
|
hexa_from = _from // self.__power
|
|
hexa_to = (_from + bits) // self.__power
|
|
|
|
if reversed:
|
|
|
|
i:int
|
|
|
|
l = _from % self.__power
|
|
from_mask = ~((1 << l) - 1)
|
|
to_mask = (1 << ((_from + bits) % self.__power)) - 1
|
|
|
|
for i in range(len(hexas)):
|
|
if i < hexa_from or i > hexa_to:
|
|
hexas[i] = 0
|
|
|
|
if hexa_from == hexa_to:
|
|
hexas[hexa_from] &= from_mask & to_mask
|
|
else:
|
|
hexas[hexa_from] &= from_mask
|
|
if hexa_to < len(hexas):
|
|
hexas[hexa_to] &= to_mask
|
|
|
|
elif hexa_from < len(hexas):
|
|
|
|
l = (_from + bits) % self.__power
|
|
from_mask = ((1 << (_from % self.__power)) - 1)
|
|
to_mask = ((1 << (self.__power - l)) - 1) << l
|
|
|
|
if hexa_from == hexa_to:
|
|
hexas[hexa_from] &= from_mask | to_mask
|
|
else:
|
|
|
|
i:int
|
|
m:int = len(hexas)
|
|
|
|
hexas[hexa_from] &= from_mask
|
|
for i in range(hexa_from + 1, hexa_to):
|
|
if i >= m:
|
|
break
|
|
hexas[i] = 0
|
|
if hexa_to < m:
|
|
hexas[hexa_to] &= to_mask
|
|
|
|
return self.clean(hexas)
|
|
if self.is_integer(code):
|
|
|
|
_from, bits = self.get_from_bits(code, _from, bits)
|
|
if _from + bits > 31:
|
|
bits = 31 - _from
|
|
|
|
return code & (
|
|
((1 << bits) - 1) << _from if reversed else
|
|
((1 << self.get_bits(code)) - 1 << _from + bits) | ((1 << _from) - 1))
|
|
return None
|
|
|
|
def has(self:Self, code:str|Sequence[int]|int, _from:int = 0, bits:int = 0) -> bool:
|
|
if self.is_string(code):
|
|
for hexa in self.get_range(code, _from, bits):
|
|
if self.__dictionary[hexa]:
|
|
return True
|
|
if self.is_array(code):
|
|
for hexa in self.get_range(code, _from, bits):
|
|
if hexa:
|
|
return True
|
|
if self.is_integer(code) and self.get_range(code, _from, bits):
|
|
return True
|
|
return False
|
|
|
|
def set(self:Self, code:str|Sequence[int]|int, error:str|Sequence[int]|int, _from:int, bits:int = 0) -> str|Sequence[int]|int|None:
|
|
if self.is_string(code):
|
|
return self.to_string(self.set(self.to_array(code), error, _from, bits))
|
|
if self.is_array(code):
|
|
|
|
l:int
|
|
n:int = len(error := self.to_array(error))
|
|
m:int = len(code)
|
|
results:list[int] = []
|
|
|
|
if bits:
|
|
m = len(code := self.reset(code, _from, bits))
|
|
if _from:
|
|
n = len(error := self.bitwise(error, _from))
|
|
l = m if m > n else n
|
|
|
|
for i in range(l):
|
|
results.append(
|
|
(error[i] if i < n else 0) |
|
|
(code[i] if i < m else 0)
|
|
)
|
|
|
|
return self.clean(results)
|
|
if self.is_integer(code):
|
|
|
|
error = self.to_integer(error)
|
|
if bits:
|
|
code = self.reset(code, _from, bits)
|
|
if _from:
|
|
error = self.bitwise(error, _from)
|
|
|
|
return error | code
|
|
return None
|
|
|
|
@classmethod
|
|
def get_keys(cls:type[Self], *items:list[Any|None]) -> list[str]:
|
|
|
|
keys:list[str] = []
|
|
|
|
for item in items:
|
|
if isinstance(item, str):
|
|
cls.RE_KEY.match(item) and item not in keys and keys.append(item)
|
|
elif isinstance(item, (list, tuple)):
|
|
|
|
key:str
|
|
|
|
for key in cls.get_keys(*item):
|
|
key not in keys and keys.append(key)
|
|
|
|
return keys
|
|
|
|
@classmethod
|
|
def get_dictionaries(cls:type[Self], *items:list[Any|None]) -> list[dict[str, Any|None]]:
|
|
|
|
dictionaries:list[dict[str, Any|None]] = []
|
|
|
|
for item in items:
|
|
if isinstance(item, dict):
|
|
dictionaries.append(item)
|
|
elif isinstance(item, (list, tuple)):
|
|
dictionaries.extend(cls.get_dictionaries(*item))
|
|
|
|
return dictionaries
|
|
|
|
@classmethod
|
|
def get(cls:type[Self], keys:str|list[str], inputs:dict[str, Any|None]|Sequence[Any|None], default:Optional[Any] = None) -> Any|None:
|
|
if len(keys := cls.get_keys(keys)):
|
|
|
|
subinputs:dict[str, Any|None]
|
|
|
|
for subinputs in cls.get_dictionaries(inputs):
|
|
|
|
key:str
|
|
|
|
for key in keys:
|
|
if key in subinputs:
|
|
return subinputs[key]
|
|
return default
|
|
|
|
@staticmethod
|
|
def unique(items:str|bytes|Sequence[Any|None]) -> list[Any|None]:
|
|
return [item for i, item in enumerate(items) if list(items).index(item) == i]
|
|
|
|
@staticmethod
|
|
def is_string(item:Any|None) -> bool:
|
|
return isinstance(item, str)
|
|
|
|
@staticmethod
|
|
def is_array(item:Any|None) -> bool:
|
|
return isinstance(item, (list, tuple))
|
|
|
|
@staticmethod
|
|
def is_integer(item:Any|None) -> bool:
|
|
return isinstance(item, int) |