Calc/.doc/refactoring/tokenizacion_ditribuida.md

44 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

Problema Arquitectónico Crítico: Conversión Prematura a SymPy

Diagnóstico del Problema Actual

Resultado Problemático Observado:

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:

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:

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)

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)

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:

# 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:

# 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

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

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

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

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

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

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

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

# 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

# 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

# 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

# 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

# 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

# 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

# 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.