Primera version con auto-descubrimiento de tipos
This commit is contained in:
parent
03964d2ff5
commit
f16c878a58
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Clase híbrida para números binarios
|
Clase híbrida para números binarios - ADAPTADA AL NUEVO SISTEMA
|
||||||
"""
|
"""
|
||||||
from sympy_Base import SympyClassBase
|
from sympy_Base import SympyClassBase
|
||||||
import re
|
import re
|
||||||
|
@ -53,3 +53,18 @@ class Class_Bin(SympyClassBase):
|
||||||
def toDecimal(self):
|
def toDecimal(self):
|
||||||
"""Convierte a decimal"""
|
"""Convierte a decimal"""
|
||||||
return self._value
|
return self._value
|
||||||
|
|
||||||
|
|
||||||
|
# ========== FUNCIÓN DE REGISTRO - NUEVA ==========
|
||||||
|
|
||||||
|
def register_classes_in_module():
|
||||||
|
"""
|
||||||
|
Devuelve una lista de clases definidas en este módulo para ser registradas.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("Bin", Class_Bin, "SympyClassBase", {
|
||||||
|
"add_lowercase": True,
|
||||||
|
"supports_brackets": True,
|
||||||
|
"description": "Números binarios"
|
||||||
|
}),
|
||||||
|
]
|
|
@ -1,5 +1,6 @@
|
||||||
"""
|
"""
|
||||||
Clase híbrida para caracteres
|
Clase híbrida para caracteres - ADAPTADA AL NUEVO SISTEMA
|
||||||
|
Archivo: custom_types/chr_type.py
|
||||||
"""
|
"""
|
||||||
from sympy_Base import SympyClassBase
|
from sympy_Base import SympyClassBase
|
||||||
import re
|
import re
|
||||||
|
@ -64,3 +65,22 @@ class Class_Chr(SympyClassBase):
|
||||||
def toBin(self):
|
def toBin(self):
|
||||||
"""Convierte a binario"""
|
"""Convierte a binario"""
|
||||||
return f"0b{self._value:08b}"
|
return f"0b{self._value:08b}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def register_classes_in_module():
|
||||||
|
"""
|
||||||
|
Devuelve una lista de clases definidas en este módulo para ser registradas.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple]: Lista de tuplas (nombre_publico, clase_objeto, categoria, opciones)
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
# (nombre_publico, clase_objeto, categoria, opciones)
|
||||||
|
("Chr", Class_Chr, "SympyClassBase", {
|
||||||
|
"add_lowercase": True, # Añadir 'chr' al contexto
|
||||||
|
"supports_brackets": True, # Soporta sintaxis Chr[...]
|
||||||
|
"description": "Caracteres y códigos ASCII"
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Clase híbrida para números decimales
|
Clase híbrida para números decimales - ADAPTADA AL NUEVO SISTEMA
|
||||||
"""
|
"""
|
||||||
from sympy_Base import SympyClassBase
|
from sympy_Base import SympyClassBase
|
||||||
import re
|
import re
|
||||||
|
@ -54,3 +54,17 @@ class Class_Dec(SympyClassBase):
|
||||||
def toBin(self):
|
def toBin(self):
|
||||||
"""Convierte a binario"""
|
"""Convierte a binario"""
|
||||||
return f"0b{self._value:08b}"
|
return f"0b{self._value:08b}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def register_classes_in_module():
|
||||||
|
"""
|
||||||
|
Devuelve una lista de clases definidas en este módulo para ser registradas.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("Dec", Class_Dec, "SympyClassBase", {
|
||||||
|
"add_lowercase": True,
|
||||||
|
"supports_brackets": True,
|
||||||
|
"description": "Números decimales"
|
||||||
|
}),
|
||||||
|
]
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Clase híbrida para números hexadecimales
|
Clase híbrida para números hexadecimales - ADAPTADA AL NUEVO SISTEMA
|
||||||
"""
|
"""
|
||||||
from sympy_Base import SympyClassBase
|
from sympy_Base import SympyClassBase
|
||||||
import re
|
import re
|
||||||
|
@ -53,3 +53,17 @@ class Class_Hex(SympyClassBase):
|
||||||
def toDecimal(self):
|
def toDecimal(self):
|
||||||
"""Convierte a decimal"""
|
"""Convierte a decimal"""
|
||||||
return self._value
|
return self._value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def register_classes_in_module():
|
||||||
|
"""
|
||||||
|
Devuelve una lista de clases definidas en este módulo para ser registradas.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("Hex", Class_Hex, "SympyClassBase", {
|
||||||
|
"add_lowercase": True,
|
||||||
|
"supports_brackets": True,
|
||||||
|
"description": "Números hexadecimales"
|
||||||
|
}),
|
||||||
|
]
|
|
@ -876,3 +876,41 @@ class Class_IP4(SympyClassBase):
|
||||||
"public_count": len(ip_list) - private_count,
|
"public_count": len(ip_list) - private_count,
|
||||||
"common_network": Class_IP4.find_common_network(ip_list)
|
"common_network": Class_IP4.find_common_network(ip_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def register_classes_in_module():
|
||||||
|
"""
|
||||||
|
Devuelve una lista de clases definidas en este módulo para ser registradas.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("IP4", Class_IP4, "SympyClassBase", {
|
||||||
|
"add_lowercase": True,
|
||||||
|
"supports_brackets": True,
|
||||||
|
"description": "Direcciones IPv4 con máscara"
|
||||||
|
}),
|
||||||
|
("IP4Mask", IP4Mask, "ClassBase", {
|
||||||
|
"add_lowercase": True,
|
||||||
|
"supports_brackets": True,
|
||||||
|
"description": "Máscaras de red IPv4"
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ========== INFORMACIÓN DEL MÓDULO ==========
|
||||||
|
|
||||||
|
def get_module_info():
|
||||||
|
"""Información adicional sobre este módulo"""
|
||||||
|
return {
|
||||||
|
"name": "IPv4 Network Types",
|
||||||
|
"description": "Clases para manejo de direcciones IP y máscaras de red",
|
||||||
|
"version": "1.0",
|
||||||
|
"depends_on": ["class_base", "sympy_Base"],
|
||||||
|
"examples": [
|
||||||
|
"IP4[192.168.1.1/24]",
|
||||||
|
"IP4[10.0.0.1;255.255.255.0]",
|
||||||
|
"IP4[192.168.1.1/24].NetworkAddress()",
|
||||||
|
"IP4Mask[24]",
|
||||||
|
"IP4Mask[255.255.255.0]"
|
||||||
|
]
|
||||||
|
}
|
497
main_calc_app.py
497
main_calc_app.py
|
@ -1,44 +1,32 @@
|
||||||
"""
|
"""
|
||||||
Calculadora MAV CAS Híbrida - Aplicación principal
|
Calculadora MAV CAS Híbrida - Aplicación principal
|
||||||
|
VERSIÓN ADAPTADA AL NUEVO SISTEMA DE TIPOS
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import scrolledtext, messagebox, Menu, filedialog
|
from tkinter import scrolledtext, messagebox, Menu, filedialog
|
||||||
import tkinter.font as tkFont
|
import tkinter.font as tkFont
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path # Added for robust path handling
|
from pathlib import Path
|
||||||
import threading
|
import threading
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# Importar componentes del CAS híbrido
|
# ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ==========
|
||||||
|
# Importar componentes del CAS híbrido con nuevo sistema de tipos
|
||||||
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
||||||
from sympy_Base import SympyClassBase
|
from tl_popup import InteractiveResultManager, PlotResult
|
||||||
from tl_popup import InteractiveResultManager, PlotResult # <--- Asegurar que PlotResult se importa
|
from type_registry import get_registered_helper_functions, get_registered_base_context
|
||||||
from ip4_type import Class_IP4
|
|
||||||
from hex_type import Class_Hex
|
|
||||||
from bin_type import Class_Bin
|
|
||||||
from dec_type import Class_Dec
|
|
||||||
from chr_type import Class_Chr
|
|
||||||
import sympy
|
import sympy
|
||||||
from sympy_helper import SympyTools as SympyHelper
|
from sympy_helper import SympyTools as SympyHelper
|
||||||
|
|
||||||
|
|
||||||
class HybridCalculatorApp:
|
class HybridCalculatorApp:
|
||||||
"""Aplicación principal del CAS híbrido"""
|
"""Aplicación principal del CAS híbrido - ADAPTADA AL NUEVO SISTEMA"""
|
||||||
|
|
||||||
SETTINGS_FILE = "hybrid_calc_settings.json"
|
SETTINGS_FILE = "hybrid_calc_settings.json"
|
||||||
HISTORY_FILE = "hybrid_calc_history.txt"
|
HISTORY_FILE = "hybrid_calc_history.txt"
|
||||||
|
|
||||||
HELPERS = [
|
|
||||||
Class_IP4.Helper,
|
|
||||||
Class_Hex.Helper,
|
|
||||||
Class_Bin.Helper,
|
|
||||||
Class_Dec.Helper,
|
|
||||||
Class_Chr.Helper,
|
|
||||||
SympyHelper.Helper,
|
|
||||||
]
|
|
||||||
|
|
||||||
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")
|
||||||
|
@ -51,10 +39,13 @@ class HybridCalculatorApp:
|
||||||
# Configurar ícono
|
# Configurar ícono
|
||||||
self._setup_icon()
|
self._setup_icon()
|
||||||
|
|
||||||
# Componentes principales
|
# ========== COMPONENTES PRINCIPALES CON NUEVO SISTEMA ==========
|
||||||
self.engine = HybridEvaluationEngine()
|
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 ==========
|
||||||
|
self._setup_dynamic_helpers()
|
||||||
|
|
||||||
# Estado de la aplicación
|
# Estado de la aplicación
|
||||||
self._debounce_job = None
|
self._debounce_job = None
|
||||||
self._syncing_yview = False
|
self._syncing_yview = False
|
||||||
|
@ -69,25 +60,85 @@ class HybridCalculatorApp:
|
||||||
# Configurar eventos de cierre
|
# Configurar eventos de cierre
|
||||||
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
|
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||||
|
|
||||||
|
def _setup_dynamic_helpers(self):
|
||||||
|
"""Configura helpers dinámicamente desde el registro de tipos"""
|
||||||
|
try:
|
||||||
|
# Obtener helpers registrados dinámicamente
|
||||||
|
self.HELPERS = get_registered_helper_functions()
|
||||||
|
|
||||||
|
# Añadir SympyHelper al final
|
||||||
|
self.HELPERS.append(SympyHelper.Helper)
|
||||||
|
|
||||||
|
print(f"🆘 Helpers dinámicos cargados: {len(self.HELPERS)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Error cargando helpers dinámicos: {e}")
|
||||||
|
# Fallback a helpers básicos
|
||||||
|
self.HELPERS = [SympyHelper.Helper]
|
||||||
|
|
||||||
|
def reload_types(self):
|
||||||
|
"""Recarga el sistema de tipos (útil para desarrollo)"""
|
||||||
|
try:
|
||||||
|
print("🔄 Recargando sistema de tipos...")
|
||||||
|
|
||||||
|
# Recargar engine
|
||||||
|
self.engine.reload_types()
|
||||||
|
|
||||||
|
# Recargar helpers
|
||||||
|
self._setup_dynamic_helpers()
|
||||||
|
|
||||||
|
# Re-evaluar contenido actual
|
||||||
|
self._evaluate_and_update()
|
||||||
|
|
||||||
|
print("✅ Sistema de tipos recargado")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error recargando tipos: {e}")
|
||||||
|
messagebox.showerror("Error", f"Error recargando tipos:\n{e}")
|
||||||
|
|
||||||
|
def show_types_info(self):
|
||||||
|
"""Muestra información sobre tipos disponibles"""
|
||||||
|
try:
|
||||||
|
types_info = self.engine.get_available_types()
|
||||||
|
|
||||||
|
info_text = f"""INFORMACIÓN DEL SISTEMA DE TIPOS
|
||||||
|
|
||||||
|
Clases registradas: {len(types_info.get('registered_classes', {}))}
|
||||||
|
Clases con sintaxis de corchetes: {len(types_info.get('bracket_classes', []))}
|
||||||
|
Entradas en contexto: {types_info.get('total_context_entries', 0)}
|
||||||
|
Helper functions: {types_info.get('helper_functions_count', 0)}
|
||||||
|
|
||||||
|
CLASES DISPONIBLES:
|
||||||
|
"""
|
||||||
|
|
||||||
|
for name, cls in types_info.get('registered_classes', {}).items():
|
||||||
|
info_text += f"• {name}: {cls.__name__}\n"
|
||||||
|
|
||||||
|
info_text += f"\nCLASES CON SINTAXIS DE CORCHETES:\n"
|
||||||
|
for name in types_info.get('bracket_classes', []):
|
||||||
|
info_text += f"• {name}[...]\n"
|
||||||
|
|
||||||
|
# Mostrar en ventana
|
||||||
|
self._show_help_window("Información de Tipos", info_text)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Error obteniendo información de tipos:\n{e}")
|
||||||
|
|
||||||
def _setup_icon(self):
|
def _setup_icon(self):
|
||||||
"""Configura el ícono de la aplicación"""
|
"""Configura el ícono de la aplicación"""
|
||||||
try:
|
try:
|
||||||
# Construct path relative to this script file (main_calc_app.py)
|
|
||||||
script_dir = Path(__file__).resolve().parent
|
script_dir = Path(__file__).resolve().parent
|
||||||
icon_path = script_dir / "icon.png"
|
icon_path = script_dir / "icon.png"
|
||||||
|
|
||||||
if not icon_path.is_file():
|
if not icon_path.is_file():
|
||||||
print(f"Advertencia: Archivo de ícono no encontrado en '{icon_path}'.")
|
print(f"Advertencia: Archivo de ícono no encontrado en '{icon_path}'.")
|
||||||
# Optionally, set a default Tk icon or simply return
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.app_icon = tk.PhotoImage(file=str(icon_path))
|
self.app_icon = tk.PhotoImage(file=str(icon_path))
|
||||||
self.root.iconphoto(True, self.app_icon)
|
self.root.iconphoto(True, self.app_icon)
|
||||||
except tk.TclError as e:
|
except tk.TclError as e:
|
||||||
# Provide more specific error, including the path and Tkinter's error message
|
|
||||||
print(f"Advertencia: No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}")
|
print(f"Advertencia: No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Catch other potential errors during icon loading
|
|
||||||
print(f"Advertencia: Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}")
|
print(f"Advertencia: Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}")
|
||||||
|
|
||||||
def _load_settings(self) -> Dict[str, Any]:
|
def _load_settings(self) -> Dict[str, Any]:
|
||||||
|
@ -222,7 +273,15 @@ class HybridCalculatorApp:
|
||||||
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ú Ayuda
|
# ========== MENÚ TIPOS (NUEVO) ==========
|
||||||
|
types_menu = Menu(menubar, tearoff=0)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Menú Ayuda (actualizado)
|
||||||
help_menu = Menu(menubar, tearoff=0)
|
help_menu = Menu(menubar, tearoff=0)
|
||||||
menubar.add_cascade(label="Ayuda", menu=help_menu)
|
menubar.add_cascade(label="Ayuda", menu=help_menu)
|
||||||
help_menu.add_command(label="Guía rápida", command=self.show_quick_guide)
|
help_menu.add_command(label="Guía rápida", command=self.show_quick_guide)
|
||||||
|
@ -268,16 +327,16 @@ class HybridCalculatorApp:
|
||||||
self.output_text.tag_configure("equation", foreground="#c792ea")
|
self.output_text.tag_configure("equation", foreground="#c792ea")
|
||||||
self.output_text.tag_configure("info", foreground="#ffcb6b")
|
self.output_text.tag_configure("info", foreground="#ffcb6b")
|
||||||
self.output_text.tag_configure("comment", foreground="#546e7a")
|
self.output_text.tag_configure("comment", foreground="#546e7a")
|
||||||
self.output_text.tag_configure("class_hint", foreground="#888888") # Gris para la pista de clase
|
self.output_text.tag_configure("class_hint", foreground="#888888")
|
||||||
self.output_text.tag_configure("type_hint", foreground="#6a6a6a")
|
self.output_text.tag_configure("type_hint", foreground="#6a6a6a")
|
||||||
|
|
||||||
# Tags para tipos especializados
|
# Tags para tipos especializados (genéricos para cualquier tipo)
|
||||||
|
self.output_text.tag_configure("custom_type", foreground="#f9a825")
|
||||||
self.output_text.tag_configure("hex", foreground="#f9a825")
|
self.output_text.tag_configure("hex", foreground="#f9a825")
|
||||||
self.output_text.tag_configure("bin", foreground="#4fc3f7")
|
self.output_text.tag_configure("bin", foreground="#4fc3f7")
|
||||||
self.output_text.tag_configure("ip", foreground="#fff176")
|
self.output_text.tag_configure("ip", foreground="#fff176")
|
||||||
self.output_text.tag_configure("date", foreground="#ff8a80")
|
self.output_text.tag_configure("date", foreground="#ff8a80")
|
||||||
self.output_text.tag_configure("chr_type", foreground="#80cbc4")
|
self.output_text.tag_configure("chr_type", foreground="#80cbc4")
|
||||||
# Agregar tag para ayuda contextual
|
|
||||||
self.output_text.tag_configure("helper", foreground="#ffd700", font=("Consolas", 11, "italic"))
|
self.output_text.tag_configure("helper", foreground="#ffd700", font=("Consolas", 11, "italic"))
|
||||||
|
|
||||||
def on_key_release(self, event=None):
|
def on_key_release(self, event=None):
|
||||||
|
@ -285,7 +344,7 @@ class HybridCalculatorApp:
|
||||||
if self._debounce_job:
|
if self._debounce_job:
|
||||||
self.root.after_cancel(self._debounce_job)
|
self.root.after_cancel(self._debounce_job)
|
||||||
|
|
||||||
# Autocompletado con punto
|
# Autocompletado con punto (usando contexto dinámico)
|
||||||
if event and event.char == '.' and self.input_text.focus_get() == self.input_text:
|
if event and event.char == '.' and self.input_text.focus_get() == self.input_text:
|
||||||
self._handle_dot_autocomplete()
|
self._handle_dot_autocomplete()
|
||||||
|
|
||||||
|
@ -293,7 +352,7 @@ class HybridCalculatorApp:
|
||||||
self._debounce_job = self.root.after(300, self._evaluate_and_update)
|
self._debounce_job = self.root.after(300, self._evaluate_and_update)
|
||||||
|
|
||||||
def _handle_dot_autocomplete(self):
|
def _handle_dot_autocomplete(self):
|
||||||
"""Maneja el autocompletado cuando se escribe un punto."""
|
"""Maneja el autocompletado cuando se escribe un punto - VERSIÓN DINÁMICA"""
|
||||||
self._close_autocomplete_popup()
|
self._close_autocomplete_popup()
|
||||||
cursor_index_str = self.input_text.index(tk.INSERT)
|
cursor_index_str = self.input_text.index(tk.INSERT)
|
||||||
line_num_str, char_num_str = cursor_index_str.split('.')
|
line_num_str, char_num_str = cursor_index_str.split('.')
|
||||||
|
@ -309,39 +368,40 @@ class HybridCalculatorApp:
|
||||||
|
|
||||||
stripped_text_before_dot = text_on_line_up_to_dot.strip()
|
stripped_text_before_dot = text_on_line_up_to_dot.strip()
|
||||||
|
|
||||||
# 1. Determinar si es un popup GLOBAL
|
# 1. Determinar si es un popup GLOBAL (usando contexto dinámico)
|
||||||
if not stripped_text_before_dot:
|
if not stripped_text_before_dot:
|
||||||
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
|
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
|
||||||
suggestions = []
|
suggestions = []
|
||||||
|
|
||||||
# MODIFIED: Get suggestions from HybridEvaluationEngine's base_context
|
# ========== USAR CONTEXTO DINÁMICO DEL REGISTRO ==========
|
||||||
if hasattr(self.engine, 'base_context') and isinstance(self.engine.base_context, dict):
|
try:
|
||||||
for name, class_or_func in self.engine.base_context.items():
|
dynamic_context = get_registered_base_context()
|
||||||
# Solo queremos clases (tipos) y funciones para el autocompletado global principal.
|
|
||||||
# Evitamos alias en minúscula si la versión capitalizada ya está (heurística simple).
|
for name, class_or_func in dynamic_context.items():
|
||||||
if name[0].isupper(): # Prioritize capitalized names for classes/main functions
|
if name[0].isupper(): # Prioritizar nombres capitalizados
|
||||||
hint = f"Tipo o función: {name}"
|
hint = f"Tipo o función: {name}"
|
||||||
if hasattr(class_or_func, '__doc__') and class_or_func.__doc__:
|
if hasattr(class_or_func, '__doc__') and class_or_func.__doc__:
|
||||||
first_line_doc = class_or_func.__doc__.strip().split('\n')[0]
|
first_line_doc = class_or_func.__doc__.strip().split('\n')[0]
|
||||||
hint = f"{name} - {first_line_doc}"
|
hint = f"{name} - {first_line_doc}"
|
||||||
elif hasattr(class_or_func, 'Helper'): # Usar Helper si está disponible
|
elif hasattr(class_or_func, 'Helper'):
|
||||||
# Para obtener un hint del Helper, necesitamos llamarlo.
|
|
||||||
# Algunas clases Helper esperan el nombre de la clase.
|
|
||||||
try:
|
try:
|
||||||
helper_text = class_or_func.Helper(name) # Pasar el nombre de la clase
|
helper_text = class_or_func.Helper(name)
|
||||||
if helper_text:
|
if helper_text:
|
||||||
hint = helper_text.split('\n')[0] # Primera línea del helper
|
hint = helper_text.split('\n')[0]
|
||||||
except Exception as e_helper:
|
except Exception as e_helper:
|
||||||
print(f"DEBUG: Error calling Helper for {name}: {e_helper}")
|
print(f"DEBUG: Error calling Helper for {name}: {e_helper}")
|
||||||
pass # Mantener el hint genérico
|
pass
|
||||||
suggestions.append((name, hint))
|
suggestions.append((name, hint))
|
||||||
|
|
||||||
# Añadir funciones de SympyHelper (si no están ya en base_context de forma similar)
|
except Exception as e:
|
||||||
# Considerar si SympyHelper.PopupFunctionList() devuelve cosas ya cubiertas.
|
print(f"DEBUG: Error obteniendo contexto dinámico: {e}")
|
||||||
|
# Fallback básico
|
||||||
|
suggestions = [("sin", "Función seno"), ("cos", "Función coseno")]
|
||||||
|
|
||||||
|
# Añadir funciones de SympyHelper
|
||||||
try:
|
try:
|
||||||
sympy_functions = SympyHelper.PopupFunctionList()
|
sympy_functions = SympyHelper.PopupFunctionList()
|
||||||
if sympy_functions:
|
if sympy_functions:
|
||||||
# Evitar duplicados si los nombres ya están de base_context
|
|
||||||
current_suggestion_names = {s[0] for s in suggestions}
|
current_suggestion_names = {s[0] for s in suggestions}
|
||||||
for fname, fhint in sympy_functions:
|
for fname, fhint in sympy_functions:
|
||||||
if fname not in current_suggestion_names:
|
if fname not in current_suggestion_names:
|
||||||
|
@ -350,25 +410,19 @@ class HybridCalculatorApp:
|
||||||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
||||||
|
|
||||||
if suggestions:
|
if suggestions:
|
||||||
# Ordenar alfabéticamente para consistencia
|
|
||||||
suggestions.sort(key=lambda x: x[0])
|
suggestions.sort(key=lambda x: x[0])
|
||||||
self._show_autocomplete_popup(suggestions, is_global_popup=True)
|
self._show_autocomplete_popup(suggestions, is_global_popup=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2. Es un popup de OBJETO. Extraer la expresión del objeto.
|
# 2. Es un popup de OBJETO
|
||||||
obj_expr_str_candidate = ""
|
obj_expr_str_candidate = ""
|
||||||
# Regex para `identificador_o_ClaseConCorchetes(.identificador_o_ClaseConCorchetes)*`
|
|
||||||
# Anclado al final de stripped_text_before_dot
|
|
||||||
obj_expr_regex = r"([a-zA-Z_][a-zA-Z0-9_]*(?:\[[^\]]*\])?(?:(?:\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*)(?:\[[^\]]*\])?)*)$"
|
obj_expr_regex = r"([a-zA-Z_][a-zA-Z0-9_]*(?:\[[^\]]*\])?(?:(?:\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*)(?:\[[^\]]*\])?)*)$"
|
||||||
match = re.search(obj_expr_regex, stripped_text_before_dot)
|
match = re.search(obj_expr_regex, stripped_text_before_dot)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
obj_expr_str_candidate = match.group(1).replace(" ", "") # Quitar espacios como en "obj . method"
|
obj_expr_str_candidate = match.group(1).replace(" ", "")
|
||||||
else:
|
else:
|
||||||
# Heurística: si el regex no coincide, tomar todo stripped_text_before_dot.
|
|
||||||
# Esto podría capturar (a+b) o mi_func()
|
|
||||||
obj_expr_str_candidate = stripped_text_before_dot
|
obj_expr_str_candidate = stripped_text_before_dot
|
||||||
# Validación simple para evitar evaluar cosas que claramente no son objetos
|
|
||||||
if not obj_expr_str_candidate or \
|
if not obj_expr_str_candidate or \
|
||||||
not re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", obj_expr_str_candidate) or \
|
not re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", obj_expr_str_candidate) or \
|
||||||
obj_expr_str_candidate.endswith(("+", "-", "*", "/", "(", ",")):
|
obj_expr_str_candidate.endswith(("+", "-", "*", "/", "(", ",")):
|
||||||
|
@ -378,7 +432,7 @@ class HybridCalculatorApp:
|
||||||
obj_expr_str = obj_expr_str_candidate
|
obj_expr_str = obj_expr_str_candidate
|
||||||
print(f"DEBUG: Autocomplete for object. Extracted: '{obj_expr_str}' from: '{text_on_line_up_to_dot}'")
|
print(f"DEBUG: Autocomplete for object. Extracted: '{obj_expr_str}' from: '{text_on_line_up_to_dot}'")
|
||||||
|
|
||||||
if not obj_expr_str: # Debería estar cubierto por el popup global, pero por si acaso.
|
if not obj_expr_str:
|
||||||
print("DEBUG: Object expression is empty after extraction. No autocomplete.")
|
print("DEBUG: Object expression is empty after extraction. No autocomplete.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -395,20 +449,18 @@ class HybridCalculatorApp:
|
||||||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList(): {e}")
|
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList(): {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 4. Preprocesar con BracketParser para sintaxis Clase[arg] y metodo[]
|
# 4. Preprocesar con BracketParser
|
||||||
# Es importante transformar obj_expr_str ANTES de pasarlo a eval().
|
if '[' in obj_expr_str:
|
||||||
if '[' in obj_expr_str: # Optimización: solo llamar si hay corchetes
|
|
||||||
original_for_debug = obj_expr_str
|
original_for_debug = obj_expr_str
|
||||||
# self.engine.parser es una instancia de BracketParser
|
|
||||||
obj_expr_str = self.engine.parser._transform_brackets(obj_expr_str)
|
obj_expr_str = self.engine.parser._transform_brackets(obj_expr_str)
|
||||||
if obj_expr_str != original_for_debug:
|
if obj_expr_str != original_for_debug:
|
||||||
print(f"DEBUG: Preprocessed by BracketParser: '{original_for_debug}' -> '{obj_expr_str}'")
|
print(f"DEBUG: Preprocessed by BracketParser: '{original_for_debug}' -> '{obj_expr_str}'")
|
||||||
|
|
||||||
# 5. Evaluar la expresión del objeto
|
# 5. Evaluar la expresión del objeto (usando contexto dinámico)
|
||||||
eval_context = self.engine._get_full_context() if hasattr(self.engine, '_get_full_context') else {}
|
eval_context = self.engine._get_full_context()
|
||||||
obj = None
|
obj = None
|
||||||
try:
|
try:
|
||||||
if not obj_expr_str.strip(): # Seguridad adicional
|
if not obj_expr_str.strip():
|
||||||
print("DEBUG: Object expression became empty before eval. No action.")
|
print("DEBUG: Object expression became empty before eval. No action.")
|
||||||
return
|
return
|
||||||
print(f"DEBUG: Attempting to eval: '{obj_expr_str}'")
|
print(f"DEBUG: Attempting to eval: '{obj_expr_str}'")
|
||||||
|
@ -423,11 +475,9 @@ class HybridCalculatorApp:
|
||||||
methods = obj.PopupFunctionList()
|
methods = obj.PopupFunctionList()
|
||||||
if methods:
|
if methods:
|
||||||
self._show_autocomplete_popup(methods, is_global_popup=False)
|
self._show_autocomplete_popup(methods, is_global_popup=False)
|
||||||
# else: Podríamos añadir un fallback a dir(obj) aquí si se desea para objetos genéricos
|
|
||||||
# print(f"DEBUG: Object {type(obj)} has no PopupFunctionList. dir(obj) could be used.")
|
|
||||||
|
|
||||||
def _show_autocomplete_popup(self, suggestions, is_global_popup=False):
|
def _show_autocomplete_popup(self, suggestions, is_global_popup=False):
|
||||||
# suggestions: lista de tuplas (nombre, hint)
|
"""Muestra popup de autocompletado (sin cambios)"""
|
||||||
cursor_bbox = self.input_text.bbox(tk.INSERT)
|
cursor_bbox = self.input_text.bbox(tk.INSERT)
|
||||||
if not cursor_bbox:
|
if not cursor_bbox:
|
||||||
return
|
return
|
||||||
|
@ -449,35 +499,29 @@ class HybridCalculatorApp:
|
||||||
if suggestions:
|
if suggestions:
|
||||||
self._autocomplete_listbox.select_set(0)
|
self._autocomplete_listbox.select_set(0)
|
||||||
self._autocomplete_listbox.pack(expand=True, fill=tk.BOTH)
|
self._autocomplete_listbox.pack(expand=True, fill=tk.BOTH)
|
||||||
self._autocomplete_listbox.bind("<Return>", self._on_autocomplete_select)
|
self._autocomplete_listbox.bind("<Return>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
||||||
self._autocomplete_listbox.bind("<Tab>", self._on_autocomplete_select)
|
self._autocomplete_listbox.bind("<Tab>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
||||||
self._autocomplete_listbox.bind("<Escape>", lambda e: self._close_autocomplete_popup())
|
self._autocomplete_listbox.bind("<Escape>", lambda e: self._close_autocomplete_popup())
|
||||||
self._autocomplete_listbox.bind("<Double-Button-1>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
self._autocomplete_listbox.bind("<Double-Button-1>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
||||||
self._autocomplete_listbox.focus_set()
|
self._autocomplete_listbox.focus_set()
|
||||||
self._autocomplete_listbox.bind("<Up>", lambda e: self._navigate_autocomplete(e, -1))
|
self._autocomplete_listbox.bind("<Up>", lambda e: self._navigate_autocomplete(e, -1))
|
||||||
self._autocomplete_listbox.bind("<Down>", lambda e: self._navigate_autocomplete(e, 1))
|
self._autocomplete_listbox.bind("<Down>", lambda e: self._navigate_autocomplete(e, 1))
|
||||||
|
|
||||||
# self.input_text.bind("<FocusOut>", lambda e: self._close_autocomplete_popup(), add=True) # Removed: Caused popup to close immediately
|
|
||||||
self.input_text.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
self.input_text.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||||
self.root.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
self.root.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||||
# self.input_text.bind("<Key>", lambda e: self._close_autocomplete_popup(), add=True) # Removed: Too aggressive
|
|
||||||
|
|
||||||
# Pasar el flag is_global_popup a los bindings que llaman a _on_autocomplete_select
|
|
||||||
self._autocomplete_listbox.bind("<Return>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
|
||||||
self._autocomplete_listbox.bind("<Tab>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
|
||||||
|
|
||||||
max_len = max(len(name) for name, _ in suggestions) if suggestions else 10
|
max_len = max(len(name) for name, _ in suggestions) if suggestions else 10
|
||||||
width = max(15, min(max_len + 10, 50))
|
width = max(15, min(max_len + 10, 50))
|
||||||
height = min(len(suggestions), 10)
|
height = min(len(suggestions), 10)
|
||||||
# Calcular el ancho basado en el texto completo que se muestra en el listbox
|
|
||||||
full_text_suggestions = [f"{name} — {hint}" for name, hint in suggestions]
|
full_text_suggestions = [f"{name} — {hint}" for name, hint in suggestions]
|
||||||
max_full_len = max(len(text) for text in full_text_suggestions) if full_text_suggestions else 20
|
max_full_len = max(len(text) for text in full_text_suggestions) if full_text_suggestions else 20
|
||||||
width = max(20, min(max_full_len + 5, 80)) # Ajustar el +5 y el límite 80 según sea necesario
|
width = max(20, min(max_full_len + 5, 80))
|
||||||
self._autocomplete_listbox.config(width=width, height=height)
|
self._autocomplete_listbox.config(width=width, height=height)
|
||||||
else:
|
else:
|
||||||
self._close_autocomplete_popup()
|
self._close_autocomplete_popup()
|
||||||
|
|
||||||
def _navigate_autocomplete(self, event, direction):
|
def _navigate_autocomplete(self, event, direction):
|
||||||
|
"""Navegación en autocomplete (sin cambios)"""
|
||||||
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
||||||
return "break"
|
return "break"
|
||||||
current_selection = self._autocomplete_listbox.curselection()
|
current_selection = self._autocomplete_listbox.curselection()
|
||||||
|
@ -495,6 +539,7 @@ class HybridCalculatorApp:
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def _on_autocomplete_select(self, event, is_global=False):
|
def _on_autocomplete_select(self, event, is_global=False):
|
||||||
|
"""Selección de autocomplete (sin cambios)"""
|
||||||
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
||||||
return "break"
|
return "break"
|
||||||
selection = self._autocomplete_listbox.curselection()
|
selection = self._autocomplete_listbox.curselection()
|
||||||
|
@ -503,49 +548,31 @@ class HybridCalculatorApp:
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
selected_text_with_hint = self._autocomplete_listbox.get(selection[0])
|
selected_text_with_hint = self._autocomplete_listbox.get(selection[0])
|
||||||
# Extraer solo el nombre del ítem, antes de " —"
|
|
||||||
item_name = selected_text_with_hint.split(" —")[0].strip()
|
item_name = selected_text_with_hint.split(" —")[0].strip()
|
||||||
|
|
||||||
if is_global:
|
if is_global:
|
||||||
# Eliminar el punto que activó el popup y luego insertar el nombre
|
cursor_pos_str = self.input_text.index(tk.INSERT)
|
||||||
cursor_pos_str = self.input_text.index(tk.INSERT) # Posición actual (después del punto)
|
|
||||||
line_num, char_num = map(int, cursor_pos_str.split('.'))
|
line_num, char_num = map(int, cursor_pos_str.split('.'))
|
||||||
|
|
||||||
# El punto está en char_num - 1 en la línea actual
|
|
||||||
dot_pos_on_line = char_num - 1
|
dot_pos_on_line = char_num - 1
|
||||||
dot_index_str = f"{line_num}.{dot_pos_on_line}"
|
dot_index_str = f"{line_num}.{dot_pos_on_line}"
|
||||||
|
|
||||||
self.input_text.delete(dot_index_str)
|
self.input_text.delete(dot_index_str)
|
||||||
|
|
||||||
# Insertar el nombre de la función/clase seguido de "()"
|
|
||||||
insert_text = item_name + "()"
|
insert_text = item_name + "()"
|
||||||
self.input_text.insert(dot_index_str, insert_text)
|
self.input_text.insert(dot_index_str, insert_text)
|
||||||
# Colocar cursor dentro de los paréntesis: después del nombre y el '('
|
|
||||||
self.input_text.mark_set(tk.INSERT, f"{dot_index_str}+{len(item_name)+1}c")
|
self.input_text.mark_set(tk.INSERT, f"{dot_index_str}+{len(item_name)+1}c")
|
||||||
else:
|
else:
|
||||||
# Comportamiento existente para métodos de objeto
|
|
||||||
self.input_text.insert(tk.INSERT, item_name + "()")
|
self.input_text.insert(tk.INSERT, item_name + "()")
|
||||||
self.input_text.mark_set(tk.INSERT, f"{tk.INSERT}-1c") # Cursor dentro de los paréntesis
|
self.input_text.mark_set(tk.INSERT, f"{tk.INSERT}-1c")
|
||||||
|
|
||||||
self._close_autocomplete_popup()
|
self._close_autocomplete_popup()
|
||||||
self.input_text.focus_set()
|
self.input_text.focus_set()
|
||||||
self.on_key_release() # Trigger re-evaluation
|
self.on_key_release()
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def _close_autocomplete_popup(self):
|
def _close_autocomplete_popup(self):
|
||||||
|
"""Cierra popup de autocomplete (sin cambios)"""
|
||||||
if hasattr(self, '_autocomplete_popup') and self._autocomplete_popup:
|
if hasattr(self, '_autocomplete_popup') and self._autocomplete_popup:
|
||||||
self._autocomplete_popup.destroy()
|
self._autocomplete_popup.destroy()
|
||||||
self._autocomplete_popup = None
|
self._autocomplete_popup = None
|
||||||
# Consider unbinding the Button-1 events if they were stored with IDs,
|
|
||||||
# for now, their guard condition `if self._autocomplete_popup:` handles multiple calls.
|
|
||||||
# Example of how to unbind if IDs were stored:
|
|
||||||
# if hasattr(self, '_input_text_b1_bind_id'):
|
|
||||||
# self.input_text.unbind("<Button-1>", self._input_text_b1_bind_id)
|
|
||||||
# del self._input_text_b1_bind_id
|
|
||||||
# if hasattr(self, '_root_b1_bind_id'):
|
|
||||||
# self.root.unbind("<Button-1>", self._root_b1_bind_id)
|
|
||||||
# del self._root_b1_bind_id
|
|
||||||
|
|
||||||
if hasattr(self, '_autocomplete_listbox') and self._autocomplete_listbox:
|
if hasattr(self, '_autocomplete_listbox') and self._autocomplete_listbox:
|
||||||
self._autocomplete_listbox = None
|
self._autocomplete_listbox = None
|
||||||
|
|
||||||
|
@ -592,7 +619,6 @@ class HybridCalculatorApp:
|
||||||
if result.is_error:
|
if result.is_error:
|
||||||
ayuda = self.obtener_ayuda(result.original_line)
|
ayuda = self.obtener_ayuda(result.original_line)
|
||||||
if ayuda:
|
if ayuda:
|
||||||
# Mostrar ayuda en un solo renglón, truncando si es necesario
|
|
||||||
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
|
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
|
||||||
if len(ayuda_linea) > 120:
|
if len(ayuda_linea) > 120:
|
||||||
ayuda_linea = ayuda_linea[:117] + "..."
|
ayuda_linea = ayuda_linea[:117] + "..."
|
||||||
|
@ -608,8 +634,8 @@ class HybridCalculatorApp:
|
||||||
else:
|
else:
|
||||||
# Resultado normal
|
# Resultado normal
|
||||||
if result.result is not None:
|
if result.result is not None:
|
||||||
# Determinar tag basado en tipo
|
# Determinar tag basado en tipo (DINÁMICO)
|
||||||
tag = self._get_result_tag(result.result)
|
tag = self._get_result_tag_dynamic(result.result)
|
||||||
|
|
||||||
# Verificar si es resultado interactivo
|
# Verificar si es resultado interactivo
|
||||||
if self.interactive_manager and result.is_interactive:
|
if self.interactive_manager and result.is_interactive:
|
||||||
|
@ -623,90 +649,122 @@ class HybridCalculatorApp:
|
||||||
|
|
||||||
# Añadir pista de clase para el resultado principal
|
# Añadir pista de clase para el resultado principal
|
||||||
primary_result_object = result.result
|
primary_result_object = result.result
|
||||||
if not isinstance(primary_result_object, PlotResult): # PlotResult ya tiene su propio formato
|
if not isinstance(primary_result_object, PlotResult):
|
||||||
class_display_name = ""
|
class_display_name = self._get_class_display_name_dynamic(primary_result_object)
|
||||||
if isinstance(primary_result_object, SympyClassBase):
|
|
||||||
class_display_name = type(primary_result_object).__name__.replace("Class_", "")
|
|
||||||
elif isinstance(primary_result_object, sympy.logic.boolalg.BooleanAtom): # sympy.true/false
|
|
||||||
class_display_name = "Boolean"
|
|
||||||
elif isinstance(primary_result_object, sympy.Basic): # Objetos SymPy generales
|
|
||||||
if hasattr(primary_result_object, 'is_number') and primary_result_object.is_number:
|
|
||||||
if hasattr(primary_result_object, 'is_Integer') and primary_result_object.is_Integer:
|
|
||||||
class_display_name = "Integer"
|
|
||||||
elif hasattr(primary_result_object, 'is_Rational') and primary_result_object.is_Rational and not primary_result_object.is_Integer :
|
|
||||||
class_display_name = "Rational"
|
|
||||||
elif hasattr(primary_result_object, 'is_Float') and primary_result_object.is_Float:
|
|
||||||
class_display_name = "Float"
|
|
||||||
else:
|
|
||||||
class_display_name = "SympyNumber" # Otros números de SymPy
|
|
||||||
else: # Expresiones SymPy, símbolos, etc.
|
|
||||||
class_display_name = "Sympy"
|
|
||||||
elif isinstance(primary_result_object, bool): # bool de Python
|
|
||||||
class_display_name = "Boolean"
|
|
||||||
elif isinstance(primary_result_object, (int, float, str, list, dict, tuple, type(None))):
|
|
||||||
class_display_name = type(primary_result_object).__name__.capitalize()
|
|
||||||
if class_display_name == "Nonetype": class_display_name = "None"
|
|
||||||
# Nombres como 'Int', 'Float', 'Str', 'List', 'Dict', 'Tuple' están bien.
|
|
||||||
|
|
||||||
if class_display_name:
|
if class_display_name:
|
||||||
output_parts.append(("class_hint", f"[{class_display_name}]"))
|
output_parts.append(("class_hint", f"[{class_display_name}]"))
|
||||||
|
|
||||||
# Mostrar evaluación numérica si existe
|
# Mostrar evaluación numérica si existe
|
||||||
if result.numeric_result is not None and result.numeric_result != result.result:
|
if result.numeric_result is not None and result.numeric_result != result.result:
|
||||||
output_parts.append(("numeric", f"≈ {result.numeric_result}")) # El espacio se controlará en _display_output
|
output_parts.append(("numeric", f"≈ {result.numeric_result}"))
|
||||||
|
|
||||||
# Mostrar información adicional
|
# Mostrar información adicional
|
||||||
if result.info:
|
if result.info:
|
||||||
output_parts.append(("info", f"({result.info})")) # El espacio se controlará en _display_output
|
output_parts.append(("info", f"({result.info})"))
|
||||||
|
|
||||||
return output_parts
|
return output_parts
|
||||||
|
|
||||||
def _get_result_tag(self, result: Any) -> str:
|
def _get_result_tag_dynamic(self, result: Any) -> str:
|
||||||
"""Determina el tag de color para un resultado"""
|
"""Determina el tag de color para un resultado - VERSIÓN DINÁMICA"""
|
||||||
if isinstance(result, Class_Hex):
|
# Obtener clases registradas dinámicamente
|
||||||
|
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":
|
||||||
return "hex"
|
return "hex"
|
||||||
elif isinstance(result, Class_Bin):
|
elif name.lower() == "bin":
|
||||||
return "bin"
|
return "bin"
|
||||||
elif isinstance(result, Class_IP4):
|
elif name.lower() in ["ip4", "ip"]:
|
||||||
return "ip"
|
return "ip"
|
||||||
elif isinstance(result, Class_Chr):
|
elif name.lower() == "chr":
|
||||||
return "chr_type"
|
return "chr_type"
|
||||||
|
else:
|
||||||
|
return "custom_type" # Tag genérico para tipos personalizados
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
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):
|
elif isinstance(result, sympy.Basic):
|
||||||
return "symbolic"
|
return "symbolic"
|
||||||
else:
|
else:
|
||||||
return "result"
|
return "result"
|
||||||
|
|
||||||
|
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
|
||||||
|
registered_classes = self.engine.get_available_types().get('registered_classes', {})
|
||||||
|
|
||||||
|
for name, cls in registered_classes.items():
|
||||||
|
if isinstance(obj, cls):
|
||||||
|
return name
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"DEBUG: Error en get_class_display_name_dynamic: {e}")
|
||||||
|
|
||||||
|
# Fallback a lógica existente
|
||||||
|
if hasattr(obj, '__class__'):
|
||||||
|
class_name = obj.__class__.__name__
|
||||||
|
if class_name.startswith('Class_'):
|
||||||
|
return class_name.replace("Class_", "")
|
||||||
|
elif class_name.endswith('Mask'):
|
||||||
|
return class_name
|
||||||
|
|
||||||
|
if isinstance(obj, sympy.logic.boolalg.BooleanAtom):
|
||||||
|
return "Boolean"
|
||||||
|
elif isinstance(obj, sympy.Basic):
|
||||||
|
if hasattr(obj, 'is_number') and obj.is_number:
|
||||||
|
if hasattr(obj, 'is_Integer') and obj.is_Integer:
|
||||||
|
return "Integer"
|
||||||
|
elif hasattr(obj, 'is_Rational') and obj.is_Rational and not obj.is_Integer:
|
||||||
|
return "Rational"
|
||||||
|
elif hasattr(obj, 'is_Float') and obj.is_Float:
|
||||||
|
return "Float"
|
||||||
|
else:
|
||||||
|
return "SympyNumber"
|
||||||
|
else:
|
||||||
|
return "Sympy"
|
||||||
|
elif isinstance(obj, bool):
|
||||||
|
return "Boolean"
|
||||||
|
elif isinstance(obj, (int, float, str, list, dict, tuple, type(None))):
|
||||||
|
class_display_name = type(obj).__name__.capitalize()
|
||||||
|
if class_display_name == "Nonetype":
|
||||||
|
class_display_name = "None"
|
||||||
|
return class_display_name
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
def _display_output(self, output_data: List[List[tuple]]):
|
def _display_output(self, output_data: List[List[tuple]]):
|
||||||
"""Muestra los datos de salida en el widget"""
|
"""Muestra los datos de salida en el widget (sin cambios)"""
|
||||||
self.output_text.config(state="normal")
|
self.output_text.config(state="normal")
|
||||||
self.output_text.delete("1.0", tk.END)
|
self.output_text.delete("1.0", tk.END)
|
||||||
|
|
||||||
for line_idx, line_parts in enumerate(output_data):
|
for line_idx, line_parts in enumerate(output_data):
|
||||||
# Línea vacía
|
|
||||||
if not line_parts or (len(line_parts) == 1 and line_parts[0][0] == "" and line_parts[0][1] == ""):
|
if not line_parts or (len(line_parts) == 1 and line_parts[0][0] == "" and line_parts[0][1] == ""):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# Mostrar partes de la línea
|
|
||||||
for part_idx, (tag, content) in enumerate(line_parts):
|
for part_idx, (tag, content) in enumerate(line_parts):
|
||||||
if not content: # Omitir contenido vacío
|
if not content:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Determinar si se necesita un separador antes de esta parte
|
|
||||||
if part_idx > 0:
|
if part_idx > 0:
|
||||||
prev_tag, prev_content = line_parts[part_idx-1] if part_idx > 0 else (None, None)
|
prev_tag, prev_content = line_parts[part_idx-1] if part_idx > 0 else (None, None)
|
||||||
|
|
||||||
# No añadir separador si la parte actual es una "anotación" o si la parte anterior estaba vacía.
|
|
||||||
if tag not in ["class_hint", "numeric", "info"] and prev_content:
|
if tag not in ["class_hint", "numeric", "info"] and prev_content:
|
||||||
self.output_text.insert(tk.END, " ; ")
|
self.output_text.insert(tk.END, " ; ")
|
||||||
# 'numeric' e 'info' necesitan un espacio precedente si siguen a contenido.
|
|
||||||
elif tag in ["numeric", "info"] and prev_content:
|
elif tag in ["numeric", "info"] and prev_content:
|
||||||
self.output_text.insert(tk.END, " ")
|
self.output_text.insert(tk.END, " ")
|
||||||
# 'class_hint' se une directamente.
|
|
||||||
|
|
||||||
if content: # Asegurarse de que hay contenido antes de insertar
|
if content:
|
||||||
self.output_text.insert(tk.END, str(content), tag)
|
self.output_text.insert(tk.END, str(content), tag)
|
||||||
|
|
||||||
# Añadir nueva línea excepto para la última línea
|
|
||||||
if line_idx < len(output_data) - 1:
|
if line_idx < len(output_data) - 1:
|
||||||
self.output_text.insert(tk.END, "\n")
|
self.output_text.insert(tk.END, "\n")
|
||||||
|
|
||||||
|
@ -753,7 +811,8 @@ class HybridCalculatorApp:
|
||||||
finally:
|
finally:
|
||||||
context_menu.grab_release()
|
context_menu.grab_release()
|
||||||
|
|
||||||
# Métodos de menú y comandos
|
# ========== MÉTODOS DE MENÚ Y COMANDOS (la mayoría sin cambios) ==========
|
||||||
|
|
||||||
def new_session(self):
|
def new_session(self):
|
||||||
"""Inicia nueva sesión"""
|
"""Inicia nueva sesión"""
|
||||||
self.clear_input()
|
self.clear_input()
|
||||||
|
@ -838,16 +897,42 @@ class HybridCalculatorApp:
|
||||||
self.root.clipboard_append(content)
|
self.root.clipboard_append(content)
|
||||||
|
|
||||||
def insert_example(self):
|
def insert_example(self):
|
||||||
"""Inserta código de ejemplo"""
|
"""Inserta código de ejemplo - ACTUALIZADO CON TIPOS DINÁMICOS"""
|
||||||
example = """# Calculadora MAV - CAS Híbrido
|
# 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
|
# Sintaxis nueva con corchetes
|
||||||
|
|
||||||
# Tipos especializados
|
"""
|
||||||
Hex[FF] + 1
|
|
||||||
IP4[192.168.1.100/24].NetworkAddress[]
|
|
||||||
Bin[1010] * 2
|
|
||||||
|
|
||||||
# Matemáticas simbólicas
|
# 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
|
x + 2*y
|
||||||
diff(x**2 + sin(x), x)
|
diff(x**2 + sin(x), x)
|
||||||
integrate(x**2, x)
|
integrate(x**2, x)
|
||||||
|
@ -870,6 +955,7 @@ plot(sin(x), (x, -2*pi, 2*pi))
|
||||||
# Matrices
|
# Matrices
|
||||||
Matrix([[1, 2], [3, 4]])
|
Matrix([[1, 2], [3, 4]])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.input_text.delete("1.0", tk.END)
|
self.input_text.delete("1.0", tk.END)
|
||||||
self.input_text.insert("1.0", example)
|
self.input_text.insert("1.0", example)
|
||||||
self._evaluate_and_update()
|
self._evaluate_and_update()
|
||||||
|
@ -963,13 +1049,64 @@ Matrix([[1, 2], [3, 4]])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", f"Error resolviendo sistema:\n{e}")
|
messagebox.showerror("Error", f"Error resolviendo sistema:\n{e}")
|
||||||
|
|
||||||
|
def show_types_syntax(self):
|
||||||
|
"""Muestra sintaxis de tipos disponibles - NUEVA FUNCIÓN"""
|
||||||
|
try:
|
||||||
|
types_info = self.engine.get_available_types()
|
||||||
|
registered_classes = types_info.get('registered_classes', {})
|
||||||
|
|
||||||
|
syntax_text = "SINTAXIS DE TIPOS DISPONIBLES\n\n"
|
||||||
|
|
||||||
|
if not registered_classes:
|
||||||
|
syntax_text += "No hay tipos personalizados disponibles.\n"
|
||||||
|
else:
|
||||||
|
syntax_text += "Tipos personalizados detectados:\n\n"
|
||||||
|
|
||||||
|
for name, cls in sorted(registered_classes.items()):
|
||||||
|
syntax_text += f"=== {name} ===\n"
|
||||||
|
|
||||||
|
# Sintaxis básica
|
||||||
|
syntax_text += f"Sintaxis: {name}[valor]\n"
|
||||||
|
syntax_text += f"Alias: {name.lower()}[valor]\n"
|
||||||
|
|
||||||
|
# Obtener ayuda si está disponible
|
||||||
|
if hasattr(cls, 'Helper'):
|
||||||
|
try:
|
||||||
|
help_text = cls.Helper(name)
|
||||||
|
if help_text:
|
||||||
|
syntax_text += f"Ayuda: {help_text}\n"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Obtener métodos si está disponible
|
||||||
|
if hasattr(cls, 'PopupFunctionList'):
|
||||||
|
try:
|
||||||
|
methods = cls.PopupFunctionList()
|
||||||
|
if methods:
|
||||||
|
syntax_text += "Métodos disponibles:\n"
|
||||||
|
for method_name, method_desc in methods:
|
||||||
|
syntax_text += f" • {method_name}() - {method_desc}\n"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
syntax_text += "\n"
|
||||||
|
|
||||||
|
self._show_help_window("Sintaxis de Tipos", syntax_text)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", f"Error obteniendo sintaxis de tipos:\n{e}")
|
||||||
|
|
||||||
def show_quick_guide(self):
|
def show_quick_guide(self):
|
||||||
"""Muestra guía rápida"""
|
"""Muestra guía rápida - ACTUALIZADA"""
|
||||||
guide = """# Calculadora MAV - CAS Híbrido
|
guide = """# 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 Nueva con Corchetes
|
||||||
- IP4[192.168.1.1/24] en lugar de IP4("192.168.1.1/24")
|
- Sintaxis: Tipo[valor] en lugar de Tipo("valor")
|
||||||
- Hex[FF], Bin[1010], Dec[10.5], Chr[A]
|
- Ejemplos: Hex[FF], Bin[1010], Dec[10.5], Chr[A]
|
||||||
|
- Use menú Tipos → Información de tipos para ver tipos disponibles
|
||||||
|
|
||||||
## Ecuaciones Automáticas
|
## Ecuaciones Automáticas
|
||||||
- x**2 + 2*x = 8 (detectado automáticamente)
|
- x**2 + 2*x = 8 (detectado automáticamente)
|
||||||
|
@ -990,26 +1127,29 @@ Matrix([[1, 2], [3, 4]])
|
||||||
- Todas las variables son símbolos SymPy
|
- Todas las variables son símbolos SymPy
|
||||||
- x = 5 crea Symbol('x') con valor 5
|
- x = 5 crea Symbol('x') con valor 5
|
||||||
- Evaluación simbólica + numérica automática
|
- 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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._show_help_window("Guía Rápida", guide)
|
self._show_help_window("Guía Rápida", guide)
|
||||||
|
|
||||||
def show_syntax_help(self):
|
def show_syntax_help(self):
|
||||||
"""Muestra ayuda de sintaxis"""
|
"""Muestra ayuda de sintaxis - ACTUALIZADA"""
|
||||||
syntax = """# Sintaxis del CAS Híbrido
|
syntax = """# Sintaxis del CAS Híbrido
|
||||||
|
|
||||||
## Clases Especializadas (solo corchetes)
|
## Sistema de Tipos Dinámico
|
||||||
IP4[dirección/prefijo] # IP4[192.168.1.1/24]
|
Los tipos se detectan automáticamente desde custom_types/
|
||||||
Hex[valor] # Hex[FF], Hex[255]
|
Use menú Tipos → Información de tipos para ver tipos disponibles
|
||||||
Bin[valor] # Bin[1010], Bin[10]
|
|
||||||
Dec[valor] # Dec[10.5], Dec[10]
|
|
||||||
Chr[carácter] # Chr[A], Chr[Hello]
|
|
||||||
|
|
||||||
## Métodos Disponibles
|
## Sintaxis con Corchetes (Dinámica)
|
||||||
IP4[...].NetworkAddress[]
|
Tipo[valor] # Sintaxis general
|
||||||
IP4[...].BroadcastAddress[]
|
Tipo[arg1; arg2] # Múltiples argumentos
|
||||||
IP4[...].Nodes()
|
|
||||||
Hex[...].toDecimal()
|
## Métodos Disponibles (Dinámicos)
|
||||||
|
Tipo[...].método() # Métodos específicos del tipo
|
||||||
|
objeto.método[] # Método sin argumentos
|
||||||
|
|
||||||
## Ecuaciones (detección automática)
|
## Ecuaciones (detección automática)
|
||||||
expresión = expresión # Ecuación simple
|
expresión = expresión # Ecuación simple
|
||||||
|
@ -1028,7 +1168,7 @@ expresión # Evaluación simbólica automática
|
||||||
self._show_help_window("Sintaxis", syntax)
|
self._show_help_window("Sintaxis", syntax)
|
||||||
|
|
||||||
def show_sympy_functions(self):
|
def show_sympy_functions(self):
|
||||||
"""Muestra funciones SymPy disponibles"""
|
"""Muestra funciones SymPy disponibles (sin cambios)"""
|
||||||
functions = """# Funciones SymPy Disponibles
|
functions = """# Funciones SymPy Disponibles
|
||||||
|
|
||||||
## Matemáticas Básicas
|
## Matemáticas Básicas
|
||||||
|
@ -1066,20 +1206,28 @@ pi, E, I (imaginario), oo (infinito)
|
||||||
self._show_help_window("Funciones SymPy", functions)
|
self._show_help_window("Funciones SymPy", functions)
|
||||||
|
|
||||||
def show_about(self):
|
def show_about(self):
|
||||||
"""Muestra información sobre la aplicación"""
|
"""Muestra información sobre la aplicación - ACTUALIZADA"""
|
||||||
about = """Calculadora MAV - CAS Híbrido
|
about = """Calculadora MAV - CAS Híbrido
|
||||||
|
|
||||||
Versión: 2.0
|
Versión: 2.1 (Sistema de Tipos Dinámico)
|
||||||
Motor: SymPy + Clases Especializadas
|
Motor: SymPy + Auto-descubrimiento de Tipos
|
||||||
|
|
||||||
Características:
|
Características:
|
||||||
• Motor algebraico completo (SymPy)
|
• Motor algebraico completo (SymPy)
|
||||||
|
• Sistema de tipos dinámico y extensible
|
||||||
• Sintaxis simplificada con corchetes
|
• Sintaxis simplificada con corchetes
|
||||||
• Detección automática de ecuaciones
|
• Detección automática de ecuaciones
|
||||||
• Resultados interactivos clickeables
|
• Resultados interactivos clickeables
|
||||||
• Tipos especializados (IP4, Hex, Bin, etc.)
|
• Auto-descubrimiento de tipos en custom_types/
|
||||||
• Variables SymPy puras
|
• Variables SymPy puras
|
||||||
• Plotting integrado
|
• Plotting integrado
|
||||||
|
• Autocompletado dinámico
|
||||||
|
|
||||||
|
NUEVO: Sistema de Tipos Dinámico
|
||||||
|
• Detección automática de nuevos tipos
|
||||||
|
• Organización modular en custom_types/
|
||||||
|
• Registro automático sin modificar código
|
||||||
|
• Escalabilidad mejorada
|
||||||
|
|
||||||
Desarrollado para cálculo matemático avanzado
|
Desarrollado para cálculo matemático avanzado
|
||||||
con soporte especializado para redes,
|
con soporte especializado para redes,
|
||||||
|
@ -1147,10 +1295,15 @@ programación y análisis numérico.
|
||||||
self.root.destroy()
|
self.root.destroy()
|
||||||
|
|
||||||
def obtener_ayuda(self, input_str):
|
def obtener_ayuda(self, input_str):
|
||||||
|
"""Obtiene ayuda usando helpers dinámicos"""
|
||||||
for helper in self.HELPERS:
|
for helper in self.HELPERS:
|
||||||
|
try:
|
||||||
ayuda = helper(input_str)
|
ayuda = helper(input_str)
|
||||||
if ayuda:
|
if ayuda:
|
||||||
return ayuda
|
return ayuda
|
||||||
|
except Exception as e:
|
||||||
|
print(f"DEBUG: Error en helper: {e}")
|
||||||
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Motor de evaluación híbrida que usa SymPy como base con clases especializadas
|
Motor de evaluación híbrida INTEGRADO con el sistema de auto-descubrimiento de tipos
|
||||||
"""
|
"""
|
||||||
import sympy
|
import sympy
|
||||||
from sympy import symbols, Symbol, sympify, solve, Eq, simplify
|
from sympy import symbols, Symbol, sympify, solve, Eq, simplify
|
||||||
|
@ -8,36 +8,64 @@ import ast
|
||||||
import re
|
import re
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
# Importaciones del sistema de tipos
|
||||||
|
from type_registry import (
|
||||||
|
discover_and_register_types,
|
||||||
|
get_registered_base_context,
|
||||||
|
get_registered_bracket_classes,
|
||||||
|
get_registered_helper_functions
|
||||||
|
)
|
||||||
|
|
||||||
|
# Importaciones existentes
|
||||||
from tl_bracket_parser import BracketParser
|
from tl_bracket_parser import BracketParser
|
||||||
from tl_popup import PlotResult
|
from tl_popup import PlotResult
|
||||||
from sympy_Base import SympyClassBase
|
|
||||||
from ip4_type import Class_IP4, IP4Mask
|
|
||||||
from hex_type import Class_Hex
|
|
||||||
from bin_type import Class_Bin
|
|
||||||
from dec_type import Class_Dec
|
|
||||||
from chr_type import Class_Chr
|
|
||||||
|
|
||||||
|
|
||||||
class HybridEvaluationEngine:
|
class HybridEvaluationEngine:
|
||||||
"""
|
"""
|
||||||
Motor de evaluación híbrida que combina SymPy con clases especializadas
|
Motor de evaluación híbrida que combina SymPy con clases especializadas
|
||||||
|
VERSIÓN INTEGRADA con auto-descubrimiento de tipos
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, auto_discover_types: bool = True, types_directory: str = "custom_types"):
|
||||||
self.parser = BracketParser()
|
self.parser = BracketParser()
|
||||||
self.symbol_table: Dict[str, Any] = {}
|
self.symbol_table: Dict[str, Any] = {}
|
||||||
self.equations: List[sympy.Eq] = []
|
self.equations: List[sympy.Eq] = []
|
||||||
self.last_result = None
|
self.last_result = None
|
||||||
|
|
||||||
# Contexto base con funciones y clases
|
# Configuración del sistema de tipos
|
||||||
self._setup_base_context()
|
self.types_directory = types_directory
|
||||||
|
self.auto_discover_enabled = auto_discover_types
|
||||||
|
|
||||||
|
# Información de tipos registrados
|
||||||
|
self.registered_types_info = {}
|
||||||
|
self.helper_functions = []
|
||||||
|
|
||||||
# Debug mode
|
# Debug mode
|
||||||
self.debug = False
|
self.debug = False
|
||||||
|
# Configurar contexto base
|
||||||
|
self._setup_base_context()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _setup_base_context(self):
|
def _setup_base_context(self):
|
||||||
"""Configura el contexto base con funciones matemáticas y clases"""
|
"""Configura el contexto base con funciones matemáticas y clases"""
|
||||||
# Funciones matemáticas de SymPy
|
|
||||||
|
# 1. DESCOBRIR Y REGISTRAR TIPOS AUTOMÁTICAMENTE
|
||||||
|
if self.auto_discover_enabled:
|
||||||
|
try:
|
||||||
|
self.registered_types_info = discover_and_register_types(self.types_directory)
|
||||||
|
if self.debug:
|
||||||
|
print(f"🔍 Tipos descubiertos: {self.registered_types_info['class_count']} clases")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Error en auto-descubrimiento: {e}")
|
||||||
|
self.registered_types_info = {
|
||||||
|
'base_context': {},
|
||||||
|
'bracket_classes': set(),
|
||||||
|
'helper_functions': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. FUNCIONES MATEMÁTICAS DE SYMPY (BASE)
|
||||||
math_functions = {
|
math_functions = {
|
||||||
'pi': sympy.pi,
|
'pi': sympy.pi,
|
||||||
'e': sympy.E,
|
'e': sympy.E,
|
||||||
|
@ -83,24 +111,10 @@ class HybridEvaluationEngine:
|
||||||
'plot3d': self._create_plot3d_placeholder,
|
'plot3d': self._create_plot3d_placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clases especializadas
|
# 3. CLASES ESPECIALIZADAS (DESDE AUTO-DESCUBRIMIENTO)
|
||||||
specialized_classes = {
|
specialized_classes = self.registered_types_info.get('base_context', {})
|
||||||
'Hex': Class_Hex,
|
|
||||||
'Bin': Class_Bin,
|
|
||||||
'Dec': Class_Dec,
|
|
||||||
'IP4': Class_IP4,
|
|
||||||
'Chr': Class_Chr,
|
|
||||||
'IP4Mask': IP4Mask,
|
|
||||||
# Alias en minúsculas
|
|
||||||
'hex': Class_Hex,
|
|
||||||
'bin': Class_Bin,
|
|
||||||
'dec': Class_Dec,
|
|
||||||
'ip4': Class_IP4,
|
|
||||||
'chr': Class_Chr,
|
|
||||||
'ip4mask': IP4Mask,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Funciones de utilidad
|
# 4. FUNCIONES DE UTILIDAD
|
||||||
utility_functions = {
|
utility_functions = {
|
||||||
'_add_equation': self._add_equation,
|
'_add_equation': self._add_equation,
|
||||||
'_assign_variable': self._assign_variable,
|
'_assign_variable': self._assign_variable,
|
||||||
|
@ -108,12 +122,65 @@ class HybridEvaluationEngine:
|
||||||
'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr),
|
'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 5. COMBINAR TODO EN EL CONTEXTO BASE
|
||||||
self.base_context = {
|
self.base_context = {
|
||||||
**math_functions,
|
**math_functions,
|
||||||
**specialized_classes,
|
**specialized_classes,
|
||||||
**utility_functions
|
**utility_functions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 6. ACTUALIZAR HELPER FUNCTIONS
|
||||||
|
self.helper_functions = get_registered_helper_functions()
|
||||||
|
|
||||||
|
# 7. ACTUALIZAR BRACKET PARSER CON CLASES DESCUBIERTAS
|
||||||
|
self._update_bracket_parser()
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
print(f"📋 Contexto base configurado: {len(self.base_context)} entradas")
|
||||||
|
print(f"🆘 Helper functions: {len(self.helper_functions)}")
|
||||||
|
|
||||||
|
def _update_bracket_parser(self):
|
||||||
|
"""Actualiza el BracketParser con las clases descubiertas"""
|
||||||
|
try:
|
||||||
|
discovered_bracket_classes = get_registered_bracket_classes()
|
||||||
|
# Combinar con clases existentes del parser
|
||||||
|
self.parser.BRACKET_CLASSES = self.parser.BRACKET_CLASSES.union(discovered_bracket_classes)
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
print(f"🔧 Bracket classes actualizadas: {self.parser.BRACKET_CLASSES}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Error actualizando bracket parser: {e}")
|
||||||
|
|
||||||
|
def reload_types(self):
|
||||||
|
"""Recarga todos los tipos del directorio (útil para desarrollo)"""
|
||||||
|
if self.debug:
|
||||||
|
print("🔄 Recargando tipos...")
|
||||||
|
|
||||||
|
self._setup_base_context()
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
print("✅ Tipos recargados")
|
||||||
|
|
||||||
|
def get_available_types(self) -> Dict[str, Any]:
|
||||||
|
"""Retorna información sobre los tipos disponibles"""
|
||||||
|
return {
|
||||||
|
'registered_classes': self.registered_types_info.get('registered_classes', {}),
|
||||||
|
'bracket_classes': list(self.registered_types_info.get('bracket_classes', set())),
|
||||||
|
'total_context_entries': len(self.base_context),
|
||||||
|
'helper_functions_count': len(self.helper_functions)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_type_help(self, type_name: str) -> Optional[str]:
|
||||||
|
"""Obtiene ayuda para un tipo específico"""
|
||||||
|
# Buscar en clases registradas
|
||||||
|
registered_classes = self.registered_types_info.get('registered_classes', {})
|
||||||
|
if type_name in registered_classes:
|
||||||
|
cls = registered_classes[type_name]
|
||||||
|
if hasattr(cls, 'Helper'):
|
||||||
|
return cls.Helper(type_name)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _create_plot_placeholder(self, *args, **kwargs):
|
def _create_plot_placeholder(self, *args, **kwargs):
|
||||||
"""Crear placeholder para plots que será manejado por resultados interactivos"""
|
"""Crear placeholder para plots que será manejado por resultados interactivos"""
|
||||||
if self.debug:
|
if self.debug:
|
||||||
|
@ -127,17 +194,31 @@ class HybridEvaluationEngine:
|
||||||
return PlotResult('plot3d', args, kwargs)
|
return PlotResult('plot3d', args, kwargs)
|
||||||
|
|
||||||
def _help_function(self, obj=None):
|
def _help_function(self, obj=None):
|
||||||
"""Función de ayuda integrada"""
|
"""Función de ayuda integrada que usa el sistema de helpers"""
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return "Ayuda disponible. Use help(función) para ayuda específica."
|
return "Ayuda disponible. Use help(función) para ayuda específica."
|
||||||
|
|
||||||
|
# Primero intentar con el objeto directamente
|
||||||
if hasattr(obj, '__doc__') and obj.__doc__:
|
if hasattr(obj, '__doc__') and obj.__doc__:
|
||||||
return obj.__doc__
|
return obj.__doc__
|
||||||
elif hasattr(obj, 'Helper'):
|
elif hasattr(obj, 'Helper'):
|
||||||
return obj.Helper("")
|
return obj.Helper("")
|
||||||
else:
|
|
||||||
|
# Luego buscar en helpers registrados
|
||||||
|
obj_name = getattr(obj, '__name__', str(obj))
|
||||||
|
for helper_func in self.helper_functions:
|
||||||
|
try:
|
||||||
|
help_result = helper_func(obj_name)
|
||||||
|
if help_result:
|
||||||
|
return help_result
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
return f"No hay ayuda disponible para {obj}"
|
return f"No hay ayuda disponible para {obj}"
|
||||||
|
|
||||||
|
# ========== RESTO DE MÉTODOS EXISTENTES ==========
|
||||||
|
# (Los métodos de evaluación permanecen igual)
|
||||||
|
|
||||||
def evaluate_line(self, line: str) -> 'EvaluationResult':
|
def evaluate_line(self, line: str) -> 'EvaluationResult':
|
||||||
"""
|
"""
|
||||||
Evalúa una línea de código y retorna el resultado
|
Evalúa una línea de código y retorna el resultado
|
||||||
|
@ -316,7 +397,7 @@ class HybridEvaluationEngine:
|
||||||
result = eval(expression, {"__builtins__": {}}, context)
|
result = eval(expression, {"__builtins__": {}}, context)
|
||||||
|
|
||||||
# Si el resultado es un objeto híbrido, integrarlo con SymPy si es necesario
|
# Si el resultado es un objeto híbrido, integrarlo con SymPy si es necesario
|
||||||
if isinstance(result, SympyClassBase):
|
if hasattr(result, '_sympystr'): # SympyClassBase
|
||||||
return result
|
return result
|
||||||
elif isinstance(result, PlotResult):
|
elif isinstance(result, PlotResult):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
|
@ -488,55 +569,59 @@ class EvaluationResult:
|
||||||
|
|
||||||
|
|
||||||
# Funciones de testing
|
# Funciones de testing
|
||||||
def test_evaluation_engine():
|
def test_hybrid_engine_with_types():
|
||||||
"""Test del motor de evaluación"""
|
"""Test del motor de evaluación con sistema de tipos"""
|
||||||
engine = HybridEvaluationEngine()
|
print("🧪 Test HybridEvaluationEngine con Type Registry")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Crear motor con auto-descubrimiento
|
||||||
|
engine = HybridEvaluationEngine(auto_discover_types=True)
|
||||||
engine.debug = 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 = [
|
test_cases = [
|
||||||
# Expresiones básicas
|
# Expresiones básicas
|
||||||
"2 + 3",
|
"2 + 3",
|
||||||
"x + 2",
|
"x + 2",
|
||||||
"sin(pi/2)",
|
"sin(pi/2)",
|
||||||
|
|
||||||
# Sintaxis con corchetes
|
# Tipo Chr si está disponible
|
||||||
"Hex[FF]",
|
"Chr[A]" if "Chr" in types_info['registered_classes'] else "# Chr no disponible",
|
||||||
"IP4[192.168.1.1/24]",
|
|
||||||
|
|
||||||
# 🧪 PLOTS - Casos específicos para testing
|
# Variables y ecuaciones
|
||||||
"plot(sin(x), (x, -pi, pi))",
|
|
||||||
"plot(x**2, (x, -5, 5))",
|
|
||||||
|
|
||||||
# Ecuaciones
|
|
||||||
"x + 2 = 5",
|
|
||||||
"y**2 = 16",
|
|
||||||
|
|
||||||
# Solve
|
|
||||||
"solve(x + 2 - 5, x)",
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
"a = 10",
|
"a = 10",
|
||||||
"b = a + 5",
|
"x + 2 = 5",
|
||||||
|
"solve(x + 2 - 5, x)",
|
||||||
|
|
||||||
# Funciones avanzadas
|
# Funciones avanzadas
|
||||||
"diff(x**2, x)",
|
"diff(x**2, x)",
|
||||||
"integrate(x**2, x)",
|
"plot(sin(x), (x, -pi, pi))",
|
||||||
]
|
]
|
||||||
|
|
||||||
print("=== Test Motor de Evaluación ===")
|
print("🔍 Ejecutando casos de prueba:")
|
||||||
for test in test_cases:
|
for test in test_cases:
|
||||||
result = engine.evaluate_line(test)
|
if test.startswith('#'):
|
||||||
print(f"'{test}' → {result} (type: {result.result_type})")
|
print(f"⏭️ {test}")
|
||||||
|
continue
|
||||||
|
|
||||||
# 🔍 Información adicional para plots
|
result = engine.evaluate_line(test)
|
||||||
if 'plot' in test:
|
print(f"✅ '{test}' → {result} (type: {result.result_type})")
|
||||||
print(f" 🎯 Es interactivo: {result.is_interactive}")
|
|
||||||
if isinstance(result.result, PlotResult):
|
|
||||||
print(f" 📊 PlotResult confirmado: {result.result.plot_type}")
|
|
||||||
|
|
||||||
if result.info:
|
if result.info:
|
||||||
print(f" Info: {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__":
|
if __name__ == "__main__":
|
||||||
test_evaluation_engine()
|
test_hybrid_engine_with_types()
|
||||||
|
|
|
@ -1,22 +1,75 @@
|
||||||
"""
|
"""
|
||||||
Bracket Parser - Transformador de sintaxis con corchetes y detección contextual de ecuaciones
|
Bracket Parser - INTEGRADO con el sistema de auto-descubrimiento de tipos
|
||||||
"""
|
"""
|
||||||
import ast
|
import ast
|
||||||
import re
|
import re
|
||||||
from typing import Tuple, Optional
|
from typing import Tuple, Optional, Set, Dict
|
||||||
|
|
||||||
|
# Importar sistema de tipos
|
||||||
|
try:
|
||||||
|
from type_registry import get_registered_bracket_classes
|
||||||
|
TYPE_REGISTRY_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
TYPE_REGISTRY_AVAILABLE = False
|
||||||
|
print("⚠️ Sistema de tipos no disponible, usando clases hardcodeadas")
|
||||||
|
|
||||||
|
|
||||||
class BracketParser:
|
class BracketParser:
|
||||||
"""Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente"""
|
"""Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente"""
|
||||||
|
|
||||||
# Clases que soportan sintaxis con corchetes
|
# Clases base que soportan sintaxis con corchetes (fallback)
|
||||||
BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr', 'IP4Mask'}
|
DEFAULT_BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr', 'IP4Mask'}
|
||||||
|
|
||||||
# Operadores de comparación que pueden formar ecuaciones
|
# Operadores de comparación que pueden formar ecuaciones
|
||||||
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
|
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, use_type_registry: bool = True):
|
||||||
self.debug = False
|
self.debug = False
|
||||||
|
self.use_type_registry = use_type_registry and TYPE_REGISTRY_AVAILABLE
|
||||||
|
|
||||||
|
# Inicializar clases de corchetes
|
||||||
|
self._update_bracket_classes()
|
||||||
|
|
||||||
|
def _update_bracket_classes(self):
|
||||||
|
"""Actualiza las clases que soportan sintaxis con corchetes"""
|
||||||
|
if self.use_type_registry:
|
||||||
|
try:
|
||||||
|
# Obtener clases del registro de tipos
|
||||||
|
registered_classes = get_registered_bracket_classes()
|
||||||
|
self.BRACKET_CLASSES = registered_classes.union(self.DEFAULT_BRACKET_CLASSES)
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
print(f"🔧 Bracket classes desde registro: {registered_classes}")
|
||||||
|
print(f"🔧 Total bracket classes: {self.BRACKET_CLASSES}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if self.debug:
|
||||||
|
print(f"⚠️ Error obteniendo clases del registro: {e}")
|
||||||
|
self.BRACKET_CLASSES = self.DEFAULT_BRACKET_CLASSES.copy()
|
||||||
|
else:
|
||||||
|
self.BRACKET_CLASSES = self.DEFAULT_BRACKET_CLASSES.copy()
|
||||||
|
|
||||||
|
def reload_bracket_classes(self):
|
||||||
|
"""Recarga las clases de corchetes desde el registro"""
|
||||||
|
if self.debug:
|
||||||
|
print("🔄 Recargando bracket classes...")
|
||||||
|
self._update_bracket_classes()
|
||||||
|
|
||||||
|
def add_bracket_class(self, class_name: str):
|
||||||
|
"""Añade una clase que soporta sintaxis con corchetes"""
|
||||||
|
self.BRACKET_CLASSES.add(class_name)
|
||||||
|
if self.debug:
|
||||||
|
print(f"➕ Añadida bracket class: {class_name}")
|
||||||
|
|
||||||
|
def remove_bracket_class(self, class_name: str):
|
||||||
|
"""Remueve una clase de la sintaxis con corchetes"""
|
||||||
|
self.BRACKET_CLASSES.discard(class_name)
|
||||||
|
if self.debug:
|
||||||
|
print(f"➖ Removida bracket class: {class_name}")
|
||||||
|
|
||||||
|
def get_bracket_classes(self) -> Set[str]:
|
||||||
|
"""Retorna el set actual de clases con sintaxis de corchetes"""
|
||||||
|
return self.BRACKET_CLASSES.copy()
|
||||||
|
|
||||||
def parse_line(self, code_line: str) -> Tuple[str, str]:
|
def parse_line(self, code_line: str) -> Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
|
@ -144,9 +197,19 @@ class BracketParser:
|
||||||
def _transform_brackets(self, line: str) -> str:
|
def _transform_brackets(self, line: str) -> str:
|
||||||
"""
|
"""
|
||||||
Transforma sintaxis Class[args] → Class("args") y maneja métodos
|
Transforma sintaxis Class[args] → Class("args") y maneja métodos
|
||||||
|
VERSIÓN DINÁMICA que usa las clases registradas
|
||||||
"""
|
"""
|
||||||
# Pattern principal: ClassName[contenido]
|
# Crear pattern dinámicamente basado en clases registradas
|
||||||
pattern = r'(\b(?:' + '|'.join(self.BRACKET_CLASSES) + r')\b)\[([^\]]*)\]'
|
if not self.BRACKET_CLASSES:
|
||||||
|
return line # No hay clases registradas
|
||||||
|
|
||||||
|
# Pattern principal: ClassName[contenido] usando clases dinámicas
|
||||||
|
bracket_classes_pattern = '|'.join(re.escape(cls) for cls in self.BRACKET_CLASSES)
|
||||||
|
pattern = rf'(\b(?:{bracket_classes_pattern})\b)\[([^\]]*)\]'
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
print(f"🔍 Usando pattern: {pattern}")
|
||||||
|
print(f"🔧 Clases registradas: {self.BRACKET_CLASSES}")
|
||||||
|
|
||||||
def replace_match(match):
|
def replace_match(match):
|
||||||
class_name = match.group(1)
|
class_name = match.group(1)
|
||||||
|
@ -169,6 +232,7 @@ class BracketParser:
|
||||||
processed_args.append(f'"{escaped_arg}"')
|
processed_args.append(f'"{escaped_arg}"')
|
||||||
|
|
||||||
return f'{class_name}({", ".join(processed_args)})'
|
return f'{class_name}({", ".join(processed_args)})'
|
||||||
|
|
||||||
# Aplicar transformación repetidamente hasta que no haya más cambios
|
# Aplicar transformación repetidamente hasta que no haya más cambios
|
||||||
transformed = line
|
transformed = line
|
||||||
while True:
|
while True:
|
||||||
|
@ -183,6 +247,25 @@ 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"""
|
||||||
|
@ -227,20 +310,20 @@ class EquationDetector:
|
||||||
|
|
||||||
|
|
||||||
# Funciones de utilidad para testing
|
# Funciones de utilidad para testing
|
||||||
def test_bracket_parser():
|
def test_bracket_parser_with_types():
|
||||||
"""Función de testing para el bracket parser"""
|
"""Función de testing para el bracket parser con tipos"""
|
||||||
parser = BracketParser()
|
print("🧪 Test BracketParser con Type Registry")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Crear parser con registro de tipos
|
||||||
|
parser = BracketParser(use_type_registry=True)
|
||||||
parser.debug = True
|
parser.debug = True
|
||||||
|
|
||||||
test_cases = [
|
print(f"🔧 Clases de corchetes disponibles: {parser.get_bracket_classes()}")
|
||||||
# Sintaxis con corchetes
|
print()
|
||||||
("Hex[FF]", 'Hex("FF")', "bracket_transform"),
|
|
||||||
("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"),
|
|
||||||
("IP4[10.0.0.5;255.255.0.0]", 'IP4("10.0.0.5", "255.255.0.0")', "bracket_transform"),
|
|
||||||
("IP4[192.168.1.1/24].NetworkAddress[]", 'IP4("192.168.1.1/24").NetworkAddress()', "bracket_transform"),
|
|
||||||
("Bin[1010]", 'Bin("1010")', "bracket_transform"),
|
|
||||||
|
|
||||||
|
# Test casos básicos (que funcionen sin importar qué tipos estén disponibles)
|
||||||
|
basic_test_cases = [
|
||||||
# Atajos solve
|
# Atajos solve
|
||||||
("x=?", "solve(x)", "solve_shortcut"),
|
("x=?", "solve(x)", "solve_shortcut"),
|
||||||
("variable_name=?", "solve(variable_name)", "solve_shortcut"),
|
("variable_name=?", "solve(variable_name)", "solve_shortcut"),
|
||||||
|
@ -248,32 +331,67 @@ def test_bracket_parser():
|
||||||
# Asignaciones
|
# Asignaciones
|
||||||
("z = 5", '_assign_variable("z", 5)', "assignment"),
|
("z = 5", '_assign_variable("z", 5)', "assignment"),
|
||||||
("w = z**2 + 3", '_assign_variable("w", z**2 + 3)', "assignment"),
|
("w = z**2 + 3", '_assign_variable("w", z**2 + 3)', "assignment"),
|
||||||
("result = Hex[FF]", '_assign_variable("result", Hex("FF"))', "assignment"),
|
|
||||||
|
|
||||||
# Ecuaciones standalone
|
# Ecuaciones standalone
|
||||||
("x + 2 = 5", '_add_equation("x + 2 = 5")', "equation"),
|
("x + 2 = 5", '_add_equation("x + 2 = 5")', "equation"),
|
||||||
("3*a + b = 10", '_add_equation("3*a + b = 10")', "equation"),
|
("3*a + b = 10", '_add_equation("3*a + b = 10")', "equation"),
|
||||||
("x > 5", "x > 5", "expression"), # Comparación válida de Python
|
|
||||||
("a == b", "a == b", "expression"), # Comparación válida de Python
|
|
||||||
|
|
||||||
# NO ecuaciones
|
|
||||||
("result = solve(x + 2, x)", '_assign_variable("result", solve(x + 2, x))', "assignment"), # Asignación Python
|
|
||||||
("2 + 3", "2 + 3", "expression"), # Expresión simple
|
|
||||||
("sin(pi/2)", "sin(pi/2)", "expression"), # Función
|
|
||||||
|
|
||||||
# Expresiones normales
|
# Expresiones normales
|
||||||
("x + 2*y", "x + 2*y", "expression"),
|
("x + 2*y", "x + 2*y", "expression"),
|
||||||
("diff(x**2, x)", "diff(x**2, x)", "expression"),
|
("diff(x**2, x)", "diff(x**2, x)", "expression"),
|
||||||
]
|
]
|
||||||
|
|
||||||
print("=== Test Bracket Parser ===")
|
# Test casos de sintaxis con corchetes (dinámico)
|
||||||
for test_input, expected_result, expected_info in test_cases:
|
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)
|
result, info = parser.parse_line(test_input)
|
||||||
status = "✅" if result == expected_result and info == expected_info else "❌"
|
status = "✅" if result == expected_result and info == expected_info else "❌"
|
||||||
print(f"{status} '{test_input}' → '{result}' ({info})")
|
print(f"{status} '{test_input}' → '{result}' ({info})")
|
||||||
if result != expected_result or info != expected_info:
|
if result != expected_result or info != expected_info:
|
||||||
print(f" Esperado: '{expected_result}' ({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__":
|
if __name__ == "__main__":
|
||||||
test_bracket_parser()
|
test_bracket_parser_with_types()
|
|
@ -0,0 +1,299 @@
|
||||||
|
"""
|
||||||
|
Sistema de auto-descubrimiento y registro de clases personalizadas
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import importlib.util
|
||||||
|
import inspect
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Tuple, Dict, Any, Type, Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Configurar logging para debugging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class TypeRegistry:
|
||||||
|
"""
|
||||||
|
Sistema centralizado para el registro automático de clases personalizadas
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, types_directory: str = "custom_types"):
|
||||||
|
self.types_directory = Path(types_directory)
|
||||||
|
self.registered_classes: Dict[str, Type] = {}
|
||||||
|
self.bracket_classes: set = set()
|
||||||
|
self.base_context: Dict[str, Any] = {}
|
||||||
|
self.helper_functions: List[callable] = []
|
||||||
|
|
||||||
|
def discover_and_register_all(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Descubre y registra todas las clases en el directorio de tipos
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict con toda la información registrada
|
||||||
|
"""
|
||||||
|
if not self.types_directory.exists():
|
||||||
|
logger.warning(f"Directorio de tipos no encontrado: {self.types_directory}")
|
||||||
|
return self._get_registry_info()
|
||||||
|
|
||||||
|
# Limpiar registros previos
|
||||||
|
self._clear_registries()
|
||||||
|
|
||||||
|
# Escanear archivos *_type.py
|
||||||
|
type_files = list(self.types_directory.glob("*_type.py"))
|
||||||
|
|
||||||
|
for type_file in type_files:
|
||||||
|
try:
|
||||||
|
self._process_type_file(type_file)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error procesando {type_file}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"Registro completado: {len(self.registered_classes)} clases encontradas")
|
||||||
|
return self._get_registry_info()
|
||||||
|
|
||||||
|
def _clear_registries(self):
|
||||||
|
"""Limpia todos los registros"""
|
||||||
|
self.registered_classes.clear()
|
||||||
|
self.bracket_classes.clear()
|
||||||
|
self.base_context.clear()
|
||||||
|
self.helper_functions.clear()
|
||||||
|
|
||||||
|
def _process_type_file(self, type_file: Path):
|
||||||
|
"""Procesa un archivo de tipo individual"""
|
||||||
|
module_name = type_file.stem
|
||||||
|
|
||||||
|
# Importar dinámicamente el módulo
|
||||||
|
spec = importlib.util.spec_from_file_location(module_name, type_file)
|
||||||
|
if not spec or not spec.loader:
|
||||||
|
logger.error(f"No se pudo cargar spec para {type_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
# Buscar función de registro
|
||||||
|
if hasattr(module, 'register_classes_in_module'):
|
||||||
|
class_info_list = module.register_classes_in_module()
|
||||||
|
self._register_classes_from_info(class_info_list, module_name)
|
||||||
|
else:
|
||||||
|
# Fallback: buscar clases automáticamente
|
||||||
|
logger.warning(f"{type_file} no tiene register_classes_in_module(), usando auto-detección")
|
||||||
|
self._auto_detect_classes(module, module_name)
|
||||||
|
|
||||||
|
def _register_classes_from_info(self, class_info_list: List[Tuple], module_name: str):
|
||||||
|
"""
|
||||||
|
Registra clases basándose en la información proporcionada
|
||||||
|
|
||||||
|
class_info_list: Lista de tuplas (nombre_publico, clase_objeto, categoria, [opciones])
|
||||||
|
"""
|
||||||
|
for class_info in class_info_list:
|
||||||
|
try:
|
||||||
|
if len(class_info) >= 3:
|
||||||
|
name, class_obj, category = class_info[:3]
|
||||||
|
options = class_info[3] if len(class_info) > 3 else {}
|
||||||
|
else:
|
||||||
|
logger.error(f"Formato incorrecto en {module_name}: {class_info}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._register_single_class(name, class_obj, category, options, module_name)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error registrando clase en {module_name}: {e}")
|
||||||
|
|
||||||
|
def _register_single_class(self, name: str, class_obj: Type, category: str,
|
||||||
|
options: Dict, module_name: str):
|
||||||
|
"""Registra una clase individual"""
|
||||||
|
|
||||||
|
# Registro básico
|
||||||
|
self.registered_classes[name] = class_obj
|
||||||
|
|
||||||
|
# Añadir al contexto base (siempre)
|
||||||
|
self.base_context[name] = class_obj
|
||||||
|
|
||||||
|
# Añadir versión en minúsculas si se especifica
|
||||||
|
if options.get('add_lowercase', True):
|
||||||
|
self.base_context[name.lower()] = class_obj
|
||||||
|
|
||||||
|
# Registrar en bracket_classes si es apropiado
|
||||||
|
if category in ['ClassBase', 'SympyClassBase'] or options.get('supports_brackets', False):
|
||||||
|
self.bracket_classes.add(name)
|
||||||
|
|
||||||
|
# Registrar función Helper si existe
|
||||||
|
if hasattr(class_obj, 'Helper') and callable(class_obj.Helper):
|
||||||
|
self.helper_functions.append(class_obj.Helper)
|
||||||
|
|
||||||
|
logger.debug(f"Registrada: {name} ({category}) desde {module_name}")
|
||||||
|
|
||||||
|
def _auto_detect_classes(self, module, module_name: str):
|
||||||
|
"""Auto-detección de clases cuando no hay función de registro"""
|
||||||
|
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||||
|
if name.startswith('Class_') or name.endswith('Mask'):
|
||||||
|
# Detectar categoría
|
||||||
|
category = "SympyClassBase" if hasattr(obj, '_sympystr') else "ClassBase"
|
||||||
|
|
||||||
|
# Usar nombre sin prefijo Class_
|
||||||
|
public_name = name.replace('Class_', '') if name.startswith('Class_') else name
|
||||||
|
|
||||||
|
self._register_single_class(public_name, obj, category, {}, module_name)
|
||||||
|
|
||||||
|
def _get_registry_info(self) -> Dict[str, Any]:
|
||||||
|
"""Retorna información completa del registro"""
|
||||||
|
return {
|
||||||
|
'base_context': self.base_context.copy(),
|
||||||
|
'bracket_classes': self.bracket_classes.copy(),
|
||||||
|
'helper_functions': self.helper_functions.copy(),
|
||||||
|
'registered_classes': self.registered_classes.copy(),
|
||||||
|
'class_count': len(self.registered_classes),
|
||||||
|
'bracket_count': len(self.bracket_classes)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_base_context(self) -> Dict[str, Any]:
|
||||||
|
"""Retorna contexto base para el motor de evaluación"""
|
||||||
|
return self.base_context.copy()
|
||||||
|
|
||||||
|
def get_bracket_classes(self) -> set:
|
||||||
|
"""Retorna set de clases que soportan sintaxis con corchetes"""
|
||||||
|
return self.bracket_classes.copy()
|
||||||
|
|
||||||
|
def get_helper_functions(self) -> List[callable]:
|
||||||
|
"""Retorna lista de funciones Helper"""
|
||||||
|
return self.helper_functions.copy()
|
||||||
|
|
||||||
|
|
||||||
|
# Instancia global del registro
|
||||||
|
_global_registry = TypeRegistry()
|
||||||
|
|
||||||
|
|
||||||
|
def discover_and_register_types(types_directory: str = "custom_types") -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Función principal para descubrir y registrar todos los tipos
|
||||||
|
|
||||||
|
Args:
|
||||||
|
types_directory: Directorio donde están los archivos *_type.py
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict con información del registro
|
||||||
|
"""
|
||||||
|
global _global_registry
|
||||||
|
_global_registry = TypeRegistry(types_directory)
|
||||||
|
return _global_registry.discover_and_register_all()
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_base_context() -> Dict[str, Any]:
|
||||||
|
"""Obtiene el contexto base con todas las clases registradas"""
|
||||||
|
return _global_registry.get_base_context()
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_bracket_classes() -> set:
|
||||||
|
"""Obtiene las clases que soportan sintaxis con corchetes"""
|
||||||
|
return _global_registry.get_bracket_classes()
|
||||||
|
|
||||||
|
|
||||||
|
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