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]
|
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
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
"window_geometry": "1020x700+2638+160",
|
"window_geometry": "1020x700+137+49",
|
||||||
"sash_pos_x": 332
|
"sash_pos_x": 353,
|
||||||
|
"symbolic_mode": true,
|
||||||
|
"show_numeric_approximation": true,
|
||||||
|
"keep_symbolic_fractions": true,
|
||||||
|
"auto_simplify": false
|
||||||
}
|
}
|
474
main_calc_app.py
474
main_calc_app.py
|
@ -12,6 +12,34 @@ import threading
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
import re
|
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 ==========
|
# ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ==========
|
||||||
# Importar componentes del CAS híbrido con nuevo sistema de tipos
|
# Importar componentes del CAS híbrido con nuevo sistema de tipos
|
||||||
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
||||||
|
@ -26,21 +54,33 @@ class HybridCalculatorApp:
|
||||||
|
|
||||||
SETTINGS_FILE = "hybrid_calc_settings.json"
|
SETTINGS_FILE = "hybrid_calc_settings.json"
|
||||||
HISTORY_FILE = "hybrid_calc_history.txt"
|
HISTORY_FILE = "hybrid_calc_history.txt"
|
||||||
|
HELP_FILE = "readme.md" # ========== NUEVO: Archivo de ayuda externo ==========
|
||||||
|
|
||||||
def __init__(self, root: tk.Tk):
|
def __init__(self, root: tk.Tk):
|
||||||
self.root = root
|
self.root = root
|
||||||
self.root.title("Calculadora MAV - CAS Híbrido")
|
self.root.title("Calculadora MAV - CAS Híbrido")
|
||||||
|
|
||||||
# Cargar configuración
|
# Configuración y estado
|
||||||
self.settings = self._load_settings()
|
self.settings = self._load_settings()
|
||||||
self.root.geometry(self.settings.get("window_geometry", "1000x700"))
|
self.root.geometry(self.settings.get("window_geometry", "1000x700"))
|
||||||
self.root.configure(bg="#2b2b2b")
|
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
|
# Configurar ícono
|
||||||
self._setup_icon()
|
self._setup_icon()
|
||||||
|
|
||||||
# ========== COMPONENTES PRINCIPALES CON NUEVO SISTEMA ==========
|
# ========== COMPONENTES PRINCIPALES CON NUEVO SISTEMA ==========
|
||||||
self.engine = HybridEvaluationEngine(auto_discover_types=True)
|
|
||||||
self.interactive_manager = None # Se inicializa después de crear widgets
|
self.interactive_manager = None # Se inicializa después de crear widgets
|
||||||
|
|
||||||
# ========== HELPERS DINÁMICOS DEL REGISTRO ==========
|
# ========== HELPERS DINÁMICOS DEL REGISTRO ==========
|
||||||
|
@ -52,7 +92,22 @@ class HybridCalculatorApp:
|
||||||
self._cached_input_font = None
|
self._cached_input_font = None
|
||||||
self.output_buffer = []
|
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.create_widgets()
|
||||||
self.setup_interactive_manager()
|
self.setup_interactive_manager()
|
||||||
self.load_history()
|
self.load_history()
|
||||||
|
@ -152,20 +207,43 @@ CLASES DISPONIBLES:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _save_settings(self):
|
def _save_settings(self):
|
||||||
"""Guarda configuración de la aplicación"""
|
"""Guarda configuraciones en archivo JSON"""
|
||||||
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
|
|
||||||
|
|
||||||
try:
|
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:
|
with open(self.SETTINGS_FILE, "w", encoding="utf-8") as f:
|
||||||
json.dump(self.settings, f, indent=4)
|
json.dump(self.settings, f, indent=4, ensure_ascii=False)
|
||||||
except IOError:
|
except Exception as e:
|
||||||
messagebox.showwarning("Error", "No se pudieron guardar los ajustes.")
|
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):
|
def create_widgets(self):
|
||||||
"""Crea la interfaz gráfica"""
|
"""Crea la interfaz gráfica"""
|
||||||
|
@ -273,11 +351,39 @@ CLASES DISPONIBLES:
|
||||||
cas_menu.add_separator()
|
cas_menu.add_separator()
|
||||||
cas_menu.add_command(label="Resolver sistema", command=self.solve_system)
|
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) ==========
|
# ========== 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)
|
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="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_separator()
|
||||||
types_menu.add_command(label="Sintaxis de tipos", command=self.show_types_syntax)
|
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))
|
output_parts.append(("equation", result.symbolic_result))
|
||||||
elif result.result_type == "assignment":
|
elif result.result_type == "assignment":
|
||||||
output_parts.append(("info", result.symbolic_result))
|
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:
|
else:
|
||||||
# Resultado normal
|
# Resultado normal
|
||||||
if result.result is not None:
|
if result.result is not None:
|
||||||
|
@ -666,32 +775,34 @@ CLASES DISPONIBLES:
|
||||||
|
|
||||||
def _get_result_tag_dynamic(self, result: Any) -> str:
|
def _get_result_tag_dynamic(self, result: Any) -> str:
|
||||||
"""Determina el tag de color para un resultado - VERSIÓN DINÁMICA"""
|
"""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:
|
try:
|
||||||
registered_classes = self.engine.get_available_types().get('registered_classes', {})
|
registered_classes = self.engine.get_available_types().get('registered_classes', {})
|
||||||
|
|
||||||
# Verificar si es una instancia de alguna clase registrada
|
# Verificar si es una instancia de alguna clase registrada
|
||||||
for name, cls in registered_classes.items():
|
for name, cls in registered_classes.items():
|
||||||
if isinstance(result, cls):
|
if isinstance(result, cls):
|
||||||
# Usar tags específicos si existen, sino usar genérico
|
# Usar tags específicos basados en el nombre de la clase
|
||||||
if name.lower() == "hex":
|
name_lower = name.lower()
|
||||||
|
if name_lower == "hex":
|
||||||
return "hex"
|
return "hex"
|
||||||
elif name.lower() == "bin":
|
elif name_lower == "bin":
|
||||||
return "bin"
|
return "bin"
|
||||||
elif name.lower() in ["ip4", "ip"]:
|
elif name_lower in ["ip4", "ip"]:
|
||||||
return "ip"
|
return "ip"
|
||||||
elif name.lower() == "chr":
|
elif name_lower == "chr":
|
||||||
return "chr_type"
|
return "chr_type"
|
||||||
|
elif name_lower == "date":
|
||||||
|
return "date"
|
||||||
else:
|
else:
|
||||||
return "custom_type" # Tag genérico para tipos personalizados
|
return "custom_type" # Tag genérico para tipos personalizados
|
||||||
|
|
||||||
except Exception as e:
|
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
|
# Fallback a tags existentes para tipos no registrados
|
||||||
if hasattr(result, '__class__') and 'Class_' in result.__class__.__name__:
|
if isinstance(result, sympy.Basic):
|
||||||
return "custom_type"
|
|
||||||
elif isinstance(result, sympy.Basic):
|
|
||||||
return "symbolic"
|
return "symbolic"
|
||||||
else:
|
else:
|
||||||
return "result"
|
return "result"
|
||||||
|
@ -699,7 +810,7 @@ CLASES DISPONIBLES:
|
||||||
def _get_class_display_name_dynamic(self, obj: Any) -> str:
|
def _get_class_display_name_dynamic(self, obj: Any) -> str:
|
||||||
"""Obtiene nombre de clase para display - VERSIÓN DINÁMICA"""
|
"""Obtiene nombre de clase para display - VERSIÓN DINÁMICA"""
|
||||||
try:
|
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', {})
|
registered_classes = self.engine.get_available_types().get('registered_classes', {})
|
||||||
|
|
||||||
for name, cls in registered_classes.items():
|
for name, cls in registered_classes.items():
|
||||||
|
@ -707,16 +818,10 @@ CLASES DISPONIBLES:
|
||||||
return name
|
return name
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error en get_class_display_name_dynamic: {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):
|
if isinstance(obj, sympy.logic.boolalg.BooleanAtom):
|
||||||
return "Boolean"
|
return "Boolean"
|
||||||
elif isinstance(obj, sympy.Basic):
|
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_command(label="Pegar", command=lambda: self.input_text.event_generate("<<Paste>>"))
|
||||||
context_menu.add_separator()
|
context_menu.add_separator()
|
||||||
context_menu.add_command(label="Limpiar entrada", command=self.clear_input)
|
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":
|
elif panel_type == "output":
|
||||||
context_menu.add_command(label="Copiar todo", command=self.copy_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_command(label="Limpiar salida", command=self.clear_output)
|
||||||
|
|
||||||
context_menu.add_separator()
|
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:
|
try:
|
||||||
context_menu.tk_popup(event.x_root, event.y_root)
|
context_menu.tk_popup(event.x_root, event.y_root)
|
||||||
|
@ -890,76 +993,12 @@ CLASES DISPONIBLES:
|
||||||
self._evaluate_and_update()
|
self._evaluate_and_update()
|
||||||
|
|
||||||
def copy_output(self):
|
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()
|
content = self.output_text.get("1.0", tk.END).strip()
|
||||||
if content:
|
if content:
|
||||||
self.root.clipboard_clear()
|
self.root.clipboard_clear()
|
||||||
self.root.clipboard_append(content)
|
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):
|
def show_variables(self):
|
||||||
"""Muestra ventana con variables definidas"""
|
"""Muestra ventana con variables definidas"""
|
||||||
variables = self.engine.symbol_table
|
variables = self.engine.symbol_table
|
||||||
|
@ -1265,13 +1304,9 @@ programación y análisis numérico.
|
||||||
if content.strip():
|
if content.strip():
|
||||||
self.input_text.insert("1.0", content)
|
self.input_text.insert("1.0", content)
|
||||||
self.root.after_idle(self._evaluate_and_update)
|
self.root.after_idle(self._evaluate_and_update)
|
||||||
return
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error cargando historial: {e}")
|
print(f"Error cargando historial: {e}")
|
||||||
|
|
||||||
# Cargar ejemplo por defecto si no hay historial
|
|
||||||
self.insert_example()
|
|
||||||
|
|
||||||
def save_history(self):
|
def save_history(self):
|
||||||
"""Guarda historial de entrada"""
|
"""Guarda historial de entrada"""
|
||||||
try:
|
try:
|
||||||
|
@ -1294,6 +1329,188 @@ programación y análisis numérico.
|
||||||
|
|
||||||
self.root.destroy()
|
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):
|
def obtener_ayuda(self, input_str):
|
||||||
"""Obtiene ayuda usando helpers dinámicos"""
|
"""Obtiene ayuda usando helpers dinámicos"""
|
||||||
for helper in self.HELPERS:
|
for helper in self.HELPERS:
|
||||||
|
@ -1306,6 +1523,43 @@ programación y análisis numérico.
|
||||||
continue
|
continue
|
||||||
return None
|
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():
|
def main():
|
||||||
"""Función principal"""
|
"""Función principal"""
|
||||||
|
|
|
@ -43,6 +43,13 @@ class HybridEvaluationEngine:
|
||||||
|
|
||||||
# Debug mode
|
# Debug mode
|
||||||
self.debug = False
|
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
|
# Configurar contexto base
|
||||||
self._setup_base_context()
|
self._setup_base_context()
|
||||||
|
|
||||||
|
@ -261,9 +268,34 @@ class HybridEvaluationEngine:
|
||||||
# Obtener el valor asignado
|
# Obtener el valor asignado
|
||||||
assigned_value = self.symbol_table.get(var_name)
|
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(
|
return EvaluationResult(
|
||||||
assigned_value, "assignment",
|
assigned_value, "assignment",
|
||||||
symbolic_result=result,
|
symbolic_result=result,
|
||||||
|
numeric_result=numeric_result,
|
||||||
original_line=original_line
|
original_line=original_line
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -304,12 +336,16 @@ class HybridEvaluationEngine:
|
||||||
# Actualizar last_result
|
# Actualizar last_result
|
||||||
self.last_result = 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
|
numeric_result = None
|
||||||
if hasattr(result, 'evalf'):
|
if self.show_numeric_approximation and hasattr(result, 'evalf'):
|
||||||
try:
|
try:
|
||||||
numeric_eval = result.evalf()
|
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
|
numeric_result = numeric_eval
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -389,42 +425,109 @@ class HybridEvaluationEngine:
|
||||||
if expression.strip().startswith('_add_equation'):
|
if expression.strip().startswith('_add_equation'):
|
||||||
return eval(expression, {"__builtins__": {}}, context)
|
return eval(expression, {"__builtins__": {}}, context)
|
||||||
elif expression.strip().startswith('_assign_variable'):
|
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:
|
else:
|
||||||
try:
|
# NUEVA LÓGICA: Priorizar SymPy en modo simbólico
|
||||||
# Primero intentar evaluación directa para objetos especializados
|
if self.symbolic_mode:
|
||||||
try:
|
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
|
# Si auto_simplify está activado, simplificar
|
||||||
if hasattr(result, '_sympystr'): # SympyClassBase
|
if self.auto_simplify and hasattr(result, 'simplify'):
|
||||||
return result
|
result = result.simplify()
|
||||||
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:
|
return result
|
||||||
# Si eval falla, intentar con SymPy
|
|
||||||
|
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:
|
||||||
|
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:
|
try:
|
||||||
result = sympify(expression, locals=context)
|
result = sympify(expression, locals=context)
|
||||||
return result
|
return result
|
||||||
except:
|
except:
|
||||||
# Si ambos fallan, re-lanzar el error original de eval
|
raise syntax_error
|
||||||
raise eval_error
|
|
||||||
|
|
||||||
except SyntaxError as syntax_error:
|
|
||||||
# Para errores de sintaxis, intentar SymPy directamente
|
|
||||||
try:
|
|
||||||
result = sympify(expression, locals=context)
|
|
||||||
return result
|
|
||||||
except:
|
|
||||||
raise syntax_error
|
|
||||||
|
|
||||||
def _get_full_context(self) -> Dict[str, Any]:
|
def _get_full_context(self) -> Dict[str, Any]:
|
||||||
"""Obtiene el contexto completo para evaluación"""
|
"""Obtiene el contexto completo para evaluación"""
|
||||||
|
@ -436,9 +539,9 @@ class HybridEvaluationEngine:
|
||||||
def _assign_variable(self, var_name: str, expression) -> str:
|
def _assign_variable(self, var_name: str, expression) -> str:
|
||||||
"""Asigna un valor a una variable"""
|
"""Asigna un valor a una variable"""
|
||||||
try:
|
try:
|
||||||
# Evaluar la expresión
|
# Evaluar la expresión usando el contexto completo y configuraciones simbólicas
|
||||||
if isinstance(expression, str):
|
if isinstance(expression, str):
|
||||||
value = sympify(expression, locals=self._get_full_context())
|
value = self._eval_in_context(expression)
|
||||||
else:
|
else:
|
||||||
value = expression
|
value = expression
|
||||||
|
|
||||||
|
@ -528,6 +631,16 @@ class HybridEvaluationEngine:
|
||||||
self.clear_equations()
|
self.clear_equations()
|
||||||
self.clear_variables()
|
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:
|
class EvaluationResult:
|
||||||
"""Resultado de evaluación con información contextual"""
|
"""Resultado de evaluación con información contextual"""
|
||||||
|
@ -566,62 +679,3 @@ class EvaluationResult:
|
||||||
elif self.result is not None:
|
elif self.result is not None:
|
||||||
return str(self.result)
|
return str(self.result)
|
||||||
return ""
|
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"""
|
"""Retorna el set actual de clases con sintaxis de corchetes"""
|
||||||
return self.BRACKET_CLASSES.copy()
|
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]:
|
def parse_line(self, code_line: str) -> Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Parsea una línea de código aplicando todas las transformaciones
|
Parsea una línea de código aplicando todas las transformaciones
|
||||||
|
@ -247,25 +251,6 @@ class BracketParser:
|
||||||
|
|
||||||
return 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:
|
class EquationDetector:
|
||||||
"""Detector específico para ecuaciones con análisis AST avanzado"""
|
"""Detector específico para ecuaciones con análisis AST avanzado"""
|
||||||
|
@ -307,91 +292,3 @@ class EquationDetector:
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
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:
|
except tk.TclError:
|
||||||
pass
|
pass
|
||||||
self.open_windows.clear()
|
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]:
|
def get_registered_helper_functions() -> List[callable]:
|
||||||
"""Obtiene las funciones Helper registradas"""
|
"""Obtiene las funciones Helper registradas"""
|
||||||
return _global_registry.get_helper_functions()
|
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