#!/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)