""" Bracket Parser - Transformador de sintaxis con corchetes y detección contextual de ecuaciones """ import ast import re from typing import Tuple, Optional class BracketParser: """Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente""" # Clases que soportan sintaxis con corchetes BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr'} # Operadores de comparación que pueden formar ecuaciones EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='} def __init__(self): self.debug = False 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)' 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) return f'solve({var_name})', True return line, False def _is_assignment(self, line: str) -> bool: """Detecta si una línea es una asignación de variable""" 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() # Verificar que la parte izquierda sea un identificador válido if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part): 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 """ try: tree = ast.parse(line.strip()) if not tree.body: return False node = tree.body[0] # Solo consideramos expresiones (no asignaciones) if not isinstance(node, ast.Expr): return False # Verificar si es una comparación con operadores de ecuación if isinstance(node.value, ast.Compare): # Verificar que use operadores de ecuación for op in node.value.ops: if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)): return True # Verificar si contiene un '=' que no sea parte de una asignación # Esto es para casos como "x + 2 = 5" que no parsea como Compare if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']): # Es un '=' simple, puede ser una ecuación return True return False except SyntaxError: # Si hay error de sintaxis, puede ser una ecuación mal formada # como "x + 2 = 5" que Python no puede parsear if '=' in line: return True return False except Exception: return False def _transform_brackets(self, line: str) -> str: """ Transforma sintaxis Class[args] → Class("args") y maneja métodos """ # Pattern principal: ClassName[contenido] pattern = r'(\b(?:' + '|'.join(self.BRACKET_CLASSES) + r')\b)\[([^\]]*)\]' 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 # Funciones de utilidad para testing def test_bracket_parser(): """Función de testing para el bracket parser""" parser = BracketParser() parser.debug = True test_cases = [ # Sintaxis con corchetes ("Hex[FF]", 'Hex("FF")', "bracket_transform"), ("IP4[192.168.1.1/24]", 'IP4("192.168.1.1/24")', "bracket_transform"), ("IP4[192.168.1.1;24]", 'IP4("192.168.1.1"; "24")', "bracket_transform"), ("IP4[10.0.0.5;255.255.0.0]", 'IP4("10.0.0.5", "255.255.0.0")', "bracket_transform"), ("IP4[192.168.1.1/24].NetworkAddress[]", 'IP4("192.168.1.1/24").NetworkAddress()', "bracket_transform"), ("Bin[1010]", 'Bin("1010")', "bracket_transform"), # Atajos solve ("x=?", "solve(x)", "solve_shortcut"), ("variable_name=?", "solve(variable_name)", "solve_shortcut"), # Asignaciones ("z = 5", '_assign_variable("z", 5)', "assignment"), ("w = z**2 + 3", '_assign_variable("w", z**2 + 3)', "assignment"), ("result = Hex[FF]", '_assign_variable("result", Hex("FF"))', "assignment"), # Ecuaciones standalone ("x + 2 = 5", '_add_equation("x + 2 = 5")', "equation"), ("3*a + b = 10", '_add_equation("3*a + b = 10")', "equation"), ("x > 5", "x > 5", "expression"), # Comparación válida de Python ("a == b", "a == b", "expression"), # Comparación válida de Python # NO ecuaciones ("result = solve(x + 2, x)", '_assign_variable("result", solve(x + 2, x))', "assignment"), # Asignación Python ("2 + 3", "2 + 3", "expression"), # Expresión simple ("sin(pi/2)", "sin(pi/2)", "expression"), # Función # Expresiones normales ("x + 2*y", "x + 2*y", "expression"), ("diff(x**2, x)", "diff(x**2, x)", "expression"), ] print("=== Test Bracket Parser ===") for test_input, expected_result, expected_info in test_cases: result, info = parser.parse_line(test_input) status = "✅" if result == expected_result and info == expected_info else "❌" print(f"{status} '{test_input}' → '{result}' ({info})") if result != expected_result or info != expected_info: print(f" Esperado: '{expected_result}' ({expected_info})") if __name__ == "__main__": test_bracket_parser()