334 lines
14 KiB
Python
334 lines
14 KiB
Python
"""
|
|
Sistema de Evaluación y Procesamiento para la Calculadora MAV CAS Híbrida
|
|
"""
|
|
import logging
|
|
from typing import List, Dict, Any, Optional, Tuple
|
|
from PySide6.QtGui import QTextCursor, QTextCharFormat, QColor, QFont
|
|
from .evaluation import EvaluationResult
|
|
from .gui_latex import LatexProcessor
|
|
import sympy
|
|
|
|
|
|
class EvaluationManager:
|
|
"""Gestor del sistema de evaluación y procesamiento de resultados"""
|
|
|
|
def __init__(self, main_window):
|
|
self.main_window = main_window
|
|
self.logger = logging.getLogger(__name__)
|
|
self._latex_equations = []
|
|
|
|
# Timer para debounce de evaluación
|
|
from PySide6.QtCore import QTimer
|
|
self._debounce_timer = QTimer()
|
|
self._debounce_timer.setSingleShot(True)
|
|
self._debounce_timer.timeout.connect(self.evaluate_and_update)
|
|
|
|
# Configurar formatos de salida
|
|
self._setup_output_formats()
|
|
|
|
def _setup_output_formats(self):
|
|
"""Configura los formatos de texto para la salida"""
|
|
self.output_formats = {
|
|
'error': self._create_format("#f44747", bold=True),
|
|
'comment': self._create_format("#6a9955", italic=True),
|
|
'assignment': self._create_format("#dcdcaa"),
|
|
'equation': self._create_format("#c586c0"),
|
|
'symbolic': self._create_format("#9cdcfe"),
|
|
'numeric': self._create_format("#b5cea8"),
|
|
'boolean': self._create_format("#569cd6"),
|
|
'string': self._create_format("#ce9178"),
|
|
'custom_type': self._create_format("#4ec9b0"),
|
|
'plot': self._create_format("#569cd6", underline=True),
|
|
'type_indicator': self._create_format("#808080"),
|
|
'clickable': self._create_format("#4fc3f7", underline=True),
|
|
'helper': self._create_format("#ffd700", italic=True)
|
|
}
|
|
|
|
def _create_format(self, color: str, bold: bool = False, italic: bool = False, underline: bool = False) -> QTextCharFormat:
|
|
"""Crea un formato de texto"""
|
|
fmt = QTextCharFormat()
|
|
fmt.setForeground(QColor(color))
|
|
if bold:
|
|
fmt.setFontWeight(QFont.Bold)
|
|
if italic:
|
|
fmt.setFontItalic(True)
|
|
if underline:
|
|
fmt.setFontUnderline(True)
|
|
return fmt
|
|
|
|
def evaluate_and_update(self):
|
|
"""Evalúa todas las líneas y actualiza la salida"""
|
|
try:
|
|
input_content = self.main_window.input_text.toPlainText()
|
|
if not input_content.strip():
|
|
self._clear_output()
|
|
return
|
|
|
|
# Limpiar contexto del motor
|
|
self.main_window.engine.equations.clear()
|
|
self.main_window.engine.symbol_table.clear()
|
|
self.main_window.engine.variables.clear()
|
|
self.logger.debug("Contexto del motor limpiado")
|
|
|
|
# Limpiar ecuaciones LaTeX en memoria
|
|
self._latex_equations.clear()
|
|
|
|
# Solo limpiar panel visual si está visible
|
|
if self.main_window.latex_panel_visible:
|
|
self.main_window.latex_panel.clear_equations()
|
|
|
|
lines = input_content.splitlines()
|
|
self._evaluate_lines(lines)
|
|
|
|
except Exception as e:
|
|
self._show_error(f"Error durante evaluación: {e}")
|
|
|
|
def _evaluate_lines(self, lines: List[str]):
|
|
"""Evalúa múltiples líneas de código"""
|
|
output_data = []
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
line_stripped = line.strip()
|
|
|
|
# Líneas vacías o comentarios
|
|
if not line_stripped or line_stripped.startswith('#'):
|
|
if line_stripped:
|
|
output_data.append([("comment", line_stripped)])
|
|
# Añadir comentario al panel LaTeX
|
|
if line_stripped.startswith('#'):
|
|
comment_text = line_stripped[1:].strip()
|
|
self._add_to_latex_panel("comment", comment_text)
|
|
else:
|
|
output_data.append([("", "")])
|
|
continue
|
|
|
|
# Evaluar línea
|
|
result = self.main_window.engine.evaluate_line(line_stripped)
|
|
line_output = self._process_evaluation_result(result)
|
|
output_data.append(line_output)
|
|
|
|
self._display_output(output_data)
|
|
|
|
def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]:
|
|
"""Procesa el resultado de evaluación para display"""
|
|
output_parts = []
|
|
indicator_text: Optional[str] = None
|
|
|
|
# Añadir al panel LaTeX si es aplicable
|
|
self._add_to_latex_panel_if_applicable(result)
|
|
|
|
if result.result_type == "comment":
|
|
output_parts.append(("comment", result.output if result.output is not None else ""))
|
|
return output_parts
|
|
|
|
if not result.success:
|
|
# Manejo de errores con ayuda contextual
|
|
error_msg = f"Error: {result.error_message}"
|
|
|
|
# Intentar obtener ayuda
|
|
ayuda_text = self._obtener_ayuda(result.input_line)
|
|
if ayuda_text:
|
|
ayuda_linea = ayuda_text.replace("\n", " ").strip()
|
|
if len(ayuda_linea) > 120:
|
|
ayuda_linea = ayuda_linea[:117] + "..."
|
|
|
|
output_parts.append(("error", error_msg))
|
|
output_parts.append(("\n", "\n"))
|
|
output_parts.append(("helper", f"Sugerencia: {ayuda_linea}"))
|
|
else:
|
|
output_parts.append(("error", error_msg))
|
|
|
|
else:
|
|
# Intentar crear tag interactivo
|
|
if self.main_window.interactive_manager:
|
|
interactive_info = self.main_window.interactive_manager.create_interactive_link(
|
|
result.actual_result_object
|
|
)
|
|
|
|
if interactive_info:
|
|
link_id, display_text, result_object = interactive_info
|
|
output_parts.append(("clickable", display_text, link_id, result_object))
|
|
|
|
# Añadir indicador de tipo algebraico
|
|
if result.algebraic_type:
|
|
indicator_text = f"[{result.algebraic_type}]"
|
|
output_parts.append((" ", " "))
|
|
output_parts.append(("type_indicator", indicator_text))
|
|
return output_parts
|
|
|
|
# Si no es interactivo, usar formato normal
|
|
main_output_tag = "base"
|
|
|
|
if result.is_assignment:
|
|
main_output_tag = "assignment"
|
|
indicator_text = "[=]"
|
|
elif result.is_equation:
|
|
main_output_tag = "equation"
|
|
indicator_text = "[eq]"
|
|
elif result.result_type == "plot":
|
|
main_output_tag = "plot"
|
|
else:
|
|
# Determinar tag según tipo algebraico
|
|
if result.algebraic_type:
|
|
type_lower = result.algebraic_type.lower()
|
|
if isinstance(result.actual_result_object, sympy.Basic):
|
|
main_output_tag = "symbolic"
|
|
elif type_lower in ["int", "float", "complex"]:
|
|
main_output_tag = "numeric"
|
|
elif type_lower == "bool":
|
|
main_output_tag = "boolean"
|
|
elif type_lower == "str":
|
|
main_output_tag = "string"
|
|
else:
|
|
main_output_tag = "custom_type"
|
|
|
|
if result.algebraic_type:
|
|
is_collection = any(kw in result.algebraic_type.lower()
|
|
for kw in ["matrix", "list", "dict", "tuple", "vector", "array"])
|
|
if is_collection or isinstance(result.actual_result_object, sympy.Basic):
|
|
indicator_text = f"[{result.algebraic_type}]"
|
|
|
|
output_parts.append((main_output_tag, result.output if result.output is not None else ""))
|
|
|
|
if indicator_text:
|
|
output_parts.append((" ", " "))
|
|
output_parts.append(("type_indicator", indicator_text))
|
|
|
|
return output_parts
|
|
|
|
def _add_to_latex_panel_if_applicable(self, result: EvaluationResult):
|
|
"""Agrega resultado al panel LaTeX si es aplicable"""
|
|
try:
|
|
should_add, equation_type = LatexProcessor.should_add_to_latex(result)
|
|
|
|
if should_add:
|
|
# Generar contenido LaTeX que incluya toda la información del output
|
|
latex_content = LatexProcessor.generate_complete_latex_content(result)
|
|
self._add_to_latex_panel(equation_type, latex_content)
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error procesando para panel LaTeX: {e}")
|
|
|
|
def _add_to_latex_panel(self, equation_type: str, latex_content: str):
|
|
"""Añade una ecuación al panel LaTeX"""
|
|
self._latex_equations.append({
|
|
'type': equation_type,
|
|
'content': latex_content
|
|
})
|
|
|
|
# Solo actualizar el panel si está visible/activo
|
|
if self.main_window.latex_panel_visible:
|
|
self.main_window.latex_panel.add_equation(equation_type, latex_content)
|
|
|
|
# Actualizar indicador visual
|
|
self._update_latex_indicator()
|
|
|
|
def _update_latex_indicator(self):
|
|
"""Actualiza el indicador visual de contenido LaTeX"""
|
|
equation_count = len(self._latex_equations)
|
|
|
|
if equation_count > 0:
|
|
self.main_window.latex_button.setToolTip(f"📐 Panel LaTeX ({equation_count} ecuaciones)")
|
|
else:
|
|
self.main_window.latex_button.setToolTip("📐 Panel LaTeX (sin ecuaciones)")
|
|
|
|
def _display_output(self, output_data: List[List[tuple]]):
|
|
"""Muestra los datos de salida en el widget"""
|
|
self.main_window.output_text.clear()
|
|
self.main_window.output_text.clickable_links.clear()
|
|
|
|
cursor = self.main_window.output_text.textCursor()
|
|
|
|
for line_idx, line_parts in enumerate(output_data):
|
|
if line_idx > 0:
|
|
cursor.insertText("\n")
|
|
|
|
if not line_parts or (len(line_parts) == 1 and line_parts[0][0] == "" and line_parts[0][1] == ""):
|
|
continue
|
|
|
|
for part_idx, part_data in enumerate(line_parts):
|
|
if len(part_data) >= 4 and part_data[0] == "clickable":
|
|
# Link clickeable
|
|
_, display_text, link_id, result_object = part_data
|
|
start_pos = cursor.position()
|
|
cursor.insertText(display_text, self.output_formats.get('clickable'))
|
|
end_pos = cursor.position()
|
|
self.main_window.output_text.clickable_links[(start_pos, end_pos)] = (link_id, result_object)
|
|
|
|
elif len(part_data) >= 2:
|
|
tag, content = part_data[0], part_data[1]
|
|
if not content:
|
|
continue
|
|
|
|
if part_idx > 0:
|
|
prev_tag = line_parts[part_idx-1][0] if part_idx > 0 else None
|
|
if tag not in ["type_indicator"] and prev_tag:
|
|
cursor.insertText(" ; ")
|
|
elif tag == "type_indicator" and prev_tag:
|
|
cursor.insertText(" ")
|
|
|
|
format_obj = self.output_formats.get(tag, None)
|
|
if format_obj:
|
|
cursor.insertText(str(content), format_obj)
|
|
else:
|
|
cursor.insertText(str(content))
|
|
|
|
def _clear_output(self):
|
|
"""Limpia el panel de salida"""
|
|
self.main_window.output_text.clear()
|
|
|
|
def _show_error(self, error_msg: str):
|
|
"""Muestra un error en el panel de salida"""
|
|
self.main_window.output_text.clear()
|
|
cursor = self.main_window.output_text.textCursor()
|
|
cursor.insertText(error_msg, self.output_formats['error'])
|
|
|
|
# Intentar obtener ayuda para el error
|
|
try:
|
|
input_content = self.main_window.input_text.toPlainText()
|
|
last_line = input_content.strip().split('\n')[-1] if input_content.strip() else ""
|
|
|
|
if last_line:
|
|
ayuda = self._obtener_ayuda(last_line)
|
|
if ayuda:
|
|
cursor.insertText("\n\n💡 Ayuda:\n", self.output_formats['helper'])
|
|
cursor.insertText(ayuda, self.output_formats['helper'])
|
|
|
|
except Exception as e:
|
|
self.logger.debug(f"Error obteniendo ayuda: {e}")
|
|
|
|
def _obtener_ayuda(self, input_str: str) -> Optional[str]:
|
|
"""Obtiene ayuda usando helpers dinámicos"""
|
|
for helper in self.main_window.HELPERS:
|
|
try:
|
|
ayuda = helper(input_str)
|
|
if ayuda:
|
|
return ayuda
|
|
except Exception as e:
|
|
self.logger.debug(f"Error en helper: {e}")
|
|
return None
|
|
|
|
def trigger_initial_latex_render(self):
|
|
"""Activa el renderizado inicial del panel LaTeX cuando MathJax está listo"""
|
|
try:
|
|
# Solo hacer evaluación inicial si hay contenido en el input
|
|
input_content = self.main_window.input_text.toPlainText()
|
|
if input_content.strip():
|
|
logging.debug("🎯 Activando renderizado inicial de LaTeX")
|
|
self.evaluate_and_update()
|
|
except Exception as e:
|
|
logging.error(f"Error en renderizado inicial de LaTeX: {e}")
|
|
|
|
def clear_latex_equations(self):
|
|
"""Limpia las ecuaciones LaTeX"""
|
|
self._latex_equations.clear()
|
|
self._update_latex_indicator()
|
|
|
|
def get_latex_equations(self):
|
|
"""Obtiene las ecuaciones LaTeX actuales"""
|
|
return self._latex_equations.copy() if self._latex_equations else []
|
|
|
|
def schedule_evaluation(self):
|
|
"""Programa una evaluación con debounce"""
|
|
self._debounce_timer.stop()
|
|
self._debounce_timer.start(300) # 300ms de debounce |