""" Clases base híbridas que combinan SymPy con funcionalidad especializada """ import sympy from sympy import Basic, Symbol, sympify from typing import Any, Optional, Dict import re class HybridCalcType(Basic): """ Clase base híbrida que combina SymPy Basic con funcionalidad de calculadora Todas las clases especializadas deben heredar de esta """ def __new__(cls, *args, **kwargs): """Crear objeto SymPy válido""" obj = Basic.__new__(cls) return obj def __init__(self, value, original_str=""): """Inicialización de funcionalidad especializada""" self._value = value self._original_str = original_str self._init_specialized() def _init_specialized(self): """Override en subclases para inicialización especializada""" pass @property def value(self): """Acceso al valor interno""" return self._value @property def original_str(self): """String original de entrada""" return self._original_str # Propiedades requeridas por SymPy @property def args(self): """Argumentos para SymPy - retorna valor como argumento""" return (sympify(self._value),) @property def func(self): """Función constructora para SymPy""" return self.__class__ def _sympystr(self, printer): """Representación SymPy string""" return f"{self.__class__.__name__}({self._original_str})" def _latex(self, printer): """Representación LaTeX""" return self._sympystr(printer) def __str__(self): """Representación string para display""" return str(self._value) def __repr__(self): """Representación para debugging""" return f"{self.__class__.__name__}('{self._original_str}')" def evalf(self, n=15, **options): """Evaluación numérica si es posible""" if isinstance(self._value, (int, float, complex)): return sympy.Float(self._value) return self def _eval_evalf(self, prec): """Evaluación numérica para SymPy""" if isinstance(self._value, (int, float, complex)): return sympy.Float(self._value, prec) return None def __dec__(self): """Conversión a decimal para compatibilidad""" if isinstance(self._value, (int, float, complex)): return self._value raise TypeError(f"Cannot convert {self.__class__.__name__} to decimal") # Métodos requeridos por SymPy para manipulación algebraica def as_coeff_Mul(self, rational=True): """Descomponer como coeficiente * términos""" if isinstance(self._value, (int, float)): return sympify(self._value), sympify(1) return sympify(1), self def as_coeff_Add(self, rational=True): """Descomponer como coeficiente + términos""" if isinstance(self._value, (int, float)): return sympify(self._value), sympify(0) return sympify(0), self def as_base_exp(self): """Descomponer como base^exponente""" return self, sympify(1) def as_numer_denom(self): """Descomponer como numerador/denominador""" return self, sympify(1) # Propiedades de tipo para SymPy @property def is_number(self): """Indica si es un número""" return isinstance(self._value, (int, float, complex)) @property def is_real(self): """Indica si es real""" return isinstance(self._value, (int, float)) and not isinstance(self._value, complex) @property def is_integer(self): """Indica si es entero""" return isinstance(self._value, int) @property def is_rational(self): """Indica si es racional""" return isinstance(self._value, (int, float)) @property def is_irrational(self): """Indica si es irracional""" return False @property def is_positive(self): """Indica si es positivo""" if isinstance(self._value, (int, float)): return self._value > 0 return None @property def is_negative(self): """Indica si es negativo""" if isinstance(self._value, (int, float)): return self._value < 0 return None @property def is_zero(self): """Indica si es cero""" if isinstance(self._value, (int, float)): return self._value == 0 return None @property def is_finite(self): """Indica si es finito""" return isinstance(self._value, (int, float, complex)) @property def is_infinite(self): """Indica si es infinito""" return False @property def is_commutative(self): """Indica si es conmutativo""" return True # Operaciones aritméticas simples que retornan el valor numérico para integración con SymPy def __add__(self, other): """Suma que integra con SymPy""" if isinstance(other, (int, float, complex)): # Retornar valor numérico para que SymPy maneje la operación return sympify(self._value + other) elif hasattr(other, '_value'): return sympify(self._value + other._value) else: # Dejar que SymPy maneje la operación return sympify(self._value) + sympify(other) def __radd__(self, other): """Suma reversa""" return sympify(other) + sympify(self._value) def __mul__(self, other): """Multiplicación que integra con SymPy""" if isinstance(other, (int, float, complex)): return sympify(self._value * other) elif hasattr(other, '_value'): return sympify(self._value * other._value) else: return sympify(self._value) * sympify(other) def __rmul__(self, other): """Multiplicación reversa""" return sympify(other) * sympify(self._value) def __sub__(self, other): """Resta""" if isinstance(other, (int, float, complex)): return sympify(self._value - other) elif hasattr(other, '_value'): return sympify(self._value - other._value) else: return sympify(self._value) - sympify(other) def __rsub__(self, other): """Resta reversa""" return sympify(other) - sympify(self._value) def __truediv__(self, other): """División""" if isinstance(other, (int, float, complex)): if other == 0: raise ZeroDivisionError("Division by zero") return sympify(self._value / other) elif hasattr(other, '_value'): if other._value == 0: raise ZeroDivisionError("Division by zero") return sympify(self._value / other._value) else: return sympify(self._value) / sympify(other) def __rtruediv__(self, other): """División reversa""" if self._value == 0: raise ZeroDivisionError("Division by zero") return sympify(other) / sympify(self._value) def __pow__(self, other): """Potencia""" if isinstance(other, (int, float, complex)): return sympify(self._value ** other) elif hasattr(other, '_value'): return sympify(self._value ** other._value) else: return sympify(self._value) ** sympify(other) def __rpow__(self, other): """Potencia reversa""" return sympify(other) ** sympify(self._value) @staticmethod def Helper(input_str): """Override en subclases para ayuda contextual""" return None class HybridHex(HybridCalcType): """Clase híbrida para números hexadecimales""" def __new__(cls, value_input): obj = HybridCalcType.__new__(cls) return obj def __init__(self, value_input): processed_value = None original_str = str(value_input) if isinstance(value_input, HybridCalcType): processed_value = int(value_input.__dec__()) elif isinstance(value_input, (int, float)): processed_value = int(value_input) else: # String input str_input = str(value_input).lower() if str_input.startswith("0x"): processed_value = int(str_input, 16) else: processed_value = int(str_input, 16) super().__init__(processed_value, original_str) def __str__(self): return f"0x{self._value:X}" def _sympystr(self, printer): return f"Hex({self._value})" @staticmethod def Helper(input_str): if re.match(r"^\s*Hex(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): return 'Ej: Hex[FF] o Hex[255]' return None def toDecimal(self): """Convertir a decimal""" return self._value class HybridBin(HybridCalcType): """Clase híbrida para números binarios""" def __new__(cls, value_input): obj = HybridCalcType.__new__(cls) return obj def __init__(self, value_input): processed_value = None original_str = str(value_input) if isinstance(value_input, HybridCalcType): processed_value = int(value_input.__dec__()) elif isinstance(value_input, (int, float)): processed_value = int(value_input) else: # String input str_input = str(value_input).lower() if str_input.startswith("0b"): processed_value = int(str_input, 2) else: processed_value = int(str_input, 2) super().__init__(processed_value, original_str) def __str__(self): return f"0b{bin(self._value)[2:]}" def _sympystr(self, printer): return f"Bin({self._value})" @staticmethod def Helper(input_str): if re.match(r"^\s*Bin(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): return 'Ej: Bin[1011] o Bin[11]' return None class HybridDec(HybridCalcType): """Clase híbrida para números decimales""" def __new__(cls, value_input): obj = HybridCalcType.__new__(cls) return obj def __init__(self, value_input): processed_value = None original_str = str(value_input) if isinstance(value_input, HybridCalcType): processed_value = float(value_input.__dec__()) elif isinstance(value_input, (int, float)): processed_value = float(value_input) else: try: processed_value = float(str(value_input)) except (ValueError, TypeError) as e: raise ValueError(f"Invalid decimal value: '{value_input}'") from e super().__init__(processed_value, original_str) def __str__(self): if self._value == int(self._value): return str(int(self._value)) return str(self._value) def _sympystr(self, printer): return f"Dec({self._value})" @staticmethod def Helper(input_str): if re.match(r"^\s*Dec(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): return 'Ej: Dec[10.5] o Dec[10]' return None class HybridIP4(HybridCalcType): """Clase híbrida para direcciones IPv4""" def __new__(cls, *args): obj = HybridCalcType.__new__(cls) return obj def __init__(self, *args): import socket import struct if not args: raise ValueError("IP4() constructor requires at least one argument.") ip_str = None prefix = None if len(args) == 1: arg = args[0] if not isinstance(arg, str): raise ValueError(f"IP4 single argument must be a string, got '{arg}'") if "/" in arg: # CIDR notation parts = arg.split('/', 1) ip_str = parts[0] if len(parts) == 2 and parts[1].isdigit(): prefix_val = int(parts[1]) if not self._is_valid_prefix(prefix_val): raise ValueError(f"Invalid CIDR prefix '{parts[1]}' in '{arg}'") prefix = prefix_val else: raise ValueError(f"Invalid CIDR format: '{arg}'") else: ip_str = arg elif len(args) == 2: ip_arg, mask_arg = args[0], args[1] if not isinstance(ip_arg, str): raise ValueError(f"IP4 first argument must be an IP string") ip_str = ip_arg if isinstance(mask_arg, int): if not self._is_valid_prefix(mask_arg): raise ValueError(f"Invalid prefix length: {mask_arg}") prefix = mask_arg elif isinstance(mask_arg, str): if mask_arg.isdigit(): prefix_val = int(mask_arg) if self._is_valid_prefix(prefix_val): prefix = prefix_val else: parsed_prefix = self._netmask_str_to_prefix(mask_arg) if parsed_prefix is None: raise ValueError(f"Invalid mask string: '{mask_arg}'") prefix = parsed_prefix else: parsed_prefix = self._netmask_str_to_prefix(mask_arg) if parsed_prefix is None: raise ValueError(f"Invalid netmask string: '{mask_arg}'") prefix = parsed_prefix else: raise ValueError(f"IP4() constructor takes 1 or 2 arguments, got {len(args)}") if not self._is_valid_ip_string(ip_str): raise ValueError(f"Invalid IP address string: '{ip_str}'") self.ip_address = ip_str self.prefix = prefix # Valor para SymPy - usar representación numérica de la IP ip_as_int = struct.unpack("!I", socket.inet_aton(ip_str))[0] canonical_repr = f"{ip_str}/{prefix}" if prefix is not None else ip_str super().__init__(ip_as_int, canonical_repr) def _is_valid_ip_string(self, ip_str: str) -> bool: """Verifica si es una dirección IP válida""" import socket try: socket.inet_aton(ip_str) parts = ip_str.split(".") return len(parts) == 4 and all(0 <= int(part) <= 255 for part in parts) except (socket.error, ValueError, TypeError): return False def _is_valid_prefix(self, prefix: int) -> bool: """Verifica si es un prefijo CIDR válido""" return isinstance(prefix, int) and 0 <= prefix <= 32 def _netmask_str_to_prefix(self, netmask_str: str) -> Optional[int]: """Convierte máscara decimal a prefijo CIDR""" import socket import struct if not self._is_valid_ip_string(netmask_str): return None try: mask_int = struct.unpack("!I", socket.inet_aton(netmask_str))[0] except socket.error: return None binary_mask = bin(mask_int)[2:].zfill(32) if '0' in binary_mask and '1' in binary_mask[binary_mask.find('0'):]: return None return binary_mask.count('1') def __str__(self): if self.prefix is not None: return f"{self.ip_address}/{self.prefix}" return self.ip_address def _sympystr(self, printer): return f"IP4({self.__str__()})" @staticmethod def Helper(input_str): if re.match(r"^\s*IP4(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): return 'Ej: IP4[192.168.1.1/24] o IP4[10.0.0.1]' return None def NetworkAddress(self): """Retorna la dirección de red""" import socket import struct if self.prefix is None: raise ValueError("Mask not set, cannot calculate network address.") ip_int = struct.unpack("!I", socket.inet_aton(self.ip_address))[0] mask_int = (0xFFFFFFFF << (32 - self.prefix)) & 0xFFFFFFFF if self.prefix > 0 else 0 net_addr_int = ip_int & mask_int net_addr_str = socket.inet_ntoa(struct.pack("!I", net_addr_int)) return HybridIP4(net_addr_str, self.prefix) def BroadcastAddress(self): """Retorna la dirección de broadcast""" import socket import struct if self.prefix is None: raise ValueError("Mask not set, cannot calculate broadcast address.") net_addr_obj = self.NetworkAddress() net_addr_int = struct.unpack("!I", socket.inet_aton(net_addr_obj.ip_address))[0] wildcard_mask = ((1 << (32 - self.prefix)) - 1) if self.prefix < 32 else 0 bcast_addr_int = net_addr_int | wildcard_mask bcast_addr_str = socket.inet_ntoa(struct.pack("!I", bcast_addr_int)) return HybridIP4(bcast_addr_str, self.prefix) def Nodes(self): """Retorna el número de hosts disponibles""" if self.prefix is None: raise ValueError("Mask not set, cannot calculate number of nodes.") host_bits = 32 - self.prefix if host_bits < 2: return 0 return (1 << host_bits) - 2 class HybridChr(HybridCalcType): """Clase híbrida para caracteres ASCII""" def __new__(cls, str_input): obj = HybridCalcType.__new__(cls) return obj def __init__(self, str_input: str): if not isinstance(str_input, str): raise TypeError(f"Chr input must be a string, got {type(str_input).__name__}") if not str_input: raise ValueError("Chr input string cannot be empty.") if len(str_input) == 1: processed_value = ord(str_input) else: processed_value = [ord(c) for c in str_input] super().__init__(processed_value, str_input) def __str__(self): if isinstance(self._value, int): return chr(self._value) elif isinstance(self._value, list): return "".join(map(chr, self._value)) return super().__str__() def _sympystr(self, printer): return f"Chr({self._original_str})" @staticmethod def Helper(input_str): if re.match(r"^\s*Chr(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): return 'Ej: Chr[A] o Chr[Hello]. Representa caracteres ASCII.' return None # Alias para compatibilidad Hex = HybridHex Bin = HybridBin Dec = HybridDec IP4 = HybridIP4 Chr = HybridChr # Funciones de testing def test_hybrid_classes(): """Test de las clases híbridas""" print("=== Test Clases Híbridas ===") # Test Hex h = Hex("FF") print(f"Hex('FF'): {h} (type: {type(h)})") print(f" SymPy str: {h._sympystr(None)}") print(f" Is SymPy Basic: {isinstance(h, sympy.Basic)}") print(f" Value: {h._value}") # Test operación simple try: result = h + 1 print(f" Hex + 1: {result} (type: {type(result)})") except Exception as e: print(f" Error en Hex + 1: {e}") # Test IP4 ip = IP4("192.168.1.100/24") print(f"IP4('192.168.1.100/24'): {ip}") print(f" NetworkAddress: {ip.NetworkAddress()}") print(f" Is SymPy Basic: {isinstance(ip, sympy.Basic)}") # Test Chr c = Chr("A") print(f"Chr('A'): {c}") print(f" ASCII value: {c._value}") print(f" Is SymPy Basic: {isinstance(c, sympy.Basic)}") if __name__ == "__main__": test_hybrid_classes()