""" Clase base híbrida que combina SymPy con funcionalidad especializada """ import sympy from sympy import Basic, Symbol, sympify from typing import Any, Optional, Dict import re from class_base import ClassBase class SympyClassBase(ClassBase, sympy.Basic): """Para clases que necesitan álgebra completa""" def __new__(cls, *args, **kwargs): # La subclase (ej. Class_IP4) es responsable de pasar los argumentos correctos # que sympy.Basic.__new__ espera. obj = sympy.Basic.__new__(cls, *args, **kwargs) return obj def __init__(self, value, original_str=""): ClassBase.__init__(self, value, original_str) # La inicialización de sympy.Basic se maneja principalmente a través de __new__. # Atributos adicionales de sympy como self.args son establecidos por sympy.Basic # basado en los argumentos pasados a __new__. def _sympystr(self, printer): # Las subclases deben implementar esto para una representación SymPy adecuada. # Por defecto, usa el __str__ de ClassBase, que es str(self.value). # Si self.value es un objeto Sympy, str(self.value) ya dará una buena representación. # Si self.value no es un objeto Sympy pero se desea una forma SymPy específica, # esto debería ser sobreescrito. return str(self.value) 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 _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 # ========== CLASES BASE UNIVERSALES PARA TOKENIZACIÓN ========== class IntBase(SympyClassBase): """ Representación universal de números en cualquier base con soporte algebraico completo y operaciones aritméticas """ def __init__(self, value_str, base=10): self.value_str = str(value_str) self.base = int(base) # Detección mejorada de símbolos: considerar la base self.has_symbols = self._has_symbols_for_base() if self.has_symbols: # Modo algebraico: mantener como expresión simbólica self._symbolic_value = self._parse_symbolic_base() self._numeric_value = None # No hay valor numérico super().__init__(self._symbolic_value, f"{self.base}#{self.value_str}") else: # Modo numérico: convertir a entero self._numeric_value = int(self.value_str, self.base) self._symbolic_value = None # No hay valor simbólico super().__init__(self._numeric_value, f"{self.base}#{self.value_str}") def _has_symbols_for_base(self): """Detecta si hay símbolos considerando la base numérica""" valid_digits = "0123456789ABCDEF"[:self.base] for char in self.value_str.upper(): if char not in valid_digits: # Es un símbolo (no un dígito válido para esta base) return True return False def _parse_symbolic_base(self): """Convierte valor con símbolos a expresión SymPy""" # Ejemplo: "x0" base 16 → x*16^1 + 0*16^0 # Ejemplo: "101x" base 2 → 1*2^3 + 0*2^2 + 1*2^1 + x*2^0 result = 0 digits = list(self.value_str) for i, digit in enumerate(reversed(digits)): power = self.base ** i if digit.isdigit(): coefficient = int(digit) elif digit in 'ABCDEF': coefficient = ord(digit) - ord('A') + 10 elif digit in 'abcdef': coefficient = ord(digit) - ord('a') + 10 else: # Es un símbolo coefficient = sympy.Symbol(digit) result += coefficient * power return result def to_base(self, new_base): """Convierte a nueva base""" if self.has_symbols: # En modo algebraico, mantener expresión return IntBase(f"({self._symbolic_value})_base{new_base}", new_base) else: # En modo numérico, convertir directamente new_value_str = self._convert_to_base_string(self._numeric_value, new_base) return IntBase(new_value_str, new_base) @staticmethod def _convert_to_base_string(value, base): """Convierte entero a string en base específica""" if value == 0: return "0" digits = "0123456789ABCDEF" result = "" while value: result = digits[value % base] + result value //= base return result @property def value(self): """Propiedad para acceder al valor (numérico o simbólico)""" if self.has_symbols: return self._symbolic_value else: return self._numeric_value # ========== OPERADORES ARITMÉTICOS ========== def __add__(self, other): """Suma: mantiene la base del operando izquierdo""" if self.has_symbols: return super().__add__(other) # Delegar a SymPy if isinstance(other, IntBase): if other.has_symbols: return super().__add__(other) result_value = self._numeric_value + other._numeric_value elif isinstance(other, int): result_value = self._numeric_value + other else: return super().__add__(other) result_str = self._convert_to_base_string(result_value, self.base) return IntBase(result_str, self.base) def __sub__(self, other): """Resta: mantiene la base del operando izquierdo""" if self.has_symbols: return super().__sub__(other) if isinstance(other, IntBase): if other.has_symbols: return super().__sub__(other) result_value = self._numeric_value - other._numeric_value elif isinstance(other, int): result_value = self._numeric_value - other else: return super().__sub__(other) result_str = self._convert_to_base_string(result_value, self.base) return IntBase(result_str, self.base) def __mul__(self, other): """Multiplicación: mantiene la base del operando izquierdo""" if self.has_symbols: return super().__mul__(other) if isinstance(other, IntBase): if other.has_symbols: return super().__mul__(other) result_value = self._numeric_value * other._numeric_value elif isinstance(other, int): result_value = self._numeric_value * other else: return super().__mul__(other) result_str = self._convert_to_base_string(result_value, self.base) return IntBase(result_str, self.base) def __truediv__(self, other): """División: mantiene la base del operando izquierdo""" if self.has_symbols: return super().__truediv__(other) if isinstance(other, IntBase): if other.has_symbols: return super().__truediv__(other) result_value = self._numeric_value // other._numeric_value # División entera elif isinstance(other, int): result_value = self._numeric_value // other else: return super().__truediv__(other) result_str = self._convert_to_base_string(result_value, self.base) return IntBase(result_str, self.base) def __mod__(self, other): """Módulo: mantiene la base del operando izquierdo""" if self.has_symbols: return super().__mod__(other) if isinstance(other, IntBase): if other.has_symbols: return super().__mod__(other) result_value = self._numeric_value % other._numeric_value elif isinstance(other, int): result_value = self._numeric_value % other else: return super().__mod__(other) result_str = self._convert_to_base_string(result_value, self.base) return IntBase(result_str, self.base) # Operadores reversos para int + IntBase def __radd__(self, other): return self.__add__(other) def __rsub__(self, other): if isinstance(other, int): result_value = other - self._numeric_value result_str = self._convert_to_base_string(result_value, self.base) return IntBase(result_str, self.base) return super().__rsub__(other) def __rmul__(self, other): return self.__mul__(other) def __str__(self): """Representación string""" return f"{self.base}#{self.value_str}" def __repr__(self): return f"IntBase('{self.value_str}', {self.base})" class FourBytes(SympyClassBase): """ Representación universal de patrones de 4 elementos con soporte algebraico completo y operaciones aritméticas """ def __init__(self, dotted_string): self.original = str(dotted_string) self.elements = self.original.split('.') if len(self.elements) != 4: raise ValueError(f"FourBytes requiere exactamente 4 elementos: {dotted_string}") self.has_symbols = any(not elem.isdigit() for elem in self.elements) if self.has_symbols: # Modo algebraico: crear expresión simbólica self._symbolic_elements = [] for elem in self.elements: if elem.isdigit(): self._symbolic_elements.append(int(elem)) else: self._symbolic_elements.append(sympy.Symbol(elem)) # CORREGIDO: Crear expresión escalar en lugar de Matrix para compatibilidad con IP4 # Convertir a representación de entero de 32 bits simbólico self._symbolic_value = (self._symbolic_elements[0] * 2**24 + self._symbolic_elements[1] * 2**16 + self._symbolic_elements[2] * 2**8 + self._symbolic_elements[3]) super().__init__(self._symbolic_value, dotted_string) else: # Modo numérico: validar rangos y convertir self._numeric_elements = [int(elem) for elem in self.elements] # Crear valor como entero de 32 bits (para IPs) self._numeric_value = (self._numeric_elements[0] << 24 | self._numeric_elements[1] << 16 | self._numeric_elements[2] << 8 | self._numeric_elements[3]) super().__init__(self._numeric_value, dotted_string) def is_valid_ip_range(self): """Verifica si todos los elementos numéricos están en rango IP""" if self.has_symbols: return None # No se puede validar con símbolos return all(0 <= x <= 255 for x in self._numeric_elements) def substitute(self, **kwargs): """Sustituye símbolos por valores""" if not self.has_symbols: return self new_elements = [] for elem in self.elements: if elem in kwargs: new_elements.append(str(kwargs[elem])) else: new_elements.append(elem) return FourBytes('.'.join(new_elements)) def __getitem__(self, index): """Acceso a elementos individuales""" if self.has_symbols: return self._symbolic_elements[index] else: return self._numeric_elements[index] def to_ip_int(self): """Convierte a entero de 32 bits (para IPs)""" if self.has_symbols: # CORREGIDO: Ya tenemos la expresión escalar directamente return self._symbolic_value else: return self._numeric_value @staticmethod def _int_to_fourbytes(value): """Convierte entero de 32 bits a formato x.y.z.w""" w = value & 0xFF z = (value >> 8) & 0xFF y = (value >> 16) & 0xFF x = (value >> 24) & 0xFF return f"{x}.{y}.{z}.{w}" # ========== OPERADORES ARITMÉTICOS ========== def __add__(self, other): """Suma: convierte a int, opera, reconvierte a FourBytes""" if self.has_symbols: return super().__add__(other) # Delegar a SymPy if isinstance(other, FourBytes): if other.has_symbols: return super().__add__(other) result_int = self._numeric_value + other._numeric_value elif isinstance(other, int): result_int = self._numeric_value + other else: return super().__add__(other) # Mantener en rango de 32 bits result_int = result_int & 0xFFFFFFFF result_str = self._int_to_fourbytes(result_int) return FourBytes(result_str) def __sub__(self, other): """Resta: convierte a int, opera, reconvierte a FourBytes""" if self.has_symbols: return super().__sub__(other) if isinstance(other, FourBytes): if other.has_symbols: return super().__sub__(other) result_int = self._numeric_value - other._numeric_value elif isinstance(other, int): result_int = self._numeric_value - other else: return super().__sub__(other) # Mantener en rango de 32 bits (underflow se convierte en valor alto) result_int = result_int & 0xFFFFFFFF result_str = self._int_to_fourbytes(result_int) return FourBytes(result_str) # ========== CONVERSIONES DE BASE ========== def ToBase(self, base): """ Convierte cada elemento a la base especificada Retorna expresión con cada elemento convertido """ if self.has_symbols: # Para elementos simbólicos, retornar expresión algebraica converted_elements = [] for elem in self.elements: if elem.isdigit(): int_val = int(elem) converted = IntBase._convert_to_base_string(int_val, base) converted_elements.append(f"{base}#{converted}") else: # Símbolo: expresar como conversión algebraica converted_elements.append(f"{base}#{elem}") return '.'.join(converted_elements) else: # Para elementos numéricos, conversión directa converted_elements = [] for elem_val in self._numeric_elements: converted = IntBase._convert_to_base_string(elem_val, base) converted_elements.append(f"{base}#{converted}") return '.'.join(converted_elements) def ToCIDR(self, prefix_length): """Convierte a notación CIDR""" return f"{self.original}/{prefix_length}" def ToHex(self): """Convierte cada elemento a hexadecimal""" return self.ToBase(16) def ToBinary(self): """Convierte cada elemento a binario""" return self.ToBase(2) def ToOctal(self): """Convierte cada elemento a octal""" return self.ToBase(8) def __str__(self): """Representación string""" return self.original def __repr__(self): return f"FourBytes('{self.original}')" # ========== TOKENIZADOR ALGEBRAICO ========== def preprocess_tokens(expression): """ Tokenizador que convierte patrones específicos en objetos tipados con precedencia correcta: IntBase (más específico) antes que FourBytes """ # 1. MAYOR PRECEDENCIA: IntBase (patrón más específico) # Pattern: base#valor expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', r'IntBase("\2", \1)', expression) # 2. MENOR PRECEDENCIA: FourBytes (patrón más general) # Pattern: x.x.x.x pero evitar casos como obj.method # Solo tokenizar si hay exactamente 4 elementos separados por puntos # y no es parte de una cadena de métodos pattern = r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b' def replace_fourbytes(match): candidate = match.group(1) # Verificar que realmente tiene 4 elementos parts = candidate.split('.') if len(parts) == 4: return f'FourBytes("{candidate}")' return candidate expression = re.sub(pattern, replace_fourbytes, expression) return expression