Calc/simple_debug.py

462 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Simple Debug API - Calculadora MAV CAS
API CLI simple que usa el motor de evaluación existente para generar debug traces
sin modificar el código base. Permite debuggear "como si usaras la aplicación"
mediante archivos JSON.
Uso:
python simple_debug.py debug_input.json
python simple_debug.py debug_input.json --output debug_results.json
python simple_debug.py debug_input.json --verbose
"""
import json
import sys
import argparse
from datetime import datetime
from pathlib import Path
# Importar el motor de evaluación existente
from main_evaluation import HybridEvaluationEngine
import sympy
def _process_evaluation_result_for_debug(result, engine):
"""
Procesa el resultado de evaluación tal como lo haría la aplicación
Retorna información de formato, colores y contenido exacto
"""
output_parts = []
if result.is_error:
# Intentar obtener ayuda como lo hace la app
ayuda = _obtener_ayuda_simulada(result.original_line, engine)
if ayuda:
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
if len(ayuda_linea) > 120:
ayuda_linea = ayuda_linea[:117] + "..."
output_parts.append(("helper", ayuda_linea))
else:
output_parts.append(("error", f"Error: {result.error}"))
elif result.result_type == "comment":
output_parts.append(("comment", result.original_line))
elif result.result_type == "equation_added":
output_parts.append(("equation", result.symbolic_result))
elif result.result_type == "assignment":
output_parts.append(("info", result.symbolic_result))
# Mostrar evaluación numérica para asignaciones si existe
if result.numeric_result is not None and result.numeric_result != result.result:
output_parts.append(("numeric", f"{result.numeric_result}"))
else:
# Resultado normal
if result.result is not None:
# Determinar tag basado en tipo (simulando lógica dinámica)
tag = _get_result_tag_dynamic_debug(result.result, engine)
# Verificar si es resultado interactivo (simulado)
if _is_interactive_result(result.result):
interactive_tag, display_text = _create_interactive_tag_debug(result.result)
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
class_display_name = _get_class_display_name_dynamic_debug(result.result, engine)
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})"))
# Convertir partes a string formateado como la app
formatted_output = _format_output_parts_debug(output_parts)
return {
'parts': output_parts,
'formatted_text': formatted_output,
'tag_info': _get_tag_color_info()
}
def _obtener_ayuda_simulada(input_str, engine):
"""Simula la obtención de ayuda como lo hace la app"""
try:
# Intentar usar los helpers del engine si están disponibles
if hasattr(engine, 'HELPERS'):
for helper in engine.HELPERS:
try:
ayuda = helper(input_str)
if ayuda:
return ayuda
except:
continue
except:
pass
return None
def _get_result_tag_dynamic_debug(result, engine):
"""Simula _get_result_tag_dynamic de la app"""
try:
registered_classes = 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):
name_lower = name.lower()
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"
elif name_lower == "date":
return "date"
else:
return "custom_type"
except Exception:
pass
# Fallback a tags existentes para tipos no registrados
if isinstance(result, sympy.Basic):
return "symbolic"
else:
return "result"
def _get_class_display_name_dynamic_debug(obj, engine):
"""Simula _get_class_display_name_dynamic de la app"""
try:
# Verificar si es una clase registrada dinámicamente
registered_classes = engine.get_available_types().get('registered_classes', {})
for name, cls in registered_classes.items():
if isinstance(obj, cls):
return name
except Exception:
pass
# 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"
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 _is_interactive_result(result):
"""Simula la lógica is_interactive de EvaluationResult"""
try:
# Intentar importar PlotResult si existe
from tl_popup import PlotResult
if isinstance(result, PlotResult):
return True
except ImportError:
pass
return isinstance(result, sympy.Matrix) or \
(isinstance(result, list) and len(result) > 3)
def _create_interactive_tag_debug(result):
"""Simula la creación de tags interactivos"""
try:
from tl_popup import PlotResult
if isinstance(result, PlotResult):
return f"plot_{id(result)}", f"📊 Ver {result.plot_type.title()}"
except ImportError:
pass
if isinstance(result, sympy.Matrix):
rows, cols = result.shape
return f"matrix_{id(result)}", f"📋 Ver Matriz {rows}×{cols}"
elif isinstance(result, list) and len(result) > 5:
return f"list_{id(result)}", f"📋 Ver Lista ({len(result)} elementos)"
elif isinstance(result, dict) and len(result) > 3:
return f"dict_{id(result)}", f"🔍 Ver Diccionario ({len(result)} entradas)"
return None, None
def _format_output_parts_debug(output_parts):
"""Simula cómo _display_output formatea las partes para mostrar"""
formatted_lines = []
for part_idx, (tag, content) in enumerate(output_parts):
if not content:
continue
prefix = ""
if part_idx > 0:
prev_tag, prev_content = output_parts[part_idx-1] if part_idx > 0 else (None, None)
if tag not in ["class_hint", "numeric", "info"] and prev_content:
prefix = " ; "
elif tag in ["numeric", "info"] and prev_content:
prefix = " "
formatted_lines.append(f"{prefix}[{tag}]{content}")
return "".join(formatted_lines)
def _get_tag_color_info():
"""Información sobre los colores de los tags como en setup_output_tags"""
return {
"error": {"fg": "#ff6b6b", "font": "bold"},
"result": {"fg": "#abdbe3"},
"symbolic": {"fg": "#82aaff"},
"numeric": {"fg": "#c3e88d"},
"equation": {"fg": "#c792ea"},
"info": {"fg": "#ffcb6b"},
"comment": {"fg": "#546e7a"},
"class_hint": {"fg": "#888888"},
"type_hint": {"fg": "#6a6a6a"},
"custom_type": {"fg": "#f9a825"},
"hex": {"fg": "#f9a825"},
"bin": {"fg": "#4fc3f7"},
"ip": {"fg": "#fff176"},
"date": {"fg": "#ff8a80"},
"chr_type": {"fg": "#80cbc4"},
"helper": {"fg": "#ffd700", "font": "italic"}
}
def run_debug(input_file: str, output_file: str = None, verbose: bool = False):
"""
Ejecuta las queries de debug desde un archivo JSON y guarda los resultados.
Args:
input_file: Archivo JSON con las queries
output_file: Archivo de salida (opcional)
verbose: Modo verboso
"""
# Cargar queries del archivo de entrada
try:
with open(input_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: No se encontró el archivo '{input_file}'")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: JSON inválido en '{input_file}': {e}")
sys.exit(1)
if 'queries' not in data:
print("Error: El archivo JSON debe contener una clave 'queries'")
sys.exit(1)
# Crear motor de evaluación
if verbose:
print("Iniciando motor de evaluación...")
engine = HybridEvaluationEngine()
results = []
successful = 0
failed = 0
# Ejecutar cada query
for query in data['queries']:
if verbose:
print(f"Ejecutando query {query.get('index', '?')}: {query.get('content', '')[:50]}...")
try:
if query['type'] == 'input':
# Query de tipo input: evaluar expresión como si fuera entrada del usuario
result = engine.evaluate_line(query['content'])
output = {
'index': query['index'],
'input': query['content'],
'output': str(result.result) if hasattr(result, 'result') else str(result),
'result_type': type(result.result).__name__ if hasattr(result, 'result') else type(result).__name__,
'success': not (hasattr(result, 'is_error') and result.is_error),
'error': result.error if hasattr(result, 'is_error') and result.is_error else None
}
# Generar output_raw: resultado exacto como se muestra en la aplicación
try:
output_raw_data = _process_evaluation_result_for_debug(result, engine)
output['output_raw'] = output_raw_data
except Exception as e:
output['output_raw'] = f"Error generando output_raw: {e}"
# Añadir información adicional si está disponible
if hasattr(result, 'result') and hasattr(result.result, '__class__'):
output['display_class'] = f"[{result.result.__class__.__name__}]"
if not (hasattr(result, 'is_error') and result.is_error):
successful += 1
else:
failed += 1
elif query['type'] == 'exec':
# Query de tipo exec: ejecutar código Python para inspeccionar el estado
exec_result = eval(query['content'], {'engine': engine})
output = {
'index': query['index'],
'input': query['content'],
'output': str(exec_result),
'result_type': type(exec_result).__name__,
'success': True,
'error': None,
'exec_result': exec_result
}
successful += 1
else:
raise ValueError(f"Tipo de query desconocido: {query['type']}")
except Exception as e:
# Manejar errores en la ejecución
output = {
'index': query['index'],
'input': query['content'],
'output': None,
'result_type': None,
'success': False,
'error': str(e)
}
failed += 1
if verbose:
print(f" Error: {str(e)}")
results.append(output)
# Preparar salida final
final_output = {
'execution_info': {
'timestamp': datetime.now().isoformat() + 'Z',
'total_queries': len(data['queries']),
'successful': successful,
'failed': failed,
'input_file': input_file
},
'results': results
}
# Determinar archivo de salida
if output_file is None:
base_name = Path(input_file).stem
output_file = f"{base_name}_results.json"
# Guardar resultados
try:
# Usar un encoder personalizado para objetos no serializables
def json_serializer(obj):
"""Serializar objetos no estándar a string"""
try:
# Intentar conversión normal primero
json.dumps(obj)
return obj
except TypeError:
# Si no es serializable, convertir a string
return str(obj)
# Aplicar serialización personalizada a exec_result
for result in final_output['results']:
if 'exec_result' in result:
result['exec_result'] = json_serializer(result['exec_result'])
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(final_output, f, indent=2, ensure_ascii=False, default=str)
if verbose:
print(f"\nResultados guardados en: {output_file}")
print(f"Queries exitosas: {successful}")
print(f"Queries con error: {failed}")
except Exception as e:
print(f"Error al guardar resultados: {e}")
sys.exit(1)
return final_output
def main():
"""Función principal del CLI"""
parser = argparse.ArgumentParser(
description='Simple Debug API para Calculadora MAV CAS',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Ejemplos de uso:
python simple_debug.py debug_input.json
python simple_debug.py debug_input.json --output custom_results.json
python simple_debug.py debug_input.json --verbose
"""
)
parser.add_argument('input_file',
help='Archivo JSON con las queries de debug')
parser.add_argument('--output', '-o',
help='Archivo de salida (por defecto: <input>_results.json)')
parser.add_argument('--verbose', '-v',
action='store_true',
help='Modo verboso')
args = parser.parse_args()
# Verificar que el archivo de entrada existe
if not Path(args.input_file).exists():
print(f"Error: El archivo '{args.input_file}' no existe")
sys.exit(1)
# Ejecutar debug
try:
results = run_debug(args.input_file, args.output, args.verbose)
# Mostrar resumen si no es modo verboso
if not args.verbose:
info = results['execution_info']
print(f"Debug completado: {info['successful']}/{info['total_queries']} exitosas")
if args.output:
print(f"Resultados en: {args.output}")
else:
base_name = Path(args.input_file).stem
print(f"Resultados en: {base_name}_results.json")
except KeyboardInterrupt:
print("\nInterrumpido por el usuario")
sys.exit(1)
except Exception as e:
print(f"Error inesperado: {e}")
sys.exit(1)
if __name__ == '__main__':
main()