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:
parent
f16c878a58
commit
ee24ef2615
|
@ -18,6 +18,10 @@ n.mask()
|
|||
|
||||
m=IP4Mask[23]
|
||||
|
||||
IP4Mask[22]
|
||||
IP4Mask[22].to_hex()
|
||||
|
||||
IP4[110.1.30.70;255.255.255.0]
|
||||
|
||||
a=25/51
|
||||
|
||||
a*52
|
|
@ -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
|
||||
}
|
468
main_calc_app.py
468
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"):
|
||||
"""Guarda configuraciones en archivo JSON"""
|
||||
try:
|
||||
sash_x_pos = self.paned_window.sash_coord(0)[0]
|
||||
self.settings["sash_pos_x"] = sash_x_pos
|
||||
except tk.TclError:
|
||||
pass
|
||||
# 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
|
||||
|
||||
try:
|
||||
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:
|
||||
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:
|
||||
if self.debug:
|
||||
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
|
||||
|
||||
# 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,13 +1304,9 @@ 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"""
|
||||
try:
|
||||
|
@ -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"""
|
||||
|
|
|
@ -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,8 +425,75 @@ class HybridEvaluationEngine:
|
|||
if expression.strip().startswith('_add_equation'):
|
||||
return eval(expression, {"__builtins__": {}}, context)
|
||||
elif expression.strip().startswith('_assign_variable'):
|
||||
# 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:
|
||||
# NUEVA LÓGICA: Priorizar SymPy en modo simbólico
|
||||
if self.symbolic_mode:
|
||||
try:
|
||||
# Primero intentar con SymPy para mantener formas simbólicas
|
||||
result = sympify(expression, locals=context, rational=self.keep_symbolic_fractions)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
|
@ -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()
|
||||
|
|
|
@ -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*
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
||||
|
@ -247,25 +251,6 @@ class BracketParser:
|
|||
|
||||
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:
|
||||
"""Detector específico para ecuaciones con análisis AST avanzado"""
|
||||
|
@ -307,91 +292,3 @@ class EquationDetector:
|
|||
|
||||
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()
|
58
tl_popup.py
58
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()
|
||||
|
|
106
type_registry.py
106
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()
|
||||
|
|
Loading…
Reference in New Issue