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:
Miguel 2025-06-05 12:28:20 +02:00
parent 0c7ed33d0d
commit 8f0b0287a5
5 changed files with 1141 additions and 39 deletions

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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"""

View File

@ -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: