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
|
||||
import re
|
||||
|
@ -52,4 +52,19 @@ class Class_Bin(SympyClassBase):
|
|||
|
||||
def toDecimal(self):
|
||||
"""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
|
||||
import re
|
||||
|
@ -63,4 +64,23 @@ class Class_Chr(SympyClassBase):
|
|||
|
||||
def toBin(self):
|
||||
"""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
|
||||
import re
|
||||
|
@ -53,4 +53,18 @@ class Class_Dec(SympyClassBase):
|
|||
|
||||
def toBin(self):
|
||||
"""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
|
||||
import re
|
||||
|
@ -52,4 +52,18 @@ class Class_Hex(SympyClassBase):
|
|||
|
||||
def toDecimal(self):
|
||||
"""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"
|
||||
}),
|
||||
]
|
|
@ -875,4 +875,42 @@ class Class_IP4(SympyClassBase):
|
|||
"private_count": private_count,
|
||||
"public_count": len(ip_list) - private_count,
|
||||
"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]"
|
||||
]
|
||||
}
|
515
main_calc_app.py
515
main_calc_app.py
|
@ -1,44 +1,32 @@
|
|||
"""
|
||||
Calculadora MAV CAS Híbrida - Aplicación principal
|
||||
VERSIÓN ADAPTADA AL NUEVO SISTEMA DE TIPOS
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import scrolledtext, messagebox, Menu, filedialog
|
||||
import tkinter.font as tkFont
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path # Added for robust path handling
|
||||
from pathlib import Path
|
||||
import threading
|
||||
from typing import List, Dict, Any, Optional
|
||||
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 sympy_Base import SympyClassBase
|
||||
from tl_popup import InteractiveResultManager, PlotResult # <--- Asegurar que PlotResult se importa
|
||||
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
|
||||
from tl_popup import InteractiveResultManager, PlotResult
|
||||
from type_registry import get_registered_helper_functions, get_registered_base_context
|
||||
import sympy
|
||||
from sympy_helper import SympyTools as SympyHelper
|
||||
|
||||
|
||||
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"
|
||||
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):
|
||||
self.root = root
|
||||
self.root.title("Calculadora MAV - CAS Híbrido")
|
||||
|
@ -51,10 +39,13 @@ class HybridCalculatorApp:
|
|||
# Configurar ícono
|
||||
self._setup_icon()
|
||||
|
||||
# Componentes principales
|
||||
self.engine = HybridEvaluationEngine()
|
||||
# ========== COMPONENTES PRINCIPALES CON NUEVO SISTEMA ==========
|
||||
self.engine = HybridEvaluationEngine(auto_discover_types=True)
|
||||
self.interactive_manager = None # Se inicializa después de crear widgets
|
||||
|
||||
# ========== HELPERS DINÁMICOS DEL REGISTRO ==========
|
||||
self._setup_dynamic_helpers()
|
||||
|
||||
# Estado de la aplicación
|
||||
self._debounce_job = None
|
||||
self._syncing_yview = False
|
||||
|
@ -69,25 +60,85 @@ class HybridCalculatorApp:
|
|||
# Configurar eventos de cierre
|
||||
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):
|
||||
"""Configura el ícono de la aplicación"""
|
||||
try:
|
||||
# Construct path relative to this script file (main_calc_app.py)
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
icon_path = script_dir / "icon.png"
|
||||
|
||||
if not icon_path.is_file():
|
||||
print(f"Advertencia: Archivo de ícono no encontrado en '{icon_path}'.")
|
||||
# Optionally, set a default Tk icon or simply return
|
||||
return
|
||||
|
||||
self.app_icon = tk.PhotoImage(file=str(icon_path))
|
||||
self.root.iconphoto(True, self.app_icon)
|
||||
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}")
|
||||
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}")
|
||||
|
||||
def _load_settings(self) -> Dict[str, Any]:
|
||||
|
@ -222,7 +273,15 @@ class HybridCalculatorApp:
|
|||
cas_menu.add_separator()
|
||||
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)
|
||||
menubar.add_cascade(label="Ayuda", menu=help_menu)
|
||||
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("info", foreground="#ffcb6b")
|
||||
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")
|
||||
|
||||
# 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("bin", foreground="#4fc3f7")
|
||||
self.output_text.tag_configure("ip", foreground="#fff176")
|
||||
self.output_text.tag_configure("date", foreground="#ff8a80")
|
||||
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"))
|
||||
|
||||
def on_key_release(self, event=None):
|
||||
|
@ -285,7 +344,7 @@ class HybridCalculatorApp:
|
|||
if 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:
|
||||
self._handle_dot_autocomplete()
|
||||
|
||||
|
@ -293,7 +352,7 @@ class HybridCalculatorApp:
|
|||
self._debounce_job = self.root.after(300, self._evaluate_and_update)
|
||||
|
||||
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()
|
||||
cursor_index_str = self.input_text.index(tk.INSERT)
|
||||
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()
|
||||
|
||||
# 1. Determinar si es un popup GLOBAL
|
||||
# 1. Determinar si es un popup GLOBAL (usando contexto dinámico)
|
||||
if not stripped_text_before_dot:
|
||||
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
|
||||
suggestions = []
|
||||
|
||||
# MODIFIED: Get suggestions from HybridEvaluationEngine's base_context
|
||||
if hasattr(self.engine, 'base_context') and isinstance(self.engine.base_context, dict):
|
||||
for name, class_or_func in self.engine.base_context.items():
|
||||
# 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).
|
||||
if name[0].isupper(): # Prioritize capitalized names for classes/main functions
|
||||
# ========== USAR CONTEXTO DINÁMICO DEL REGISTRO ==========
|
||||
try:
|
||||
dynamic_context = get_registered_base_context()
|
||||
|
||||
for name, class_or_func in dynamic_context.items():
|
||||
if name[0].isupper(): # Prioritizar nombres capitalizados
|
||||
hint = f"Tipo o función: {name}"
|
||||
if hasattr(class_or_func, '__doc__') and class_or_func.__doc__:
|
||||
first_line_doc = class_or_func.__doc__.strip().split('\n')[0]
|
||||
hint = f"{name} - {first_line_doc}"
|
||||
elif hasattr(class_or_func, 'Helper'): # Usar Helper si está disponible
|
||||
# Para obtener un hint del Helper, necesitamos llamarlo.
|
||||
# Algunas clases Helper esperan el nombre de la clase.
|
||||
elif hasattr(class_or_func, 'Helper'):
|
||||
try:
|
||||
helper_text = class_or_func.Helper(name) # Pasar el nombre de la clase
|
||||
helper_text = class_or_func.Helper(name)
|
||||
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:
|
||||
print(f"DEBUG: Error calling Helper for {name}: {e_helper}")
|
||||
pass # Mantener el hint genérico
|
||||
pass
|
||||
suggestions.append((name, hint))
|
||||
|
||||
except Exception as e:
|
||||
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 (si no están ya en base_context de forma similar)
|
||||
# Considerar si SympyHelper.PopupFunctionList() devuelve cosas ya cubiertas.
|
||||
# Añadir funciones de SympyHelper
|
||||
try:
|
||||
sympy_functions = SympyHelper.PopupFunctionList()
|
||||
if sympy_functions:
|
||||
# Evitar duplicados si los nombres ya están de base_context
|
||||
current_suggestion_names = {s[0] for s in suggestions}
|
||||
for fname, fhint in sympy_functions:
|
||||
if fname not in current_suggestion_names:
|
||||
|
@ -350,25 +410,19 @@ class HybridCalculatorApp:
|
|||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
||||
|
||||
if suggestions:
|
||||
# Ordenar alfabéticamente para consistencia
|
||||
suggestions.sort(key=lambda x: x[0])
|
||||
self._show_autocomplete_popup(suggestions, is_global_popup=True)
|
||||
return
|
||||
|
||||
# 2. Es un popup de OBJETO. Extraer la expresión del objeto.
|
||||
# 2. Es un popup de OBJETO
|
||||
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_]*)(?:\[[^\]]*\])?)*)$"
|
||||
match = re.search(obj_expr_regex, stripped_text_before_dot)
|
||||
|
||||
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:
|
||||
# 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
|
||||
# Validación simple para evitar evaluar cosas que claramente no son objetos
|
||||
if not obj_expr_str_candidate or \
|
||||
not re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", obj_expr_str_candidate) or \
|
||||
obj_expr_str_candidate.endswith(("+", "-", "*", "/", "(", ",")):
|
||||
|
@ -378,7 +432,7 @@ class HybridCalculatorApp:
|
|||
obj_expr_str = obj_expr_str_candidate
|
||||
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.")
|
||||
return
|
||||
|
||||
|
@ -395,20 +449,18 @@ class HybridCalculatorApp:
|
|||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList(): {e}")
|
||||
return
|
||||
|
||||
# 4. Preprocesar con BracketParser para sintaxis Clase[arg] y metodo[]
|
||||
# Es importante transformar obj_expr_str ANTES de pasarlo a eval().
|
||||
if '[' in obj_expr_str: # Optimización: solo llamar si hay corchetes
|
||||
# 4. Preprocesar con BracketParser
|
||||
if '[' in 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)
|
||||
if obj_expr_str != original_for_debug:
|
||||
print(f"DEBUG: Preprocessed by BracketParser: '{original_for_debug}' -> '{obj_expr_str}'")
|
||||
|
||||
# 5. Evaluar la expresión del objeto
|
||||
eval_context = self.engine._get_full_context() if hasattr(self.engine, '_get_full_context') else {}
|
||||
# 5. Evaluar la expresión del objeto (usando contexto dinámico)
|
||||
eval_context = self.engine._get_full_context()
|
||||
obj = None
|
||||
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.")
|
||||
return
|
||||
print(f"DEBUG: Attempting to eval: '{obj_expr_str}'")
|
||||
|
@ -423,11 +475,9 @@ class HybridCalculatorApp:
|
|||
methods = obj.PopupFunctionList()
|
||||
if methods:
|
||||
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):
|
||||
# suggestions: lista de tuplas (nombre, hint)
|
||||
"""Muestra popup de autocompletado (sin cambios)"""
|
||||
cursor_bbox = self.input_text.bbox(tk.INSERT)
|
||||
if not cursor_bbox:
|
||||
return
|
||||
|
@ -449,35 +499,29 @@ class HybridCalculatorApp:
|
|||
if suggestions:
|
||||
self._autocomplete_listbox.select_set(0)
|
||||
self._autocomplete_listbox.pack(expand=True, fill=tk.BOTH)
|
||||
self._autocomplete_listbox.bind("<Return>", self._on_autocomplete_select)
|
||||
self._autocomplete_listbox.bind("<Tab>", 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>", 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("<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.bind("<Up>", 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.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
|
||||
width = max(15, min(max_len + 10, 50))
|
||||
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]
|
||||
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)
|
||||
else:
|
||||
self._close_autocomplete_popup()
|
||||
|
||||
def _navigate_autocomplete(self, event, direction):
|
||||
"""Navegación en autocomplete (sin cambios)"""
|
||||
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
||||
return "break"
|
||||
current_selection = self._autocomplete_listbox.curselection()
|
||||
|
@ -495,6 +539,7 @@ class HybridCalculatorApp:
|
|||
return "break"
|
||||
|
||||
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:
|
||||
return "break"
|
||||
selection = self._autocomplete_listbox.curselection()
|
||||
|
@ -503,49 +548,31 @@ class HybridCalculatorApp:
|
|||
return "break"
|
||||
|
||||
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()
|
||||
|
||||
if is_global:
|
||||
# Eliminar el punto que activó el popup y luego insertar el nombre
|
||||
cursor_pos_str = self.input_text.index(tk.INSERT) # Posición actual (después del punto)
|
||||
cursor_pos_str = self.input_text.index(tk.INSERT)
|
||||
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_index_str = f"{line_num}.{dot_pos_on_line}"
|
||||
|
||||
self.input_text.delete(dot_index_str)
|
||||
|
||||
# Insertar el nombre de la función/clase seguido de "()"
|
||||
insert_text = item_name + "()"
|
||||
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")
|
||||
else:
|
||||
# Comportamiento existente para métodos de objeto
|
||||
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.input_text.focus_set()
|
||||
self.on_key_release() # Trigger re-evaluation
|
||||
self.on_key_release()
|
||||
return "break"
|
||||
|
||||
def _close_autocomplete_popup(self):
|
||||
"""Cierra popup de autocomplete (sin cambios)"""
|
||||
if hasattr(self, '_autocomplete_popup') and self._autocomplete_popup:
|
||||
self._autocomplete_popup.destroy()
|
||||
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:
|
||||
self._autocomplete_listbox = None
|
||||
|
||||
|
@ -592,7 +619,6 @@ class HybridCalculatorApp:
|
|||
if result.is_error:
|
||||
ayuda = self.obtener_ayuda(result.original_line)
|
||||
if ayuda:
|
||||
# Mostrar ayuda en un solo renglón, truncando si es necesario
|
||||
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
|
||||
if len(ayuda_linea) > 120:
|
||||
ayuda_linea = ayuda_linea[:117] + "..."
|
||||
|
@ -608,8 +634,8 @@ class HybridCalculatorApp:
|
|||
else:
|
||||
# Resultado normal
|
||||
if result.result is not None:
|
||||
# Determinar tag basado en tipo
|
||||
tag = self._get_result_tag(result.result)
|
||||
# Determinar tag basado en tipo (DINÁMICO)
|
||||
tag = self._get_result_tag_dynamic(result.result)
|
||||
|
||||
# Verificar si es resultado interactivo
|
||||
if self.interactive_manager and result.is_interactive:
|
||||
|
@ -623,90 +649,122 @@ class HybridCalculatorApp:
|
|||
|
||||
# Añadir pista de clase para el resultado principal
|
||||
primary_result_object = result.result
|
||||
if not isinstance(primary_result_object, PlotResult): # PlotResult ya tiene su propio formato
|
||||
class_display_name = ""
|
||||
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 not isinstance(primary_result_object, PlotResult):
|
||||
class_display_name = self._get_class_display_name_dynamic(primary_result_object)
|
||||
if class_display_name:
|
||||
output_parts.append(("class_hint", f"[{class_display_name}]"))
|
||||
|
||||
# Mostrar evaluación numérica si existe
|
||||
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
|
||||
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
|
||||
|
||||
def _get_result_tag(self, result: Any) -> str:
|
||||
"""Determina el tag de color para un resultado"""
|
||||
if isinstance(result, Class_Hex):
|
||||
return "hex"
|
||||
elif isinstance(result, Class_Bin):
|
||||
return "bin"
|
||||
elif isinstance(result, Class_IP4):
|
||||
return "ip"
|
||||
elif isinstance(result, Class_Chr):
|
||||
return "chr_type"
|
||||
def _get_result_tag_dynamic(self, result: Any) -> str:
|
||||
"""Determina el tag de color para un resultado - VERSIÓN DINÁMICA"""
|
||||
# Obtener clases registradas dinámicamente
|
||||
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"
|
||||
elif name.lower() == "bin":
|
||||
return "bin"
|
||||
elif name.lower() in ["ip4", "ip"]:
|
||||
return "ip"
|
||||
elif name.lower() == "chr":
|
||||
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):
|
||||
return "symbolic"
|
||||
else:
|
||||
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]]):
|
||||
"""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.delete("1.0", tk.END)
|
||||
|
||||
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] == ""):
|
||||
pass
|
||||
else:
|
||||
# Mostrar partes de la línea
|
||||
for part_idx, (tag, content) in enumerate(line_parts):
|
||||
if not content: # Omitir contenido vacío
|
||||
if not content:
|
||||
continue
|
||||
|
||||
# Determinar si se necesita un separador antes de esta parte
|
||||
if part_idx > 0:
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
|
||||
# Añadir nueva línea excepto para la última línea
|
||||
if line_idx < len(output_data) - 1:
|
||||
self.output_text.insert(tk.END, "\n")
|
||||
|
||||
|
@ -753,7 +811,8 @@ class HybridCalculatorApp:
|
|||
finally:
|
||||
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):
|
||||
"""Inicia nueva sesión"""
|
||||
self.clear_input()
|
||||
|
@ -838,16 +897,42 @@ class HybridCalculatorApp:
|
|||
self.root.clipboard_append(content)
|
||||
|
||||
def insert_example(self):
|
||||
"""Inserta código de ejemplo"""
|
||||
example = """# Calculadora MAV - CAS Híbrido
|
||||
"""Inserta código de ejemplo - ACTUALIZADO CON TIPOS DINÁMICOS"""
|
||||
# Obtener tipos disponibles dinámicamente
|
||||
try:
|
||||
available_types = self.engine.get_available_types()
|
||||
registered_classes = available_types.get('registered_classes', {})
|
||||
except:
|
||||
registered_classes = {}
|
||||
|
||||
# Crear ejemplo base
|
||||
example = """# Calculadora MAV - CAS Híbrido con Sistema de Tipos Dinámico
|
||||
# Sintaxis nueva con corchetes
|
||||
|
||||
# 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
|
||||
diff(x**2 + sin(x), x)
|
||||
integrate(x**2, x)
|
||||
|
@ -870,6 +955,7 @@ plot(sin(x), (x, -2*pi, 2*pi))
|
|||
# Matrices
|
||||
Matrix([[1, 2], [3, 4]])
|
||||
"""
|
||||
|
||||
self.input_text.delete("1.0", tk.END)
|
||||
self.input_text.insert("1.0", example)
|
||||
self._evaluate_and_update()
|
||||
|
@ -963,13 +1049,64 @@ Matrix([[1, 2], [3, 4]])
|
|||
except Exception as 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):
|
||||
"""Muestra guía rápida"""
|
||||
"""Muestra guía rápida - ACTUALIZADA"""
|
||||
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
|
||||
- IP4[192.168.1.1/24] en lugar de IP4("192.168.1.1/24")
|
||||
- Hex[FF], Bin[1010], Dec[10.5], Chr[A]
|
||||
- Sintaxis: Tipo[valor] en lugar de Tipo("valor")
|
||||
- Ejemplos: Hex[FF], Bin[1010], Dec[10.5], Chr[A]
|
||||
- Use menú Tipos → Información de tipos para ver tipos disponibles
|
||||
|
||||
## Ecuaciones Automáticas
|
||||
- x**2 + 2*x = 8 (detectado automáticamente)
|
||||
|
@ -990,26 +1127,29 @@ Matrix([[1, 2], [3, 4]])
|
|||
- Todas las variables son símbolos SymPy
|
||||
- x = 5 crea Symbol('x') con valor 5
|
||||
- Evaluación simbólica + numérica automática
|
||||
|
||||
## Autocompletado Dinámico
|
||||
- Escriba "." después de cualquier objeto para ver métodos
|
||||
- El sistema usa los tipos registrados automáticamente
|
||||
"""
|
||||
|
||||
self._show_help_window("Guía Rápida", guide)
|
||||
|
||||
def show_syntax_help(self):
|
||||
"""Muestra ayuda de sintaxis"""
|
||||
"""Muestra ayuda de sintaxis - ACTUALIZADA"""
|
||||
syntax = """# Sintaxis del CAS Híbrido
|
||||
|
||||
## Clases Especializadas (solo corchetes)
|
||||
IP4[dirección/prefijo] # IP4[192.168.1.1/24]
|
||||
Hex[valor] # Hex[FF], Hex[255]
|
||||
Bin[valor] # Bin[1010], Bin[10]
|
||||
Dec[valor] # Dec[10.5], Dec[10]
|
||||
Chr[carácter] # Chr[A], Chr[Hello]
|
||||
## Sistema de Tipos Dinámico
|
||||
Los tipos se detectan automáticamente desde custom_types/
|
||||
Use menú Tipos → Información de tipos para ver tipos disponibles
|
||||
|
||||
## Métodos Disponibles
|
||||
IP4[...].NetworkAddress[]
|
||||
IP4[...].BroadcastAddress[]
|
||||
IP4[...].Nodes()
|
||||
Hex[...].toDecimal()
|
||||
## Sintaxis con Corchetes (Dinámica)
|
||||
Tipo[valor] # Sintaxis general
|
||||
Tipo[arg1; arg2] # Múltiples argumentos
|
||||
|
||||
## 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)
|
||||
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)
|
||||
|
||||
def show_sympy_functions(self):
|
||||
"""Muestra funciones SymPy disponibles"""
|
||||
"""Muestra funciones SymPy disponibles (sin cambios)"""
|
||||
functions = """# Funciones SymPy Disponibles
|
||||
|
||||
## Matemáticas Básicas
|
||||
|
@ -1066,20 +1206,28 @@ pi, E, I (imaginario), oo (infinito)
|
|||
self._show_help_window("Funciones SymPy", functions)
|
||||
|
||||
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
|
||||
|
||||
Versión: 2.0
|
||||
Motor: SymPy + Clases Especializadas
|
||||
Versión: 2.1 (Sistema de Tipos Dinámico)
|
||||
Motor: SymPy + Auto-descubrimiento de Tipos
|
||||
|
||||
Características:
|
||||
• Motor algebraico completo (SymPy)
|
||||
• Sistema de tipos dinámico y extensible
|
||||
• Sintaxis simplificada con corchetes
|
||||
• Detección automática de ecuaciones
|
||||
• Resultados interactivos clickeables
|
||||
• Tipos especializados (IP4, Hex, Bin, etc.)
|
||||
• Auto-descubrimiento de tipos en custom_types/
|
||||
• Variables SymPy puras
|
||||
• 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
|
||||
con soporte especializado para redes,
|
||||
|
@ -1147,10 +1295,15 @@ programación y análisis numérico.
|
|||
self.root.destroy()
|
||||
|
||||
def obtener_ayuda(self, input_str):
|
||||
"""Obtiene ayuda usando helpers dinámicos"""
|
||||
for helper in self.HELPERS:
|
||||
ayuda = helper(input_str)
|
||||
if ayuda:
|
||||
return ayuda
|
||||
try:
|
||||
ayuda = helper(input_str)
|
||||
if ayuda:
|
||||
return ayuda
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error en helper: {e}")
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
|
@ -1168,4 +1321,4 @@ def main():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
|
@ -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
|
||||
from sympy import symbols, Symbol, sympify, solve, Eq, simplify
|
||||
|
@ -8,36 +8,64 @@ import ast
|
|||
import re
|
||||
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_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:
|
||||
"""
|
||||
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.symbol_table: Dict[str, Any] = {}
|
||||
self.equations: List[sympy.Eq] = []
|
||||
self.last_result = None
|
||||
|
||||
# Contexto base con funciones y clases
|
||||
self._setup_base_context()
|
||||
# Configuración del sistema de tipos
|
||||
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
|
||||
self.debug = False
|
||||
# Configurar contexto base
|
||||
self._setup_base_context()
|
||||
|
||||
|
||||
|
||||
def _setup_base_context(self):
|
||||
"""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 = {
|
||||
'pi': sympy.pi,
|
||||
'e': sympy.E,
|
||||
|
@ -83,24 +111,10 @@ class HybridEvaluationEngine:
|
|||
'plot3d': self._create_plot3d_placeholder,
|
||||
}
|
||||
|
||||
# Clases especializadas
|
||||
specialized_classes = {
|
||||
'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,
|
||||
}
|
||||
# 3. CLASES ESPECIALIZADAS (DESDE AUTO-DESCUBRIMIENTO)
|
||||
specialized_classes = self.registered_types_info.get('base_context', {})
|
||||
|
||||
# Funciones de utilidad
|
||||
# 4. FUNCIONES DE UTILIDAD
|
||||
utility_functions = {
|
||||
'_add_equation': self._add_equation,
|
||||
'_assign_variable': self._assign_variable,
|
||||
|
@ -108,11 +122,64 @@ class HybridEvaluationEngine:
|
|||
'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 = {
|
||||
**math_functions,
|
||||
**specialized_classes,
|
||||
**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):
|
||||
"""Crear placeholder para plots que será manejado por resultados interactivos"""
|
||||
|
@ -127,16 +194,30 @@ class HybridEvaluationEngine:
|
|||
return PlotResult('plot3d', args, kwargs)
|
||||
|
||||
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:
|
||||
return "Ayuda disponible. Use help(función) para ayuda específica."
|
||||
|
||||
# Primero intentar con el objeto directamente
|
||||
if hasattr(obj, '__doc__') and obj.__doc__:
|
||||
return obj.__doc__
|
||||
elif hasattr(obj, 'Helper'):
|
||||
return obj.Helper("")
|
||||
else:
|
||||
return f"No hay ayuda disponible para {obj}"
|
||||
|
||||
# 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}"
|
||||
|
||||
# ========== RESTO DE MÉTODOS EXISTENTES ==========
|
||||
# (Los métodos de evaluación permanecen igual)
|
||||
|
||||
def evaluate_line(self, line: str) -> 'EvaluationResult':
|
||||
"""
|
||||
|
@ -316,7 +397,7 @@ class HybridEvaluationEngine:
|
|||
result = eval(expression, {"__builtins__": {}}, context)
|
||||
|
||||
# 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
|
||||
elif isinstance(result, PlotResult):
|
||||
if self.debug:
|
||||
|
@ -488,55 +569,59 @@ class EvaluationResult:
|
|||
|
||||
|
||||
# Funciones de testing
|
||||
def test_evaluation_engine():
|
||||
"""Test del motor de evaluación"""
|
||||
engine = HybridEvaluationEngine()
|
||||
def test_hybrid_engine_with_types():
|
||||
"""Test del motor de evaluación con sistema de tipos"""
|
||||
print("🧪 Test HybridEvaluationEngine con Type Registry")
|
||||
print("=" * 60)
|
||||
|
||||
# Crear motor con auto-descubrimiento
|
||||
engine = HybridEvaluationEngine(auto_discover_types=True)
|
||||
engine.debug = True
|
||||
|
||||
# Mostrar información de tipos
|
||||
types_info = engine.get_available_types()
|
||||
print(f"📊 Tipos disponibles: {types_info}")
|
||||
print()
|
||||
|
||||
# Test casos básicos
|
||||
test_cases = [
|
||||
# Expresiones básicas
|
||||
"2 + 3",
|
||||
"x + 2",
|
||||
"sin(pi/2)",
|
||||
|
||||
# Sintaxis con corchetes
|
||||
"Hex[FF]",
|
||||
"IP4[192.168.1.1/24]",
|
||||
# Tipo Chr si está disponible
|
||||
"Chr[A]" if "Chr" in types_info['registered_classes'] else "# Chr no disponible",
|
||||
|
||||
# 🧪 PLOTS - Casos específicos para testing
|
||||
"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
|
||||
# Variables y ecuaciones
|
||||
"a = 10",
|
||||
"b = a + 5",
|
||||
"x + 2 = 5",
|
||||
"solve(x + 2 - 5, x)",
|
||||
|
||||
# Funciones avanzadas
|
||||
"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:
|
||||
if test.startswith('#'):
|
||||
print(f"⏭️ {test}")
|
||||
continue
|
||||
|
||||
result = engine.evaluate_line(test)
|
||||
print(f"'{test}' → {result} (type: {result.result_type})")
|
||||
|
||||
# 🔍 Información adicional para plots
|
||||
if 'plot' in test:
|
||||
print(f" 🎯 Es interactivo: {result.is_interactive}")
|
||||
if isinstance(result.result, PlotResult):
|
||||
print(f" 📊 PlotResult confirmado: {result.result.plot_type}")
|
||||
print(f"✅ '{test}' → {result} (type: {result.result_type})")
|
||||
|
||||
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__":
|
||||
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 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:
|
||||
"""Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente"""
|
||||
|
||||
# Clases que soportan sintaxis con corchetes
|
||||
BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr', 'IP4Mask'}
|
||||
# Clases base que soportan sintaxis con corchetes (fallback)
|
||||
DEFAULT_BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr', 'IP4Mask'}
|
||||
|
||||
# Operadores de comparación que pueden formar ecuaciones
|
||||
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, use_type_registry: bool = True):
|
||||
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]:
|
||||
"""
|
||||
|
@ -144,9 +197,19 @@ class BracketParser:
|
|||
def _transform_brackets(self, line: str) -> str:
|
||||
"""
|
||||
Transforma sintaxis Class[args] → Class("args") y maneja métodos
|
||||
VERSIÓN DINÁMICA que usa las clases registradas
|
||||
"""
|
||||
# Pattern principal: ClassName[contenido]
|
||||
pattern = r'(\b(?:' + '|'.join(self.BRACKET_CLASSES) + r')\b)\[([^\]]*)\]'
|
||||
# Crear pattern dinámicamente basado en clases registradas
|
||||
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):
|
||||
class_name = match.group(1)
|
||||
|
@ -169,6 +232,7 @@ class BracketParser:
|
|||
processed_args.append(f'"{escaped_arg}"')
|
||||
|
||||
return f'{class_name}({", ".join(processed_args)})'
|
||||
|
||||
# Aplicar transformación repetidamente hasta que no haya más cambios
|
||||
transformed = line
|
||||
while True:
|
||||
|
@ -182,6 +246,25 @@ class BracketParser:
|
|||
transformed = re.sub(method_pattern, r'.\1()', transformed)
|
||||
|
||||
return transformed
|
||||
|
||||
def test_bracket_transformation(self, test_line: str) -> Dict[str, str]:
|
||||
"""Test de transformación de una línea específica"""
|
||||
result = {
|
||||
'input': test_line,
|
||||
'bracket_classes': list(self.BRACKET_CLASSES),
|
||||
'output': None,
|
||||
'parse_info': None,
|
||||
'error': None
|
||||
}
|
||||
|
||||
try:
|
||||
output, parse_info = self.parse_line(test_line)
|
||||
result['output'] = output
|
||||
result['parse_info'] = parse_info
|
||||
except Exception as e:
|
||||
result['error'] = str(e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class EquationDetector:
|
||||
|
@ -227,20 +310,20 @@ class EquationDetector:
|
|||
|
||||
|
||||
# Funciones de utilidad para testing
|
||||
def test_bracket_parser():
|
||||
"""Función de testing para el bracket parser"""
|
||||
parser = BracketParser()
|
||||
def test_bracket_parser_with_types():
|
||||
"""Función de testing para el bracket parser con tipos"""
|
||||
print("🧪 Test BracketParser con Type Registry")
|
||||
print("=" * 50)
|
||||
|
||||
# Crear parser con registro de tipos
|
||||
parser = BracketParser(use_type_registry=True)
|
||||
parser.debug = True
|
||||
|
||||
test_cases = [
|
||||
# Sintaxis con corchetes
|
||||
("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"),
|
||||
|
||||
print(f"🔧 Clases de corchetes disponibles: {parser.get_bracket_classes()}")
|
||||
print()
|
||||
|
||||
# Test casos básicos (que funcionen sin importar qué tipos estén disponibles)
|
||||
basic_test_cases = [
|
||||
# Atajos solve
|
||||
("x=?", "solve(x)", "solve_shortcut"),
|
||||
("variable_name=?", "solve(variable_name)", "solve_shortcut"),
|
||||
|
@ -248,32 +331,67 @@ def test_bracket_parser():
|
|||
# Asignaciones
|
||||
("z = 5", '_assign_variable("z", 5)', "assignment"),
|
||||
("w = z**2 + 3", '_assign_variable("w", z**2 + 3)', "assignment"),
|
||||
("result = Hex[FF]", '_assign_variable("result", Hex("FF"))', "assignment"),
|
||||
|
||||
# Ecuaciones standalone
|
||||
("x + 2 = 5", '_add_equation("x + 2 = 5")', "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
|
||||
("x + 2*y", "x + 2*y", "expression"),
|
||||
("diff(x**2, x)", "diff(x**2, x)", "expression"),
|
||||
]
|
||||
|
||||
print("=== Test Bracket Parser ===")
|
||||
for test_input, expected_result, expected_info in test_cases:
|
||||
# Test casos de sintaxis con corchetes (dinámico)
|
||||
bracket_test_cases = []
|
||||
available_classes = parser.get_bracket_classes()
|
||||
|
||||
if "Chr" in available_classes:
|
||||
bracket_test_cases.extend([
|
||||
("Chr[A]", 'Chr("A")', "bracket_transform"),
|
||||
("Chr[65]", 'Chr("65")', "bracket_transform"),
|
||||
])
|
||||
|
||||
if "Hex" in available_classes:
|
||||
bracket_test_cases.extend([
|
||||
("Hex[FF]", 'Hex("FF")', "bracket_transform"),
|
||||
("Hex[255]", 'Hex("255")', "bracket_transform"),
|
||||
])
|
||||
|
||||
if "IP4" in available_classes:
|
||||
bracket_test_cases.extend([
|
||||
("IP4[192.168.1.1/24]", 'IP4("192.168.1.1/24")', "bracket_transform"),
|
||||
("IP4[192.168.1.1;24]", 'IP4("192.168.1.1", "24")', "bracket_transform"),
|
||||
])
|
||||
|
||||
# Combinar casos de test
|
||||
all_test_cases = basic_test_cases + bracket_test_cases
|
||||
|
||||
print("🔍 Ejecutando casos de prueba:")
|
||||
for test_input, expected_result, expected_info in all_test_cases:
|
||||
result, info = parser.parse_line(test_input)
|
||||
status = "✅" if result == expected_result and info == expected_info else "❌"
|
||||
print(f"{status} '{test_input}' → '{result}' ({info})")
|
||||
if result != expected_result or info != expected_info:
|
||||
print(f" Esperado: '{expected_result}' ({expected_info})")
|
||||
print(f" 💭 Esperado: '{expected_result}' ({expected_info})")
|
||||
|
||||
# Test de añadir/remover clases dinámicamente
|
||||
print(f"\n🔧 Test modificación dinámica de clases:")
|
||||
print(f" Clases iniciales: {len(parser.get_bracket_classes())}")
|
||||
|
||||
parser.add_bracket_class("TestClass")
|
||||
print(f" Después de añadir TestClass: {len(parser.get_bracket_classes())}")
|
||||
|
||||
test_result = parser.test_bracket_transformation("TestClass[example]")
|
||||
print(f" Test TestClass[example]: {test_result['output']}")
|
||||
|
||||
parser.remove_bracket_class("TestClass")
|
||||
print(f" Después de remover TestClass: {len(parser.get_bracket_classes())}")
|
||||
|
||||
# Test recarga
|
||||
print(f"\n🔄 Test recarga:")
|
||||
parser.reload_bracket_classes()
|
||||
print(f" Clases después de recarga: {len(parser.get_bracket_classes())}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_bracket_parser()
|
||||
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