Calc/tl_bracket_parser.py

344 lines
12 KiB
Python

"""
Tokenization Parser - SISTEMA NUEVO que reemplaza los corchetes
Convierte automáticamente patrones específicos en objetos tipados
"""
import ast
import re
from typing import Tuple, Optional, Set, Dict
# Importar tokenizador desde sympy_Base
try:
from sympy_Base import preprocess_tokens
TOKENIZER_AVAILABLE = True
except ImportError:
TOKENIZER_AVAILABLE = False
print("⚠️ Tokenizador no disponible, usando parser básico")
class TokenizationParser:
"""
Nuevo parser que reemplaza el sistema de corchetes
Convierte automáticamente patrones en objetos tipados
"""
# Operadores de comparación que pueden formar ecuaciones
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
def __init__(self, use_tokenizer: bool = True):
self.debug = False
self.use_tokenizer = use_tokenizer and TOKENIZER_AVAILABLE
# Estadísticas de tokenización
self.stats = {
'intbase_conversions': 0,
'fourbytes_conversions': 0,
'total_lines_processed': 0
}
def get_tokenization_stats(self) -> Dict[str, int]:
"""Retorna estadísticas de tokenización"""
return self.stats.copy()
def reset_stats(self):
"""Reinicia estadísticas"""
self.stats = {k: 0 for k in self.stats}
def parse_line(self, code_line: str) -> Tuple[str, str]:
"""
Parsea una línea de código aplicando tokenización automática
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"
self.stats['total_lines_processed'] += 1
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. NUEVO: Aplicar tokenización automática
if self.use_tokenizer:
transformed_line = self._apply_tokenization(original_line)
# Contar conversiones para estadísticas
intbase_count = transformed_line.count('IntBase(')
fourbytes_count = transformed_line.count('FourBytes(')
self.stats['intbase_conversions'] += intbase_count
self.stats['fourbytes_conversions'] += fourbytes_count
if transformed_line != original_line:
if self.debug:
print(f"🔧 Tokenización: '{original_line}''{transformed_line}'")
return transformed_line, "tokenized"
# 5. Si no hay transformaciones, devolver original
return original_line, "expression"
except Exception as e:
if self.debug:
print(f"Error parsing line '{original_line}': {e}")
return code_line, "parse_error"
def _apply_tokenization(self, line: str) -> str:
"""
Aplica tokenización automática usando el tokenizador de sympy_Base
"""
if not TOKENIZER_AVAILABLE:
return line
try:
return preprocess_tokens(line)
except Exception as e:
if self.debug:
print(f"Error en tokenización: {e}")
return line
def _transform_solve_shortcut(self, line: str) -> Tuple[str, bool]:
"""
Transforma 'variable=?' en '_solve_variable_in_system("variable")'
"""
# 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_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
NUEVA LÓGICA: Priorizar asignaciones, ser menos estricto
"""
try:
# Pattern: variable = expresión (que no sea comparació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: Si tiene formato de asignación válida, asumir que ES asignación
# Las ecuaciones son solo para casos muy específicos como solve()
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()
# Aplicar tokenización a la expresión
if self.use_tokenizer:
expression = self._apply_tokenization(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
NUEVA LÓGICA: Solo ecuaciones matemáticas obvias, no asignaciones
"""
try:
# Primero verificar si contiene '=' simple
if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']):
return False
# NUEVA LÓGICA: Si ya fue clasificada como asignación, NO es ecuación
if self._is_assignment(line):
return False
try:
tree = ast.parse(line.strip())
if not tree.body:
return False
node = tree.body[0]
# 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:
# NUEVA LÓGICA: Solo tratar como ecuación si NO tiene formato de asignación válida
parts = line.split('=', 1)
if len(parts) == 2:
var_part = parts[0].strip()
# Si la parte izquierda no es un identificador válido, puede ser ecuación matemática
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part):
if self.debug:
print(f"🔍 '{line}' error AST y no es asignación válida → Tratando como ECUACIÓN")
return True
return False
except Exception as e:
if self.debug:
print(f"🚨 Error en _is_standalone_equation: {e}")
return False
def preview_tokenization(self, text: str) -> str:
"""
Muestra preview de cómo se tokenizaría el texto sin ejecutar
Útil para debugging y testing
"""
if not TOKENIZER_AVAILABLE:
return "Tokenizador no disponible"
lines = text.split('\n')
preview_lines = []
for i, line in enumerate(lines, 1):
original = line.strip()
if not original or original.startswith('#'):
preview_lines.append(f"{i:2}: {line}")
continue
tokenized = self._apply_tokenization(original)
if tokenized != original:
preview_lines.append(f"{i:2}: {original}")
preview_lines.append(f"{tokenized}")
else:
preview_lines.append(f"{i:2}: {line}")
return '\n'.join(preview_lines)
def test_patterns(self) -> str:
"""
Prueba los patrones de tokenización con ejemplos
"""
test_cases = [
# IntBase patterns
"16#FF",
"2#1010",
"8#777",
"16#x0",
"2#101x",
# FourBytes patterns
"192.168.1.1",
"255.255.0.0",
"10.1.x.2",
"a.b.c.d",
# Mixed patterns
"16#FF + 192.168.1.1",
"2#1010 * 10.0.0.1",
# Should NOT be tokenized
"obj.method.call()",
"x.y", # Only 2 elements
"a.b.c.d.e", # 5 elements
]
results = []
for test in test_cases:
tokenized = self._apply_tokenization(test) if TOKENIZER_AVAILABLE else test
status = "✓ TOKENIZED" if tokenized != test else "○ NO CHANGE"
results.append(f"{status}: '{test}''{tokenized}'")
return '\n'.join(results)
# Mantener compatibilidad con el sistema anterior
class BracketParser(TokenizationParser):
"""
Alias para compatibilidad con código existente
Ahora usa el nuevo sistema de tokenización
"""
def __init__(self, use_type_registry: bool = True):
# Ignorar use_type_registry, ya no se usa
super().__init__(use_tokenizer=True)
def reload_bracket_classes(self):
"""Método de compatibilidad - no hace nada en el nuevo sistema"""
if self.debug:
print("🔄 reload_bracket_classes() llamado - no necesario en nuevo sistema")
def add_bracket_class(self, class_name: str):
"""Método de compatibilidad - no hace nada en el nuevo sistema"""
if self.debug:
print(f"🔄 add_bracket_class({class_name}) llamado - no necesario en nuevo sistema")
def remove_bracket_class(self, class_name: str):
"""Método de compatibilidad - no hace nada en el nuevo sistema"""
if self.debug:
print(f"🔄 remove_bracket_class({class_name}) llamado - no necesario en nuevo sistema")
def get_bracket_classes(self) -> Set[str]:
"""Método de compatibilidad - retorna conjunto vacío"""
return set()
def has_bracket_class(self, class_name: str) -> bool:
"""Método de compatibilidad - siempre retorna False"""
return False
# ========== FUNCIONES DE UTILIDAD ==========
def test_tokenization_system():
"""Función de testing completa del sistema de tokenización"""
print("🧪 TESTING SISTEMA DE TOKENIZACIÓN")
print("=" * 50)
parser = TokenizationParser(use_tokenizer=True)
parser.debug = True
# Test patterns
print("\n1. Test de patrones básicos:")
print(parser.test_patterns())
# Test de líneas completas
test_lines = [
"ip = 192.168.1.1",
"mask = 16#ffffff00",
"net = 10.x.y.0",
"result = 16#FF + 2#1010",
"a.b.c.d + 255.255.255.0",
"x = solve(y + 2)", # No debería tokenizarse solve
]
print(f"\n2. Test de líneas completas:")
for line in test_lines:
transformed, info = parser.parse_line(line)
print(f" '{line}''{transformed}' [{info}]")
# Estadísticas
print(f"\n3. Estadísticas:")
stats = parser.get_tokenization_stats()
for key, value in stats.items():
print(f" {key}: {value}")
print("\n✅ Testing completado")
if __name__ == "__main__":
test_tokenization_system()