""" Motor de evaluación híbrida que usa SymPy como base con clases especializadas """ import sympy from sympy import symbols, Symbol, sympify, solve, Eq, simplify from typing import Dict, Any, Optional, Tuple, List, Union import ast import re from contextlib import contextmanager from tl_bracket_parser import BracketParser from tl_popup import PlotResult from sympy_Base import SympyClassBase from ip4_type import Class_IP4 as Class_IP4 from hex_type import Class_Hex as Class_Hex from bin_type import Class_Bin as Class_Bin from dec_type import Class_Dec as Class_Dec from chr_type import Class_Chr as Class_Chr class HybridEvaluationEngine: """ Motor de evaluación híbrida que combina SymPy con clases especializadas """ def __init__(self): self.parser = BracketParser() self.symbol_table: Dict[str, Any] = {} self.equations: List[sympy.Eq] = [] self.last_result = None # Contexto base con funciones y clases self._setup_base_context() # Debug mode self.debug = False def _setup_base_context(self): """Configura el contexto base con funciones matemáticas y clases""" # Funciones matemáticas de SymPy math_functions = { 'pi': sympy.pi, 'e': sympy.E, 'I': sympy.I, 'oo': sympy.oo, 'sin': sympy.sin, 'cos': sympy.cos, 'tan': sympy.tan, 'asin': sympy.asin, 'acos': sympy.acos, 'atan': sympy.atan, 'sinh': sympy.sinh, 'cosh': sympy.cosh, 'tanh': sympy.tanh, 'exp': sympy.exp, 'log': sympy.log, 'ln': sympy.log, 'sqrt': sympy.sqrt, 'abs': sympy.Abs, 'sign': sympy.sign, 'floor': sympy.floor, 'ceiling': sympy.ceiling, 'factorial': sympy.factorial, # Funciones de cálculo 'diff': sympy.diff, 'integrate': sympy.integrate, 'limit': sympy.limit, 'series': sympy.series, 'solve': sympy.solve, 'simplify': sympy.simplify, 'expand': sympy.expand, 'factor': sympy.factor, 'collect': sympy.collect, 'cancel': sympy.cancel, 'apart': sympy.apart, 'together': sympy.together, # Álgebra lineal 'Matrix': sympy.Matrix, 'det': lambda m: m.det() if hasattr(m, 'det') else sympy.det(m), 'inv': lambda m: m.inv() if hasattr(m, 'inv') else sympy.Matrix(m).inv(), # Plotting (será manejado por resultados interactivos) 'plot': self._create_plot_placeholder, 'plot3d': self._create_plot3d_placeholder, } # Clases especializadas specialized_classes = { 'Hex': Class_Hex, 'Bin': Class_Bin, 'Dec': Class_Dec, 'IP4': Class_IP4, 'Chr': Class_Chr, # Alias en minúsculas 'hex': Class_Hex, 'bin': Class_Bin, 'dec': Class_Dec, 'ip4': Class_IP4, 'chr': Class_Chr, } # Funciones de utilidad utility_functions = { '_add_equation': self._add_equation, '_assign_variable': self._assign_variable, 'help': self._help_function, 'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr), } self.base_context = { **math_functions, **specialized_classes, **utility_functions } def _create_plot_placeholder(self, *args, **kwargs): """Crear placeholder para plots que será manejado por resultados interactivos""" if self.debug: print(f"🎯 Creando PlotResult con args: {args}, kwargs: {kwargs}") return PlotResult('plot', args, kwargs) def _create_plot3d_placeholder(self, *args, **kwargs): """Crear placeholder para plots 3D""" if self.debug: print(f"🎯 Creando PlotResult 3D con args: {args}, kwargs: {kwargs}") return PlotResult('plot3d', args, kwargs) def _help_function(self, obj=None): """Función de ayuda integrada""" if obj is None: return "Ayuda disponible. Use help(función) para ayuda específica." if hasattr(obj, '__doc__') and obj.__doc__: return obj.__doc__ elif hasattr(obj, 'Helper'): return obj.Helper("") else: return f"No hay ayuda disponible para {obj}" def evaluate_line(self, line: str) -> 'EvaluationResult': """ Evalúa una línea de código y retorna el resultado """ try: # 1. Parsear la línea parsed_line, parse_info = self.parser.parse_line(line) if self.debug: print(f"Parse: '{line}' → '{parsed_line}' ({parse_info})") # 2. Manejar casos especiales if parse_info == "comment": return EvaluationResult(None, "comment", original_line=line) elif parse_info == "equation": return self._evaluate_equation_addition(parsed_line, line) elif parse_info == "assignment": return self._evaluate_assignment(parsed_line, line) # 3. Evaluación SymPy return self._evaluate_sympy_expression(parsed_line, parse_info, line) except Exception as e: return EvaluationResult( None, "error", error=str(e), original_line=line ) def _evaluate_assignment(self, parsed_line: str, original_line: str) -> 'EvaluationResult': """Maneja la asignación de variables""" try: # Ejecutar _assign_variable result = self._eval_in_context(parsed_line) # Extraer nombre de variable y valor del resultado parts = original_line.split('=', 1) var_name = parts[0].strip() # Obtener el valor asignado assigned_value = self.symbol_table.get(var_name) return EvaluationResult( assigned_value, "assignment", symbolic_result=result, original_line=original_line ) except Exception as e: return EvaluationResult( None, "error", error=f"Error en asignación: {e}", original_line=original_line ) def _evaluate_equation_addition(self, parsed_line: str, original_line: str) -> 'EvaluationResult': """Maneja la adición de ecuaciones al sistema""" try: # Ejecutar _add_equation result = self._eval_in_context(parsed_line) return EvaluationResult( result, "equation_added", symbolic_result=f"Ecuación agregada: {original_line}", original_line=original_line ) except Exception as e: return EvaluationResult( None, "error", error=f"Error agregando ecuación: {e}", original_line=original_line ) def _evaluate_sympy_expression(self, expression: str, parse_info: str, original_line: str) -> 'EvaluationResult': """Evalúa una expresión usando SymPy""" try: # Evaluar en contexto SymPy result = self._eval_in_context(expression) if self.debug: print(f"🔍 Resultado evaluación: {result} (tipo: {type(result)})") if isinstance(result, PlotResult): print(f" 📊 Es PlotResult: plot_type={result.plot_type}") # Actualizar last_result self.last_result = result # Intentar evaluación numérica si es posible numeric_result = None if hasattr(result, 'evalf'): try: numeric_eval = result.evalf() if numeric_eval != result: numeric_result = numeric_eval except: pass return EvaluationResult( result, "expression", symbolic_result=result, numeric_result=numeric_result, parse_info=parse_info, original_line=original_line ) except NameError as e: # Intentar crear símbolos automáticamente return self._handle_undefined_symbols(expression, original_line, e) except Exception as e: return EvaluationResult( None, "error", error=str(e), original_line=original_line ) def _handle_undefined_symbols(self, expression: str, original_line: str, error: Exception) -> 'EvaluationResult': """Maneja símbolos no definidos creándolos automáticamente""" try: # Extraer nombres de variables de la expresión var_names = self._extract_variable_names(expression) # Crear símbolos automáticamente new_symbols = {} for name in var_names: if name not in self.symbol_table and name not in self.base_context: new_symbols[name] = Symbol(name) self.symbol_table[name] = Symbol(name) if new_symbols: # Reintentar evaluación result = self._eval_in_context(expression) symbol_names = list(new_symbols.keys()) info_msg = f"Símbolos creados: {', '.join(symbol_names)}" return EvaluationResult( result, "symbolic_with_new_vars", symbolic_result=result, info=info_msg, original_line=original_line ) else: raise error except Exception as e: return EvaluationResult( None, "error", error=str(e), original_line=original_line ) def _extract_variable_names(self, expression: str) -> List[str]: """Extrae nombres de variables de una expresión""" try: # Usar SymPy para extraer símbolos expr = sympify(expression, locals=self._get_full_context()) return [str(symbol) for symbol in expr.free_symbols] except: # Fallback: usar regex pattern = r'\b[a-zA-Z_][a-zA-Z0-9_]*\b' names = re.findall(pattern, expression) # Filtrar funciones conocidas return [name for name in names if name not in self.base_context] def _eval_in_context(self, expression: str) -> Any: """Evalúa una expresión en el contexto completo""" context = self._get_full_context() # Casos especiales para funciones del sistema if expression.strip().startswith('_add_equation'): return eval(expression, {"__builtins__": {}}, context) elif expression.strip().startswith('_assign_variable'): return eval(expression, {"__builtins__": {}}, context) else: try: # Primero intentar evaluación directa para objetos especializados try: result = eval(expression, {"__builtins__": {}}, context) # Si el resultado es un objeto híbrido, integrarlo con SymPy si es necesario if isinstance(result, SympyClassBase): return result elif isinstance(result, PlotResult): if self.debug: print(f" 📊 PlotResult detectado en eval: {result}") return result elif hasattr(result, '__iter__') and not isinstance(result, str): # Si es una lista/tupla, verificar si contiene objetos híbridos return result else: return result except (NameError, TypeError) as eval_error: # Si eval falla, intentar con SymPy try: result = sympify(expression, locals=context) return result except: # Si ambos fallan, re-lanzar el error original de eval raise eval_error except SyntaxError as syntax_error: # Para errores de sintaxis, intentar SymPy directamente try: result = sympify(expression, locals=context) return result except: raise syntax_error def _get_full_context(self) -> Dict[str, Any]: """Obtiene el contexto completo para evaluación""" context = self.base_context.copy() context.update(self.symbol_table) context['last'] = self.last_result return context def _assign_variable(self, var_name: str, expression) -> str: """Asigna un valor a una variable""" try: # Evaluar la expresión if isinstance(expression, str): value = sympify(expression, locals=self._get_full_context()) else: value = expression # Asignar al contexto self.symbol_table[var_name] = value return f"{var_name} = {value}" except Exception as e: raise ValueError(f"Error asignando variable '{var_name}': {e}") def _add_equation(self, equation_str: str) -> str: """Agrega una ecuación al sistema""" try: # Parsear ecuación if '=' in equation_str and '==' not in equation_str: # Ecuación simple: convertir a igualdad SymPy left, right = equation_str.split('=', 1) left_expr = sympify(left.strip(), locals=self._get_full_context()) right_expr = sympify(right.strip(), locals=self._get_full_context()) equation = Eq(left_expr, right_expr) else: # Ya es una comparación válida de SymPy equation = sympify(equation_str, locals=self._get_full_context()) self.equations.append(equation) return f"Ecuación {len(self.equations)}: {equation}" except Exception as e: raise ValueError(f"Error parseando ecuación '{equation_str}': {e}") def solve_system(self, variables: Optional[List[str]] = None) -> Dict[str, Any]: """Resuelve el sistema de ecuaciones""" if not self.equations: raise ValueError("No hay ecuaciones en el sistema") if variables is None: # Obtener todas las variables libres all_symbols = set() for eq in self.equations: all_symbols.update(eq.free_symbols) variables = [str(s) for s in all_symbols] # Convertir nombres a símbolos symbol_vars = [] for var_name in variables: if var_name in self.symbol_table: symbol_vars.append(self.symbol_table[var_name]) else: symbol_vars.append(Symbol(var_name)) # Resolver sistema solutions = solve(self.equations, symbol_vars) # Convertir resultado a diccionario con nombres de variables if isinstance(solutions, dict): result = {} for symbol, value in solutions.items(): result[str(symbol)] = value # Actualizar tabla de símbolos self.symbol_table[str(symbol)] = value return result elif isinstance(solutions, list): # Múltiples soluciones return {"solutions": solutions} else: return {"result": solutions} def assign_variable(self, name: str, value: Any): """Asigna un valor a una variable""" self.symbol_table[name] = value def get_variable(self, name: str) -> Optional[Any]: """Obtiene el valor de una variable""" return self.symbol_table.get(name) def clear_equations(self): """Limpia todas las ecuaciones""" self.equations.clear() def clear_variables(self): """Limpia todas las variables""" self.symbol_table.clear() def clear_all(self): """Limpia ecuaciones y variables""" self.clear_equations() self.clear_variables() class EvaluationResult: """Resultado de evaluación con información contextual""" def __init__(self, result: Any, result_type: str, symbolic_result: Any = None, numeric_result: Any = None, error: Optional[str] = None, info: Optional[str] = None, parse_info: Optional[str] = None, original_line: Optional[str] = None): self.result = result self.result_type = result_type self.symbolic_result = symbolic_result or result self.numeric_result = numeric_result self.error = error self.info = info self.parse_info = parse_info self.original_line = original_line @property def is_error(self) -> bool: return self.result_type == "error" @property def is_interactive(self) -> bool: """Determina si el resultado requiere interactividad""" return isinstance(self.result, (PlotResult, sympy.Matrix)) or \ (isinstance(self.result, list) and len(self.result) > 3) def __str__(self): if self.is_error: return f"Error: {self.error}" elif self.result is not None: return str(self.result) return "" # Funciones de testing def test_evaluation_engine(): """Test del motor de evaluación""" engine = HybridEvaluationEngine() engine.debug = True test_cases = [ # Expresiones básicas "2 + 3", "x + 2", "sin(pi/2)", # Sintaxis con corchetes "Hex[FF]", "IP4[192.168.1.1/24]", # 🧪 PLOTS - Casos específicos para testing "plot(sin(x), (x, -pi, pi))", "plot(x**2, (x, -5, 5))", # Ecuaciones "x + 2 = 5", "y**2 = 16", # Solve "solve(x + 2 - 5, x)", # Variables "a = 10", "b = a + 5", # Funciones avanzadas "diff(x**2, x)", "integrate(x**2, x)", ] print("=== Test Motor de Evaluación ===") for test in test_cases: result = engine.evaluate_line(test) print(f"'{test}' → {result} (type: {result.result_type})") # 🔍 Información adicional para plots if 'plot' in test: print(f" 🎯 Es interactivo: {result.is_interactive}") if isinstance(result.result, PlotResult): print(f" 📊 PlotResult confirmado: {result.result.plot_type}") if result.info: print(f" Info: {result.info}") if __name__ == "__main__": test_evaluation_engine()