607 lines
20 KiB
Python
607 lines
20 KiB
Python
"""
|
|
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()
|