Calc/.doc/refactoring/tokenizacion_ditribuida.md

1262 lines
44 KiB
Markdown

# 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`
```python
# 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`
```python
# 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
```python
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
```
## Problema Arquitectónico Crítico: Conversión Prematura a SymPy
### Diagnóstico del Problema Actual
#### **Resultado Problemático Observado:**
```python
mm = 255.240.0.x # → 255.240.0.x[Sympy] ❌
IP4(mm, 24) # → Error: MutableDenseMatrix + int ❌
j.next_ip() # → Error: unsupported operand ❌
```
#### **Causa Raíz:**
- **Conversión prematura**: FourBytes se convierte inmediatamente a SymPy Matrix
- **Pérdida de control**: SymPy toma control antes de que el objeto pueda manejar sus operaciones
- **Incompatibilidad operacional**: Matrices de SymPy no entienden aritmética de direcciones IP
### Arquitectura Correcta: Objetos Nativos con Conversión Perezosa
#### **Principio Fundamental:**
> **"Cada objeto debe mantenerse en su forma nativa hasta que se involucre en una ecuación algebraica compleja"**
#### **Jerarquía Revisada:**
```python
ClassBase (SIN SymPy - manejo interno de símbolos)
├── FourBytes (símbolos internos, aritmética nativa)
├── IntBase (símbolos internos, aritmética nativa)
├── IP4, Hex, Dec, etc. (usan objetos nativos)
└── IP4Mask (nativo)
SympyClassBase (solo cuando sea necesario)
├── Para ecuaciones algebraicas complejas
├── Para integración/derivación
└── Para sistemas de ecuaciones
```
#### **Comportamiento Deseado:**
```python
mm = 255.240.0.x # → FourBytes('255.240.0.x') [FourBytes]
IP4(mm, 24) # → IP4 con FourBytes interno [IP4]
j.substitute(x=1) # → Lógica interna de FourBytes [IP4]
j.next_ip() # → Aritmética interna nativa [IP4]
hh = 16#ff # → IntBase('ff', 16) [IntBase]
Dec(hh) # → Dec con IntBase interno [Dec]
```
## Clases Base Universales (Arquitectura Corregida)
### IntBase: Universal para Bases Numéricas (ClassBase)
```python
class IntBase(ClassBase): # ← CAMBIO: ClassBase, NO SympyClassBase
"""
Representación universal de números en cualquier base
con manejo interno de símbolos y conversión perezosa a SymPy
"""
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 símbolos INTERNAMENTE
self._symbols = self._extract_symbols()
self._expression = self._build_internal_expression()
super().__init__(self._expression, 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 _extract_symbols(self):
"""Extrae símbolos sin convertir a SymPy aún"""
return re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*', self.value_str)
def _build_internal_expression(self):
"""Construye expresión interna SIN SymPy"""
# Mantener como string procesable internamente
# Solo convertir a SymPy cuando se llame to_sympy()
return f"base{self.base}_expression({self.value_str})"
def substitute(self, **kwargs):
"""Sustitución interna sin involucrar SymPy"""
if not self.has_symbols:
return self
new_value_str = self.value_str
for symbol, value in kwargs.items():
new_value_str = new_value_str.replace(symbol, str(value))
return IntBase(new_value_str, self.base)
def to_sympy(self):
"""Conversión EXPLÍCITA a SymPy cuando se necesite álgebra"""
if self.has_symbols:
# AHORA sí convertir a expresión SymPy
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)
result += coefficient * power
return result
else:
return sympy.sympify(self._numeric_value)
# ========== OPERADORES ARITMÉTICOS NATIVOS ==========
def __add__(self, other):
"""Suma: aritmética nativa, mantiene la base"""
if self.has_symbols:
# Para símbolos, mantener como expresión interna
return IntBase(f"({self.value_str}) + ({other})", self.base)
if isinstance(other, IntBase):
if other.has_symbols:
return IntBase(f"({self.value_str}) + ({other.value_str})", self.base)
result_value = self._numeric_value + other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value + other
else:
# Para operaciones complejas, convertir a SymPy
return self.to_sympy() + other
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
# ... resto de operadores aritméticos nativos
```
### FourBytes: Universal para Patrones x.x.x.x (ClassBase)
```python
class FourBytes(ClassBase): # ← CAMBIO: ClassBase, NO SympyClassBase
"""
Representación universal de patrones de 4 elementos
con manejo interno de símbolos y conversión perezosa a SymPy
"""
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: mantener símbolos INTERNAMENTE
self._symbols = self._extract_symbols()
super().__init__(dotted_string, dotted_string) # Mantener como 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 _extract_symbols(self):
"""Extrae símbolos sin convertir a SymPy aún"""
symbols = []
for elem in self.elements:
if not elem.isdigit():
if elem not in symbols:
symbols.append(elem)
return symbols
def substitute(self, **kwargs):
"""Sustitución interna sin involucrar SymPy"""
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 to_sympy(self):
"""Conversión EXPLÍCITA a SymPy cuando se necesite álgebra"""
if self.has_symbols:
symbolic_elements = []
for elem in self.elements:
if elem.isdigit():
symbolic_elements.append(int(elem))
else:
symbolic_elements.append(sympy.Symbol(elem))
# Retornar como Matrix o expresión según el contexto
return sympy.Matrix(symbolic_elements)
else:
return sympy.sympify(self._numeric_value)
def __add__(self, other):
"""Suma: aritmética nativa de 32-bit"""
if self.has_symbols:
# Para símbolos, mantener como expresión interna
return FourBytes(f"({self.original}) + ({other})")
if isinstance(other, FourBytes):
if other.has_symbols:
return FourBytes(f"({self.original}) + ({other.original})")
result_int = self._numeric_value + other._numeric_value
elif isinstance(other, int):
result_int = self._numeric_value + other
else:
# Para operaciones complejas, convertir a SymPy
return self.to_sympy() + other
# Mantener en rango de 32 bits
result_int = result_int & 0xFFFFFFFF
result_str = self._int_to_fourbytes(result_int)
return FourBytes(result_str)
# ... resto de operadores aritméticos nativos
```
## Conversión Perezosa a SymPy
### **Cuándo Convertir:**
```python
# NUNCA conversión automática - mantener nativo:
mm = FourBytes('255.240.0.x') # → [FourBytes] ✅
ip = IP4(mm, 24) # → [IP4] ✅
ip.next_ip() # → [IP4] ✅
# Conversión EXPLÍCITA solo cuando se necesite álgebra:
eq = Eq(mm.to_sympy(), other_expr) # → [SymPy Equation] ✅
solve(eq, [x, y]) # → [SymPy Solutions] ✅
```
### **Beneficios de la Conversión Perezosa:**
1. **Control granular**: Cada clase decide cuándo necesita SymPy
2. **Operaciones nativas**: Sin overhead para operaciones simples
3. **Errores claros**: Comportamiento predecible por tipo
4. **Flexibilidad**: Conversión explícita cuando se requiera álgebra compleja
### **Integración con Ecuaciones:**
```python
# Para resolver ecuaciones complejas:
x, y = symbols('x y')
fb = FourBytes('10.x.1.y')
# Crear constraints usando conversión explícita:
constraints = [
fb.to_sympy()[1] >= 0, # x >= 0
fb.to_sympy()[1] <= 255, # x <= 255
fb.to_sympy()[3] >= 0, # y >= 0
fb.to_sympy()[3] <= 255 # y <= 255
]
solutions = solve(constraints, [x, y])
```
## Integración con Clases Especializadas
### Constructores Mejorados
```python
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
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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**
```python
# 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
```
## Integración Completa en el Sistema de Tipos
### **Todas las Clases en `custom_types/`**
#### **Consistencia Arquitectónica:**
- **IntBase** y **FourBytes** son tipos como cualquier otro
- Deben seguir el mismo patrón de registro y auto-descubrimiento
- El parser los importa desde el registro, no desde ubicaciones hardcodeadas
#### **Estructura de Directorios Revisada:**
```
custom_types/
├── intbase_type.py # IntBase + función de registro
├── fourbytes_type.py # FourBytes + función de registro
├── hex_type.py # Hex (usa IntBase del registro)
├── bin_type.py # Bin (usa IntBase del registro)
├── dec_type.py # Dec (usa IntBase del registro)
├── ip4_type.py # IP4 + IP4Mask (usan FourBytes del registro)
├── chr_type.py # Chr
└── latex_type.py # LaTeX
```
### **Archivos de Tipos Fundamentales**
#### **`custom_types/intbase_type.py`**
```python
"""
Clase base universal para números en cualquier base - TIPO REGISTRADO
"""
from class_base import ClassBase
import sympy
import re
class IntBase(ClassBase):
"""
Representación universal de números en cualquier base
con manejo interno de símbolos y conversión perezosa a SymPy
"""
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 símbolos INTERNAMENTE
self._symbols = self._extract_symbols()
self._expression = self._build_internal_expression()
super().__init__(self._expression, 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}")
# ... implementación completa ...
@staticmethod
def Helper(input_str):
"""Ayuda contextual para IntBase"""
if re.search(r'\d+#[0-9A-Fa-fx]+', input_str):
return '''IntBase - Números en cualquier base con álgebra simbólica
Sintaxis: base#valor
Ejemplos:
16#FF → IntBase('FF', 16) = 255
2#1010 → IntBase('1010', 2) = 10
8#777 → IntBase('777', 8) = 511
16#x0 → IntBase('x0', 16) = simbólico
Operaciones aritméticas mantienen la base original:
16#FF + 16#10 → IntBase('10F', 16)
2#1010 * 3 → IntBase('11110', 2)
Métodos disponibles:
.to_base(nueva_base) - Convierte a otra base
.substitute(x=valor) - Sustituye símbolos
.to_sympy() - Conversión explícita a SymPy'''
return None
@staticmethod
def PopupFunctionList():
"""Lista de métodos para autocompletado"""
return [
("to_base", "Convierte a otra base"),
("substitute", "Sustituye símbolos por valores"),
("to_sympy", "Conversión explícita a SymPy para álgebra"),
("to_decimal", "Obtiene valor decimal"),
("to_hex", "Convierte a hexadecimal"),
("to_binary", "Convierte a binario"),
("to_octal", "Convierte a octal"),
]
def register_classes_in_module():
"""Registro de IntBase en el sistema de tipos"""
return [
("IntBase", IntBase, "ClassBase", {
"add_lowercase": True,
"supports_brackets": False, # Se maneja por tokenización
"is_fundamental": True, # Clase fundamental del sistema
"description": "Números universales en cualquier base con álgebra simbólica"
}),
]
```
#### **`custom_types/fourbytes_type.py`**
```python
"""
Clase base universal para patrones x.x.x.x - TIPO REGISTRADO
"""
from class_base import ClassBase
import sympy
import re
class FourBytes(ClassBase):
"""
Representación universal de patrones de 4 elementos
con manejo interno de símbolos y conversión perezosa a SymPy
"""
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: mantener símbolos INTERNAMENTE
self._symbols = self._extract_symbols()
super().__init__(dotted_string, dotted_string) # Mantener como 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)
# ... implementación completa ...
@staticmethod
def Helper(input_str):
"""Ayuda contextual para FourBytes"""
if re.search(r'\d+\.\d+\.\d+\.\d+', input_str):
return '''FourBytes - Patrones x.x.x.x con álgebra simbólica
Sintaxis: x.y.z.w (donde cada elemento puede ser número o símbolo)
Ejemplos:
192.168.1.1 → FourBytes('192.168.1.1')
255.255.0.0 → FourBytes('255.255.0.0')
10.x.1.y → FourBytes('10.x.1.y') = simbólico
Aritmética de 32-bit:
192.168.1.1 + 5 → FourBytes('192.168.1.6')
10.0.0.0 + 256 → FourBytes('10.0.1.0')
Métodos disponibles:
.ToBase(base) - Convierte cada elemento a base específica
.ToHex() - Convierte a hexadecimal
.ToBinary() - Convierte a binario
.substitute(x=val) - Sustituye símbolos
.to_sympy() - Conversión explícita a SymPy'''
return None
@staticmethod
def PopupFunctionList():
"""Lista de métodos para autocompletado"""
return [
("ToBase", "Convierte cada elemento a base específica"),
("ToHex", "Convierte cada elemento a hexadecimal"),
("ToBinary", "Convierte cada elemento a binario"),
("ToOctal", "Convierte cada elemento a octal"),
("ToCIDR", "Añade notación CIDR"),
("substitute", "Sustituye símbolos por valores"),
("to_sympy", "Conversión explícita a SymPy para álgebra"),
("is_valid_ip_range", "Verifica si es rango IP válido"),
]
def register_classes_in_module():
"""Registro de FourBytes en el sistema de tipos"""
return [
("FourBytes", FourBytes, "ClassBase", {
"add_lowercase": True,
"supports_brackets": False, # Se maneja por tokenización
"is_fundamental": True, # Clase fundamental del sistema
"description": "Patrones universales x.x.x.x con álgebra simbólica"
}),
]
```
### **Parser Completamente Dinámico y Auto-Descubridor**
#### **Principio: Responsabilidad de Tokenización Distribuida**
> **"Cada clase define cómo debe ser reconocida y tokenizada, el parser simplemente ejecuta las reglas descubiertas"**
#### **`tl_universal_tokenizer.py` (Completamente Genérico)**
```python
"""
Tokenizador universal que auto-descubre reglas de tokenización
"""
import re
from typing import List, Dict, Any, Callable
# Importar desde el registro de tipos
try:
from type_registry import get_registered_base_context
TYPE_REGISTRY_AVAILABLE = True
except ImportError:
TYPE_REGISTRY_AVAILABLE = False
class UniversalTokenizer:
"""
Tokenizador que auto-descubre reglas de tokenización desde las clases registradas
"""
def __init__(self):
self.debug = False
self.tokenization_rules: List[Dict[str, Any]] = []
self._discover_tokenization_rules()
def _discover_tokenization_rules(self):
"""Auto-descubre reglas de tokenización desde todas las clases registradas"""
if not TYPE_REGISTRY_AVAILABLE:
if self.debug:
print("⚠️ Sistema de tipos no disponible")
return
try:
# Obtener todas las clases registradas
registered_classes = get_registered_base_context()
self.tokenization_rules.clear()
for class_name, class_obj in registered_classes.items():
if hasattr(class_obj, 'get_tokenization_patterns'):
try:
patterns = class_obj.get_tokenization_patterns()
for pattern_info in patterns:
self.tokenization_rules.append({
'class_name': class_name,
'class_obj': class_obj,
**pattern_info
})
if self.debug:
print(f"📋 Regla tokenización: {class_name} - {pattern_info.get('description', 'Sin descripción')}")
except Exception as e:
if self.debug:
print(f"⚠️ Error obteniendo patrones de {class_name}: {e}")
# Ordenar por prioridad (menor número = mayor prioridad)
self.tokenization_rules.sort(key=lambda x: x.get('priority', 100))
if self.debug:
print(f"🔧 {len(self.tokenization_rules)} reglas de tokenización cargadas")
for rule in self.tokenization_rules:
print(f" {rule['priority']:2d}: {rule['class_name']} - {rule.get('description', '')}")
except Exception as e:
if self.debug:
print(f"⚠️ Error en auto-descubrimiento: {e}")
self.tokenization_rules.clear()
def reload_tokenization_rules(self):
"""Recarga reglas de tokenización del registro"""
if self.debug:
print("🔄 Recargando reglas de tokenización...")
self._discover_tokenization_rules()
def preprocess_tokens(self, expression: str) -> str:
"""Aplica todas las reglas de tokenización descubiertas"""
if not self.tokenization_rules:
return expression
result = expression
original = expression
for rule in self.tokenization_rules:
try:
pattern = rule['pattern']
replacement_func = rule['replacement']
# Aplicar transformación
if callable(replacement_func):
result = re.sub(pattern, replacement_func, result)
else:
result = re.sub(pattern, replacement_func, result)
except Exception as e:
if self.debug:
print(f"⚠️ Error aplicando regla {rule['class_name']}: {e}")
continue
if self.debug and result != original:
print(f"🔧 Tokenización: '{original}' → '{result}'")
return result
def get_tokenization_info(self) -> List[Dict[str, Any]]:
"""Retorna información sobre las reglas de tokenización activas"""
return [
{
'class': rule['class_name'],
'priority': rule.get('priority', 100),
'description': rule.get('description', 'Sin descripción'),
'pattern': rule['pattern']
}
for rule in self.tokenization_rules
]
```
### **Implementación en Clases de Tipos**
#### **`custom_types/intbase_type.py` (con Tokenización)**
```python
"""
Clase base universal para números en cualquier base - CON TOKENIZACIÓN PROPIA
"""
from class_base import ClassBase
import sympy
import re
class IntBase(ClassBase):
"""
Representación universal de números en cualquier base
con manejo interno de símbolos y conversión perezosa a SymPy
"""
def __init__(self, value_str, base=10):
# ... implementación completa ...
@staticmethod
def get_tokenization_patterns():
"""
Define cómo esta clase debe ser reconocida y tokenizada
Returns:
List[Dict]: Lista de reglas de tokenización
"""
return [
{
'pattern': r'(\d+)#([0-9A-Fa-fx]+)',
'replacement': lambda match: f'IntBase("{match.group(2)}", {match.group(1)})',
'priority': 5, # ALTA prioridad (patrón muy específico)
'description': 'Números con base: 16#FF, 2#1010, etc.'
}
]
# ... resto de la implementación ...
def register_classes_in_module():
"""Registro de IntBase en el sistema de tipos"""
return [
("IntBase", IntBase, "ClassBase", {
"add_lowercase": True,
"supports_brackets": False,
"is_fundamental": True,
"has_tokenization": True, # NUEVO: Indica que tiene tokenización
"description": "Números universales en cualquier base con álgebra simbólica"
}),
]
```
#### **`custom_types/fourbytes_type.py` (con Tokenización)**
```python
"""
Clase base universal para patrones x.x.x.x - CON TOKENIZACIÓN PROPIA
"""
from class_base import ClassBase
import sympy
import re
class FourBytes(ClassBase):
"""
Representación universal de patrones de 4 elementos
con manejo interno de símbolos y conversión perezosa a SymPy
"""
def __init__(self, dotted_string):
# ... implementación completa ...
@staticmethod
def get_tokenization_patterns():
"""
Define cómo esta clase debe ser reconocida y tokenizada
Returns:
List[Dict]: Lista de reglas de tokenización
"""
return [
{
'pattern': r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b',
'replacement': lambda match: f'FourBytes("{match.group(1)}")',
'priority': 10, # MENOR prioridad que IntBase (patrón más general)
'description': 'Patrones de 4 elementos: 192.168.1.1, 10.x.y.z'
}
]
# ... resto de la implementación ...
def register_classes_in_module():
"""Registro de FourBytes en el sistema de tipos"""
return [
("FourBytes", FourBytes, "ClassBase", {
"add_lowercase": True,
"supports_brackets": False,
"is_fundamental": True,
"has_tokenization": True, # NUEVO: Indica que tiene tokenización
"description": "Patrones universales x.x.x.x con álgebra simbólica"
}),
]
```
#### **Ejemplo: Tipo Futuro con Tokenización Compleja**
```python
# custom_types/mac_type.py
class MACAddress(ClassBase):
"""Direcciones MAC con tokenización sofisticada"""
@staticmethod
def get_tokenization_patterns():
"""Múltiples patrones de MAC address"""
return [
{
'pattern': r'\b([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})\b',
'replacement': lambda match: f'MACAddress("{match.group(1)}", format="colon")',
'priority': 6,
'description': 'MAC con dos puntos: AA:BB:CC:DD:EE:FF'
},
{
'pattern': r'\b([0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2})\b',
'replacement': lambda match: f'MACAddress("{match.group(1)}", format="dash")',
'priority': 6,
'description': 'MAC con guiones: AA-BB-CC-DD-EE-FF'
},
{
'pattern': r'\b([0-9A-Fa-f]{12})\b',
'replacement': lambda match: f'MACAddress("{match.group(1)}", format="compact")',
'priority': 15, # Baja prioridad (patrón muy general)
'description': 'MAC compacta: AABBCCDDEEFF'
}
]
# custom_types/time_type.py
class TimeStamp(ClassBase):
"""Timestamps con múltiples formatos"""
@staticmethod
def get_tokenization_patterns():
return [
{
'pattern': r'\b(\d{1,2}:\d{2}:\d{2})\b',
'replacement': lambda match: f'TimeStamp("{match.group(1)}", format="HMS")',
'priority': 8,
'description': 'Tiempo HH:MM:SS'
},
{
'pattern': r'\b(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\b',
'replacement': lambda match: f'TimeStamp("{match.group(1)}", format="ISO")',
'priority': 7,
'description': 'ISO timestamp: 2024-01-01T12:30:45'
}
]
```
### **Ventajas de la Tokenización Distribuida**
#### **1. Responsabilidad Única**
- Cada clase es responsable de su reconocimiento
- Parser genérico sin conocimiento específico de tipos
- Lógica de tokenización encapsulada con la clase
#### **2. Escalabilidad Perfecta**
```python
# Agregar nuevo tipo con tokenización:
# 1. Crear clase en custom_types/
# 2. Implementar get_tokenization_patterns()
# 3. ¡AUTOMÁTICAMENTE funciona!
# NO se requiere:
# - Modificar parser
# - Modificar motor de evaluación
# - Registrar patrones manualmente
```
#### **3. Control de Precedencia Granular**
```python
# Precedencias típicas:
IntBase: priority = 5 # MUY específico: 16#FF
IPv6: priority = 6 # Específico: 2001:db8::1
MAC: priority = 7 # Específico: AA:BB:CC:DD:EE:FF
FourBytes: priority = 10 # General: x.x.x.x
Compact: priority = 15 # MUY general: AABBCCDD
```
#### **4. Flexibilidad Extrema**
- Múltiples patrones por clase
- Lógica de reemplazo compleja con funciones lambda
- Validación condicional dentro del patrón
- Soporte para formatos alternativos
#### **5. Auto-Descubrimiento Completo**
```python
# El sistema automáticamente descubre:
tokenizer.get_tokenization_info()
# [
# {'class': 'IntBase', 'priority': 5, 'description': 'Números con base'},
# {'class': 'IPv6', 'priority': 6, 'description': 'Direcciones IPv6'},
# {'class': 'FourBytes', 'priority': 10, 'description': 'Patrones x.x.x.x'}
# ]
```
#### **6. Debugging y Introspección**
```python
# Información completa del proceso:
tokenizer.debug = True
tokenizer.preprocess_tokens("16#FF + 192.168.1.1")
# Output:
# 📋 Regla tokenización: IntBase - Números con base: 16#FF, 2#1010
# 📋 Regla tokenización: FourBytes - Patrones de 4 elementos: 192.168.1.1, 10.x.y.z
# 🔧 Tokenización: '16#FF + 192.168.1.1' → 'IntBase("FF", 16) + FourBytes("192.168.1.1")'
```
### **Ventajas de la Integración Completa**
#### **1. Consistencia Arquitectónica**
- **Todo** el sistema de tipos en un lugar
- Patrón unificado de registro y descubrimiento
- No hay clases "especiales" hardcodeadas
#### **2. Modularidad Completa**
- Cada tipo es independiente y auto-contenido
- Fácil agregar/remover tipos sin modificar código core
- Sistema verdaderamente extensible
#### **3. Autocompletado y Ayuda Unificados**
- **IntBase** y **FourBytes** tienen `PopupFunctionList()` automáticamente
- Ayuda contextual mediante `Helper()`
- Integración completa con el sistema de autocompletado
#### **4. Carga Dinámica**
- Parser obtiene clases desde el registro, no por importación directa
- Permite recargar tipos en tiempo de ejecución
- Sistema totalmente dinámico
#### **5. Escalabilidad**
```python
# Agregar nuevos tipos fundamentales es trivial:
custom_types/
├── intbase_type.py # Base universal para números
├── fourbytes_type.py # Base universal para x.x.x.x
├── sixbytes_type.py # NUEVO: Para MACs (xx:xx:xx:xx:xx:xx)
├── time_type.py # NUEVO: Para HH:MM:SS
└── ...
```
#### **6. Pureza del Parser**
```python
# ANTES (hardcodeado):
from intbase import IntBase
from fourbytes import FourBytes
# DESPUÉS (dinámico):
IntBase = registry.get('IntBase')
FourBytes = registry.get('FourBytes')
```
## 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.