""" Bracket Parser - INTEGRADO con el sistema de auto-descubrimiento de tipos """ import ast import re 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 de fallback mínimas si falla el registro dinámico FALLBACK_BRACKET_CLASSES = {'Hex', 'Bin'} # Operadores de comparación que pueden formar ecuaciones EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='} 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 dinámicamente self._update_bracket_classes() def _get_dynamic_bracket_classes(self) -> Set[str]: """ Obtiene las clases de corchetes dinámicamente del registro de tipos NUEVA FUNCIÓN que reemplaza el hardcoding de DEFAULT_BRACKET_CLASSES """ if not self.use_type_registry: return self.FALLBACK_BRACKET_CLASSES.copy() try: # Obtener TODAS las clases registradas que soportan corchetes registered_classes = get_registered_bracket_classes() if self.debug: print(f"🔧 Clases dinámicas obtenidas del registro: {registered_classes}") # Si no hay clases registradas, usar fallback if not registered_classes: if self.debug: print("⚠️ No se encontraron clases registradas, usando fallback") return self.FALLBACK_BRACKET_CLASSES.copy() return registered_classes except Exception as e: if self.debug: print(f"⚠️ Error obteniendo clases dinámicas: {e}") return self.FALLBACK_BRACKET_CLASSES.copy() def _update_bracket_classes(self): """ Actualiza las clases que soportan sintaxis con corchetes dinámicamente MODIFICADO: Ya no usa DEFAULT_BRACKET_CLASSES hardcodeadas """ # Obtener clases dinámicamente del registro de tipos self.BRACKET_CLASSES = self._get_dynamic_bracket_classes() if self.debug: print(f"🔧 Bracket classes actualizadas dinámicamente: {self.BRACKET_CLASSES}") def reload_bracket_classes(self): """Recarga las clases de corchetes desde el registro dinámicamente""" if self.debug: print("🔄 Recargando bracket classes dinámicamente...") 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 has_bracket_class(self, class_name: str) -> bool: """Verifica si una clase está registrada para sintaxis con corchetes""" return class_name in self.BRACKET_CLASSES def parse_line(self, code_line: str) -> Tuple[str, str]: """ Parsea una línea de código aplicando todas las transformaciones Returns: (transformed_code, parse_info): Código transformado e información de parsing """ original_line = code_line.strip() if not original_line or original_line.startswith('#'): return code_line, "comment" try: # 1. Detectar y transformar atajo solve transformed_line, has_solve_shortcut = self._transform_solve_shortcut(original_line) if has_solve_shortcut: return transformed_line, "solve_shortcut" # 2. Detectar asignaciones de variables if self._is_assignment(original_line): return self._transform_assignment(original_line), "assignment" # 3. Detectar ecuaciones standalone if self._is_standalone_equation(original_line): return f'_add_equation("{original_line}")', "equation" # 4. Transformar sintaxis con corchetes transformed_line = self._transform_brackets(original_line) # 5. Si no hay transformaciones, devolver original if transformed_line == original_line: return original_line, "expression" else: return transformed_line, "bracket_transform" except Exception as e: if self.debug: print(f"Error parsing line '{original_line}': {e}") return code_line, "parse_error" def _transform_solve_shortcut(self, line: str) -> Tuple[str, bool]: """ Transforma 'variable=?' en '_solve_variable_in_system("variable")' en lugar de 'solve(variable)' para resolver usando las ecuaciones del sistema Returns: (transformed_line, was_transformed) """ # Pattern: variable_name = ? pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\?$' match = re.match(pattern, line.strip()) if match: var_name = match.group(1) # CAMBIO: usar función personalizada que resuelve usando el sistema de ecuaciones return f'_solve_variable_in_system("{var_name}")', True return line, False def _is_assignment(self, line: str) -> bool: """ Detecta si una línea es una asignación de variable MEJORADO: Considera si contiene símbolos desconocidos que sugieren ecuación """ try: # Pattern: variable = expresión (que no sea ecuación) if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']): # Verificar que sea una asignación válida de Python parts = line.split('=', 1) if len(parts) == 2: var_part = parts[0].strip() expr_part = parts[1].strip() # Verificar que la parte izquierda sea un identificador válido if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part): # NUEVA LÓGICA: Detectar símbolos desconocidos en la expresión # Si hay símbolos desconocidos, puede ser mejor tratarlo como ecuación symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_part) # Funciones y constantes conocidas de SymPy/matemáticas known_functions = { # Funciones trigonométricas 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', # Funciones exponenciales y logarítmicas 'exp', 'log', 'ln', 'sqrt', # Constantes 'pi', 'e', 'I', 'oo', # Funciones especiales 'abs', 'sign', 'floor', 'ceiling', 'factorial', # Álgebra 'diff', 'integrate', 'limit', 'series', 'solve', 'simplify', 'expand', 'factor', 'collect', 'cancel', 'apart', 'together', # Matrices 'Matrix', 'det', 'inv', # Funciones de Python comunes 'int', 'float', 'str', 'len', 'max', 'min', 'sum' } # Encontrar símbolos que no son funciones conocidas unknown_symbols = [s for s in symbols_in_expr if s not in known_functions] if unknown_symbols: if self.debug: print(f"🔍 '{line}' contiene símbolos desconocidos: {unknown_symbols}") print(f"🔍 Considerando como ECUACIÓN en lugar de asignación") # Si hay símbolos desconocidos, tratarlo como ecuación return False # No es asignación, será detectado como ecuación else: # Solo funciones/constantes conocidas: asignación normal return True return False except: return False def _transform_assignment(self, line: str) -> str: """Transforma asignación a llamada de función especial""" parts = line.split('=', 1) var_name = parts[0].strip() expression = parts[1].strip() # Transformar corchetes en la expresión expression = self._transform_brackets(expression) return f'_assign_variable("{var_name}", {expression})' def _is_standalone_equation(self, line: str) -> bool: """ Determina si una línea es una ecuación standalone usando análisis AST SIMPLIFICADO: Evita recursión y es más directo """ try: # Primero verificar si contiene '=' simple if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']): return False # Si no es una asignación (según la lógica ya evaluada en parse_line) # y tiene '=', entonces es una ecuación try: tree = ast.parse(line.strip()) if not tree.body: return False node = tree.body[0] # Si es una asignación Python válida, NO es ecuación standalone if isinstance(node, ast.Assign): # Pero si contiene símbolos desconocidos, SÍ es ecuación # Verificar si la expresión contiene símbolos desconocidos if len(node.targets) == 1: # Obtener la expresión del lado derecho try: expr_code = ast.get_source_segment(line, node.value) if expr_code: symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_code) known_functions = { 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'exp', 'log', 'ln', 'sqrt', 'pi', 'e', 'I', 'oo', 'abs', 'sign', 'floor', 'ceiling', 'factorial', 'diff', 'integrate', 'limit', 'series', 'solve', 'simplify', 'expand', 'factor', 'collect', 'cancel', 'apart', 'together', 'Matrix', 'det', 'inv', 'int', 'float', 'str', 'len', 'max', 'min', 'sum' } unknown_symbols = [s for s in symbols_in_expr if s not in known_functions] if unknown_symbols: if self.debug: print(f"🔍 '{line}' es asignación pero con símbolos desconocidos: {unknown_symbols} → ECUACIÓN") return True except: # Si falla get_source_segment, usar método alternativo parts = line.split('=', 1) if len(parts) == 2: expr_part = parts[1].strip() symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_part) known_functions = { 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'exp', 'log', 'ln', 'sqrt', 'pi', 'e', 'I', 'oo', 'abs', 'sign', 'floor', 'ceiling', 'factorial', 'diff', 'integrate', 'limit', 'series', 'solve', 'simplify', 'expand', 'factor', 'collect', 'cancel', 'apart', 'together', 'Matrix', 'det', 'inv', 'int', 'float', 'str', 'len', 'max', 'min', 'sum' } unknown_symbols = [s for s in symbols_in_expr if s not in known_functions] if unknown_symbols: if self.debug: print(f"🔍 '{line}' es asignación pero con símbolos desconocidos: {unknown_symbols} → ECUACIÓN") return True return False # Si es una expresión (no asignación), verificar comparaciones if isinstance(node, ast.Expr): if isinstance(node.value, ast.Compare): for op in node.value.ops: if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)): return True return False except SyntaxError: # Error de sintaxis: probablemente ecuación mal formada como "x + 2 = 5" if self.debug: print(f"🔍 '{line}' error AST → Tratando como ECUACIÓN") return True except Exception as e: if self.debug: print(f"🚨 Error en _is_standalone_equation: {e}") return False 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 """ # 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) args_content = match.group(2).strip() if not args_content: # Caso: Class[] → Class() return f'{class_name}()' else: # Split arguments by semicolon if present # Each argument will be individually quoted. # Example: Class[arg1; arg2] -> Class("arg1", "arg2") # Example: Class[arg1] -> Class("arg1") args_list = [arg.strip() for arg in args_content.split(';')] processed_args = [] for arg_val in args_list: # Escape backslashes first, then double quotes for string literals escaped_arg = arg_val.replace('\\', '\\\\').replace('"', '\\"') 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: new_transformed = re.sub(pattern, replace_match, transformed) if new_transformed == transformed: break transformed = new_transformed # Transformar corchetes vacíos en métodos: .método[] → .método() method_pattern = r'\.([a-zA-Z_][a-zA-Z0-9_]*)\[\]' transformed = re.sub(method_pattern, r'.\1()', transformed) return transformed class EquationDetector: """Detector específico para ecuaciones con análisis AST avanzado""" @staticmethod def is_equation_in_context(code: str, context: str = "standalone") -> bool: """ Determina si el código contiene una ecuación considerando el contexto Args: code: Código a analizar context: Contexto ("standalone", "function_arg", "assignment") """ try: tree = ast.parse(code.strip()) for node in ast.walk(tree): if isinstance(node, ast.Compare): # Verificar operadores de ecuación for op in node.ops: if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)): if context == "standalone": # En contexto standalone, es una ecuación return True elif context == "function_arg": # En argumentos de función, generalmente NO es ecuación return False # Verificar '=' simple (no comparación) if '=' in code and context == "standalone": # Verificar que no sea asignación Python válida try: ast.parse(code.strip()) return False # Es sintaxis Python válida except SyntaxError: return True # Puede ser ecuación mal formada para Python return False except Exception: return False