27 KiB
27 KiB
Refactorización: Sistema de Tokenización Algebraica
Resumen Ejecutivo
Reemplazar el sistema actual de corchetes por un tokenizador que convierte automáticamente patrones específicos en objetos tipados con capacidades algebraicas completas.
Problemas del Sistema Actual
Sistema de Corchetes
- ❌ Complejidad innecesaria del
BracketParser
- ❌ Conflictos con
eval()
y SymPy - ❌ Sintaxis no estándar
- ❌ Beneficios limitados (solo IPs realmente)
- ❌ Dificulta la integración algebraica
Impacto
- Código complejo para casos simples
- Curva de aprendizaje innecesaria
- Mantenimiento difícil
- Extensibilidad limitada
Nuevo Sistema: Tokenización Algebraica
Filosofía Central
El usuario escribe código Python normal. El parser mejora automáticamente los tipos de datos.
Patrones de Tokenización
1. IntBase: base#valor
# Input usuario # Post-tokenización
16#FF → IntBase('FF', 16)
2#1010 → IntBase('1010', 2)
8#777 → IntBase('777', 8)
16#x0 → IntBase('x0', 16) # ← ALGEBRAICO
2#101x → IntBase('101x', 2) # ← ALGEBRAICO
2. FourBytes: x.x.x.x
# Input usuario # Post-tokenización
192.168.1.1 → FourBytes('192.168.1.1')
255.255.0.0 → FourBytes('255.255.0.0')
10.1.x.2 → FourBytes('10.1.x.2') # ← ALGEBRAICO
a.b.c.d → FourBytes('a.b.c.d') # ← ALGEBRAICO
Precedencia de Tokenización
def preprocess_tokens(expression):
# 1. MAYOR PRECEDENCIA: IntBase (patrón más específico)
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', r'IntBase("\2", \1)', expression)
# 2. MENOR PRECEDENCIA: FourBytes (patrón más general)
expression = re.sub(r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b',
r'FourBytes("\1")', expression)
return expression
Clases Base Universales
IntBase: Universal para Bases Numéricas
class IntBase(SympyClassBase):
"""
Representación universal de números en cualquier base
con soporte algebraico completo y operaciones aritméticas
"""
def __init__(self, value_str, base=10):
self.value_str = value_str
self.base = base
self.has_symbols = bool(re.search(r'[a-zA-Z_]', value_str))
if self.has_symbols:
# Modo algebraico: mantener como expresión simbólica
self._symbolic_value = self._parse_symbolic_base()
super().__init__(self._symbolic_value, f"{base}#{value_str}")
else:
# Modo numérico: convertir a entero
self._numeric_value = int(value_str, base)
super().__init__(self._numeric_value, f"{base}#{value_str}")
def _parse_symbolic_base(self):
"""Convierte valor con símbolos a expresión SymPy"""
# Ejemplo: "x0" base 16 → x*16^1 + 0*16^0
# Ejemplo: "101x" base 2 → 1*2^3 + 0*2^2 + 1*2^1 + x*2^0
symbols = []
result = 0
digits = list(self.value_str)
for i, digit in enumerate(reversed(digits)):
power = self.base ** i
if digit.isdigit():
coefficient = int(digit)
elif digit in 'ABCDEF':
coefficient = ord(digit) - ord('A') + 10
elif digit in 'abcdef':
coefficient = ord(digit) - ord('a') + 10
else:
# Es un símbolo
coefficient = sympy.Symbol(digit)
symbols.append(digit)
result += coefficient * power
return result
def to_base(self, new_base):
"""Convierte a nueva base"""
if self.has_symbols:
# En modo algebraico, mantener expresión
return IntBase(f"({self._symbolic_value})_base{new_base}", new_base)
else:
# En modo numérico, convertir directamente
new_value_str = self._convert_to_base_string(self._numeric_value, new_base)
return IntBase(new_value_str, new_base)
@staticmethod
def _convert_to_base_string(value, base):
"""Convierte entero a string en base específica"""
if value == 0:
return "0"
digits = "0123456789ABCDEF"
result = ""
while value:
result = digits[value % base] + result
value //= base
return result
# ========== OPERADORES ARITMÉTICOS ==========
def __add__(self, other):
"""Suma: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__add__(other) # Delegar a SymPy
if isinstance(other, IntBase):
if other.has_symbols:
return super().__add__(other)
result_value = self._numeric_value + other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value + other
else:
return super().__add__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __sub__(self, other):
"""Resta: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__sub__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__sub__(other)
result_value = self._numeric_value - other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value - other
else:
return super().__sub__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __mul__(self, other):
"""Multiplicación: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__mul__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__mul__(other)
result_value = self._numeric_value * other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value * other
else:
return super().__mul__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __truediv__(self, other):
"""División: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__truediv__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__truediv__(other)
result_value = self._numeric_value // other._numeric_value # División entera
elif isinstance(other, int):
result_value = self._numeric_value // other
else:
return super().__truediv__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __mod__(self, other):
"""Módulo: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__mod__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__mod__(other)
result_value = self._numeric_value % other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value % other
else:
return super().__mod__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
# Operadores reversos para int + IntBase
def __radd__(self, other):
return self.__add__(other)
def __rsub__(self, other):
if isinstance(other, int):
result_value = other - self._numeric_value
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
return super().__rsub__(other)
def __rmul__(self, other):
return self.__mul__(other)
FourBytes: Universal para Patrones x.x.x.x
class FourBytes(SympyClassBase):
"""
Representación universal de patrones de 4 elementos
con soporte algebraico completo y operaciones aritméticas
"""
def __init__(self, dotted_string):
self.original = dotted_string
self.elements = dotted_string.split('.')
if len(self.elements) != 4:
raise ValueError(f"FourBytes requiere exactamente 4 elementos: {dotted_string}")
self.has_symbols = any(not elem.isdigit() for elem in self.elements)
if self.has_symbols:
# Modo algebraico: crear expresión simbólica
self._symbolic_elements = []
for elem in self.elements:
if elem.isdigit():
self._symbolic_elements.append(int(elem))
else:
self._symbolic_elements.append(sympy.Symbol(elem))
# Crear expresión como vector de 4 elementos
self._symbolic_value = sympy.Matrix(self._symbolic_elements)
super().__init__(self._symbolic_value, dotted_string)
else:
# Modo numérico: validar rangos y convertir
self._numeric_elements = [int(elem) for elem in self.elements]
# Crear valor como entero de 32 bits (para IPs)
self._numeric_value = (self._numeric_elements[0] << 24 |
self._numeric_elements[1] << 16 |
self._numeric_elements[2] << 8 |
self._numeric_elements[3])
super().__init__(self._numeric_value, dotted_string)
def is_valid_ip_range(self):
"""Verifica si todos los elementos numéricos están en rango IP"""
if self.has_symbols:
return None # No se puede validar con símbolos
return all(0 <= x <= 255 for x in self._numeric_elements)
def substitute(self, **kwargs):
"""Sustituye símbolos por valores"""
if not self.has_symbols:
return self
new_elements = []
for elem in self.elements:
if elem in kwargs:
new_elements.append(str(kwargs[elem]))
else:
new_elements.append(elem)
return FourBytes('.'.join(new_elements))
def __getitem__(self, index):
"""Acceso a elementos individuales"""
if self.has_symbols:
return self._symbolic_elements[index]
else:
return self._numeric_elements[index]
def to_ip_int(self):
"""Convierte a entero de 32 bits (para IPs)"""
if self.has_symbols:
# Crear expresión algebraica
return (self._symbolic_elements[0] * 2**24 +
self._symbolic_elements[1] * 2**16 +
self._symbolic_elements[2] * 2**8 +
self._symbolic_elements[3])
else:
return self._numeric_value
@staticmethod
def _int_to_fourbytes(value):
"""Convierte entero de 32 bits a formato x.y.z.w"""
w = value & 0xFF
z = (value >> 8) & 0xFF
y = (value >> 16) & 0xFF
x = (value >> 24) & 0xFF
return f"{x}.{y}.{z}.{w}"
# ========== OPERADORES ARITMÉTICOS ==========
def __add__(self, other):
"""Suma: convierte a int, opera, reconvierte a FourBytes"""
if self.has_symbols:
return super().__add__(other) # Delegar a SymPy
if isinstance(other, FourBytes):
if other.has_symbols:
return super().__add__(other)
result_int = self._numeric_value + other._numeric_value
elif isinstance(other, int):
result_int = self._numeric_value + other
else:
return super().__add__(other)
# Mantener en rango de 32 bits
result_int = result_int & 0xFFFFFFFF
result_str = self._int_to_fourbytes(result_int)
return FourBytes(result_str)
def __sub__(self, other):
"""Resta: convierte a int, opera, reconvierte a FourBytes"""
if self.has_symbols:
return super().__sub__(other)
if isinstance(other, FourBytes):
if other.has_symbols:
return super().__sub__(other)
result_int = self._numeric_value - other._numeric_value
elif isinstance(other, int):
result_int = self._numeric_value - other
else:
return super().__sub__(other)
# Mantener en rango de 32 bits (underflow se convierte en valor alto)
result_int = result_int & 0xFFFFFFFF
result_str = self._int_to_fourbytes(result_int)
return FourBytes(result_str)
# ========== CONVERSIONES DE BASE ==========
def ToBase(self, base):
"""
Convierte cada elemento a la base especificada
Retorna expresión con cada elemento convertido
"""
if self.has_symbols:
# Para elementos simbólicos, retornar expresión algebraica
converted_elements = []
for elem in self.elements:
if elem.isdigit():
int_val = int(elem)
converted = IntBase._convert_to_base_string(int_val, base)
converted_elements.append(f"{base}#{converted}")
else:
# Símbolo: expresar como conversión algebraica
converted_elements.append(f"{base}#{elem}")
return '.'.join(converted_elements)
else:
# Para elementos numéricos, conversión directa
converted_elements = []
for elem_val in self._numeric_elements:
converted = IntBase._convert_to_base_string(elem_val, base)
converted_elements.append(f"{base}#{converted}")
return '.'.join(converted_elements)
def ToCIDR(self, prefix_length):
"""Convierte a notación CIDR"""
return f"{self.original}/{prefix_length}"
def ToHex(self):
"""Convierte cada elemento a hexadecimal"""
return self.ToBase(16)
def ToBinary(self):
"""Convierte cada elemento a binario"""
return self.ToBase(2)
def ToOctal(self):
"""Convierte cada elemento a octal"""
return self.ToBase(8)
Integración con Clases Especializadas
Constructores Mejorados
class IP4(SympyClassBase):
def __init__(self, address, mask=None):
# address es FourBytes (ya tokenizado)
if not isinstance(address, FourBytes):
raise TypeError("address debe ser FourBytes")
self.address = address
if mask is not None:
if isinstance(mask, int):
self.mask = IP4Mask(mask) # CIDR
elif isinstance(mask, FourBytes):
self.mask = IP4Mask(mask) # Dotted decimal
elif isinstance(mask, IntBase):
# Conversión automática desde hex: 16#ffffff00 → máscara
if not mask.has_symbols:
mask_int = mask._numeric_value
# Convertir a FourBytes primero
mask_fourbytes = FourBytes(FourBytes._int_to_fourbytes(mask_int))
self.mask = IP4Mask(mask_fourbytes)
else:
self.mask = mask # Mantener simbólico
else:
self.mask = mask
class Hex(SympyClassBase):
def __init__(self, value):
if isinstance(value, IntBase):
# value es IntBase (ya tokenizado)
self.int_base = value
super().__init__(value.value, value.original)
elif isinstance(value, FourBytes):
# Conversión automática desde FourBytes
if not value.has_symbols:
# Convertir a valor hex único (32 bits)
hex_value = hex(value._numeric_value)[2:].upper()
self.int_base = IntBase(hex_value, 16)
super().__init__(value._numeric_value, f"16#{hex_value}")
else:
# Mantener simbólico para análisis algebraico
self.int_base = value
super().__init__(value._symbolic_value, str(value))
else:
raise TypeError("value debe ser IntBase o FourBytes")
class Bin(SympyClassBase):
def __init__(self, value):
if isinstance(value, IntBase):
# Conversión automática de cualquier base a binario
if not value.has_symbols:
bin_value = bin(value._numeric_value)[2:] # Remover '0b'
self.int_base = IntBase(bin_value, 2)
super().__init__(value._numeric_value, f"2#{bin_value}")
else:
self.int_base = value
super().__init__(value._symbolic_value, str(value))
elif isinstance(value, FourBytes):
# Convertir cada elemento a binario
if not value.has_symbols:
# Para FourBytes, crear representación elemento por elemento
bin_elements = []
for elem in value._numeric_elements:
bin_elements.append(bin(elem)[2:].zfill(8)) # 8 bits por elemento
bin_representation = '.'.join(f"2#{elem}" for elem in bin_elements)
# Crear IntBase con valor completo
full_bin = ''.join(bin_elements)
self.int_base = IntBase(full_bin, 2)
super().__init__(value._numeric_value, bin_representation)
else:
self.int_base = value
super().__init__(value._symbolic_value, str(value))
else:
raise TypeError("value debe ser IntBase o FourBytes")
class IP4Mask(ClassBase):
def __init__(self, mask_input):
if isinstance(mask_input, int):
# CIDR notation
self.prefix = mask_input
self.mask_int = self._prefix_to_mask_int(mask_input)
elif isinstance(mask_input, FourBytes):
# Dotted decimal desde tokenización automática
if not mask_input.has_symbols:
self.mask_int = mask_input._numeric_value
self.prefix = self._mask_int_to_prefix(self.mask_int)
else:
# Mantener simbólico
self.prefix = None
self.mask_int = mask_input._symbolic_value
elif isinstance(mask_input, IntBase):
# Desde hex u otra base
if not mask_input.has_symbols:
self.mask_int = mask_input._numeric_value
self.prefix = self._mask_int_to_prefix(self.mask_int)
else:
# Mantener simbólico
self.prefix = None
self.mask_int = mask_input._symbolic_value
else:
raise TypeError("mask_input debe ser int, FourBytes, o IntBase")
super().__init__(self.mask_int, str(mask_input))
Conversiones Automáticas Bidireccionales
# Sistema de conversión fluido entre tipos:
class UniversalConverter:
"""Conversiones automáticas entre todos los tipos"""
@staticmethod
def auto_convert(source, target_type):
"""Convierte automáticamente entre tipos compatibles"""
if target_type == Hex:
if isinstance(source, FourBytes):
return Hex(source) # Usa constructor mejorado
elif isinstance(source, IntBase):
return Hex(source)
elif target_type == IP4:
if isinstance(source, FourBytes):
return IP4(source)
elif isinstance(source, str) and '.' in source:
# Tokenización automática
tokenized = preprocess_tokens(source)
return eval(f"IP4({tokenized})")
elif target_type == Bin:
if isinstance(source, (IntBase, FourBytes)):
return Bin(source)
# Continuar para otros tipos...
# Ejemplos de uso fluido:
# 1. IP con máscara hexadecimal (automático):
ip1 = IP4(192.168.1.1, 16#ffffff00)
# Tokeniza a: IP4(FourBytes('192.168.1.1'), IntBase('ffffff00', 16))
# Constructor IP4 convierte IntBase → IP4Mask automáticamente
# 2. Hex desde IP (automático):
ip_bytes = FourBytes('192.168.1.1')
hex_ip = Hex(ip_bytes)
# Constructor Hex convierte FourBytes → representación hex
# 3. Análisis de máscara en múltiples bases:
mask = FourBytes('255.255.255.0')
mask_hex = Hex(mask) # → Hex basado en 32-bit value
mask_bin = Bin(mask) # → Bin con representación por elementos
mask_cidr = IP4Mask(mask) # → /24
# 4. Operaciones mixtas automáticas:
base_ip = FourBytes('10.0.0.0')
offset = 16#100 # Tokeniza a IntBase('100', 16) = 256
next_ip = base_ip + offset # → FourBytes('10.0.1.0')
Casos de Uso Algebraicos
1. Redes con Variables
# Usuario escribe:
network = IP4(192.168.x.0, 24)
# Tokenizado automáticamente a:
network = IP4(FourBytes('192.168.x.0'), 24)
# Operaciones algebraicas:
network.substitute(x=1) # → IP4(192.168.1.0, 24)
network.address[2] # → Symbol('x')
2. Aritmética de Bases
# Usuario escribe:
hex_val = Hex(16#x0)
# Tokenizado automáticamente a:
hex_val = Hex(IntBase('x0', 16))
# Operaciones algebraicas:
hex_val.substitute(x=15) # → Hex(240)
hex_val + 1 # → 16*x + 1
3. Operaciones Aritméticas con IntBase
# Operaciones mantienen la base original:
a = 16#FF # → IntBase('FF', 16)
b = 16#10 # → IntBase('10', 16)
result = a + b # → IntBase('10F', 16) [255 + 16 = 271]
# Operaciones mixtas con enteros:
c = 16#A0 + 32 # → IntBase('C0', 16) [160 + 32 = 192]
d = 2#1010 * 3 # → IntBase('11110', 2) [10 * 3 = 30]
# Operaciones entre bases diferentes:
e = 16#FF + 2#1010 # → IntBase('109', 16) [255 + 10 = 265]
4. Operaciones Aritméticas con FourBytes
# Aritmética de direcciones:
ip1 = FourBytes('192.168.1.1') # → ip_int = 3232235777
ip2 = FourBytes('0.0.0.5') # → ip_int = 5
next_ip = ip1 + ip2 # → FourBytes('192.168.1.6')
# Incremento simple:
base_ip = FourBytes('10.0.0.0')
next_net = base_ip + 256 # → FourBytes('10.0.1.0')
# Cálculos de rango:
start = FourBytes('192.168.1.0')
end = start + 255 # → FourBytes('192.168.1.255')
5. Conversiones Automáticas Bidireccionales
# FourBytes → Otras bases:
ip = FourBytes('10.1.3.15')
ip.ToHex() # → "16#A.16#1.16#3.16#F"
ip.ToBinary() # → "2#1010.2#1.2#11.2#1111"
ip.ToBase(8) # → "8#12.8#1.8#3.8#17"
# Conversión directa en constructores:
hex_ip = Hex(FourBytes('10.1.3.15')) # Automático via tokenización
ip_from_hex = IP4(FourBytes('10.1.3.16#ff')) # 16#ff se tokeniza a IntBase
# Conversiones fluidas:
mask = FourBytes('255.255.255.0')
mask.ToBase(2) # → "2#11111111.2#11111111.2#11111111.2#0"
6. Análisis de Rangos con Constraints
# Red con parámetros:
net = IP4(10.a.b.0, c)
# Encontrar valores válidos:
constraints = [
net.address[1] >= 0, # a >= 0
net.address[1] <= 255, # a <= 255
net.address[2] >= 0, # b >= 0
net.address[2] <= 255, # b <= 255
c >= 8, # c >= 8
c <= 30 # c <= 30
]
solutions = solve(constraints, [a, b, c])
7. Ejemplos Prácticos de Conversión
# Ejemplo 1: IP con máscara hexadecimal
ip_hex_mask = IP4(192.168.1.1, 16#ffffff00) # Tokeniza automáticamente
# Equivale a: IP4(FourBytes('192.168.1.1'), IntBase('ffffff00', 16))
# Ejemplo 2: Conversión de máscara
mask_fourbytes = FourBytes('255.255.255.0')
mask_hex = mask_fourbytes.ToHex() # → "16#FF.16#FF.16#FF.16#0"
mask_cidr = mask_fourbytes.ToCIDR(24) # → "255.255.255.0/24"
# Ejemplo 3: Aritmética mixta
base_net = FourBytes('192.168.0.0')
subnet_size = 2#100000000 # 256 en binario
next_subnet = base_net + subnet_size # → FourBytes('192.168.1.0')
# Ejemplo 4: Análisis de subredes
network = FourBytes('10.0.0.0')
for i in range(4):
subnet = network + (i * 256)
print(f"Subred {i}: {subnet}")
# Subred 0: 10.0.0.0
# Subred 1: 10.0.1.0
# Subred 2: 10.0.2.0
# Subred 3: 10.0.3.0
Ventajas del Nuevo Sistema
1. Simplicidad
- ✅ Sin parser complejo
- ✅ Sintaxis estándar (Python puro)
- ✅ Tokenización simple y rápida
- ✅ Integración natural con eval()
2. Potencia Algebraica
- ✅ Variables en cualquier posición
- ✅ Sustituciones automáticas
- ✅ Integración completa con SymPy
- ✅ Análisis simbólico de redes/números
3. Extensibilidad
- ✅ Fácil agregar nuevos patrones
- ✅ Clases especializadas más simples
- ✅ Reutilización de lógica base
- ✅ Escalabilidad natural
4. Intuitividad
- ✅ Usuario escribe código normal
- ✅ Sin sintaxis especial que memorizar
- ✅ Comportamiento predecible
- ✅ Curva de aprendizaje mínima
Posibles Problemas y Soluciones
1. Ambigüedad x.y vs x.y.z.w
# PROBLEMA: ¿x.y es acceso a atributo o parte de FourBytes?
# SOLUCIÓN: Solo tokenizar si hay exactamente 4 elementos
pattern = r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b'
2. Precedencia de Operadores
# PROBLEMA: 192.168.1.1 + 1
# SOLUCIÓN: Tokenización antes de parsing de operadores
# Resultado: FourBytes('192.168.1.1') + 1
3. Validación con Símbolos
# PROBLEMA: ¿Cómo validar 10.x.1.2 como IP?
# SOLUCIÓN: Validación condicional y constraint solving
def is_valid_ip_symbolic(fourbytes):
if not fourbytes.has_symbols:
return fourbytes.is_valid_ip_range()
# Crear constraints para símbolos
constraints = []
for i, elem in enumerate(fourbytes.elements):
if not elem.isdigit():
symbol = Symbol(elem)
constraints.extend([symbol >= 0, symbol <= 255])
return constraints
Plan de Implementación
Fase 1: Clases Base
- Implementar
IntBase
con soporte algebraico - Implementar
FourBytes
con soporte algebraico - Tests exhaustivos de ambas clases
Fase 2: Tokenizador
- Implementar
preprocess_tokens()
- Integrar con
HybridEvaluationEngine
- Tests de tokenización
Fase 3: Migración de Clases
- Migrar
Hex
,Bin
,Dec
a usarIntBase
- Migrar
IP4
,IP4Mask
a usarFourBytes
- Eliminar sistema de corchetes
Fase 4: Nuevas Capacidades
- Operaciones algebraicas avanzadas
- Constraint solving para redes
- Análisis simbólico de rangos
Conclusión
Esta refactorización elimina complejidad innecesaria mientras añade capacidades algebraicas poderosas. El resultado será:
- Más simple para casos básicos
- Más poderoso para casos avanzados
- Más intuitivo para el usuario
- Más extensible para el futuro
El tokenizador automático hace que el usuario no tenga que aprender sintaxis especial, mientras que el soporte algebraico completo permite análisis sofisticados cuando se necesitan.