Refactorización del motor de evaluación para utilizar un sistema algebraico puro. Se actualizan las configuraciones de la interfaz y se simplifican las funciones de evaluación. Se añaden patrones de tokenización en el registro de tipos y se mejora la gestión de errores en el motor. Se eliminan configuraciones obsoletas y se optimiza el menú de herramientas.
This commit is contained in:
parent
ece028e837
commit
7d2033c60e
|
@ -1,12 +1,9 @@
|
||||||
|
|
||||||
a = x + 5
|
a = x + 5 / z
|
||||||
x=?
|
|
||||||
solve(x)
|
|
||||||
|
|
||||||
m=t+u * 5
|
z=?
|
||||||
|
|
||||||
t=4
|
a=2
|
||||||
m=3
|
x=3
|
||||||
u=?
|
|
||||||
|
|
||||||
u
|
IP4(10.1.1.2)
|
275
main_calc_app.py
275
main_calc_app.py
|
@ -45,7 +45,7 @@ if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE is None:
|
||||||
|
|
||||||
# ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ==========
|
# ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ==========
|
||||||
# Importar componentes del CAS híbrido con nuevo sistema de tipos
|
# Importar componentes del CAS híbrido con nuevo sistema de tipos
|
||||||
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
from main_evaluation_puro import PureAlgebraicEngine, EvaluationResult
|
||||||
from tl_popup import InteractiveResultManager, PlotResult
|
from tl_popup import InteractiveResultManager, PlotResult
|
||||||
from type_registry import get_registered_helper_functions, get_registered_base_context
|
from type_registry import get_registered_helper_functions, get_registered_base_context
|
||||||
import sympy
|
import sympy
|
||||||
|
@ -69,13 +69,15 @@ class HybridCalculatorApp:
|
||||||
self.root.geometry(self.settings.get("window_geometry", "1000x700"))
|
self.root.geometry(self.settings.get("window_geometry", "1000x700"))
|
||||||
self.root.configure(bg="#2b2b2b")
|
self.root.configure(bg="#2b2b2b")
|
||||||
|
|
||||||
# Configurar motor con configuraciones cargadas
|
# Debug desde configuración (definir antes del motor)
|
||||||
self.engine = HybridEvaluationEngine(auto_discover_types=True, types_directory="custom_types")
|
|
||||||
self._apply_symbolic_settings() # NUEVO: Aplicar configuraciones simbólicas
|
|
||||||
|
|
||||||
# Debug desde configuración
|
|
||||||
self.debug = self.settings.get("debug", False)
|
self.debug = self.settings.get("debug", False)
|
||||||
self.engine.debug = self.debug
|
|
||||||
|
# Motor de evaluación - SISTEMA ALGEBRAICO PURO
|
||||||
|
self.engine = PureAlgebraicEngine()
|
||||||
|
|
||||||
|
# Configurar motor
|
||||||
|
if hasattr(self.engine, 'logger'):
|
||||||
|
self.engine.logger.setLevel(logging.DEBUG if self.debug else logging.INFO)
|
||||||
|
|
||||||
# Autocompletado
|
# Autocompletado
|
||||||
self.autocomplete_popup = None
|
self.autocomplete_popup = None
|
||||||
|
@ -103,7 +105,7 @@ class HybridCalculatorApp:
|
||||||
|
|
||||||
self.status_label = tk.Label(
|
self.status_label = tk.Label(
|
||||||
self.status_frame,
|
self.status_frame,
|
||||||
text=self._get_status_text(),
|
text="🔢 Calculadora MAV - Sistema Algebraico Puro",
|
||||||
bg="#2b2b2b",
|
bg="#2b2b2b",
|
||||||
fg="#80c7f7",
|
fg="#80c7f7",
|
||||||
font=("Consolas", 9),
|
font=("Consolas", 9),
|
||||||
|
@ -140,49 +142,43 @@ class HybridCalculatorApp:
|
||||||
def reload_types(self):
|
def reload_types(self):
|
||||||
"""Recarga el sistema de tipos (útil para desarrollo)"""
|
"""Recarga el sistema de tipos (útil para desarrollo)"""
|
||||||
try:
|
try:
|
||||||
self.logger.info("Recargando sistema de tipos...") # Original: 🔄
|
self.logger.info("Recargando sistema de tipos...")
|
||||||
|
|
||||||
# Recargar engine
|
|
||||||
self.engine.reload_types()
|
|
||||||
|
|
||||||
# Recargar helpers
|
# Recargar helpers
|
||||||
self._setup_dynamic_helpers()
|
self._setup_dynamic_helpers()
|
||||||
# Re-evaluar contenido actual
|
# Re-evaluar contenido actual
|
||||||
self._evaluate_and_update()
|
self._evaluate_and_update()
|
||||||
|
|
||||||
self.logger.info("Sistema de tipos recargado.") # Original: ✅
|
self.logger.info("Sistema de tipos recargado.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error recargando tipos: {e}", exc_info=True) # Original: ❌
|
self.logger.error(f"Error recargando tipos: {e}", exc_info=True)
|
||||||
messagebox.showerror("Error", f"Error recargando tipos:\n{e}")
|
messagebox.showerror("Error", f"Error recargando tipos:\n{e}")
|
||||||
|
|
||||||
def show_types_info(self):
|
def show_types_info(self):
|
||||||
"""Muestra información sobre tipos disponibles"""
|
"""Muestra información sobre tipos disponibles"""
|
||||||
try:
|
try:
|
||||||
types_info = self.engine.get_available_types()
|
context_info = self.engine.get_context_info()
|
||||||
|
|
||||||
info_text = f"""INFORMACIÓN DEL SISTEMA DE TIPOS
|
info_text = f"""INFORMACIÓN DEL SISTEMA ALGEBRAICO PURO
|
||||||
|
|
||||||
Clases registradas: {len(types_info.get('registered_classes', {}))}
|
Ecuaciones en el sistema: {context_info.get('equations', 0)}
|
||||||
Clases con sintaxis de corchetes: {len(types_info.get('bracket_classes', []))}
|
Variables definidas: {context_info.get('variables', 0)}
|
||||||
Entradas en contexto: {types_info.get('total_context_entries', 0)}
|
Variables activas: {', '.join(context_info.get('variable_names', []))}
|
||||||
Helper functions: {types_info.get('helper_functions_count', 0)}
|
|
||||||
|
|
||||||
CLASES DISPONIBLES:
|
CARACTERÍSTICAS:
|
||||||
|
• Sistema de ecuaciones puras con SymPy
|
||||||
|
• Todas las asignaciones son ecuaciones
|
||||||
|
• Resolución automática de sistemas
|
||||||
|
• Evaluación numérica inteligente
|
||||||
|
• Atajo x=? equivale a solve(x)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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
|
# Mostrar en ventana
|
||||||
self._show_help_window("Información de Tipos", info_text)
|
self._show_help_window("Información del Sistema", info_text)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", f"Error obteniendo información de tipos:\n{e}")
|
messagebox.showerror("Error", f"Error obteniendo información del sistema:\n{e}")
|
||||||
|
|
||||||
def _setup_icon(self):
|
def _setup_icon(self):
|
||||||
"""Configura el ícono de la aplicación"""
|
"""Configura el ícono de la aplicación"""
|
||||||
|
@ -228,27 +224,7 @@ CLASES DISPONIBLES:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
self.logger.error(f"Error guardando configuración: {e}", exc_info=True)
|
self.logger.error(f"Error guardando configuración: {e}", exc_info=True)
|
||||||
|
|
||||||
def update_symbolic_settings(self, symbolic_mode=None, show_numeric=None,
|
|
||||||
keep_fractions=None, auto_simplify=None):
|
|
||||||
"""Actualiza configuraciones simbólicas y las guarda"""
|
|
||||||
if symbolic_mode is not None:
|
|
||||||
self.settings["symbolic_mode"] = symbolic_mode
|
|
||||||
if show_numeric is not None:
|
|
||||||
self.settings["show_numeric_approximation"] = show_numeric
|
|
||||||
if keep_fractions is not None:
|
|
||||||
self.settings["keep_symbolic_fractions"] = keep_fractions
|
|
||||||
if auto_simplify is not None:
|
|
||||||
self.settings["auto_simplify"] = auto_simplify
|
|
||||||
|
|
||||||
# Aplicar al motor
|
|
||||||
self._apply_symbolic_settings()
|
|
||||||
|
|
||||||
# Actualizar barra de estado
|
|
||||||
if hasattr(self, 'status_label'):
|
|
||||||
self.status_label.config(text=self._get_status_text())
|
|
||||||
|
|
||||||
# Guardar configuraciones
|
|
||||||
self._save_settings()
|
|
||||||
|
|
||||||
def create_widgets(self):
|
def create_widgets(self):
|
||||||
"""Crea la interfaz gráfica"""
|
"""Crea la interfaz gráfica"""
|
||||||
|
@ -346,34 +322,10 @@ CLASES DISPONIBLES:
|
||||||
edit_menu.add_separator()
|
edit_menu.add_separator()
|
||||||
edit_menu.add_command(label="Limpiar historial", command=self.clear_history)
|
edit_menu.add_command(label="Limpiar historial", command=self.clear_history)
|
||||||
|
|
||||||
# Menú Configuración
|
# Menú Herramientas (simplificado)
|
||||||
config_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white")
|
tools_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white")
|
||||||
menubar.add_cascade(label="Configuración", menu=config_menu)
|
menubar.add_cascade(label="Herramientas", menu=tools_menu)
|
||||||
|
tools_menu.add_command(label="Recargar Tipos Personalizados", command=self.reload_types)
|
||||||
# Variables para checkbuttons
|
|
||||||
self.symbolic_mode_var = tk.BooleanVar(value=self.settings.get("symbolic_mode", True))
|
|
||||||
self.show_numeric_var = tk.BooleanVar(value=self.settings.get("show_numeric_approximation", True))
|
|
||||||
self.keep_fractions_var = tk.BooleanVar(value=self.settings.get("keep_symbolic_fractions", True))
|
|
||||||
|
|
||||||
# Modo simbólico
|
|
||||||
config_menu.add_checkbutton(
|
|
||||||
label="Modo Simbólico",
|
|
||||||
variable=self.symbolic_mode_var,
|
|
||||||
command=self.toggle_symbolic_mode
|
|
||||||
)
|
|
||||||
config_menu.add_checkbutton(
|
|
||||||
label="Mostrar Aproximación Numérica",
|
|
||||||
variable=self.show_numeric_var,
|
|
||||||
command=self.toggle_numeric_approximation
|
|
||||||
)
|
|
||||||
config_menu.add_checkbutton(
|
|
||||||
label="Mantener Fracciones Simbólicas",
|
|
||||||
variable=self.keep_fractions_var,
|
|
||||||
command=self.toggle_symbolic_fractions
|
|
||||||
)
|
|
||||||
config_menu.add_separator()
|
|
||||||
|
|
||||||
config_menu.add_command(label="Recargar Tipos Personalizados", command=self.reload_types)
|
|
||||||
|
|
||||||
# ========== MENÚ TIPOS (NUEVO) ==========
|
# ========== MENÚ TIPOS (NUEVO) ==========
|
||||||
types_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white")
|
types_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white")
|
||||||
|
@ -687,7 +639,7 @@ CLASES DISPONIBLES:
|
||||||
|
|
||||||
# NUEVO: Limpiar completamente el contexto antes de cada evaluación
|
# NUEVO: Limpiar completamente el contexto antes de cada evaluación
|
||||||
# Esto garantiza que cada modificación reevalúe todo desde cero
|
# Esto garantiza que cada modificación reevalúe todo desde cero
|
||||||
self.engine.clear_all()
|
self.engine.clear_context()
|
||||||
|
|
||||||
lines = input_content.splitlines()
|
lines = input_content.splitlines()
|
||||||
self._evaluate_lines(lines)
|
self._evaluate_lines(lines)
|
||||||
|
@ -718,132 +670,73 @@ CLASES DISPONIBLES:
|
||||||
self._display_output(output_data)
|
self._display_output(output_data)
|
||||||
|
|
||||||
def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]:
|
def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]:
|
||||||
"""Procesa el resultado de evaluación para display"""
|
"""Procesa el resultado de evaluación para display - ADAPTADO AL MOTOR PURO"""
|
||||||
output_parts = []
|
output_parts = []
|
||||||
|
|
||||||
if result.is_error:
|
if not result.success:
|
||||||
ayuda = self.obtener_ayuda(result.original_line)
|
# Error
|
||||||
|
ayuda = self.obtener_ayuda(result.input_line)
|
||||||
if ayuda:
|
if ayuda:
|
||||||
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] + "..."
|
||||||
output_parts.append(("helper", ayuda_linea))
|
output_parts.append(("helper", ayuda_linea))
|
||||||
else:
|
else:
|
||||||
output_parts.append(("error", f"Error: {result.error}"))
|
output_parts.append(("error", f"Error: {result.error_message}"))
|
||||||
elif result.result_type == "comment":
|
elif result.result_type == "comment":
|
||||||
output_parts.append(("comment", result.original_line))
|
output_parts.append(("comment", result.input_line))
|
||||||
elif result.result_type == "equation_added":
|
elif result.result_type == "equation":
|
||||||
output_parts.append(("equation", result.symbolic_result))
|
output_parts.append(("equation", result.output))
|
||||||
elif result.result_type == "assignment":
|
elif result.result_type == "symbolic":
|
||||||
output_parts.append(("info", result.symbolic_result))
|
output_parts.append(("symbolic", result.output))
|
||||||
# Mostrar evaluación numérica para asignaciones si existe
|
|
||||||
if result.numeric_result is not None and result.numeric_result != result.result:
|
|
||||||
output_parts.append(("numeric", f"≈ {result.numeric_result}"))
|
|
||||||
else:
|
else:
|
||||||
# Resultado normal
|
# Resultado general
|
||||||
if result.result is not None:
|
output_parts.append(("result", result.output))
|
||||||
# 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:
|
|
||||||
interactive_tag, display_text = self.interactive_manager.create_interactive_tag(result.result, self.output_text, "1.0")
|
|
||||||
if interactive_tag:
|
|
||||||
output_parts.append((interactive_tag, display_text))
|
|
||||||
else:
|
|
||||||
output_parts.append((tag, str(result.result)))
|
|
||||||
else:
|
|
||||||
output_parts.append((tag, str(result.result)))
|
|
||||||
|
|
||||||
# Añadir pista de clase para el resultado principal
|
|
||||||
primary_result_object = result.result
|
|
||||||
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}"))
|
|
||||||
|
|
||||||
# Mostrar información adicional
|
|
||||||
if result.info:
|
|
||||||
output_parts.append(("info", f"({result.info})"))
|
|
||||||
|
|
||||||
return output_parts
|
return output_parts
|
||||||
|
|
||||||
def _get_result_tag_dynamic(self, result: Any) -> str:
|
def _get_result_tag_dynamic(self, result: Any) -> str:
|
||||||
"""Determina el tag de color para un resultado - VERSIÓN DINÁMICA"""
|
"""Determina el tag de color para un resultado - SIMPLIFICADO"""
|
||||||
# Obtener clases registradas dinámicamente del sistema de tipos
|
# Determinar tag basado en tipo
|
||||||
try:
|
if hasattr(result, '__class__'):
|
||||||
registered_classes = self.engine.get_available_types().get('registered_classes', {})
|
class_name = result.__class__.__name__.lower()
|
||||||
|
if 'hex' in class_name:
|
||||||
# Verificar si es una instancia de alguna clase registrada
|
|
||||||
for name, cls in registered_classes.items():
|
|
||||||
if isinstance(result, cls):
|
|
||||||
# Usar tags específicos basados en el nombre de la clase
|
|
||||||
name_lower = name.lower()
|
|
||||||
if name_lower == "hex":
|
|
||||||
return "hex"
|
return "hex"
|
||||||
elif name_lower == "bin":
|
elif 'bin' in class_name:
|
||||||
return "bin"
|
return "bin"
|
||||||
elif name_lower in ["ip4", "ip"]:
|
elif 'ip' in class_name:
|
||||||
return "ip"
|
return "ip"
|
||||||
elif name_lower == "chr":
|
elif 'chr' in class_name:
|
||||||
return "chr_type"
|
return "chr_type"
|
||||||
elif name_lower == "date":
|
elif 'date' in class_name:
|
||||||
return "date"
|
return "date"
|
||||||
else:
|
|
||||||
return "custom_type" # Tag genérico para tipos personalizados
|
|
||||||
|
|
||||||
except Exception as e:
|
# Fallback a tags existentes
|
||||||
if self.debug:
|
try:
|
||||||
self.logger.debug(f"Error en get_result_tag_dynamic: {e}")
|
import sympy
|
||||||
|
|
||||||
# Fallback a tags existentes para tipos no registrados
|
|
||||||
if isinstance(result, sympy.Basic):
|
if isinstance(result, sympy.Basic):
|
||||||
return "symbolic"
|
return "symbolic"
|
||||||
else:
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return "result"
|
return "result"
|
||||||
|
|
||||||
def _get_class_display_name_dynamic(self, obj: Any) -> str:
|
def _get_class_display_name_dynamic(self, obj: Any) -> str:
|
||||||
"""Obtiene nombre de clase para display - VERSIÓN DINÁMICA"""
|
"""Obtiene nombre de clase para display - SIMPLIFICADO"""
|
||||||
try:
|
try:
|
||||||
# Verificar si es una clase registrada dinámicamente
|
import sympy
|
||||||
registered_classes = self.engine.get_available_types().get('registered_classes', {})
|
if isinstance(obj, sympy.Basic):
|
||||||
|
|
||||||
for name, cls in registered_classes.items():
|
|
||||||
if isinstance(obj, cls):
|
|
||||||
return name
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if self.debug:
|
|
||||||
self.logger.debug(f"Error en get_class_display_name_dynamic: {e}")
|
|
||||||
|
|
||||||
# Fallback a lógica existente para tipos nativos
|
|
||||||
if isinstance(obj, sympy.logic.boolalg.BooleanAtom):
|
|
||||||
return "Boolean"
|
|
||||||
elif isinstance(obj, sympy.Basic):
|
|
||||||
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"
|
return "Sympy"
|
||||||
elif isinstance(obj, bool):
|
except:
|
||||||
return "Boolean"
|
pass
|
||||||
elif isinstance(obj, (int, float, str, list, dict, tuple, type(None))):
|
|
||||||
|
if isinstance(obj, (int, float, str, list, dict, tuple, bool, type(None))):
|
||||||
class_display_name = type(obj).__name__.capitalize()
|
class_display_name = type(obj).__name__.capitalize()
|
||||||
if class_display_name == "Nonetype":
|
if class_display_name == "Nonetype":
|
||||||
class_display_name = "None"
|
class_display_name = "None"
|
||||||
return class_display_name
|
return class_display_name
|
||||||
|
|
||||||
return ""
|
return type(obj).__name__
|
||||||
|
|
||||||
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 (sin cambios)"""
|
"""Muestra los datos de salida en el widget (sin cambios)"""
|
||||||
|
@ -919,6 +812,7 @@ CLASES DISPONIBLES:
|
||||||
"""Inicia nueva sesión"""
|
"""Inicia nueva sesión"""
|
||||||
self.clear_input()
|
self.clear_input()
|
||||||
self.clear_output()
|
self.clear_output()
|
||||||
|
self.engine.clear_context() # Limpiar contexto del motor
|
||||||
|
|
||||||
def load_file(self):
|
def load_file(self):
|
||||||
"""Carga archivo en el editor"""
|
"""Carga archivo en el editor"""
|
||||||
|
@ -1428,42 +1322,7 @@ Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el
|
||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _apply_symbolic_settings(self):
|
|
||||||
"""Aplica configuraciones simbólicas al motor de evaluación"""
|
|
||||||
symbolic_mode = self.settings.get("symbolic_mode", True)
|
|
||||||
show_numeric = self.settings.get("show_numeric_approximation", True)
|
|
||||||
keep_fractions = self.settings.get("keep_symbolic_fractions", True)
|
|
||||||
auto_simplify = self.settings.get("auto_simplify", False)
|
|
||||||
|
|
||||||
self.engine.set_symbolic_mode(
|
|
||||||
symbolic_mode=symbolic_mode,
|
|
||||||
show_numeric=show_numeric,
|
|
||||||
keep_fractions=keep_fractions,
|
|
||||||
auto_simplify=auto_simplify
|
|
||||||
)
|
|
||||||
|
|
||||||
def toggle_symbolic_mode(self):
|
|
||||||
"""Alterna el modo simbólico"""
|
|
||||||
new_value = self.symbolic_mode_var.get()
|
|
||||||
self.update_symbolic_settings(symbolic_mode=new_value)
|
|
||||||
|
|
||||||
def toggle_numeric_approximation(self):
|
|
||||||
"""Alterna la aproximación numérica"""
|
|
||||||
new_value = self.show_numeric_var.get()
|
|
||||||
self.update_symbolic_settings(show_numeric=new_value)
|
|
||||||
|
|
||||||
def toggle_symbolic_fractions(self):
|
|
||||||
"""Alterna la mantención de fracciones simbólicas"""
|
|
||||||
new_value = self.keep_fractions_var.get()
|
|
||||||
self.update_symbolic_settings(keep_fractions=new_value)
|
|
||||||
|
|
||||||
def _get_status_text(self):
|
|
||||||
"""Obtiene el texto de estado actual"""
|
|
||||||
mode = "🔢 Simbólico" if self.settings.get("symbolic_mode", True) else "🧮 Numérico"
|
|
||||||
numeric_indicator = " ≈" if self.settings.get("show_numeric_approximation", True) else ""
|
|
||||||
fractions_indicator = " 📐" if self.settings.get("keep_symbolic_fractions", True) else ""
|
|
||||||
|
|
||||||
return f"{mode}{numeric_indicator}{fractions_indicator}"
|
|
||||||
|
|
||||||
def _get_input_font(self):
|
def _get_input_font(self):
|
||||||
"""Obtiene o crea y cachea el objeto tk.Font para el panel de entrada."""
|
"""Obtiene o crea y cachea el objeto tk.Font para el panel de entrada."""
|
||||||
|
|
|
@ -0,0 +1,395 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Motor de Evaluación Algebraico Puro para Calculadora MAV
|
||||||
|
ARQUITECTURA SIMPLIFICADA: Todas las líneas con = son ecuaciones
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import sympy as sp
|
||||||
|
from sympy import symbols, Eq, solve, sympify, latex, simplify
|
||||||
|
from typing import List, Dict, Any, Optional, Tuple, Union
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sympy_helper import SympyHelper
|
||||||
|
HAS_SYMPY_HELPER = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_SYMPY_HELPER = False
|
||||||
|
|
||||||
|
from type_registry import get_registered_base_context, get_registered_tokenization_patterns, discover_and_register_types
|
||||||
|
from tl_bracket_parser import BracketParser
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EvaluationResult:
|
||||||
|
"""Resultado de evaluación simplificado"""
|
||||||
|
input_line: str
|
||||||
|
output: str
|
||||||
|
result_type: str
|
||||||
|
success: bool
|
||||||
|
error_message: Optional[str] = None
|
||||||
|
is_equation: bool = False
|
||||||
|
is_solve_query: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class PureAlgebraicEngine:
|
||||||
|
"""Motor algebraico puro - Todas las asignaciones son ecuaciones"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.equations = [] # Lista de ecuaciones Eq()
|
||||||
|
self.variables = set() # Variables conocidas
|
||||||
|
self.context = {} # Contexto de evaluación
|
||||||
|
self.bracket_parser = BracketParser()
|
||||||
|
self.tokenization_patterns = [] # Patrones de tokenización
|
||||||
|
|
||||||
|
# Cargar tipos personalizados PRIMERO
|
||||||
|
self._load_custom_types()
|
||||||
|
|
||||||
|
# Cargar contexto base
|
||||||
|
self._load_base_context()
|
||||||
|
self._load_tokenization_patterns()
|
||||||
|
|
||||||
|
def _load_custom_types(self):
|
||||||
|
"""Carga los tipos personalizados desde custom_types/"""
|
||||||
|
try:
|
||||||
|
discover_and_register_types("custom_types")
|
||||||
|
self.logger.debug("Tipos personalizados cargados")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error cargando tipos personalizados: {e}")
|
||||||
|
|
||||||
|
def _load_base_context(self):
|
||||||
|
"""Carga el contexto base con funciones y tipos"""
|
||||||
|
try:
|
||||||
|
# Contexto de SymPy
|
||||||
|
self.context.update({
|
||||||
|
'sin': sp.sin, 'cos': sp.cos, 'tan': sp.tan,
|
||||||
|
'log': sp.log, 'ln': sp.ln, 'exp': sp.exp,
|
||||||
|
'sqrt': sp.sqrt, 'abs': sp.Abs,
|
||||||
|
'pi': sp.pi, 'e': sp.E, 'I': sp.I,
|
||||||
|
'oo': sp.oo, 'inf': sp.oo,
|
||||||
|
'solve': self._smart_solve, # Usar nuestro solve inteligente
|
||||||
|
'Eq': sp.Eq, 'simplify': sp.simplify,
|
||||||
|
'expand': sp.expand, 'factor': sp.factor,
|
||||||
|
'diff': sp.diff, 'integrate': sp.integrate,
|
||||||
|
'Matrix': sp.Matrix, 'symbols': sp.symbols,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Contexto dinámico de tipos personalizados
|
||||||
|
dynamic_context = get_registered_base_context()
|
||||||
|
self.context.update(dynamic_context)
|
||||||
|
|
||||||
|
self.logger.debug(f"Contexto cargado: {len(self.context)} elementos")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error cargando contexto: {e}")
|
||||||
|
|
||||||
|
def _load_tokenization_patterns(self):
|
||||||
|
"""Carga los patrones de tokenización dinámicos"""
|
||||||
|
try:
|
||||||
|
self.tokenization_patterns = get_registered_tokenization_patterns()
|
||||||
|
self.logger.debug(f"Patrones de tokenización cargados: {len(self.tokenization_patterns)}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error cargando patrones de tokenización: {e}")
|
||||||
|
self.tokenization_patterns = []
|
||||||
|
|
||||||
|
def _apply_tokenization(self, line: str) -> str:
|
||||||
|
"""Aplica tokenización dinámica a la línea de entrada"""
|
||||||
|
if not self.tokenization_patterns:
|
||||||
|
return line
|
||||||
|
|
||||||
|
tokenized_line = line
|
||||||
|
|
||||||
|
# Ordenar patrones por prioridad (mayor prioridad primero)
|
||||||
|
sorted_patterns = sorted(self.tokenization_patterns,
|
||||||
|
key=lambda p: p.get('priority', 0),
|
||||||
|
reverse=True)
|
||||||
|
|
||||||
|
for pattern_info in sorted_patterns:
|
||||||
|
pattern = pattern_info['pattern']
|
||||||
|
replacement_func = pattern_info['replacement']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Aplicar el patrón con su función de reemplazo
|
||||||
|
tokenized_line = re.sub(pattern, replacement_func, tokenized_line)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(f"Error aplicando patrón {pattern}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if tokenized_line != line:
|
||||||
|
self.logger.debug(f"Tokenización: '{line}' → '{tokenized_line}'")
|
||||||
|
|
||||||
|
return tokenized_line
|
||||||
|
|
||||||
|
def evaluate_line(self, line: str) -> EvaluationResult:
|
||||||
|
"""Evalúa una línea de entrada"""
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
return EvaluationResult(line, "", "comment", True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Aplicar tokenización dinámica PRIMERO
|
||||||
|
tokenized_line = self._apply_tokenization(line)
|
||||||
|
|
||||||
|
# 2. Preprocesar con bracket parser
|
||||||
|
processed_line = self.bracket_parser.process_expression(tokenized_line)
|
||||||
|
self.logger.debug(f"Línea procesada: {processed_line}")
|
||||||
|
|
||||||
|
# 3. Determinar tipo de entrada
|
||||||
|
if self._is_solve_shortcut(processed_line):
|
||||||
|
return self._evaluate_solve_shortcut(processed_line)
|
||||||
|
elif '=' in processed_line and not self._is_comparison(processed_line):
|
||||||
|
return self._evaluate_equation(processed_line)
|
||||||
|
else:
|
||||||
|
return self._evaluate_expression(processed_line)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error: {type(e).__name__}: {str(e)}"
|
||||||
|
self.logger.error(f"Error evaluando '{line}': {e}")
|
||||||
|
return EvaluationResult(line, error_msg, "error", False, str(e))
|
||||||
|
|
||||||
|
def _is_solve_shortcut(self, line: str) -> bool:
|
||||||
|
"""Detecta atajos de resolución como x=? o solve(x)"""
|
||||||
|
return line.endswith('=?') or line.startswith('solve(')
|
||||||
|
|
||||||
|
def _is_comparison(self, line: str) -> bool:
|
||||||
|
"""Detecta comparaciones como ==, <=, >=, !="""
|
||||||
|
comparison_ops = ['==', '<=', '>=', '!=', '<', '>']
|
||||||
|
return any(op in line for op in comparison_ops)
|
||||||
|
|
||||||
|
def _evaluate_solve_shortcut(self, line: str) -> EvaluationResult:
|
||||||
|
"""Evalúa atajos de resolución"""
|
||||||
|
try:
|
||||||
|
if line.endswith('=?'):
|
||||||
|
# Atajo x=?
|
||||||
|
var_name = line[:-2].strip()
|
||||||
|
var_symbol = sp.Symbol(var_name)
|
||||||
|
solution = self._solve_for_variable(var_symbol)
|
||||||
|
|
||||||
|
# Output conciso
|
||||||
|
if solution != var_symbol: # Si hay solución real
|
||||||
|
output = str(solution)
|
||||||
|
numeric = self._get_numeric_approximation(solution)
|
||||||
|
if numeric and str(solution) != str(numeric):
|
||||||
|
output += f" ≈ {numeric}"
|
||||||
|
else:
|
||||||
|
output = str(var_symbol) # Variable sin resolver
|
||||||
|
|
||||||
|
return EvaluationResult(line, output, "symbolic", True, is_solve_query=True)
|
||||||
|
|
||||||
|
elif line.startswith('solve('):
|
||||||
|
# Función solve()
|
||||||
|
result = self._evaluate_expression(line)
|
||||||
|
result.is_solve_query = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error en resolución: {str(e)}"
|
||||||
|
return EvaluationResult(line, error_msg, "error", False, str(e))
|
||||||
|
|
||||||
|
def _evaluate_equation(self, line: str) -> EvaluationResult:
|
||||||
|
"""Evalúa una ecuación y la añade al sistema"""
|
||||||
|
try:
|
||||||
|
# Separar left = right
|
||||||
|
left_str, right_str = line.split('=', 1)
|
||||||
|
left_str = left_str.strip()
|
||||||
|
right_str = right_str.strip()
|
||||||
|
|
||||||
|
# Convertir a expresiones SymPy
|
||||||
|
left_expr = sympify(left_str, locals=self.context)
|
||||||
|
right_expr = sympify(right_str, locals=self.context)
|
||||||
|
|
||||||
|
# Crear ecuación
|
||||||
|
equation = Eq(left_expr, right_expr)
|
||||||
|
|
||||||
|
# Añadir al sistema
|
||||||
|
self.equations.append(equation)
|
||||||
|
|
||||||
|
# Extraer variables
|
||||||
|
eq_vars = equation.free_symbols
|
||||||
|
self.variables.update(eq_vars)
|
||||||
|
|
||||||
|
# Output conciso de una línea
|
||||||
|
output = str(equation)
|
||||||
|
|
||||||
|
# Evaluación numérica si es posible (solo para lado derecho)
|
||||||
|
numeric = self._get_numeric_approximation(equation.rhs)
|
||||||
|
if numeric and str(equation.rhs) != str(numeric):
|
||||||
|
output += f" ≈ {numeric}"
|
||||||
|
|
||||||
|
return EvaluationResult(line, output, "equation", True, is_equation=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error en ecuación: {str(e)}"
|
||||||
|
return EvaluationResult(line, error_msg, "error", False, str(e))
|
||||||
|
|
||||||
|
def _evaluate_expression(self, line: str) -> EvaluationResult:
|
||||||
|
"""Evalúa una expresión libre"""
|
||||||
|
try:
|
||||||
|
# Evaluar con SymPy
|
||||||
|
expr = sympify(line, locals=self.context)
|
||||||
|
|
||||||
|
# Evaluar la expresión
|
||||||
|
result = expr
|
||||||
|
if hasattr(expr, 'evalf'): # Es expresión SymPy, simplificar
|
||||||
|
result = simplify(expr)
|
||||||
|
|
||||||
|
output = str(result)
|
||||||
|
|
||||||
|
# Añadir aproximación numérica
|
||||||
|
numeric = self._get_numeric_approximation(result)
|
||||||
|
if numeric and str(result) != str(numeric):
|
||||||
|
output += f" ≈ {numeric}"
|
||||||
|
|
||||||
|
return EvaluationResult(line, output, "symbolic", True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error: {str(e)}"
|
||||||
|
return EvaluationResult(line, error_msg, "error", False, str(e))
|
||||||
|
|
||||||
|
def _smart_solve(self, *args, **kwargs):
|
||||||
|
"""Función solve inteligente que usa nuestro sistema de ecuaciones"""
|
||||||
|
if not args:
|
||||||
|
# solve() sin argumentos - resolver todo el sistema
|
||||||
|
if not self.equations:
|
||||||
|
return "No hay ecuaciones en el sistema"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Resolver usando todas las ecuaciones
|
||||||
|
all_vars = list(self.variables)
|
||||||
|
solution = solve(self.equations, all_vars, dict=True)
|
||||||
|
|
||||||
|
if solution:
|
||||||
|
return solution[0] if len(solution) == 1 else solution
|
||||||
|
else:
|
||||||
|
return "Sin solución"
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error resolviendo sistema: {e}"
|
||||||
|
else:
|
||||||
|
# solve() con argumentos específicos
|
||||||
|
return solve(*args, **kwargs)
|
||||||
|
|
||||||
|
def _solve_for_variable(self, var_symbol):
|
||||||
|
"""Resuelve una variable específica usando el sistema actual"""
|
||||||
|
if not self.equations:
|
||||||
|
return var_symbol
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Intentar resolver la variable en el contexto del sistema
|
||||||
|
solution = solve(self.equations, var_symbol, dict=True)
|
||||||
|
|
||||||
|
if solution and var_symbol in solution[0]:
|
||||||
|
return solution[0][var_symbol]
|
||||||
|
else:
|
||||||
|
# Intentar resolver solo las ecuaciones que contienen esta variable
|
||||||
|
relevant_eqs = [eq for eq in self.equations if var_symbol in eq.free_symbols]
|
||||||
|
if relevant_eqs:
|
||||||
|
solution = solve(relevant_eqs, var_symbol)
|
||||||
|
if solution:
|
||||||
|
return solution[0] if isinstance(solution, list) else solution
|
||||||
|
|
||||||
|
return var_symbol
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(f"Error resolviendo {var_symbol}: {e}")
|
||||||
|
return var_symbol
|
||||||
|
|
||||||
|
def _get_numeric_approximation(self, expr) -> Optional[str]:
|
||||||
|
"""Obtiene aproximación numérica si es posible"""
|
||||||
|
try:
|
||||||
|
if hasattr(expr, 'evalf'):
|
||||||
|
numeric_val = expr.evalf()
|
||||||
|
|
||||||
|
# Solo mostrar si es diferente de la forma simbólica
|
||||||
|
if str(numeric_val) != str(expr):
|
||||||
|
# Formatear números con precisión razonable
|
||||||
|
if hasattr(numeric_val, 'is_real') and numeric_val.is_real:
|
||||||
|
try:
|
||||||
|
float_val = float(numeric_val)
|
||||||
|
if abs(float_val) > 1e-10: # Evitar números muy pequeños
|
||||||
|
return f"{float_val:.6f}".rstrip('0').rstrip('.')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return str(numeric_val)
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def clear_context(self):
|
||||||
|
"""Limpia el contexto de evaluación pero mantiene los tipos base"""
|
||||||
|
self.equations.clear()
|
||||||
|
self.variables.clear()
|
||||||
|
self.logger.info("Contexto limpio")
|
||||||
|
|
||||||
|
def get_context_info(self) -> Dict[str, Any]:
|
||||||
|
"""Información del contexto actual"""
|
||||||
|
return {
|
||||||
|
"equations": len(self.equations),
|
||||||
|
"variables": list(self.variables),
|
||||||
|
"context_size": len(self.context),
|
||||||
|
"tokenization_patterns": len(self.tokenization_patterns),
|
||||||
|
"recent_equations": [str(eq) for eq in self.equations[-5:]] # Últimas 5
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_full_context(self) -> Dict[str, Any]:
|
||||||
|
"""Obtiene el contexto completo para autocompletado (compatibilidad)"""
|
||||||
|
# Este método es necesario para el autocompletado en la GUI
|
||||||
|
full_context = self.context.copy()
|
||||||
|
|
||||||
|
# Añadir variables actuales
|
||||||
|
for var in self.variables:
|
||||||
|
full_context[str(var)] = var
|
||||||
|
|
||||||
|
return full_context
|
||||||
|
|
||||||
|
def get_available_types(self) -> List[str]:
|
||||||
|
"""Obtiene tipos disponibles (compatibilidad)"""
|
||||||
|
available_types = []
|
||||||
|
for name, obj in self.context.items():
|
||||||
|
if hasattr(obj, '__class__') and hasattr(obj.__class__, '__name__'):
|
||||||
|
if obj.__class__.__name__ not in ['function', 'builtin_function_or_method']:
|
||||||
|
available_types.append(name)
|
||||||
|
return available_types
|
||||||
|
|
||||||
|
def reload_types(self):
|
||||||
|
"""Recarga los tipos dinámicos (compatibilidad)"""
|
||||||
|
self._load_base_context()
|
||||||
|
self._load_tokenization_patterns()
|
||||||
|
self.logger.info("Tipos y patrones recargados")
|
||||||
|
|
||||||
|
|
||||||
|
# ========== FUNCIÓN DE EVALUACIÓN DIRECTA ==========
|
||||||
|
|
||||||
|
def evaluate_line(line: str, engine: PureAlgebraicEngine = None) -> EvaluationResult:
|
||||||
|
"""Función de evaluación directa para uso desde otros módulos"""
|
||||||
|
if engine is None:
|
||||||
|
engine = PureAlgebraicEngine()
|
||||||
|
|
||||||
|
return engine.evaluate_line(line)
|
||||||
|
|
||||||
|
|
||||||
|
# ========== EJEMPLO DE USO ==========
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Demo del motor puro
|
||||||
|
engine = PureAlgebraicEngine()
|
||||||
|
|
||||||
|
test_lines = [
|
||||||
|
"x = 5",
|
||||||
|
"y = x + 3",
|
||||||
|
"z = y + x",
|
||||||
|
"x=?",
|
||||||
|
"solve()",
|
||||||
|
"10.1.1.1", # Debería tokenizarse a FourBytes
|
||||||
|
"16#FF", # Debería tokenizarse a IntBase
|
||||||
|
"2#1010" # Debería tokenizarse a IntBase
|
||||||
|
]
|
||||||
|
|
||||||
|
print("=== DEMO MOTOR ALGEBRAICO PURO ===")
|
||||||
|
for line in test_lines:
|
||||||
|
result = engine.evaluate_line(line)
|
||||||
|
status = "✅" if result.success else "❌"
|
||||||
|
print(f"{status} {line} → {result.output}")
|
||||||
|
|
||||||
|
print(f"\nContexto: {engine.get_context_info()}")
|
|
@ -21,7 +21,7 @@ import argparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Importar el motor de evaluación existente
|
# Importar motores de evaluación
|
||||||
from main_evaluation import HybridEvaluationEngine
|
from main_evaluation import HybridEvaluationEngine
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,10 +50,19 @@ def run_debug(input_file: str, output_file: str = None, verbose: bool = False):
|
||||||
print("Error: El archivo JSON debe contener una clave 'queries'")
|
print("Error: El archivo JSON debe contener una clave 'queries'")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Crear motor de evaluación
|
# Determinar qué motor usar
|
||||||
|
engine_module = data.get('engine_module', 'main_evaluation')
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
|
print(f"Usando motor: {engine_module}")
|
||||||
print("Iniciando motor de evaluación...")
|
print("Iniciando motor de evaluación...")
|
||||||
|
|
||||||
|
# Crear motor de evaluación según el módulo especificado
|
||||||
|
if engine_module == 'main_evaluation_puro':
|
||||||
|
from main_evaluation_puro import PureAlgebraicEngine
|
||||||
|
engine = PureAlgebraicEngine()
|
||||||
|
else:
|
||||||
|
# Motor por defecto
|
||||||
engine = HybridEvaluationEngine()
|
engine = HybridEvaluationEngine()
|
||||||
results = []
|
results = []
|
||||||
successful = 0
|
successful = 0
|
||||||
|
@ -85,7 +94,27 @@ def run_debug(input_file: str, output_file: str = None, verbose: bool = False):
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
||||||
elif query['type'] == 'exec':
|
elif query['type'] == 'exec':
|
||||||
# Query de tipo exec: ejecutar código Python para inspeccionar el estado
|
# Query de tipo exec: evaluar usando el motor adecuado
|
||||||
|
|
||||||
|
if engine_module == 'main_evaluation_puro':
|
||||||
|
# Para motor puro, evaluar directamente
|
||||||
|
result = engine.evaluate_line(query['content'])
|
||||||
|
|
||||||
|
output = {
|
||||||
|
'index': query['index'],
|
||||||
|
'input': query['content'],
|
||||||
|
'output': result.output,
|
||||||
|
'result_type': result.result_type,
|
||||||
|
'success': result.success,
|
||||||
|
'error': result.error_message
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
successful += 1
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
else:
|
||||||
|
# Motor original: ejecutar código Python para inspeccionar el estado
|
||||||
exec_result = eval(query['content'], {'engine': engine})
|
exec_result = eval(query['content'], {'engine': engine})
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "import sympy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "from sympy import symbols, Eq, solve"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "x, y, z = symbols('x y z')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 3,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "# Sistema puramente algebraico"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 4,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "eq1 = Eq(x, 5)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 5,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "eq2 = Eq(y, x + 3)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 6,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "eq3 = Eq(z, x + y)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 7,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "sistema = [eq1, eq2, eq3]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 8,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "solve(sistema)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 9,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "# Test más complejo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 10,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "m, t, u = symbols('m t u')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 11,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "eq4 = Eq(m, t + u * 5)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 12,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "eq5 = Eq(t, 4)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 13,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "eq6 = Eq(m, 3)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 14,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "sistema2 = [eq4, eq5, eq6]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 15,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "solve(sistema2)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"execution_info": {
|
||||||
|
"timestamp": "2025-06-05T22:55:36.685672Z",
|
||||||
|
"total_queries": 16,
|
||||||
|
"successful": 0,
|
||||||
|
"failed": 16,
|
||||||
|
"input_file": "test_ecuaciones_puras.json"
|
||||||
|
},
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"input": "import sympy",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"input": "from sympy import symbols, Eq, solve",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"input": "x, y, z = symbols('x y z')",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 3,
|
||||||
|
"input": "# Sistema puramente algebraico",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 4,
|
||||||
|
"input": "eq1 = Eq(x, 5)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 5,
|
||||||
|
"input": "eq2 = Eq(y, x + 3)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 6,
|
||||||
|
"input": "eq3 = Eq(z, x + y)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 7,
|
||||||
|
"input": "sistema = [eq1, eq2, eq3]",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 8,
|
||||||
|
"input": "solve(sistema)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "name 'solve' is not defined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 9,
|
||||||
|
"input": "# Test más complejo",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 10,
|
||||||
|
"input": "m, t, u = symbols('m t u')",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 11,
|
||||||
|
"input": "eq4 = Eq(m, t + u * 5)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 12,
|
||||||
|
"input": "eq5 = Eq(t, 4)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 13,
|
||||||
|
"input": "eq6 = Eq(m, 3)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 14,
|
||||||
|
"input": "sistema2 = [eq4, eq5, eq6]",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "invalid syntax (<string>, line 1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 15,
|
||||||
|
"input": "solve(sistema2)",
|
||||||
|
"output": null,
|
||||||
|
"result_type": null,
|
||||||
|
"success": false,
|
||||||
|
"error": "name 'solve' is not defined"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"engine_module": "main_evaluation_puro",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "x = 5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "y = x + 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "z = x + y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 3,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "x=?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 4,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "y=?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 5,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "z=?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 6,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "solve(x)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 7,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "solve(y)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 8,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "2 + 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 9,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "sin(pi/2)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 10,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "16#FF + 10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 11,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "a = b + 5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 12,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "b = 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 13,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "a=?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 14,
|
||||||
|
"type": "exec",
|
||||||
|
"content": "4/5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
{
|
||||||
|
"execution_info": {
|
||||||
|
"timestamp": "2025-06-05T23:11:43.398040Z",
|
||||||
|
"total_queries": 15,
|
||||||
|
"successful": 15,
|
||||||
|
"failed": 0,
|
||||||
|
"input_file": "test_motor_puro.json"
|
||||||
|
},
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"input": "x = 5",
|
||||||
|
"output": "Eq(x, 5)",
|
||||||
|
"result_type": "equation",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"input": "y = x + 3",
|
||||||
|
"output": "Eq(y, x + 3)",
|
||||||
|
"result_type": "equation",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"input": "z = x + y",
|
||||||
|
"output": "Eq(z, x + y)",
|
||||||
|
"result_type": "equation",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 3,
|
||||||
|
"input": "x=?",
|
||||||
|
"output": "x",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 4,
|
||||||
|
"input": "y=?",
|
||||||
|
"output": "y",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 5,
|
||||||
|
"input": "z=?",
|
||||||
|
"output": "x + y",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 6,
|
||||||
|
"input": "solve(x)",
|
||||||
|
"output": "[]",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 7,
|
||||||
|
"input": "solve(y)",
|
||||||
|
"output": "[]",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 8,
|
||||||
|
"input": "2 + 3",
|
||||||
|
"output": "5",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 9,
|
||||||
|
"input": "sin(pi/2)",
|
||||||
|
"output": "1",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 10,
|
||||||
|
"input": "16#FF + 10",
|
||||||
|
"output": "16",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 11,
|
||||||
|
"input": "a = b + 5",
|
||||||
|
"output": "Eq(a, b + 5)",
|
||||||
|
"result_type": "equation",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 12,
|
||||||
|
"input": "b = 3",
|
||||||
|
"output": "Eq(b, 3)",
|
||||||
|
"result_type": "equation",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 13,
|
||||||
|
"input": "a=?",
|
||||||
|
"output": "b + 5",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 14,
|
||||||
|
"input": "4/5",
|
||||||
|
"output": "4/5 ≈ 0.800000",
|
||||||
|
"result_type": "symbolic",
|
||||||
|
"success": true,
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from sympy import symbols, Eq, solve, pprint
|
||||||
|
|
||||||
|
print("🧪 Probando Sistema de Ecuaciones Puras con SymPy")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Test 1: Sistema simple
|
||||||
|
print("\n📌 Test 1: Sistema simple")
|
||||||
|
x, y, z = symbols('x y z')
|
||||||
|
eq1 = Eq(x, 5)
|
||||||
|
eq2 = Eq(y, x + 3)
|
||||||
|
eq3 = Eq(z, x + y)
|
||||||
|
sistema1 = [eq1, eq2, eq3]
|
||||||
|
|
||||||
|
print("Sistema:")
|
||||||
|
for i, eq in enumerate(sistema1, 1):
|
||||||
|
print(f" eq{i}: {eq}")
|
||||||
|
|
||||||
|
result1 = solve(sistema1)
|
||||||
|
print(f"Solución: {result1}")
|
||||||
|
|
||||||
|
# Test 2: Sistema más complejo
|
||||||
|
print("\n📌 Test 2: Sistema con conflicto")
|
||||||
|
m, t, u = symbols('m t u')
|
||||||
|
eq4 = Eq(m, t + u * 5)
|
||||||
|
eq5 = Eq(t, 4)
|
||||||
|
eq6 = Eq(m, 3)
|
||||||
|
sistema2 = [eq4, eq5, eq6]
|
||||||
|
|
||||||
|
print("Sistema:")
|
||||||
|
for i, eq in enumerate(sistema2, 1):
|
||||||
|
print(f" eq{i}: {eq}")
|
||||||
|
|
||||||
|
result2 = solve(sistema2)
|
||||||
|
print(f"Solución: {result2}")
|
||||||
|
|
||||||
|
# Test 3: Sistema inconsistente
|
||||||
|
print("\n📌 Test 3: Sistema inconsistente")
|
||||||
|
a, b = symbols('a b')
|
||||||
|
eq7 = Eq(a, 5)
|
||||||
|
eq8 = Eq(a, 10)
|
||||||
|
sistema3 = [eq7, eq8]
|
||||||
|
|
||||||
|
print("Sistema:")
|
||||||
|
for i, eq in enumerate(sistema3, 1):
|
||||||
|
print(f" eq{i}: {eq}")
|
||||||
|
|
||||||
|
result3 = solve(sistema3)
|
||||||
|
print(f"Solución: {result3}")
|
||||||
|
|
||||||
|
# Test 4: Variables libres
|
||||||
|
print("\n📌 Test 4: Variables libres")
|
||||||
|
p, q, r = symbols('p q r')
|
||||||
|
eq9 = Eq(p, q + 2)
|
||||||
|
eq10 = Eq(r, p * 3)
|
||||||
|
sistema4 = [eq9, eq10]
|
||||||
|
|
||||||
|
print("Sistema:")
|
||||||
|
for i, eq in enumerate(sistema4, 1):
|
||||||
|
print(f" eq{i}: {eq}")
|
||||||
|
|
||||||
|
result4 = solve(sistema4)
|
||||||
|
print(f"Solución: {result4}")
|
||||||
|
|
||||||
|
# Test 5: Evaluación numérica automática
|
||||||
|
print("\n📌 Test 5: Evaluación numérica")
|
||||||
|
if result2:
|
||||||
|
print("Evaluación numérica automática:")
|
||||||
|
for var, val in result2.items():
|
||||||
|
print(f" {var} = {val} ≈ {val.evalf()}")
|
||||||
|
|
||||||
|
print("\n✅ Conclusión: SymPy maneja perfectamente sistemas de ecuaciones puras!")
|
|
@ -22,6 +22,7 @@ class TypeRegistry:
|
||||||
self.bracket_classes: set = set()
|
self.bracket_classes: set = set()
|
||||||
self.base_context: Dict[str, Any] = {}
|
self.base_context: Dict[str, Any] = {}
|
||||||
self.helper_functions: List[callable] = []
|
self.helper_functions: List[callable] = []
|
||||||
|
self.tokenization_patterns: List[Dict] = [] # NUEVO: patrones de tokenización
|
||||||
|
|
||||||
def discover_and_register_all(self) -> Dict[str, Any]:
|
def discover_and_register_all(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -47,7 +48,7 @@ class TypeRegistry:
|
||||||
logger.error(f"Error procesando {type_file}: {e}")
|
logger.error(f"Error procesando {type_file}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f"Registro completado: {len(self.registered_classes)} clases encontradas")
|
logger.info(f"Registro completado: {len(self.registered_classes)} clases, {len(self.tokenization_patterns)} patrones")
|
||||||
return self._get_registry_info()
|
return self._get_registry_info()
|
||||||
|
|
||||||
def _clear_registries(self):
|
def _clear_registries(self):
|
||||||
|
@ -56,6 +57,7 @@ class TypeRegistry:
|
||||||
self.bracket_classes.clear()
|
self.bracket_classes.clear()
|
||||||
self.base_context.clear()
|
self.base_context.clear()
|
||||||
self.helper_functions.clear()
|
self.helper_functions.clear()
|
||||||
|
self.tokenization_patterns.clear() # NUEVO
|
||||||
|
|
||||||
def _process_type_file(self, type_file: Path):
|
def _process_type_file(self, type_file: Path):
|
||||||
"""Procesa un archivo de tipo individual"""
|
"""Procesa un archivo de tipo individual"""
|
||||||
|
@ -125,6 +127,16 @@ class TypeRegistry:
|
||||||
if hasattr(class_obj, 'Helper') and callable(class_obj.Helper):
|
if hasattr(class_obj, 'Helper') and callable(class_obj.Helper):
|
||||||
self.helper_functions.append(class_obj.Helper)
|
self.helper_functions.append(class_obj.Helper)
|
||||||
|
|
||||||
|
# NUEVO: Registrar patrones de tokenización si existen
|
||||||
|
if hasattr(class_obj, 'get_tokenization_patterns') and callable(class_obj.get_tokenization_patterns):
|
||||||
|
try:
|
||||||
|
patterns = class_obj.get_tokenization_patterns()
|
||||||
|
if patterns:
|
||||||
|
self.tokenization_patterns.extend(patterns)
|
||||||
|
logger.debug(f"Patrones de tokenización añadidos desde {name}: {len(patterns)}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error obteniendo patrones de tokenización de {name}: {e}")
|
||||||
|
|
||||||
logger.debug(f"Registrada: {name} ({category}) desde {module_name}")
|
logger.debug(f"Registrada: {name} ({category}) desde {module_name}")
|
||||||
|
|
||||||
def _auto_detect_classes(self, module, module_name: str):
|
def _auto_detect_classes(self, module, module_name: str):
|
||||||
|
@ -146,8 +158,10 @@ class TypeRegistry:
|
||||||
'bracket_classes': self.bracket_classes.copy(),
|
'bracket_classes': self.bracket_classes.copy(),
|
||||||
'helper_functions': self.helper_functions.copy(),
|
'helper_functions': self.helper_functions.copy(),
|
||||||
'registered_classes': self.registered_classes.copy(),
|
'registered_classes': self.registered_classes.copy(),
|
||||||
|
'tokenization_patterns': self.tokenization_patterns.copy(), # NUEVO
|
||||||
'class_count': len(self.registered_classes),
|
'class_count': len(self.registered_classes),
|
||||||
'bracket_count': len(self.bracket_classes)
|
'bracket_count': len(self.bracket_classes),
|
||||||
|
'pattern_count': len(self.tokenization_patterns) # NUEVO
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_base_context(self) -> Dict[str, Any]:
|
def get_base_context(self) -> Dict[str, Any]:
|
||||||
|
@ -162,6 +176,10 @@ class TypeRegistry:
|
||||||
"""Retorna lista de funciones Helper"""
|
"""Retorna lista de funciones Helper"""
|
||||||
return self.helper_functions.copy()
|
return self.helper_functions.copy()
|
||||||
|
|
||||||
|
def get_tokenization_patterns(self) -> List[Dict]:
|
||||||
|
"""NUEVO: Retorna lista de patrones de tokenización"""
|
||||||
|
return self.tokenization_patterns.copy()
|
||||||
|
|
||||||
|
|
||||||
# Instancia global del registro
|
# Instancia global del registro
|
||||||
_global_registry = TypeRegistry()
|
_global_registry = TypeRegistry()
|
||||||
|
@ -195,3 +213,8 @@ def get_registered_bracket_classes() -> set:
|
||||||
def get_registered_helper_functions() -> List[callable]:
|
def get_registered_helper_functions() -> List[callable]:
|
||||||
"""Obtiene las funciones Helper registradas"""
|
"""Obtiene las funciones Helper registradas"""
|
||||||
return _global_registry.get_helper_functions()
|
return _global_registry.get_helper_functions()
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_tokenization_patterns() -> List[Dict]:
|
||||||
|
"""NUEVO: Obtiene los patrones de tokenización registrados"""
|
||||||
|
return _global_registry.get_tokenization_patterns()
|
||||||
|
|
Loading…
Reference in New Issue