Implementación de un nuevo sistema de ayuda en la aplicación, que incluye soporte para mostrar contenido en formato Markdown y HTML. Se añaden configuraciones simbólicas para el motor de evaluación, permitiendo alternar entre modos simbólico y numérico, así como opciones para mantener fracciones simbólicas y simplificación automática. Se actualizan las configuraciones de la ventana y se mejora la gestión del historial de entradas. Se eliminan funciones de prueba obsoletas y se optimiza el manejo de errores.

This commit is contained in:
Miguel 2025-06-02 22:39:31 +02:00
parent f16c878a58
commit ee24ef2615
10 changed files with 902 additions and 478 deletions

View File

@ -18,6 +18,10 @@ n.mask()
m=IP4Mask[23]
IP4Mask[22]
IP4Mask[22].to_hex()
IP4[110.1.30.70;255.255.255.0]
IP4[110.1.30.70;255.255.255.0]
a=25/51
a*52

View File

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

View File

@ -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("<<Paste>>"))
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 = """
<style>
h1, h2, h3, h4, h5, h6 {
color: #569cd6;
margin-top: 1.2em;
margin-bottom: 0.6em;
border-bottom: 1px solid #3a3a3a;
padding-bottom: 0.3em;
}
h1 { font-size: 1.8em; }
h2 { font-size: 1.5em; }
h3 { font-size: 1.3em; }
p {
line-height: 1.65;
margin-bottom: 0.8em;
}
a {
color: #4fc3f7;
text-decoration: none;
}
a:hover {
color: #80dfff;
text-decoration: underline;
}
code {
font-family: "Consolas", "Courier New", monospace;
background-color: #2d2d2d;
color: #ce9178;
padding: 3px 6px;
border-radius: 4px;
border: 1px solid #3c3c3c;
font-size: 0.95em;
}
pre {
background-color: #1e1e1e;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 12px;
overflow-x: auto;
margin: 1em 0;
}
pre > code {
background-color: transparent !important;
color: inherit !important;
padding: 0 !important;
border-radius: 0 !important;
border: none !important;
font-size: 1em !important;
}
ul, ol { padding-left: 25px; color: #c8c8c8; }
li { margin-bottom: 0.4em; }
hr { border: 0; height: 1px; background: #3a3a3a; margin: 1.5em 0; }
table { border-collapse: collapse; width: 90%; margin: 1em auto; }
th, td { border: 1px solid #4a4a4a; padding: 8px; text-align: left; }
th { background-color: #2d2d2d; color: #9cdcfe; font-weight: bold; }
td { background-color: #1e1e1e; }
blockquote {
border-left: 4px solid #569cd6;
margin: 1em 0;
padding: 0.5em 1em;
background-color: #2d2d30;
font-style: italic;
}
</style>
"""
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"""
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Ayuda de Calculadora</title>
{dark_theme_css}
</head>
<body style="background-color: #1e1e1e; color: #d4d4d4; font-family: 'Segoe UI', sans-serif; font-size: 10pt; margin:0; padding:12px;">
{html_fragment}
</body>
</html>
"""
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"""

View File

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

233
readme.md Normal file
View File

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

92
test_final.py Normal file
View File

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

50
test_symbolic.py Normal file
View File

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

View File

@ -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()
return False

View File

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

View File

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