404 lines
18 KiB
Python
404 lines
18 KiB
Python
"""
|
||
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 |