From 8f0b0287a562fda1a545ccfef13839a03cbd63e1 Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 5 Jun 2025 12:28:20 +0200 Subject: [PATCH] =?UTF-8?q?Implementaci=C3=B3n=20de=20nuevas=20funciones?= =?UTF-8?q?=20en=20el=20motor=20de=20evaluaci=C3=B3n,=20incluyendo=20m?= =?UTF-8?q?=C3=A9todos=20para=20limpiar=20variables=20y=20contextos,=20as?= =?UTF-8?q?=C3=AD=20como=20una=20funci=C3=B3n=20para=20resolver=20variable?= =?UTF-8?q?s=20en=20sistemas=20de=20ecuaciones.=20Se=20actualiza=20la=20l?= =?UTF-8?q?=C3=B3gica=20de=20an=C3=A1lisis=20de=20asignaciones=20y=20ecuac?= =?UTF-8?q?iones=20en=20el=20parser=20de=20corchetes,=20mejorando=20la=20d?= =?UTF-8?q?etecci=C3=B3n=20de=20s=C3=ADmbolos=20desconocidos.=20Se=20ajust?= =?UTF-8?q?a=20la=20configuraci=C3=B3n=20de=20la=20ventana=20y=20se=20opti?= =?UTF-8?q?mizan=20las=20funciones=20de=20transformaci=C3=B3n=20de=20l?= =?UTF-8?q?=C3=ADneas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .doc/refactoring/remover_corchetes.md | 784 ++++++++++++++++++++++++++ hybrid_calc_history.txt | 11 +- hybrid_calc_settings.json | 4 +- main_evaluation.py | 223 ++++++++ tl_bracket_parser.py | 158 ++++-- 5 files changed, 1141 insertions(+), 39 deletions(-) create mode 100644 .doc/refactoring/remover_corchetes.md diff --git a/.doc/refactoring/remover_corchetes.md b/.doc/refactoring/remover_corchetes.md new file mode 100644 index 0000000..cbcff3c --- /dev/null +++ b/.doc/refactoring/remover_corchetes.md @@ -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. \ No newline at end of file diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index db58340..e945386 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -1,5 +1,10 @@ -a = 10 + b -a=? - \ No newline at end of file +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 \ No newline at end of file diff --git a/hybrid_calc_settings.json b/hybrid_calc_settings.json index f679c19..b234ffc 100644 --- a/hybrid_calc_settings.json +++ b/hybrid_calc_settings.json @@ -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, diff --git a/main_evaluation.py b/main_evaluation.py index 9e1613f..383b7a4 100644 --- a/main_evaluation.py +++ b/main_evaluation.py @@ -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""" diff --git a/tl_bracket_parser.py b/tl_bracket_parser.py index 7cb0137..44f5902 100644 --- a/tl_bracket_parser.py +++ b/tl_bracket_parser.py @@ -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: