Implementación de nuevas funciones en el motor de evaluación, incluyendo métodos para limpiar variables y contextos, así como una función para resolver variables en sistemas de ecuaciones. Se actualiza la lógica de análisis de asignaciones y ecuaciones en el parser de corchetes, mejorando la detección de símbolos desconocidos. Se ajusta la configuración de la ventana y se optimizan las funciones de transformación de líneas.
This commit is contained in:
parent
0c7ed33d0d
commit
8f0b0287a5
|
@ -0,0 +1,784 @@
|
|||
# Refactorización: Sistema de Tokenización Algebraica
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
Reemplazar el sistema actual de corchetes por un tokenizador que convierte automáticamente patrones específicos en objetos tipados con capacidades algebraicas completas.
|
||||
|
||||
## Problemas del Sistema Actual
|
||||
|
||||
### Sistema de Corchetes
|
||||
- ❌ Complejidad innecesaria del `BracketParser`
|
||||
- ❌ Conflictos con `eval()` y SymPy
|
||||
- ❌ Sintaxis no estándar
|
||||
- ❌ Beneficios limitados (solo IPs realmente)
|
||||
- ❌ Dificulta la integración algebraica
|
||||
|
||||
### Impacto
|
||||
- Código complejo para casos simples
|
||||
- Curva de aprendizaje innecesaria
|
||||
- Mantenimiento difícil
|
||||
- Extensibilidad limitada
|
||||
|
||||
## Nuevo Sistema: Tokenización Algebraica
|
||||
|
||||
### Filosofía Central
|
||||
**El usuario escribe código Python normal. El parser mejora automáticamente los tipos de datos.**
|
||||
|
||||
### Patrones de Tokenización
|
||||
|
||||
#### 1. **IntBase**: `base#valor`
|
||||
```python
|
||||
# Input usuario # Post-tokenización
|
||||
16#FF → IntBase('FF', 16)
|
||||
2#1010 → IntBase('1010', 2)
|
||||
8#777 → IntBase('777', 8)
|
||||
16#x0 → IntBase('x0', 16) # ← ALGEBRAICO
|
||||
2#101x → IntBase('101x', 2) # ← ALGEBRAICO
|
||||
```
|
||||
|
||||
#### 2. **FourBytes**: `x.x.x.x`
|
||||
```python
|
||||
# Input usuario # Post-tokenización
|
||||
192.168.1.1 → FourBytes('192.168.1.1')
|
||||
255.255.0.0 → FourBytes('255.255.0.0')
|
||||
10.1.x.2 → FourBytes('10.1.x.2') # ← ALGEBRAICO
|
||||
a.b.c.d → FourBytes('a.b.c.d') # ← ALGEBRAICO
|
||||
```
|
||||
|
||||
### Precedencia de Tokenización
|
||||
```python
|
||||
def preprocess_tokens(expression):
|
||||
# 1. MAYOR PRECEDENCIA: IntBase (patrón más específico)
|
||||
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', r'IntBase("\2", \1)', expression)
|
||||
|
||||
# 2. MENOR PRECEDENCIA: FourBytes (patrón más general)
|
||||
expression = re.sub(r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b',
|
||||
r'FourBytes("\1")', expression)
|
||||
|
||||
return expression
|
||||
```
|
||||
|
||||
## Clases Base Universales
|
||||
|
||||
### IntBase: Universal para Bases Numéricas
|
||||
|
||||
```python
|
||||
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 = value_str
|
||||
self.base = base
|
||||
self.has_symbols = bool(re.search(r'[a-zA-Z_]', value_str))
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: mantener como expresión simbólica
|
||||
self._symbolic_value = self._parse_symbolic_base()
|
||||
super().__init__(self._symbolic_value, f"{base}#{value_str}")
|
||||
else:
|
||||
# Modo numérico: convertir a entero
|
||||
self._numeric_value = int(value_str, base)
|
||||
super().__init__(self._numeric_value, f"{base}#{value_str}")
|
||||
|
||||
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
|
||||
symbols = []
|
||||
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)
|
||||
symbols.append(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
|
||||
|
||||
# ========== 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)
|
||||
```
|
||||
|
||||
### FourBytes: Universal para Patrones x.x.x.x
|
||||
|
||||
```python
|
||||
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 = dotted_string
|
||||
self.elements = dotted_string.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))
|
||||
|
||||
# Crear expresión como vector de 4 elementos
|
||||
self._symbolic_value = sympy.Matrix(self._symbolic_elements)
|
||||
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:
|
||||
# Crear expresión algebraica
|
||||
return (self._symbolic_elements[0] * 2**24 +
|
||||
self._symbolic_elements[1] * 2**16 +
|
||||
self._symbolic_elements[2] * 2**8 +
|
||||
self._symbolic_elements[3])
|
||||
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)
|
||||
```
|
||||
|
||||
## Integración con Clases Especializadas
|
||||
|
||||
### Constructores Mejorados
|
||||
|
||||
```python
|
||||
class IP4(SympyClassBase):
|
||||
def __init__(self, address, mask=None):
|
||||
# address es FourBytes (ya tokenizado)
|
||||
if not isinstance(address, FourBytes):
|
||||
raise TypeError("address debe ser FourBytes")
|
||||
|
||||
self.address = address
|
||||
|
||||
if mask is not None:
|
||||
if isinstance(mask, int):
|
||||
self.mask = IP4Mask(mask) # CIDR
|
||||
elif isinstance(mask, FourBytes):
|
||||
self.mask = IP4Mask(mask) # Dotted decimal
|
||||
elif isinstance(mask, IntBase):
|
||||
# Conversión automática desde hex: 16#ffffff00 → máscara
|
||||
if not mask.has_symbols:
|
||||
mask_int = mask._numeric_value
|
||||
# Convertir a FourBytes primero
|
||||
mask_fourbytes = FourBytes(FourBytes._int_to_fourbytes(mask_int))
|
||||
self.mask = IP4Mask(mask_fourbytes)
|
||||
else:
|
||||
self.mask = mask # Mantener simbólico
|
||||
else:
|
||||
self.mask = mask
|
||||
|
||||
class Hex(SympyClassBase):
|
||||
def __init__(self, value):
|
||||
if isinstance(value, IntBase):
|
||||
# value es IntBase (ya tokenizado)
|
||||
self.int_base = value
|
||||
super().__init__(value.value, value.original)
|
||||
elif isinstance(value, FourBytes):
|
||||
# Conversión automática desde FourBytes
|
||||
if not value.has_symbols:
|
||||
# Convertir a valor hex único (32 bits)
|
||||
hex_value = hex(value._numeric_value)[2:].upper()
|
||||
self.int_base = IntBase(hex_value, 16)
|
||||
super().__init__(value._numeric_value, f"16#{hex_value}")
|
||||
else:
|
||||
# Mantener simbólico para análisis algebraico
|
||||
self.int_base = value
|
||||
super().__init__(value._symbolic_value, str(value))
|
||||
else:
|
||||
raise TypeError("value debe ser IntBase o FourBytes")
|
||||
|
||||
class Bin(SympyClassBase):
|
||||
def __init__(self, value):
|
||||
if isinstance(value, IntBase):
|
||||
# Conversión automática de cualquier base a binario
|
||||
if not value.has_symbols:
|
||||
bin_value = bin(value._numeric_value)[2:] # Remover '0b'
|
||||
self.int_base = IntBase(bin_value, 2)
|
||||
super().__init__(value._numeric_value, f"2#{bin_value}")
|
||||
else:
|
||||
self.int_base = value
|
||||
super().__init__(value._symbolic_value, str(value))
|
||||
elif isinstance(value, FourBytes):
|
||||
# Convertir cada elemento a binario
|
||||
if not value.has_symbols:
|
||||
# Para FourBytes, crear representación elemento por elemento
|
||||
bin_elements = []
|
||||
for elem in value._numeric_elements:
|
||||
bin_elements.append(bin(elem)[2:].zfill(8)) # 8 bits por elemento
|
||||
bin_representation = '.'.join(f"2#{elem}" for elem in bin_elements)
|
||||
|
||||
# Crear IntBase con valor completo
|
||||
full_bin = ''.join(bin_elements)
|
||||
self.int_base = IntBase(full_bin, 2)
|
||||
super().__init__(value._numeric_value, bin_representation)
|
||||
else:
|
||||
self.int_base = value
|
||||
super().__init__(value._symbolic_value, str(value))
|
||||
else:
|
||||
raise TypeError("value debe ser IntBase o FourBytes")
|
||||
|
||||
class IP4Mask(ClassBase):
|
||||
def __init__(self, mask_input):
|
||||
if isinstance(mask_input, int):
|
||||
# CIDR notation
|
||||
self.prefix = mask_input
|
||||
self.mask_int = self._prefix_to_mask_int(mask_input)
|
||||
elif isinstance(mask_input, FourBytes):
|
||||
# Dotted decimal desde tokenización automática
|
||||
if not mask_input.has_symbols:
|
||||
self.mask_int = mask_input._numeric_value
|
||||
self.prefix = self._mask_int_to_prefix(self.mask_int)
|
||||
else:
|
||||
# Mantener simbólico
|
||||
self.prefix = None
|
||||
self.mask_int = mask_input._symbolic_value
|
||||
elif isinstance(mask_input, IntBase):
|
||||
# Desde hex u otra base
|
||||
if not mask_input.has_symbols:
|
||||
self.mask_int = mask_input._numeric_value
|
||||
self.prefix = self._mask_int_to_prefix(self.mask_int)
|
||||
else:
|
||||
# Mantener simbólico
|
||||
self.prefix = None
|
||||
self.mask_int = mask_input._symbolic_value
|
||||
else:
|
||||
raise TypeError("mask_input debe ser int, FourBytes, o IntBase")
|
||||
|
||||
super().__init__(self.mask_int, str(mask_input))
|
||||
```
|
||||
|
||||
### Conversiones Automáticas Bidireccionales
|
||||
|
||||
```python
|
||||
# Sistema de conversión fluido entre tipos:
|
||||
|
||||
class UniversalConverter:
|
||||
"""Conversiones automáticas entre todos los tipos"""
|
||||
|
||||
@staticmethod
|
||||
def auto_convert(source, target_type):
|
||||
"""Convierte automáticamente entre tipos compatibles"""
|
||||
|
||||
if target_type == Hex:
|
||||
if isinstance(source, FourBytes):
|
||||
return Hex(source) # Usa constructor mejorado
|
||||
elif isinstance(source, IntBase):
|
||||
return Hex(source)
|
||||
|
||||
elif target_type == IP4:
|
||||
if isinstance(source, FourBytes):
|
||||
return IP4(source)
|
||||
elif isinstance(source, str) and '.' in source:
|
||||
# Tokenización automática
|
||||
tokenized = preprocess_tokens(source)
|
||||
return eval(f"IP4({tokenized})")
|
||||
|
||||
elif target_type == Bin:
|
||||
if isinstance(source, (IntBase, FourBytes)):
|
||||
return Bin(source)
|
||||
|
||||
# Continuar para otros tipos...
|
||||
|
||||
# Ejemplos de uso fluido:
|
||||
|
||||
# 1. IP con máscara hexadecimal (automático):
|
||||
ip1 = IP4(192.168.1.1, 16#ffffff00)
|
||||
# Tokeniza a: IP4(FourBytes('192.168.1.1'), IntBase('ffffff00', 16))
|
||||
# Constructor IP4 convierte IntBase → IP4Mask automáticamente
|
||||
|
||||
# 2. Hex desde IP (automático):
|
||||
ip_bytes = FourBytes('192.168.1.1')
|
||||
hex_ip = Hex(ip_bytes)
|
||||
# Constructor Hex convierte FourBytes → representación hex
|
||||
|
||||
# 3. Análisis de máscara en múltiples bases:
|
||||
mask = FourBytes('255.255.255.0')
|
||||
mask_hex = Hex(mask) # → Hex basado en 32-bit value
|
||||
mask_bin = Bin(mask) # → Bin con representación por elementos
|
||||
mask_cidr = IP4Mask(mask) # → /24
|
||||
|
||||
# 4. Operaciones mixtas automáticas:
|
||||
base_ip = FourBytes('10.0.0.0')
|
||||
offset = 16#100 # Tokeniza a IntBase('100', 16) = 256
|
||||
next_ip = base_ip + offset # → FourBytes('10.0.1.0')
|
||||
```
|
||||
|
||||
## Casos de Uso Algebraicos
|
||||
|
||||
### 1. **Redes con Variables**
|
||||
```python
|
||||
# Usuario escribe:
|
||||
network = IP4(192.168.x.0, 24)
|
||||
|
||||
# Tokenizado automáticamente a:
|
||||
network = IP4(FourBytes('192.168.x.0'), 24)
|
||||
|
||||
# Operaciones algebraicas:
|
||||
network.substitute(x=1) # → IP4(192.168.1.0, 24)
|
||||
network.address[2] # → Symbol('x')
|
||||
```
|
||||
|
||||
### 2. **Aritmética de Bases**
|
||||
```python
|
||||
# Usuario escribe:
|
||||
hex_val = Hex(16#x0)
|
||||
|
||||
# Tokenizado automáticamente a:
|
||||
hex_val = Hex(IntBase('x0', 16))
|
||||
|
||||
# Operaciones algebraicas:
|
||||
hex_val.substitute(x=15) # → Hex(240)
|
||||
hex_val + 1 # → 16*x + 1
|
||||
```
|
||||
|
||||
### 3. **Operaciones Aritméticas con IntBase**
|
||||
```python
|
||||
# Operaciones mantienen la base original:
|
||||
a = 16#FF # → IntBase('FF', 16)
|
||||
b = 16#10 # → IntBase('10', 16)
|
||||
result = a + b # → IntBase('10F', 16) [255 + 16 = 271]
|
||||
|
||||
# Operaciones mixtas con enteros:
|
||||
c = 16#A0 + 32 # → IntBase('C0', 16) [160 + 32 = 192]
|
||||
d = 2#1010 * 3 # → IntBase('11110', 2) [10 * 3 = 30]
|
||||
|
||||
# Operaciones entre bases diferentes:
|
||||
e = 16#FF + 2#1010 # → IntBase('109', 16) [255 + 10 = 265]
|
||||
```
|
||||
|
||||
### 4. **Operaciones Aritméticas con FourBytes**
|
||||
```python
|
||||
# Aritmética de direcciones:
|
||||
ip1 = FourBytes('192.168.1.1') # → ip_int = 3232235777
|
||||
ip2 = FourBytes('0.0.0.5') # → ip_int = 5
|
||||
next_ip = ip1 + ip2 # → FourBytes('192.168.1.6')
|
||||
|
||||
# Incremento simple:
|
||||
base_ip = FourBytes('10.0.0.0')
|
||||
next_net = base_ip + 256 # → FourBytes('10.0.1.0')
|
||||
|
||||
# Cálculos de rango:
|
||||
start = FourBytes('192.168.1.0')
|
||||
end = start + 255 # → FourBytes('192.168.1.255')
|
||||
```
|
||||
|
||||
### 5. **Conversiones Automáticas Bidireccionales**
|
||||
```python
|
||||
# FourBytes → Otras bases:
|
||||
ip = FourBytes('10.1.3.15')
|
||||
ip.ToHex() # → "16#A.16#1.16#3.16#F"
|
||||
ip.ToBinary() # → "2#1010.2#1.2#11.2#1111"
|
||||
ip.ToBase(8) # → "8#12.8#1.8#3.8#17"
|
||||
|
||||
# Conversión directa en constructores:
|
||||
hex_ip = Hex(FourBytes('10.1.3.15')) # Automático via tokenización
|
||||
ip_from_hex = IP4(FourBytes('10.1.3.16#ff')) # 16#ff se tokeniza a IntBase
|
||||
|
||||
# Conversiones fluidas:
|
||||
mask = FourBytes('255.255.255.0')
|
||||
mask.ToBase(2) # → "2#11111111.2#11111111.2#11111111.2#0"
|
||||
```
|
||||
|
||||
### 6. **Análisis de Rangos con Constraints**
|
||||
```python
|
||||
# Red con parámetros:
|
||||
net = IP4(10.a.b.0, c)
|
||||
|
||||
# Encontrar valores válidos:
|
||||
constraints = [
|
||||
net.address[1] >= 0, # a >= 0
|
||||
net.address[1] <= 255, # a <= 255
|
||||
net.address[2] >= 0, # b >= 0
|
||||
net.address[2] <= 255, # b <= 255
|
||||
c >= 8, # c >= 8
|
||||
c <= 30 # c <= 30
|
||||
]
|
||||
|
||||
solutions = solve(constraints, [a, b, c])
|
||||
```
|
||||
|
||||
### 7. **Ejemplos Prácticos de Conversión**
|
||||
```python
|
||||
# Ejemplo 1: IP con máscara hexadecimal
|
||||
ip_hex_mask = IP4(192.168.1.1, 16#ffffff00) # Tokeniza automáticamente
|
||||
# Equivale a: IP4(FourBytes('192.168.1.1'), IntBase('ffffff00', 16))
|
||||
|
||||
# Ejemplo 2: Conversión de máscara
|
||||
mask_fourbytes = FourBytes('255.255.255.0')
|
||||
mask_hex = mask_fourbytes.ToHex() # → "16#FF.16#FF.16#FF.16#0"
|
||||
mask_cidr = mask_fourbytes.ToCIDR(24) # → "255.255.255.0/24"
|
||||
|
||||
# Ejemplo 3: Aritmética mixta
|
||||
base_net = FourBytes('192.168.0.0')
|
||||
subnet_size = 2#100000000 # 256 en binario
|
||||
next_subnet = base_net + subnet_size # → FourBytes('192.168.1.0')
|
||||
|
||||
# Ejemplo 4: Análisis de subredes
|
||||
network = FourBytes('10.0.0.0')
|
||||
for i in range(4):
|
||||
subnet = network + (i * 256)
|
||||
print(f"Subred {i}: {subnet}")
|
||||
# Subred 0: 10.0.0.0
|
||||
# Subred 1: 10.0.1.0
|
||||
# Subred 2: 10.0.2.0
|
||||
# Subred 3: 10.0.3.0
|
||||
```
|
||||
|
||||
## Ventajas del Nuevo Sistema
|
||||
|
||||
### 1. **Simplicidad**
|
||||
- ✅ Sin parser complejo
|
||||
- ✅ Sintaxis estándar (Python puro)
|
||||
- ✅ Tokenización simple y rápida
|
||||
- ✅ Integración natural con eval()
|
||||
|
||||
### 2. **Potencia Algebraica**
|
||||
- ✅ Variables en cualquier posición
|
||||
- ✅ Sustituciones automáticas
|
||||
- ✅ Integración completa con SymPy
|
||||
- ✅ Análisis simbólico de redes/números
|
||||
|
||||
### 3. **Extensibilidad**
|
||||
- ✅ Fácil agregar nuevos patrones
|
||||
- ✅ Clases especializadas más simples
|
||||
- ✅ Reutilización de lógica base
|
||||
- ✅ Escalabilidad natural
|
||||
|
||||
### 4. **Intuitividad**
|
||||
- ✅ Usuario escribe código normal
|
||||
- ✅ Sin sintaxis especial que memorizar
|
||||
- ✅ Comportamiento predecible
|
||||
- ✅ Curva de aprendizaje mínima
|
||||
|
||||
## Posibles Problemas y Soluciones
|
||||
|
||||
### 1. **Ambigüedad x.y vs x.y.z.w**
|
||||
```python
|
||||
# PROBLEMA: ¿x.y es acceso a atributo o parte de FourBytes?
|
||||
# SOLUCIÓN: Solo tokenizar si hay exactamente 4 elementos
|
||||
pattern = r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b'
|
||||
```
|
||||
|
||||
### 2. **Precedencia de Operadores**
|
||||
```python
|
||||
# PROBLEMA: 192.168.1.1 + 1
|
||||
# SOLUCIÓN: Tokenización antes de parsing de operadores
|
||||
# Resultado: FourBytes('192.168.1.1') + 1
|
||||
```
|
||||
|
||||
### 3. **Validación con Símbolos**
|
||||
```python
|
||||
# PROBLEMA: ¿Cómo validar 10.x.1.2 como IP?
|
||||
# SOLUCIÓN: Validación condicional y constraint solving
|
||||
def is_valid_ip_symbolic(fourbytes):
|
||||
if not fourbytes.has_symbols:
|
||||
return fourbytes.is_valid_ip_range()
|
||||
|
||||
# Crear constraints para símbolos
|
||||
constraints = []
|
||||
for i, elem in enumerate(fourbytes.elements):
|
||||
if not elem.isdigit():
|
||||
symbol = Symbol(elem)
|
||||
constraints.extend([symbol >= 0, symbol <= 255])
|
||||
|
||||
return constraints
|
||||
```
|
||||
|
||||
## Plan de Implementación
|
||||
|
||||
### Fase 1: Clases Base
|
||||
1. Implementar `IntBase` con soporte algebraico
|
||||
2. Implementar `FourBytes` con soporte algebraico
|
||||
3. Tests exhaustivos de ambas clases
|
||||
|
||||
### Fase 2: Tokenizador
|
||||
1. Implementar `preprocess_tokens()`
|
||||
2. Integrar con `HybridEvaluationEngine`
|
||||
3. Tests de tokenización
|
||||
|
||||
### Fase 3: Migración de Clases
|
||||
1. Migrar `Hex`, `Bin`, `Dec` a usar `IntBase`
|
||||
2. Migrar `IP4`, `IP4Mask` a usar `FourBytes`
|
||||
3. Eliminar sistema de corchetes
|
||||
|
||||
### Fase 4: Nuevas Capacidades
|
||||
1. Operaciones algebraicas avanzadas
|
||||
2. Constraint solving para redes
|
||||
3. Análisis simbólico de rangos
|
||||
|
||||
## Conclusión
|
||||
|
||||
Esta refactorización elimina complejidad innecesaria mientras añade capacidades algebraicas poderosas. El resultado será:
|
||||
|
||||
- **Más simple** para casos básicos
|
||||
- **Más poderoso** para casos avanzados
|
||||
- **Más intuitivo** para el usuario
|
||||
- **Más extensible** para el futuro
|
||||
|
||||
El tokenizador automático hace que el usuario no tenga que aprender sintaxis especial, mientras que el soporte algebraico completo permite análisis sofisticados cuando se necesitan.
|
|
@ -1,5 +1,10 @@
|
|||
|
||||
a = 10 + b
|
||||
a=?
|
||||
|
||||
|
||||
m=IP4Mask[255.240.0.0]
|
||||
|
||||
m.get_prefix_int()
|
||||
m.hosts_count()
|
||||
|
||||
i=IP4("0.1.30.70",m.get_prefix_int())
|
||||
|
||||
9*3*36.0
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"window_geometry": "1020x700+467+199",
|
||||
"sash_pos_x": 363,
|
||||
"window_geometry": "1020x700+708+147",
|
||||
"sash_pos_x": 368,
|
||||
"symbolic_mode": true,
|
||||
"show_numeric_approximation": true,
|
||||
"keep_symbolic_fractions": true,
|
||||
|
|
|
@ -127,6 +127,9 @@ class HybridEvaluationEngine:
|
|||
utility_functions = {
|
||||
'_add_equation': self._add_equation,
|
||||
'_assign_variable': self._assign_variable,
|
||||
'_solve_variable_in_system': self._solve_variable_in_system,
|
||||
'clear': self.clear,
|
||||
'clearContext': self.clearContext,
|
||||
'help': self._help_function,
|
||||
'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr),
|
||||
}
|
||||
|
@ -642,6 +645,226 @@ class HybridEvaluationEngine:
|
|||
self.keep_symbolic_fractions = keep_fractions
|
||||
self.auto_simplify = auto_simplify
|
||||
|
||||
def clear(self, *variables) -> str:
|
||||
"""
|
||||
Función flexible para limpiar variables
|
||||
|
||||
Sin argumentos: Limpia TODAS las variables
|
||||
Con argumentos: Limpia solo las variables especificadas
|
||||
|
||||
Examples:
|
||||
clear() # Limpia todas las variables
|
||||
clear("x", "y") # Limpia solo x e y
|
||||
clear("a") # Limpia solo a
|
||||
"""
|
||||
if not variables:
|
||||
# Sin argumentos: limpiar todas las variables
|
||||
cleared_count = len(self.symbol_table)
|
||||
self.symbol_table.clear()
|
||||
return f"Limpiadas {cleared_count} variables del contexto"
|
||||
else:
|
||||
# Con argumentos: limpiar variables específicas
|
||||
cleared_vars = []
|
||||
not_found_vars = []
|
||||
|
||||
for var_name in variables:
|
||||
if var_name in self.symbol_table:
|
||||
del self.symbol_table[var_name]
|
||||
cleared_vars.append(var_name)
|
||||
else:
|
||||
not_found_vars.append(var_name)
|
||||
|
||||
result_parts = []
|
||||
if cleared_vars:
|
||||
result_parts.append(f"Variables limpiadas: {', '.join(cleared_vars)}")
|
||||
if not_found_vars:
|
||||
result_parts.append(f"Variables no encontradas: {', '.join(not_found_vars)}")
|
||||
|
||||
return " | ".join(result_parts) if result_parts else "No hay variables para limpiar"
|
||||
|
||||
def clearContext(self) -> str:
|
||||
"""
|
||||
Limpia completamente el contexto: variables Y ecuaciones
|
||||
|
||||
Equivale a hacer clear() + clear_equations()
|
||||
"""
|
||||
var_count = len(self.symbol_table)
|
||||
eq_count = len(self.equations)
|
||||
|
||||
self.symbol_table.clear()
|
||||
self.equations.clear()
|
||||
|
||||
return f"Contexto limpiado: {var_count} variables y {eq_count} ecuaciones eliminadas"
|
||||
|
||||
def _solve_variable_in_system(self, var_name: str) -> Any:
|
||||
"""
|
||||
Resuelve una variable específica usando las ecuaciones del sistema
|
||||
Esta función reemplaza el comportamiento de solve(variable) para el atajo =?
|
||||
|
||||
Implementa mejores prácticas según la documentación de SymPy:
|
||||
- Usa dict=True para formato consistente
|
||||
- Especifica explícitamente la variable a resolver
|
||||
- Manejo robusto de diferentes tipos de resultado
|
||||
- Manejo especial para sistemas de múltiples variables
|
||||
- Manejo mejorado de sistemas subdeterminados
|
||||
"""
|
||||
# 1. Buscar la variable en el contexto o crearla
|
||||
var_symbol = self.symbol_table.get(var_name)
|
||||
if var_symbol is None:
|
||||
var_symbol = Symbol(var_name)
|
||||
self.symbol_table[var_name] = var_symbol
|
||||
|
||||
# 2. Buscar ecuaciones que contengan esta variable
|
||||
relevant_equations = []
|
||||
for eq in self.equations:
|
||||
if var_symbol in eq.free_symbols:
|
||||
relevant_equations.append(eq)
|
||||
|
||||
if not relevant_equations:
|
||||
# Si no hay ecuaciones, mostrar el valor actual de la variable
|
||||
if var_name in self.symbol_table:
|
||||
current_value = self.symbol_table[var_name]
|
||||
if isinstance(current_value, Symbol):
|
||||
return f"'{var_name}' es un símbolo sin valor definido. No hay ecuaciones para resolverlo."
|
||||
else:
|
||||
return current_value
|
||||
else:
|
||||
return f"No se encontraron ecuaciones para '{var_name}' y la variable no está definida"
|
||||
|
||||
# 3. Resolver las ecuaciones para esta variable usando mejores prácticas de SymPy
|
||||
try:
|
||||
if self.debug:
|
||||
print(f"🔍 Resolviendo '{var_name}' usando ecuaciones: {relevant_equations}")
|
||||
|
||||
# MEJORA: Detectar si es un sistema de múltiples variables
|
||||
all_variables = set()
|
||||
for eq in relevant_equations:
|
||||
all_variables.update(eq.free_symbols)
|
||||
|
||||
if len(all_variables) > 1:
|
||||
# Sistema de múltiples variables: resolver todo el sistema y extraer la variable deseada
|
||||
if self.debug:
|
||||
print(f"🔍 Sistema detectado con variables: {all_variables}")
|
||||
|
||||
# ESTRATEGIA MEJORADA: Intentar primero resolver solo la variable deseada
|
||||
# Si falla, resolver el sistema completo
|
||||
|
||||
# Intento 1: Resolver solo la variable específica
|
||||
try:
|
||||
solutions_specific = solve(relevant_equations, var_symbol, dict=True)
|
||||
if self.debug:
|
||||
print(f"🔍 Soluciones específicas para {var_name}: {solutions_specific}")
|
||||
|
||||
if solutions_specific and len(solutions_specific) > 0:
|
||||
# Éxito con resolución específica
|
||||
if len(solutions_specific) == 1:
|
||||
solution_dict = solutions_specific[0]
|
||||
if var_symbol in solution_dict:
|
||||
solution = solution_dict[var_symbol]
|
||||
self.symbol_table[var_name] = solution
|
||||
return solution
|
||||
else:
|
||||
# Múltiples soluciones específicas
|
||||
solution_values = []
|
||||
for sol_dict in solutions_specific:
|
||||
if var_symbol in sol_dict:
|
||||
solution_values.append(sol_dict[var_symbol])
|
||||
if solution_values:
|
||||
# MEJORA: Asignar la lista completa a la variable para permitir t[0], t[1], etc.
|
||||
self.symbol_table[var_name] = solution_values
|
||||
return solution_values
|
||||
|
||||
except:
|
||||
if self.debug:
|
||||
print(f"🔍 Resolución específica falló, probando sistema completo")
|
||||
|
||||
# Intento 2: Resolver el sistema completo y extraer
|
||||
all_vars_list = list(all_variables)
|
||||
solutions = solve(relevant_equations, all_vars_list, dict=True)
|
||||
|
||||
if self.debug:
|
||||
print(f"🔍 Soluciones del sistema: {solutions}")
|
||||
|
||||
# Extraer la variable específica del resultado
|
||||
if isinstance(solutions, list) and len(solutions) > 0:
|
||||
solution_dict = solutions[0] # Tomar la primera solución
|
||||
if var_symbol in solution_dict:
|
||||
solution = solution_dict[var_symbol]
|
||||
# Actualizar la variable en el sistema
|
||||
self.symbol_table[var_name] = solution
|
||||
# También actualizar las otras variables resueltas
|
||||
for sym, val in solution_dict.items():
|
||||
self.symbol_table[str(sym)] = val
|
||||
return solution
|
||||
else:
|
||||
# NUEVO: Variable no en solución directa, intentar despeje algebraico
|
||||
if self.debug:
|
||||
print(f"🔍 '{var_name}' no en solución directa, intentando despeje")
|
||||
|
||||
# Buscar una ecuación simple que podamos despejar
|
||||
for eq in relevant_equations:
|
||||
if len(eq.free_symbols) == 2 and var_symbol in eq.free_symbols:
|
||||
# Ecuación con 2 variables que incluye la deseada
|
||||
try:
|
||||
# Intentar resolver esta ecuación específica para nuestra variable
|
||||
simple_solution = solve(eq, var_symbol)
|
||||
if simple_solution:
|
||||
result = simple_solution[0] if len(simple_solution) == 1 else simple_solution
|
||||
self.symbol_table[var_name] = result
|
||||
return result
|
||||
except:
|
||||
continue
|
||||
|
||||
return f"Variable '{var_name}' no se pudo resolver directamente. Solución del sistema: {solution_dict}"
|
||||
else:
|
||||
return f"No se pudo resolver el sistema para '{var_name}'"
|
||||
|
||||
else:
|
||||
# Variable única: usar el método original
|
||||
solutions = solve(relevant_equations, var_symbol, dict=True)
|
||||
|
||||
if self.debug:
|
||||
print(f"🔍 Soluciones obtenidas: {solutions}")
|
||||
|
||||
# Manejar diferentes tipos de resultado según la documentación
|
||||
if isinstance(solutions, list):
|
||||
if len(solutions) == 0:
|
||||
return f"No se encontraron soluciones para '{var_name}'"
|
||||
elif len(solutions) == 1:
|
||||
# Una solución única - extraer del diccionario
|
||||
solution_dict = solutions[0]
|
||||
if var_symbol in solution_dict:
|
||||
solution = solution_dict[var_symbol]
|
||||
# Actualizar la variable en el sistema
|
||||
self.symbol_table[var_name] = solution
|
||||
return solution
|
||||
else:
|
||||
return f"Solución encontrada pero no contiene '{var_name}': {solution_dict}"
|
||||
else:
|
||||
# Múltiples soluciones - extraer valores del diccionario
|
||||
solution_values = []
|
||||
for sol_dict in solutions:
|
||||
if var_symbol in sol_dict:
|
||||
solution_values.append(sol_dict[var_symbol])
|
||||
|
||||
if solution_values:
|
||||
# MEJORA: Asignar la lista completa a la variable para permitir t[0], t[1], etc.
|
||||
self.symbol_table[var_name] = solution_values
|
||||
return solution_values
|
||||
else:
|
||||
return f"Múltiples soluciones encontradas pero ninguna contiene '{var_name}': {solutions}"
|
||||
else:
|
||||
# Resultado no esperado - mostrar tal como está
|
||||
return solutions
|
||||
|
||||
except Exception as e:
|
||||
# Información de error más detallada
|
||||
error_msg = f"Error resolviendo '{var_name}': {e}"
|
||||
if self.debug:
|
||||
print(f"🚨 {error_msg}")
|
||||
print(f"🚨 Ecuaciones que causaron el error: {relevant_equations}")
|
||||
return error_msg
|
||||
|
||||
|
||||
class EvaluationResult:
|
||||
"""Resultado de evaluación con información contextual"""
|
||||
|
|
|
@ -136,7 +136,8 @@ class BracketParser:
|
|||
|
||||
def _transform_solve_shortcut(self, line: str) -> Tuple[str, bool]:
|
||||
"""
|
||||
Transforma 'variable=?' en 'solve(variable)'
|
||||
Transforma 'variable=?' en '_solve_variable_in_system("variable")'
|
||||
en lugar de 'solve(variable)' para resolver usando las ecuaciones del sistema
|
||||
|
||||
Returns:
|
||||
(transformed_line, was_transformed)
|
||||
|
@ -147,12 +148,16 @@ class BracketParser:
|
|||
|
||||
if match:
|
||||
var_name = match.group(1)
|
||||
return f'solve({var_name})', True
|
||||
# CAMBIO: usar función personalizada que resuelve usando el sistema de ecuaciones
|
||||
return f'_solve_variable_in_system("{var_name}")', True
|
||||
|
||||
return line, False
|
||||
|
||||
def _is_assignment(self, line: str) -> bool:
|
||||
"""Detecta si una línea es una asignación de variable"""
|
||||
"""
|
||||
Detecta si una línea es una asignación de variable
|
||||
MEJORADO: Considera si contiene símbolos desconocidos que sugieren ecuación
|
||||
"""
|
||||
try:
|
||||
# Pattern: variable = expresión (que no sea ecuación)
|
||||
if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']):
|
||||
|
@ -160,9 +165,48 @@ class BracketParser:
|
|||
parts = line.split('=', 1)
|
||||
if len(parts) == 2:
|
||||
var_part = parts[0].strip()
|
||||
expr_part = parts[1].strip()
|
||||
|
||||
# Verificar que la parte izquierda sea un identificador válido
|
||||
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part):
|
||||
return True
|
||||
|
||||
# NUEVA LÓGICA: Detectar símbolos desconocidos en la expresión
|
||||
# Si hay símbolos desconocidos, puede ser mejor tratarlo como ecuación
|
||||
symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_part)
|
||||
|
||||
# Funciones y constantes conocidas de SymPy/matemáticas
|
||||
known_functions = {
|
||||
# Funciones trigonométricas
|
||||
'sin', 'cos', 'tan', 'asin', 'acos', 'atan',
|
||||
'sinh', 'cosh', 'tanh',
|
||||
# Funciones exponenciales y logarítmicas
|
||||
'exp', 'log', 'ln', 'sqrt',
|
||||
# Constantes
|
||||
'pi', 'e', 'I', 'oo',
|
||||
# Funciones especiales
|
||||
'abs', 'sign', 'floor', 'ceiling', 'factorial',
|
||||
# Álgebra
|
||||
'diff', 'integrate', 'limit', 'series', 'solve',
|
||||
'simplify', 'expand', 'factor', 'collect',
|
||||
'cancel', 'apart', 'together',
|
||||
# Matrices
|
||||
'Matrix', 'det', 'inv',
|
||||
# Funciones de Python comunes
|
||||
'int', 'float', 'str', 'len', 'max', 'min', 'sum'
|
||||
}
|
||||
|
||||
# Encontrar símbolos que no son funciones conocidas
|
||||
unknown_symbols = [s for s in symbols_in_expr if s not in known_functions]
|
||||
|
||||
if unknown_symbols:
|
||||
if self.debug:
|
||||
print(f"🔍 '{line}' contiene símbolos desconocidos: {unknown_symbols}")
|
||||
print(f"🔍 Considerando como ECUACIÓN en lugar de asignación")
|
||||
# Si hay símbolos desconocidos, tratarlo como ecuación
|
||||
return False # No es asignación, será detectado como ecuación
|
||||
else:
|
||||
# Solo funciones/constantes conocidas: asignación normal
|
||||
return True
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
@ -181,41 +225,87 @@ class BracketParser:
|
|||
def _is_standalone_equation(self, line: str) -> bool:
|
||||
"""
|
||||
Determina si una línea es una ecuación standalone usando análisis AST
|
||||
SIMPLIFICADO: Evita recursión y es más directo
|
||||
"""
|
||||
try:
|
||||
tree = ast.parse(line.strip())
|
||||
|
||||
if not tree.body:
|
||||
# Primero verificar si contiene '=' simple
|
||||
if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']):
|
||||
return False
|
||||
|
||||
node = tree.body[0]
|
||||
|
||||
# Solo consideramos expresiones (no asignaciones)
|
||||
if not isinstance(node, ast.Expr):
|
||||
|
||||
# Si no es una asignación (según la lógica ya evaluada en parse_line)
|
||||
# y tiene '=', entonces es una ecuación
|
||||
try:
|
||||
tree = ast.parse(line.strip())
|
||||
|
||||
if not tree.body:
|
||||
return False
|
||||
|
||||
node = tree.body[0]
|
||||
|
||||
# Si es una asignación Python válida, NO es ecuación standalone
|
||||
if isinstance(node, ast.Assign):
|
||||
# Pero si contiene símbolos desconocidos, SÍ es ecuación
|
||||
# Verificar si la expresión contiene símbolos desconocidos
|
||||
if len(node.targets) == 1:
|
||||
# Obtener la expresión del lado derecho
|
||||
try:
|
||||
expr_code = ast.get_source_segment(line, node.value)
|
||||
if expr_code:
|
||||
symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_code)
|
||||
known_functions = {
|
||||
'sin', 'cos', 'tan', 'asin', 'acos', 'atan',
|
||||
'sinh', 'cosh', 'tanh', 'exp', 'log', 'ln', 'sqrt',
|
||||
'pi', 'e', 'I', 'oo', 'abs', 'sign', 'floor', 'ceiling',
|
||||
'factorial', 'diff', 'integrate', 'limit', 'series',
|
||||
'solve', 'simplify', 'expand', 'factor', 'collect',
|
||||
'cancel', 'apart', 'together', 'Matrix', 'det', 'inv',
|
||||
'int', 'float', 'str', 'len', 'max', 'min', 'sum'
|
||||
}
|
||||
unknown_symbols = [s for s in symbols_in_expr if s not in known_functions]
|
||||
if unknown_symbols:
|
||||
if self.debug:
|
||||
print(f"🔍 '{line}' es asignación pero con símbolos desconocidos: {unknown_symbols} → ECUACIÓN")
|
||||
return True
|
||||
except:
|
||||
# Si falla get_source_segment, usar método alternativo
|
||||
parts = line.split('=', 1)
|
||||
if len(parts) == 2:
|
||||
expr_part = parts[1].strip()
|
||||
symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_part)
|
||||
known_functions = {
|
||||
'sin', 'cos', 'tan', 'asin', 'acos', 'atan',
|
||||
'sinh', 'cosh', 'tanh', 'exp', 'log', 'ln', 'sqrt',
|
||||
'pi', 'e', 'I', 'oo', 'abs', 'sign', 'floor', 'ceiling',
|
||||
'factorial', 'diff', 'integrate', 'limit', 'series',
|
||||
'solve', 'simplify', 'expand', 'factor', 'collect',
|
||||
'cancel', 'apart', 'together', 'Matrix', 'det', 'inv',
|
||||
'int', 'float', 'str', 'len', 'max', 'min', 'sum'
|
||||
}
|
||||
unknown_symbols = [s for s in symbols_in_expr if s not in known_functions]
|
||||
if unknown_symbols:
|
||||
if self.debug:
|
||||
print(f"🔍 '{line}' es asignación pero con símbolos desconocidos: {unknown_symbols} → ECUACIÓN")
|
||||
return True
|
||||
return False
|
||||
|
||||
# Si es una expresión (no asignación), verificar comparaciones
|
||||
if isinstance(node, ast.Expr):
|
||||
if isinstance(node.value, ast.Compare):
|
||||
for op in node.value.ops:
|
||||
if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# Verificar si es una comparación con operadores de ecuación
|
||||
if isinstance(node.value, ast.Compare):
|
||||
# Verificar que use operadores de ecuación
|
||||
for op in node.value.ops:
|
||||
if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)):
|
||||
return True
|
||||
|
||||
# Verificar si contiene un '=' que no sea parte de una asignación
|
||||
# Esto es para casos como "x + 2 = 5" que no parsea como Compare
|
||||
if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']):
|
||||
# Es un '=' simple, puede ser una ecuación
|
||||
|
||||
except SyntaxError:
|
||||
# Error de sintaxis: probablemente ecuación mal formada como "x + 2 = 5"
|
||||
if self.debug:
|
||||
print(f"🔍 '{line}' error AST → Tratando como ECUACIÓN")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except SyntaxError:
|
||||
# Si hay error de sintaxis, puede ser una ecuación mal formada
|
||||
# como "x + 2 = 5" que Python no puede parsear
|
||||
if '=' in line:
|
||||
return True
|
||||
return False
|
||||
except Exception:
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"🚨 Error en _is_standalone_equation: {e}")
|
||||
return False
|
||||
|
||||
def _transform_brackets(self, line: str) -> str:
|
||||
|
|
Loading…
Reference in New Issue