Calc/tl_bracket_parser.py

314 lines
12 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.

"""
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)'
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
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