344 lines
12 KiB
Python
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() |