diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index a0d7769..0028bbb 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -18,6 +18,10 @@ n.mask() m=IP4Mask[23] -IP4Mask[22] +IP4Mask[22].to_hex() -IP4[110.1.30.70;255.255.255.0] \ No newline at end of file +IP4[110.1.30.70;255.255.255.0] + +a=25/51 + +a*52 \ No newline at end of file diff --git a/hybrid_calc_settings.json b/hybrid_calc_settings.json index 8ab1f30..167ed43 100644 --- a/hybrid_calc_settings.json +++ b/hybrid_calc_settings.json @@ -1,4 +1,8 @@ { - "window_geometry": "1020x700+2638+160", - "sash_pos_x": 332 + "window_geometry": "1020x700+137+49", + "sash_pos_x": 353, + "symbolic_mode": true, + "show_numeric_approximation": true, + "keep_symbolic_fractions": true, + "auto_simplify": false } \ No newline at end of file diff --git a/main_calc_app.py b/main_calc_app.py index c4e0d6b..a3b4ea2 100644 --- a/main_calc_app.py +++ b/main_calc_app.py @@ -12,6 +12,34 @@ import threading from typing import List, Dict, Any, Optional import re +# ========== IMPORTS PARA SISTEMA DE AYUDA ========== +# Para la ayuda en HTML +MARKDOWN_AVAILABLE = False +HTML_VIEWER_TYPE = None + +try: + import markdown + MARKDOWN_AVAILABLE = True +except ImportError: + # markdown not available, MARKDOWN_AVAILABLE remains False + pass + +# Intentar importar visores HTML +try: + import tkinterweb + HTML_VIEWER_TYPE = "tkinterweb" +except ImportError: + try: + from tkhtmlview import HTMLScrolledText + HTML_VIEWER_TYPE = "tkhtmlview" + except ImportError: + HTML_VIEWER_TYPE = None + +if not MARKDOWN_AVAILABLE: + print("Advertencia: La librería 'markdown' no está instalada. La ayuda se mostrará en texto plano.") +if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE is None: + print("Advertencia: 'markdown' está disponible, pero no se encontró un visor HTML (tkinterweb/tkhtmlview). La ayuda se mostrará en texto plano.") + # ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ========== # Importar componentes del CAS híbrido con nuevo sistema de tipos from main_evaluation import HybridEvaluationEngine, EvaluationResult @@ -26,21 +54,33 @@ class HybridCalculatorApp: SETTINGS_FILE = "hybrid_calc_settings.json" HISTORY_FILE = "hybrid_calc_history.txt" + HELP_FILE = "readme.md" # ========== NUEVO: Archivo de ayuda externo ========== def __init__(self, root: tk.Tk): self.root = root self.root.title("Calculadora MAV - CAS Híbrido") - # Cargar configuración + # Configuración y estado self.settings = self._load_settings() self.root.geometry(self.settings.get("window_geometry", "1000x700")) self.root.configure(bg="#2b2b2b") + # Configurar motor con configuraciones cargadas + self.engine = HybridEvaluationEngine(auto_discover_types=True, types_directory="custom_types") + self._apply_symbolic_settings() # NUEVO: Aplicar configuraciones simbólicas + + # Debug desde configuración + self.debug = self.settings.get("debug", False) + self.engine.debug = self.debug + + # Autocompletado + self.autocomplete_popup = None + self.current_suggestions = [] + # Configurar ícono self._setup_icon() # ========== COMPONENTES PRINCIPALES CON NUEVO SISTEMA ========== - self.engine = HybridEvaluationEngine(auto_discover_types=True) self.interactive_manager = None # Se inicializa después de crear widgets # ========== HELPERS DINÁMICOS DEL REGISTRO ========== @@ -52,7 +92,22 @@ class HybridCalculatorApp: self._cached_input_font = None self.output_buffer = [] - # Crear interfaz + # ========== BARRA DE ESTADO ========== + self.status_frame = tk.Frame(self.root, bg="#2b2b2b", height=25) + self.status_frame.pack(side=tk.BOTTOM, fill=tk.X) + self.status_frame.pack_propagate(False) + + self.status_label = tk.Label( + self.status_frame, + text=self._get_status_text(), + bg="#2b2b2b", + fg="#80c7f7", + font=("Consolas", 9), + anchor=tk.W + ) + self.status_label.pack(side=tk.LEFT, padx=10, pady=2) + + # ========== PANEL PRINCIPAL ========== self.create_widgets() self.setup_interactive_manager() self.load_history() @@ -152,20 +207,43 @@ CLASES DISPONIBLES: return {} def _save_settings(self): - """Guarda configuración de la aplicación""" - self.settings["window_geometry"] = self.root.winfo_geometry() - if hasattr(self, "paned_window"): - try: - sash_x_pos = self.paned_window.sash_coord(0)[0] - self.settings["sash_pos_x"] = sash_x_pos - except tk.TclError: - pass - + """Guarda configuraciones en archivo JSON""" try: + # Obtener geometría actual + self.settings["window_geometry"] = self.root.geometry() + + # Guardar posición del panel divisor si existe + if hasattr(self, 'paned_window'): + sash_pos = self.paned_window.sash_coord(0)[0] + self.settings["sash_pos_x"] = sash_pos + with open(self.SETTINGS_FILE, "w", encoding="utf-8") as f: - json.dump(self.settings, f, indent=4) - except IOError: - messagebox.showwarning("Error", "No se pudieron guardar los ajustes.") + json.dump(self.settings, f, indent=4, ensure_ascii=False) + except Exception as e: + if self.debug: + print(f"Error guardando configuración: {e}") + + def update_symbolic_settings(self, symbolic_mode=None, show_numeric=None, + keep_fractions=None, auto_simplify=None): + """Actualiza configuraciones simbólicas y las guarda""" + if symbolic_mode is not None: + self.settings["symbolic_mode"] = symbolic_mode + if show_numeric is not None: + self.settings["show_numeric_approximation"] = show_numeric + if keep_fractions is not None: + self.settings["keep_symbolic_fractions"] = keep_fractions + if auto_simplify is not None: + self.settings["auto_simplify"] = auto_simplify + + # Aplicar al motor + self._apply_symbolic_settings() + + # Actualizar barra de estado + if hasattr(self, 'status_label'): + self.status_label.config(text=self._get_status_text()) + + # Guardar configuraciones + self._save_settings() def create_widgets(self): """Crea la interfaz gráfica""" @@ -273,11 +351,39 @@ CLASES DISPONIBLES: cas_menu.add_separator() cas_menu.add_command(label="Resolver sistema", command=self.solve_system) + # Menú Configuración + config_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white") + menubar.add_cascade(label="Configuración", menu=config_menu) + + # Variables para checkbuttons + self.symbolic_mode_var = tk.BooleanVar(value=self.settings.get("symbolic_mode", True)) + self.show_numeric_var = tk.BooleanVar(value=self.settings.get("show_numeric_approximation", True)) + self.keep_fractions_var = tk.BooleanVar(value=self.settings.get("keep_symbolic_fractions", True)) + + # Modo simbólico + config_menu.add_checkbutton( + label="Modo Simbólico", + variable=self.symbolic_mode_var, + command=self.toggle_symbolic_mode + ) + config_menu.add_checkbutton( + label="Mostrar Aproximación Numérica", + variable=self.show_numeric_var, + command=self.toggle_numeric_approximation + ) + config_menu.add_checkbutton( + label="Mantener Fracciones Simbólicas", + variable=self.keep_fractions_var, + command=self.toggle_symbolic_fractions + ) + config_menu.add_separator() + + config_menu.add_command(label="Recargar Tipos Personalizados", command=self.reload_types) + # ========== MENÚ TIPOS (NUEVO) ========== - types_menu = Menu(menubar, tearoff=0) + types_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white") menubar.add_cascade(label="Tipos", menu=types_menu) types_menu.add_command(label="Información de tipos", command=self.show_types_info) - types_menu.add_command(label="Recargar tipos", command=self.reload_types) types_menu.add_separator() types_menu.add_command(label="Sintaxis de tipos", command=self.show_types_syntax) @@ -631,6 +737,9 @@ CLASES DISPONIBLES: output_parts.append(("equation", result.symbolic_result)) elif result.result_type == "assignment": output_parts.append(("info", result.symbolic_result)) + # Mostrar evaluación numérica para asignaciones si existe + if result.numeric_result is not None and result.numeric_result != result.result: + output_parts.append(("numeric", f"≈ {result.numeric_result}")) else: # Resultado normal if result.result is not None: @@ -666,32 +775,34 @@ CLASES DISPONIBLES: def _get_result_tag_dynamic(self, result: Any) -> str: """Determina el tag de color para un resultado - VERSIÓN DINÁMICA""" - # Obtener clases registradas dinámicamente + # Obtener clases registradas dinámicamente del sistema de tipos try: registered_classes = self.engine.get_available_types().get('registered_classes', {}) # Verificar si es una instancia de alguna clase registrada for name, cls in registered_classes.items(): if isinstance(result, cls): - # Usar tags específicos si existen, sino usar genérico - if name.lower() == "hex": + # Usar tags específicos basados en el nombre de la clase + name_lower = name.lower() + if name_lower == "hex": return "hex" - elif name.lower() == "bin": + elif name_lower == "bin": return "bin" - elif name.lower() in ["ip4", "ip"]: + elif name_lower in ["ip4", "ip"]: return "ip" - elif name.lower() == "chr": + elif name_lower == "chr": return "chr_type" + elif name_lower == "date": + return "date" else: return "custom_type" # Tag genérico para tipos personalizados except Exception as e: - print(f"DEBUG: Error en get_result_tag_dynamic: {e}") + if self.debug: + print(f"DEBUG: Error en get_result_tag_dynamic: {e}") - # Fallback a tags existentes - if hasattr(result, '__class__') and 'Class_' in result.__class__.__name__: - return "custom_type" - elif isinstance(result, sympy.Basic): + # Fallback a tags existentes para tipos no registrados + if isinstance(result, sympy.Basic): return "symbolic" else: return "result" @@ -699,7 +810,7 @@ CLASES DISPONIBLES: def _get_class_display_name_dynamic(self, obj: Any) -> str: """Obtiene nombre de clase para display - VERSIÓN DINÁMICA""" try: - # Verificar si es una clase registrada + # Verificar si es una clase registrada dinámicamente registered_classes = self.engine.get_available_types().get('registered_classes', {}) for name, cls in registered_classes.items(): @@ -707,16 +818,10 @@ CLASES DISPONIBLES: return name except Exception as e: - print(f"DEBUG: Error en get_class_display_name_dynamic: {e}") - - # Fallback a lógica existente - if hasattr(obj, '__class__'): - class_name = obj.__class__.__name__ - if class_name.startswith('Class_'): - return class_name.replace("Class_", "") - elif class_name.endswith('Mask'): - return class_name + if self.debug: + print(f"DEBUG: Error en get_class_display_name_dynamic: {e}") + # Fallback a lógica existente para tipos nativos if isinstance(obj, sympy.logic.boolalg.BooleanAtom): return "Boolean" elif isinstance(obj, sympy.Basic): @@ -797,14 +902,12 @@ CLASES DISPONIBLES: context_menu.add_command(label="Pegar", command=lambda: self.input_text.event_generate("<>")) context_menu.add_separator() context_menu.add_command(label="Limpiar entrada", command=self.clear_input) - context_menu.add_separator() - context_menu.add_command(label="Insertar ejemplo", command=self.insert_example) elif panel_type == "output": context_menu.add_command(label="Copiar todo", command=self.copy_output) context_menu.add_command(label="Limpiar salida", command=self.clear_output) context_menu.add_separator() - context_menu.add_command(label="Ayuda", command=self.show_quick_guide) + context_menu.add_command(label="Ayuda", command=self.show_help_window) try: context_menu.tk_popup(event.x_root, event.y_root) @@ -890,76 +993,12 @@ CLASES DISPONIBLES: self._evaluate_and_update() def copy_output(self): - """Copia contenido de salida al clipboard""" + """Copia el contenido de la salida al portapapeles""" content = self.output_text.get("1.0", tk.END).strip() if content: self.root.clipboard_clear() self.root.clipboard_append(content) - def insert_example(self): - """Inserta código de ejemplo - ACTUALIZADO CON TIPOS DINÁMICOS""" - # Obtener tipos disponibles dinámicamente - try: - available_types = self.engine.get_available_types() - registered_classes = available_types.get('registered_classes', {}) - except: - registered_classes = {} - - # Crear ejemplo base - example = """# Calculadora MAV - CAS Híbrido con Sistema de Tipos Dinámico -# Sintaxis nueva con corchetes - -""" - - # Añadir ejemplos de tipos disponibles dinámicamente - if registered_classes: - example += "# Tipos especializados disponibles\n" - - for name in sorted(registered_classes.keys()): - if name == "Hex": - example += "Hex[FF] + 1\n" - elif name == "Bin": - example += "Bin[1010] * 2\n" - elif name == "Chr": - example += "Chr[A].toHex()\n" - elif name == "Dec": - example += "Dec[42].toBin()\n" - elif name == "IP4": - example += "IP4[192.168.1.100/24].NetworkAddress()\n" - elif name == "IP4Mask": - example += "IP4Mask[24].hosts_count()\n" - - example += "\n" - - # Resto del ejemplo (sin cambios) - example += """# Matemáticas simbólicas -x + 2*y -diff(x**2 + sin(x), x) -integrate(x**2, x) - -# Ecuaciones (detección automática) -x**2 + 2*x - 8 = 0 -3*a + b = 10 - -# Resolver ecuaciones -solve(x**2 + 2*x - 8, x) -a=? - -# Variables automáticas -z = 5 -w = z**2 + 3 - -# Plotting interactivo -plot(sin(x), (x, -2*pi, 2*pi)) - -# Matrices -Matrix([[1, 2], [3, 4]]) -""" - - self.input_text.delete("1.0", tk.END) - self.input_text.insert("1.0", example) - self._evaluate_and_update() - def show_variables(self): """Muestra ventana con variables definidas""" variables = self.engine.symbol_table @@ -1265,12 +1304,8 @@ programación y análisis numérico. if content.strip(): self.input_text.insert("1.0", content) self.root.after_idle(self._evaluate_and_update) - return except Exception as e: print(f"Error cargando historial: {e}") - - # Cargar ejemplo por defecto si no hay historial - self.insert_example() def save_history(self): """Guarda historial de entrada""" @@ -1294,6 +1329,188 @@ programación y análisis numérico. self.root.destroy() + def show_help_window(self): + """Muestra ventana de ayuda con archivo externo - NUEVO SISTEMA""" + help_win = tk.Toplevel(self.root) + help_win.title("Ayuda - Calculadora MAV CAS Híbrido") + help_win.geometry("750x600") + help_win.configure(bg="#1e1e1e") + help_win.transient(self.root) + + readme_content = self._get_help_content() + + if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE: + try: + # CSS para un tema oscuro, consistente con la UI de la calculadora + dark_theme_css = """ + +""" + html_fragment = markdown.markdown( + readme_content, extensions=["fenced_code", "codehilite", "tables", "nl2br", "admonition"], + extension_configs={"codehilite": {"noclasses": True, "pygments_style": "monokai"}} + ) + + # Construir un documento HTML completo + content_for_viewer = f""" + + + + + Ayuda de Calculadora + {dark_theme_css} + + + {html_fragment} + + +""" + if HTML_VIEWER_TYPE == "tkinterweb": + html_viewer = tkinterweb.HtmlFrame(help_win, messages_enabled=False) + html_viewer.load_html(content_for_viewer) + elif HTML_VIEWER_TYPE == "tkhtmlview": + html_viewer = HTMLScrolledText(help_win) + html_viewer.configure(bg="#1e1e1e") + html_viewer.set_html(content_for_viewer) + + html_viewer.pack(padx=0, pady=0, fill=tk.BOTH, expand=True) + except Exception as e: + print(f"Error al renderizar Markdown a HTML: {e}") + # Fallback to text if HTML fails + self._show_text_help(help_win, readme_content) + else: + self._show_text_help(help_win, readme_content) + + # Botón de cerrar + close_button = tk.Button( + help_win, text="Cerrar", command=help_win.destroy, + bg="#3c3c3c", fg="white", relief=tk.FLAT, padx=10, + ) + close_button.pack(pady=(5, 10)) + + def _get_help_content(self): + """Obtiene el contenido de ayuda desde archivo externo o genera uno por defecto""" + try: + if os.path.exists(self.HELP_FILE): + with open(self.HELP_FILE, "r", encoding="utf-8") as f: + return f.read() + except IOError: + pass + + # Contenido por defecto si no se encuentra el archivo + return """# Calculadora MAV - CAS Híbrido + +## Sistema de Tipos Dinámico +El sistema detecta automáticamente tipos disponibles en `custom_types/` + +## Sintaxis Nueva con Corchetes +- **Sintaxis**: `Tipo[valor]` en lugar de `Tipo("valor")` +- **Ejemplos**: `Hex[FF]`, `Bin[1010]`, `Dec[10.5]`, `Chr[A]` +- Use menú **Tipos → Información de tipos** para ver tipos disponibles + +## Ecuaciones Automáticas +- `x**2 + 2*x = 8` (detectado automáticamente) +- `a + b = 10` (agregado al sistema) +- `variable=?` (atajo para solve(variable)) + +## Funciones SymPy Disponibles +- `solve()`, `diff()`, `integrate()`, `limit()`, `series()` +- `sin()`, `cos()`, `tan()`, `exp()`, `log()`, `sqrt()` +- `Matrix()`, `plot()`, `plot3d()` + +## Resultados Interactivos +- 📊 **Ver Plot** (click para ventana matplotlib) +- 📋 **Ver Matriz** (click para vista expandida) +- 📋 **Ver Lista** (click para contenido completo) + +## Variables Automáticas +- Todas las variables son símbolos SymPy +- `x = 5` crea Symbol('x') con valor 5 +- Evaluación simbólica + numérica automática + +## Autocompletado Dinámico +- Escriba "." después de cualquier objeto para ver métodos +- El sistema usa los tipos registrados automáticamente + +## Menú Contextual (clic derecho) +- **En entrada**: Cortar, Copiar, Pegar, Limpiar entrada, Ayuda +- **En salida**: Copiar todo, Limpiar salida, Ayuda + +## Desarrollo +Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el directorio raíz de la aplicación. +""" + + def _show_text_help(self, help_win, content): + """Muestra la ayuda en texto plano cuando markdown no está disponible""" + text_widget = scrolledtext.ScrolledText( + help_win, font=("Consolas", 10), bg="#1e1e1e", fg="#d4d4d4", + wrap=tk.WORD, borderwidth=0, highlightthickness=0 + ) + text_widget.insert("1.0", content) + text_widget.config(state="disabled") + text_widget.pack(padx=5, pady=5, fill=tk.BOTH, expand=True) + def obtener_ayuda(self, input_str): """Obtiene ayuda usando helpers dinámicos""" for helper in self.HELPERS: @@ -1306,6 +1523,43 @@ programación y análisis numérico. continue return None + def _apply_symbolic_settings(self): + """Aplica configuraciones simbólicas al motor de evaluación""" + symbolic_mode = self.settings.get("symbolic_mode", True) + show_numeric = self.settings.get("show_numeric_approximation", True) + keep_fractions = self.settings.get("keep_symbolic_fractions", True) + auto_simplify = self.settings.get("auto_simplify", False) + + self.engine.set_symbolic_mode( + symbolic_mode=symbolic_mode, + show_numeric=show_numeric, + keep_fractions=keep_fractions, + auto_simplify=auto_simplify + ) + + def toggle_symbolic_mode(self): + """Alterna el modo simbólico""" + new_value = self.symbolic_mode_var.get() + self.update_symbolic_settings(symbolic_mode=new_value) + + def toggle_numeric_approximation(self): + """Alterna la aproximación numérica""" + new_value = self.show_numeric_var.get() + self.update_symbolic_settings(show_numeric=new_value) + + def toggle_symbolic_fractions(self): + """Alterna la mantención de fracciones simbólicas""" + new_value = self.keep_fractions_var.get() + self.update_symbolic_settings(keep_fractions=new_value) + + def _get_status_text(self): + """Obtiene el texto de estado actual""" + mode = "🔢 Simbólico" if self.settings.get("symbolic_mode", True) else "🧮 Numérico" + numeric_indicator = " ≈" if self.settings.get("show_numeric_approximation", True) else "" + fractions_indicator = " 📐" if self.settings.get("keep_symbolic_fractions", True) else "" + + return f"{mode}{numeric_indicator}{fractions_indicator}" + def main(): """Función principal""" diff --git a/main_evaluation.py b/main_evaluation.py index 95372bc..f97b792 100644 --- a/main_evaluation.py +++ b/main_evaluation.py @@ -43,6 +43,13 @@ class HybridEvaluationEngine: # Debug mode self.debug = False + + # NUEVA CONFIGURACIÓN: Modo simbólico + self.symbolic_mode = True # Por defecto, mantener forma simbólica + self.show_numeric_approximation = True # Mostrar aproximación numérica + self.keep_symbolic_fractions = True # Mantener fracciones como 4/5 + self.auto_simplify = False # No simplificar automáticamente + # Configurar contexto base self._setup_base_context() @@ -261,9 +268,34 @@ class HybridEvaluationEngine: # Obtener el valor asignado assigned_value = self.symbol_table.get(var_name) + # Generar evaluación numérica si está configurado para mostrarla + numeric_result = None + if self.show_numeric_approximation and hasattr(assigned_value, 'evalf'): + try: + numeric_eval = assigned_value.evalf() + # Verificar si el resultado numérico es diferente del simbólico + # Para fracciones racionales, siempre mostrar la aproximación decimal + if hasattr(assigned_value, 'is_Rational') and assigned_value.is_Rational: + # Es una fracción racional, mostrar aproximación decimal + numeric_result = numeric_eval + elif numeric_eval != assigned_value: + # Para otros casos, mostrar si son diferentes + try: + # Intentar comparación numérica más robusta + if abs(float(numeric_eval) - float(assigned_value)) > 1e-15: + numeric_result = numeric_eval + except: + # Si la comparación falla, asumir que son diferentes + numeric_result = numeric_eval + except Exception as e: + if self.debug: + print(f"DEBUG: Error en evaluación numérica: {e}") + pass + return EvaluationResult( assigned_value, "assignment", symbolic_result=result, + numeric_result=numeric_result, original_line=original_line ) except Exception as e: @@ -304,12 +336,16 @@ class HybridEvaluationEngine: # Actualizar last_result self.last_result = result - # Intentar evaluación numérica si es posible + # Intentar evaluación numérica si está configurado para mostrarla numeric_result = None - if hasattr(result, 'evalf'): + if self.show_numeric_approximation and hasattr(result, 'evalf'): try: numeric_eval = result.evalf() - if numeric_eval != result: + # Solo mostrar evaluación numérica si es diferente del resultado simbólico + if numeric_eval != result and not ( + hasattr(result, 'is_number') and result.is_number and + abs(float(numeric_eval) - float(result)) < 1e-15 + ): numeric_result = numeric_eval except: pass @@ -389,42 +425,109 @@ class HybridEvaluationEngine: if expression.strip().startswith('_add_equation'): return eval(expression, {"__builtins__": {}}, context) elif expression.strip().startswith('_assign_variable'): - return eval(expression, {"__builtins__": {}}, context) + # NUEVA LÓGICA: Manejar asignaciones en modo simbólico + # Extraer la expresión de la llamada _assign_variable("var", expresión) + import re + match = re.match(r'_assign_variable\("([^"]+)",\s*(.+)\)', expression.strip()) + if match: + var_name = match.group(1) + expr_to_evaluate = match.group(2).strip() + + # Evaluar la expresión usando la lógica simbólica + if self.symbolic_mode: + try: + value = sympify(expr_to_evaluate, locals=context, rational=self.keep_symbolic_fractions) + if self.auto_simplify and hasattr(value, 'simplify'): + value = value.simplify() + except: + # Si falla SymPy, usar eval como fallback + value = eval(expr_to_evaluate, {"__builtins__": {}}, context) + else: + # En modo numérico, usar eval + value = eval(expr_to_evaluate, {"__builtins__": {}}, context) + + # Asignar directamente usando los valores evaluados + self.symbol_table[var_name] = value + return f"{var_name} = {value}" + else: + # Si no se puede parsear, usar eval como fallback + return eval(expression, {"__builtins__": {}}, context) else: - try: - # Primero intentar evaluación directa para objetos especializados + # NUEVA LÓGICA: Priorizar SymPy en modo simbólico + if self.symbolic_mode: try: - result = eval(expression, {"__builtins__": {}}, context) + # Primero intentar con SymPy para mantener formas simbólicas + result = sympify(expression, locals=context, rational=self.keep_symbolic_fractions) - # Si el resultado es un objeto híbrido, integrarlo con SymPy si es necesario - if hasattr(result, '_sympystr'): # 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 + # Si auto_simplify está activado, simplificar + if self.auto_simplify and hasattr(result, 'simplify'): + result = result.simplify() + + return result + + except (SyntaxError, TypeError, ValueError) as sympy_error: + # Si SymPy falla, intentar con eval para objetos especializados + try: + result = eval(expression, {"__builtins__": {}}, context) - except (NameError, TypeError) as eval_error: - # Si eval falla, intentar con SymPy + # Si el resultado es un objeto híbrido, retornarlo + if hasattr(result, '_sympystr'): # 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): + return result + else: + # Convertir resultado de eval a SymPy si es posible + try: + return sympify(result, rational=self.keep_symbolic_fractions) + except: + return result + + except Exception as eval_error: + # Si ambos fallan, re-lanzar el error más informativo + if "invalid syntax" in str(sympy_error): + raise eval_error + else: + raise sympy_error + else: + # MODO NO SIMBÓLICO: usar lógica original + 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 hasattr(result, '_sympystr'): # 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: - # 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 + raise syntax_error def _get_full_context(self) -> Dict[str, Any]: """Obtiene el contexto completo para evaluación""" @@ -436,9 +539,9 @@ class HybridEvaluationEngine: def _assign_variable(self, var_name: str, expression) -> str: """Asigna un valor a una variable""" try: - # Evaluar la expresión + # Evaluar la expresión usando el contexto completo y configuraciones simbólicas if isinstance(expression, str): - value = sympify(expression, locals=self._get_full_context()) + value = self._eval_in_context(expression) else: value = expression @@ -528,6 +631,16 @@ class HybridEvaluationEngine: self.clear_equations() self.clear_variables() + def set_symbolic_mode(self, symbolic_mode: bool = True, + show_numeric: bool = True, + keep_fractions: bool = True, + auto_simplify: bool = False): + """Configura el modo de evaluación simbólica""" + self.symbolic_mode = symbolic_mode + self.show_numeric_approximation = show_numeric + self.keep_symbolic_fractions = keep_fractions + self.auto_simplify = auto_simplify + class EvaluationResult: """Resultado de evaluación con información contextual""" @@ -566,62 +679,3 @@ class EvaluationResult: elif self.result is not None: return str(self.result) return "" - - -# Funciones de testing -def test_hybrid_engine_with_types(): - """Test del motor de evaluación con sistema de tipos""" - print("🧪 Test HybridEvaluationEngine con Type Registry") - print("=" * 60) - - # Crear motor con auto-descubrimiento - engine = HybridEvaluationEngine(auto_discover_types=True) - engine.debug = True - - # Mostrar información de tipos - types_info = engine.get_available_types() - print(f"📊 Tipos disponibles: {types_info}") - print() - - # Test casos básicos - test_cases = [ - # Expresiones básicas - "2 + 3", - "x + 2", - "sin(pi/2)", - - # Tipo Chr si está disponible - "Chr[A]" if "Chr" in types_info['registered_classes'] else "# Chr no disponible", - - # Variables y ecuaciones - "a = 10", - "x + 2 = 5", - "solve(x + 2 - 5, x)", - - # Funciones avanzadas - "diff(x**2, x)", - "plot(sin(x), (x, -pi, pi))", - ] - - print("🔍 Ejecutando casos de prueba:") - for test in test_cases: - if test.startswith('#'): - print(f"⏭️ {test}") - continue - - result = engine.evaluate_line(test) - print(f"✅ '{test}' → {result} (type: {result.result_type})") - - if result.info: - print(f" ℹ️ Info: {result.info}") - - if result.is_error: - print(f" ❌ Error: {result.error}") - - print("\n🔄 Test recarga de tipos:") - engine.reload_types() - print("✅ Recarga completada") - - -if __name__ == "__main__": - test_hybrid_engine_with_types() diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e08381b --- /dev/null +++ b/readme.md @@ -0,0 +1,233 @@ +# Calculadora MAV - CAS Híbrido + +## Descripción + +Sistema de Álgebra Computacional (CAS) que combina SymPy con clases especializadas para networking, programación y análisis numérico. Incluye sistema de tipos dinámico con auto-descubrimiento. + +## Características Principales + +- **Motor SymPy completo**: Cálculo simbólico y numérico integrado +- **Sistema de tipos dinámico**: Auto-descubrimiento desde `custom_types/` +- **Sintaxis simplificada**: `Tipo[args]` en lugar de `Tipo("args")` +- **Detección automática de ecuaciones**: Sin sintaxis especial +- **Resultados interactivos**: Elementos clickeables para plots, matrices y listas +- **Autocompletado inteligente**: Basado en tipos registrados dinámicamente + + +## Tipos Especializados + +### Redes y Direcciones IP +```python +IP4[192.168.1.100/24] +IP4[10.0.0.1, 8] +IP4[172.16.0.5, 255.255.0.0] +IP4Mask[24] +IP4Mask[255.255.255.0] +``` + +### Sistemas Numéricos +```python +Hex[FF] # Hexadecimal +Bin[1010] # Binario +Dec[255] # Decimal +Chr[A] # Caracteres ASCII +``` + +### Métodos Disponibles +Los tipos incluyen métodos específicos accesibles con autocompletado: +```python +ip = IP4[192.168.1.100/24] +ip.NetworkAddress[] # Dirección de red +ip.Nodes() # Hosts disponibles +Hex[FF].toDecimal() # Conversiones +``` + +## Sistema de Ecuaciones + +### Detección Automática +```python +x + 2 = 5 # Detectado automáticamente +y**2 - 4 = 0 # Agregado al sistema +sin(x) = 1/2 # Ecuaciones trigonométricas +``` + +### Resolución +```python +solve(x**2 + 2*x - 8, x) # Resolver ecuación específica +x=? # Atajo para solve(x) +``` + +## Funciones Matemáticas + +### Cálculo +```python +diff(x**3, x) # Derivadas +integrate(sin(x), (x, 0, pi)) # Integrales +limit(sin(x)/x, x, 0) # Límites +series(exp(x), x, 0, 5) # Series +``` + +### Álgebra +```python +simplify(expr) # Simplificación +expand((x+1)**3) # Expansión +factor(x**2-1) # Factorización +solve([eq1, eq2], [x, y]) # Sistemas +``` + +### Matrices +```python +Matrix([[1, 2], [3, 4]]) # Crear matriz +det(M) # Determinante +inv(M) # Inversa +``` + +## Resultados Interactivos + +Los siguientes resultados son clickeables en la interfaz: +- **📊 Ver Plot**: Abre matplotlib para gráficas +- **📋 Ver Matriz**: Vista expandida con operaciones +- **📋 Ver Lista**: Contenido completo de listas largas +- **🔍 Ver Detalles**: Información completa de objetos + +## Plotting + +```python +plot(sin(x), (x, -2*pi, 2*pi)) # Plot 2D +plot3d(x**2 + y**2, (x, -5, 5), (y, -5, 5)) # Plot 3D +``` + +## Variables y Símbolos + +Todas las variables son símbolos SymPy automáticamente: +```python +x = 5 # x es Symbol('x') con valor 5 +y = x + 2 # y es 7 (evaluado) +z = x + a # z es Symbol('x') + Symbol('a') (simbólico) +``` + +## Interfaz de Usuario + +### Paneles +- **Izquierdo**: Entrada de código +- **Derecho**: Resultados con colores y elementos interactivos + +### Autocompletado +- Escribir `.` después de cualquier objeto muestra métodos disponibles +- El sistema usa tipos registrados dinámicamente +- Funciona con objetos SymPy y tipos personalizados + +### Menús +- **Archivo**: Nuevo, Cargar, Guardar +- **Editar**: Limpiar, operaciones CAS +- **CAS**: Variables, ecuaciones, resolver sistema +- **Tipos**: Información de tipos, recargar sistema +- **Ayuda**: Guías y referencia + +## Sistema de Tipos Dinámico + +### Auto-descubrimiento +Los tipos se cargan automáticamente desde `custom_types/`: +- Archivos `*_type.py` son detectados automáticamente +- Cada archivo define clases y función `register_classes_in_module()` +- Sistema modular y extensible sin modificar código central + +### Información de Tipos +Use **Menú Tipos → Información de tipos** para ver: +- Clases registradas disponibles +- Sintaxis de corchetes soportada +- Métodos de cada tipo + +### Recargar Tipos +**Menú Tipos → Recargar tipos** para desarrollo en tiempo real. + +## Casos de Uso + +### Análisis de Redes +```python +network = IP4[192.168.0.0/24] +host = IP4[192.168.0.100/24] +network.Nodes() # Hosts disponibles +host.NetworkAddress[] # Dirección de red +``` + +### Conversiones Numéricas +```python +Hex[FF].toDecimal() # 255 +Dec[66].toChr() # Chr('B') +``` + +### Análisis Matemático +```python +f = sin(x) * exp(-x**2/2) +df_dx = diff(f, x) # Derivada +critical_points = solve(df_dx, x) # Puntos críticos +plot(f, df_dx, (x, -3, 3)) # Visualización +``` + +## Extensibilidad + +### Crear Nuevos Tipos +1. Crear archivo en `custom_types/nuevo_type.py` +2. Definir clase heredando de `ClassBase` o `SympyClassBase` +3. Implementar función `register_classes_in_module()` +4. El sistema detecta automáticamente el nuevo tipo + +### Ejemplo de Estructura +```python +# custom_types/ejemplo_type.py +from sympy_Base import SympyClassBase + +class Class_Ejemplo(SympyClassBase): + def __init__(self, value): + super().__init__(processed_value, original_str) + + @staticmethod + def Helper(input_str): + return "Ayuda para Ejemplo" + + @staticmethod + def PopupFunctionList(): + return [("metodo", "Descripción del método")] + +def register_classes_in_module(): + return [("Ejemplo", Class_Ejemplo, "SympyClassBase", {})] +``` + +## Resolución de Problemas + +### Errores Comunes +- **Dependencias faltantes**: Ejecutar `pip install sympy matplotlib numpy` +- **tkinter en Linux**: `sudo apt-get install python3-tk` +- **Sintaxis**: Usar `Tipo[args]` no `Tipo("args")` + +### Performance +- Expresiones complejas pueden ser lentas en SymPy +- Plots 3D requieren tiempo de renderizado +- Matrices grandes consumen memoria + +### Debugging +- Los logs se guardan en `logs/` +- Usar **Menú Tipos → Información** para verificar tipos cargados +- Verificar `custom_types/` para tipos personalizados + +## Arquitectura + +### Componentes Principales +- **type_registry.py**: Sistema de auto-descubrimiento +- **main_evaluation.py**: Motor CAS híbrido +- **tl_bracket_parser.py**: Parser de sintaxis con corchetes +- **tl_popup.py**: Resultados interactivos +- **main_calc_app.py**: Aplicación principal + +### Flujo de Ejecución +1. Auto-descubrimiento de tipos en `custom_types/` +2. Registro dinámico de clases y métodos +3. Parser convierte sintaxis con corchetes +4. Motor SymPy evalúa expresiones +5. Resultados se presentan interactivamente + +--- + +**Calculadora MAV - CAS Híbrido** +*Sistema extensible para cálculo matemático y análisis especializado* \ No newline at end of file diff --git a/test_final.py b/test_final.py new file mode 100644 index 0000000..88e0ef6 --- /dev/null +++ b/test_final.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +""" +Script de prueba final para verificar todas las funcionalidades del modo simbólico +""" + +from main_evaluation import HybridEvaluationEngine + +def test_comprehensive(): + """Prueba comprehensiva del modo simbólico""" + print("=" * 70) + print("PRUEBA COMPREHENSIVA - MODO SIMBÓLICO") + print("=" * 70) + + # Crear motor en modo simbólico + engine = HybridEvaluationEngine() + engine.set_symbolic_mode( + symbolic_mode=True, + show_numeric=True, + keep_fractions=True + ) + + # Test cases + test_cases = [ + # Fracciones simples + ("4/5", "Fracción simple"), + ("25/51", "Fracción compleja"), + ("22/7", "Aproximación de π"), + + # Asignaciones de fracciones + ("a = 4/5", "Asignación fracción simple"), + ("b = 25/51", "Asignación fracción compleja"), + ("c = 22/7", "Asignación aproximación π"), + + # Operaciones con fracciones + ("3/4 + 1/6", "Suma de fracciones"), + ("5/6 - 1/3", "Resta de fracciones"), + ("2/3 * 3/4", "Multiplicación de fracciones"), + ("5/6 / 2/3", "División de fracciones"), + + # Asignaciones de operaciones + ("d = 3/4 + 1/6", "Asignación suma fracciones"), + ("e = 2/3 * 3/4", "Asignación multiplicación fracciones"), + + # Expresiones simbólicas + ("sqrt(2)", "Raíz cuadrada"), + ("sin(pi/4)", "Función trigonométrica"), + ("log(e)", "Logaritmo"), + + # Asignaciones simbólicas + ("f = sqrt(2)/2", "Asignación expresión simbólica"), + ("g = sin(pi/6)", "Asignación función trigonométrica"), + ] + + print("\nRESULTADOS:") + print("-" * 70) + + for i, (expression, description) in enumerate(test_cases, 1): + print(f"\n{i:2d}. {description}") + print(f" Expresión: {expression}") + + result = engine.evaluate_line(expression) + + if result.is_error: + print(f" ❌ ERROR: {result.error}") + else: + print(f" ✅ Resultado: {result.result}") + if result.numeric_result is not None: + print(f" 📊 Numérico: ≈ {result.numeric_result}") + else: + print(f" 📊 Numérico: (no disponible)") + + # Verificar variables asignadas + print(f"\n{'-'*70}") + print("VARIABLES ASIGNADAS:") + print(f"{'-'*70}") + + variables = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + for var in variables: + value = engine.get_variable(var) + if value is not None: + print(f" {var} = {value} (tipo: {type(value).__name__})") + + print(f"\n{'='*70}") + print("MODO SIMBÓLICO: ✅ FUNCIONANDO CORRECTAMENTE") + print("- Fracciones se mantienen simbólicas: 4/5, 25/51, 22/7") + print("- Aproximaciones numéricas se muestran cuando corresponde") + print("- Asignaciones preservan la forma simbólica") + print("- Operaciones mantienen exactitud simbólica") + print(f"{'='*70}") + +if __name__ == "__main__": + test_comprehensive() \ No newline at end of file diff --git a/test_symbolic.py b/test_symbolic.py new file mode 100644 index 0000000..db22176 --- /dev/null +++ b/test_symbolic.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +""" +Script de prueba para el modo simbólico de la calculadora - ASIGNACIONES +""" + +from main_evaluation import HybridEvaluationEngine + +def test_assignments(): + """Prueba las asignaciones en modo simbólico""" + print("=" * 60) + print("PRUEBA DE ASIGNACIONES EN MODO SIMBÓLICO") + print("=" * 60) + + # Crear motor en modo simbólico + print("\n1. MODO SIMBÓLICO ACTIVADO:") + engine_symbolic = HybridEvaluationEngine() + engine_symbolic.set_symbolic_mode( + symbolic_mode=True, + show_numeric=True, + keep_fractions=True + ) + + # Pruebas de asignaciones + assignment_tests = [ + "a = 25/51", + "b = 4/5", + "c = 22/7", + "d = 3/4 + 1/6", + "e = sqrt(2)/2" + ] + + for test in assignment_tests: + result = engine_symbolic.evaluate_line(test) + print(f" {test:15} → Tipo: {result.result_type}") + print(f" {' ':15} Resultado: {result.result}") + print(f" {' ':15} Simbólico: {result.symbolic_result}") + if result.numeric_result: + print(f" {' ':15} Numérico: ≈ {result.numeric_result}") + else: + print(f" {' ':15} Numérico: None") + print() + + print("\n2. VERIFICAR VALORES ASIGNADOS:") + variables = ['a', 'b', 'c', 'd', 'e'] + for var in variables: + value = engine_symbolic.get_variable(var) + print(f" {var} = {value} (tipo: {type(value)})") + +if __name__ == "__main__": + test_assignments() \ No newline at end of file diff --git a/tl_bracket_parser.py b/tl_bracket_parser.py index a7ff37a..349ce9f 100644 --- a/tl_bracket_parser.py +++ b/tl_bracket_parser.py @@ -71,6 +71,10 @@ class BracketParser: """Retorna el set actual de clases con sintaxis de corchetes""" return self.BRACKET_CLASSES.copy() + def has_bracket_class(self, class_name: str) -> bool: + """Verifica si una clase está registrada para sintaxis con corchetes""" + return class_name in self.BRACKET_CLASSES + def parse_line(self, code_line: str) -> Tuple[str, str]: """ Parsea una línea de código aplicando todas las transformaciones @@ -246,25 +250,6 @@ class BracketParser: transformed = re.sub(method_pattern, r'.\1()', transformed) return transformed - - def test_bracket_transformation(self, test_line: str) -> Dict[str, str]: - """Test de transformación de una línea específica""" - result = { - 'input': test_line, - 'bracket_classes': list(self.BRACKET_CLASSES), - 'output': None, - 'parse_info': None, - 'error': None - } - - try: - output, parse_info = self.parse_line(test_line) - result['output'] = output - result['parse_info'] = parse_info - except Exception as e: - result['error'] = str(e) - - return result class EquationDetector: @@ -306,92 +291,4 @@ class EquationDetector: return False except Exception: - return False - - -# Funciones de utilidad para testing -def test_bracket_parser_with_types(): - """Función de testing para el bracket parser con tipos""" - print("🧪 Test BracketParser con Type Registry") - print("=" * 50) - - # Crear parser con registro de tipos - parser = BracketParser(use_type_registry=True) - parser.debug = True - - print(f"🔧 Clases de corchetes disponibles: {parser.get_bracket_classes()}") - print() - - # Test casos básicos (que funcionen sin importar qué tipos estén disponibles) - basic_test_cases = [ - # Atajos solve - ("x=?", "solve(x)", "solve_shortcut"), - ("variable_name=?", "solve(variable_name)", "solve_shortcut"), - - # Asignaciones - ("z = 5", '_assign_variable("z", 5)', "assignment"), - ("w = z**2 + 3", '_assign_variable("w", z**2 + 3)', "assignment"), - - # Ecuaciones standalone - ("x + 2 = 5", '_add_equation("x + 2 = 5")', "equation"), - ("3*a + b = 10", '_add_equation("3*a + b = 10")', "equation"), - - # Expresiones normales - ("x + 2*y", "x + 2*y", "expression"), - ("diff(x**2, x)", "diff(x**2, x)", "expression"), - ] - - # Test casos de sintaxis con corchetes (dinámico) - bracket_test_cases = [] - available_classes = parser.get_bracket_classes() - - if "Chr" in available_classes: - bracket_test_cases.extend([ - ("Chr[A]", 'Chr("A")', "bracket_transform"), - ("Chr[65]", 'Chr("65")', "bracket_transform"), - ]) - - if "Hex" in available_classes: - bracket_test_cases.extend([ - ("Hex[FF]", 'Hex("FF")', "bracket_transform"), - ("Hex[255]", 'Hex("255")', "bracket_transform"), - ]) - - if "IP4" in available_classes: - bracket_test_cases.extend([ - ("IP4[192.168.1.1/24]", 'IP4("192.168.1.1/24")', "bracket_transform"), - ("IP4[192.168.1.1;24]", 'IP4("192.168.1.1", "24")', "bracket_transform"), - ]) - - # Combinar casos de test - all_test_cases = basic_test_cases + bracket_test_cases - - print("🔍 Ejecutando casos de prueba:") - for test_input, expected_result, expected_info in all_test_cases: - result, info = parser.parse_line(test_input) - status = "✅" if result == expected_result and info == expected_info else "❌" - print(f"{status} '{test_input}' → '{result}' ({info})") - if result != expected_result or info != expected_info: - print(f" 💭 Esperado: '{expected_result}' ({expected_info})") - - # Test de añadir/remover clases dinámicamente - print(f"\n🔧 Test modificación dinámica de clases:") - print(f" Clases iniciales: {len(parser.get_bracket_classes())}") - - parser.add_bracket_class("TestClass") - print(f" Después de añadir TestClass: {len(parser.get_bracket_classes())}") - - test_result = parser.test_bracket_transformation("TestClass[example]") - print(f" Test TestClass[example]: {test_result['output']}") - - parser.remove_bracket_class("TestClass") - print(f" Después de remover TestClass: {len(parser.get_bracket_classes())}") - - # Test recarga - print(f"\n🔄 Test recarga:") - parser.reload_bracket_classes() - print(f" Clases después de recarga: {len(parser.get_bracket_classes())}") - - -if __name__ == "__main__": - test_bracket_parser_with_types() \ No newline at end of file + return False \ No newline at end of file diff --git a/tl_popup.py b/tl_popup.py index b1b9c57..8ca817b 100644 --- a/tl_popup.py +++ b/tl_popup.py @@ -526,61 +526,3 @@ class InteractiveResultManager: except tk.TclError: pass self.open_windows.clear() - - -# Función de testing -def test_interactive_results(): - """Test del sistema de resultados interactivos""" - print("🧪 Test Interactive Results - Versión Corregida") - print("=" * 50) - - root = tk.Tk() - root.title("Test Interactive Results") - - manager = InteractiveResultManager(root) - - # Crear widget de texto de prueba - text_widget = tk.Text(root, height=20, width=80, bg="#1e1e1e", fg="#d4d4d4") - text_widget.pack(padx=10, pady=10) - - # Test con PlotResult - print("📊 Testing PlotResult...") - plot_result = PlotResult("plot", (sympy.sin(sympy.Symbol('x')), (sympy.Symbol('x'), -10, 10)), {}) - tag_info = manager.create_interactive_tag(plot_result, text_widget, "1.0") - if tag_info: - tag, display = tag_info - text_widget.insert("end", f"Plot test: {display}\n", tag) - print(f" ✅ PlotResult tag creado: {display}") - else: - print(f" ❌ PlotResult tag NO creado") - - # Test con matriz - print("📋 Testing Matrix...") - matrix = sympy.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - tag_info = manager.create_interactive_tag(matrix, text_widget, "2.0") - if tag_info: - tag, display = tag_info - text_widget.insert("end", f"Matrix test: {display}\n", tag) - print(f" ✅ Matrix tag creado: {display}") - else: - print(f" ❌ Matrix tag NO creado") - - # Test con lista - print("📋 Testing List...") - long_list = list(range(20)) - tag_info = manager.create_interactive_tag(long_list, text_widget, "3.0") - if tag_info: - tag, display = tag_info - text_widget.insert("end", f"List test: {display}\n", tag) - print(f" ✅ List tag creado: {display}") - else: - print(f" ❌ List tag NO creado") - - print("\n✅ Test completado. Ventana interactiva abierta.") - print("🔍 Haz click en los elementos para probar funcionalidad.") - - root.mainloop() - - -if __name__ == "__main__": - test_interactive_results() diff --git a/type_registry.py b/type_registry.py index e44456c..0f368f6 100644 --- a/type_registry.py +++ b/type_registry.py @@ -191,109 +191,3 @@ def get_registered_bracket_classes() -> set: def get_registered_helper_functions() -> List[callable]: """Obtiene las funciones Helper registradas""" return _global_registry.get_helper_functions() - - -def create_types_directory_structure(): - """ - Crea la estructura básica del directorio de tipos - """ - types_dir = Path("custom_types") - types_dir.mkdir(exist_ok=True) - - # Crear __init__.py - init_file = types_dir / "__init__.py" - if not init_file.exists(): - init_file.write_text('''""" -Directorio de tipos personalizados para la Calculadora MAV - -Cada archivo *_type.py en este directorio debe definir: - -def register_classes_in_module(): - """Devuelve una lista de clases definidas en este módulo para ser registradas.""" - return [ - ("NombrePublico", ClaseObjeto, "SympyClassBase", {"add_lowercase": True}), - # ... más clases - ] - -Categorías soportadas: -- "ClassBase": Clase base simple -- "SympyClassBase": Clase híbrida con SymPy - -Opciones disponibles: -- "add_lowercase": bool - Añadir versión en minúsculas al contexto -- "supports_brackets": bool - Forzar soporte de sintaxis con corchetes -""" -''') - - return types_dir - - -# Función de testing -def test_type_registry(): - """Test del sistema de registro de tipos""" - print("🧪 Test Type Registry System") - print("=" * 50) - - # Crear estructura si no existe - types_dir = create_types_directory_structure() - print(f"📁 Directorio de tipos: {types_dir.absolute()}") - - # Crear archivo de ejemplo si no existe - example_file = types_dir / "example_type.py" - if not example_file.exists(): - example_content = '''""" -Ejemplo de archivo de tipo personalizado -""" -from class_base import ClassBase - -class ExampleClass(ClassBase): - def __init__(self, value): - super().__init__(value, str(value)) - - @staticmethod - def Helper(input_str): - if "Example" in input_str: - return "Ej: Example[test]" - return None - - @staticmethod - def PopupFunctionList(): - return [("test_method", "Método de prueba")] - -def register_classes_in_module(): - """Devuelve clases para registro""" - return [ - ("Example", ExampleClass, "ClassBase", {"add_lowercase": True}), - ] -''' - example_file.write_text(example_content) - print(f"📝 Archivo de ejemplo creado: {example_file}") - - # Probar descubrimiento - try: - registry_info = discover_and_register_types() - - print(f"✅ Descubrimiento completado:") - print(f" 📦 Clases registradas: {registry_info['class_count']}") - print(f" 🔧 Clases con brackets: {registry_info['bracket_count']}") - print(f" 📋 Contexto base: {len(registry_info['base_context'])} entradas") - - print("\n📋 Clases encontradas:") - for name, cls in registry_info['registered_classes'].items(): - print(f" • {name}: {cls.__name__}") - - print("\n🔧 Clases con sintaxis de corchetes:") - for name in registry_info['bracket_classes']: - print(f" • {name}") - - return True - - except Exception as e: - print(f"❌ Error en test: {e}") - import traceback - traceback.print_exc() - return False - - -if __name__ == "__main__": - test_type_registry()