306 lines
10 KiB
Python
306 lines
10 KiB
Python
"""
|
|
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
|
|
|
|
|
|
# ========== FUNCIÓN DE TOKENIZACIÓN ACTUALIZADA ==========
|
|
|
|
def preprocess_tokens(expression):
|
|
"""
|
|
Función de tokenización que usa el registro dinámico de tipos
|
|
NUEVA VERSIÓN: Obtiene clases desde custom_types/ automáticamente
|
|
"""
|
|
# Intentar obtener las clases desde el registro
|
|
try:
|
|
from type_registry import get_registered_base_context
|
|
context = get_registered_base_context()
|
|
|
|
IntBase = context.get('IntBase')
|
|
FourBytes = context.get('FourBytes')
|
|
|
|
if not IntBase or not FourBytes:
|
|
# Fallback: tokenización básica sin clases
|
|
return _basic_tokenization(expression)
|
|
|
|
except ImportError:
|
|
# Si el registro no está disponible, usar tokenización básica
|
|
return _basic_tokenization(expression)
|
|
|
|
# Tokenización completa con clases disponibles
|
|
return _full_tokenization(expression, IntBase, FourBytes)
|
|
|
|
def _full_tokenization(expression, IntBase, FourBytes):
|
|
"""Tokenización completa usando las clases registradas"""
|
|
import re
|
|
|
|
def replace_intbase(match):
|
|
base = match.group(1)
|
|
value = match.group(2)
|
|
return f'IntBase("{value}", {base})'
|
|
|
|
def replace_fourbytes(match):
|
|
pattern = match.group(1)
|
|
return f'FourBytes("{pattern}")'
|
|
|
|
# PASO 1: IntBase - precedencia MAYOR (patrón más específico)
|
|
# Patrón: número#valor (ej: 16#FF, 2#1010)
|
|
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', replace_intbase, expression)
|
|
|
|
# PASO 2: FourBytes - precedencia MENOR (patrón más general)
|
|
# Patrón: x.x.x.x donde x puede ser número o símbolo
|
|
# Usar \b para boundary, evitar conflictos con acceso a atributos
|
|
expression = re.sub(r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b',
|
|
replace_fourbytes, expression)
|
|
|
|
return expression
|
|
|
|
def _basic_tokenization(expression):
|
|
"""Tokenización básica cuando las clases no están disponibles"""
|
|
# Solo hacer transformaciones básicas sin objetos
|
|
return expression |