From 2cf06fb5f5b9ee0d0c281aec73a1d38ea73fe158 Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 5 Jun 2025 17:26:23 +0200 Subject: [PATCH] =?UTF-8?q?Actualizaci=C3=B3n=20del=20sistema=20de=20token?= =?UTF-8?q?izaci=C3=B3n=20y=20refactorizaci=C3=B3n=20de=20clases=20base.?= =?UTF-8?q?=20Se=20implementa=20un=20nuevo=20enfoque=20de=20tokenizaci?= =?UTF-8?q?=C3=B3n=20distribuida=20que=20permite=20la=20carga=20din=C3=A1m?= =?UTF-8?q?ica=20de=20clases=20desde=20el=20registro,=20mejorando=20la=20m?= =?UTF-8?q?odularidad=20y=20escalabilidad=20del=20sistema.=20Se=20eliminan?= =?UTF-8?q?=20dependencias=20de=20clases=20codificadas=20y=20se=20optimiza?= =?UTF-8?q?n=20las=20funciones=20de=20evaluaci=C3=B3n=20y=20an=C3=A1lisis?= =?UTF-8?q?=20de=20expresiones.=20Se=20ajustan=20las=20configuraciones=20d?= =?UTF-8?q?e=20la=20ventana=20y=20se=20mejora=20la=20gesti=C3=B3n=20de=20e?= =?UTF-8?q?rrores=20y=20advertencias=20en=20el=20motor=20de=20evaluaci?= =?UTF-8?q?=C3=B3n.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .doc/refactoring/remover_corchetes.md | 765 +++++++---- .doc/refactoring/tokenizacion_ditribuida.md | 1262 +++++++++++++++++++ SISTEMA_TOKENIZACION_DISTRIBUIDA.md | 166 +++ custom_types/bin_type.py | 34 +- custom_types/fourbytes_type.py | 382 ++++++ custom_types/hex_type.py | 38 +- custom_types/intbase_type.py | 309 +++++ custom_types/ip4_type.py | 72 +- debug_assignment_problem.py | 99 ++ debug_user_issues.py | 100 ++ hybrid_calc_history.txt | 25 +- hybrid_calc_settings.json | 4 +- main_evaluation.py | 213 +++- sympy_Base.py | 438 +------ test_distributed_tokenization.py | 164 +++ test_fixed_issues.py | 82 ++ test_fourbytes_add.py | 75 ++ test_quick_tokenization.py | 54 + test_real_usage.py | 49 + test_user_case.py | 105 ++ tl_bracket_parser.py | 537 ++++---- 21 files changed, 3925 insertions(+), 1048 deletions(-) create mode 100644 .doc/refactoring/tokenizacion_ditribuida.md create mode 100644 SISTEMA_TOKENIZACION_DISTRIBUIDA.md create mode 100644 custom_types/fourbytes_type.py create mode 100644 custom_types/intbase_type.py create mode 100644 debug_assignment_problem.py create mode 100644 debug_user_issues.py create mode 100644 test_distributed_tokenization.py create mode 100644 test_fixed_issues.py create mode 100644 test_fourbytes_add.py create mode 100644 test_quick_tokenization.py create mode 100644 test_real_usage.py create mode 100644 test_user_case.py diff --git a/.doc/refactoring/remover_corchetes.md b/.doc/refactoring/remover_corchetes.md index cbcff3c..7afc9f4 100644 --- a/.doc/refactoring/remover_corchetes.md +++ b/.doc/refactoring/remover_corchetes.md @@ -58,15 +58,60 @@ def preprocess_tokens(expression): return expression ``` -## Clases Base Universales +## Problema Arquitectónico Crítico: Conversión Prematura a SymPy -### IntBase: Universal para Bases Numéricas +### 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(SympyClassBase): +class IntBase(ClassBase): # ← CAMBIO: ClassBase, NO SympyClassBase """ Representación universal de números en cualquier base - con soporte algebraico completo y operaciones aritméticas + con manejo interno de símbolos y conversión perezosa a SymPy """ def __init__(self, value_str, base=10): @@ -75,170 +120,92 @@ class IntBase(SympyClassBase): 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}") + # 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 _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 _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 to_base(self, new_base): - """Convierte a nueva base""" + 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: - # En modo algebraico, mantener expresión - return IntBase(f"({self._symbolic_value})_base{new_base}", new_base) + # 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: - # 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) + return sympy.sympify(self._numeric_value) - @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 ========== + # ========== OPERADORES ARITMÉTICOS NATIVOS ========== def __add__(self, other): - """Suma: mantiene la base del operando izquierdo""" + """Suma: aritmética nativa, mantiene la base""" if self.has_symbols: - return super().__add__(other) # Delegar a SymPy + # 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 super().__add__(other) + 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: - return super().__add__(other) + # 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) - 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) + # ... resto de operadores aritméticos nativos ``` -### FourBytes: Universal para Patrones x.x.x.x +### FourBytes: Universal para Patrones x.x.x.x (ClassBase) ```python -class FourBytes(SympyClassBase): +class FourBytes(ClassBase): # ← CAMBIO: ClassBase, NO SympyClassBase """ Representación universal de patrones de 4 elementos - con soporte algebraico completo y operaciones aritméticas + con manejo interno de símbolos y conversión perezosa a SymPy """ def __init__(self, dotted_string): @@ -251,17 +218,9 @@ class FourBytes(SympyClassBase): 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) + # 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] @@ -272,14 +231,17 @@ class FourBytes(SympyClassBase): 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 _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): - """Sustituye símbolos por valores""" + """Sustitución interna sin involucrar SymPy""" if not self.has_symbols: return self @@ -292,115 +254,80 @@ class FourBytes(SympyClassBase): return FourBytes('.'.join(new_elements)) - def __getitem__(self, index): - """Acceso a elementos individuales""" + def to_sympy(self): + """Conversión EXPLÍCITA a SymPy cuando se necesite álgebra""" if self.has_symbols: - return self._symbolic_elements[index] + 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 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 ========== + return sympy.sympify(self._numeric_value) def __add__(self, other): - """Suma: convierte a int, opera, reconvierte a FourBytes""" + """Suma: aritmética nativa de 32-bit""" if self.has_symbols: - return super().__add__(other) # Delegar a SymPy + # Para símbolos, mantener como expresión interna + return FourBytes(f"({self.original}) + ({other})") if isinstance(other, FourBytes): if other.has_symbols: - return super().__add__(other) + 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: - return super().__add__(other) + # 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) - 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) + # ... 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 @@ -750,27 +677,337 @@ def is_valid_ip_symbolic(fourbytes): return constraints ``` -## Plan de Implementación +## Integración Completa en el Sistema de Tipos -### Fase 1: Clases Base -1. Implementar `IntBase` con soporte algebraico -2. Implementar `FourBytes` con soporte algebraico -3. Tests exhaustivos de ambas clases +### **Todas las Clases en `custom_types/`** -### Fase 2: Tokenizador -1. Implementar `preprocess_tokens()` -2. Integrar con `HybridEvaluationEngine` -3. Tests de tokenización +#### **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 -### Fase 3: Migración de Clases -1. Migrar `Hex`, `Bin`, `Dec` a usar `IntBase` -2. Migrar `IP4`, `IP4Mask` a usar `FourBytes` -3. Eliminar sistema de corchetes +#### **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 +``` -### Fase 4: Nuevas Capacidades -1. Operaciones algebraicas avanzadas -2. Constraint solving para redes -3. Análisis simbólico de rangos +### **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 Integrado con el Registro** + +#### **`tl_bracket_parser.py` (Actualizado)** +```python +""" +Tokenizador integrado con el sistema de auto-descubrimiento de tipos +""" +import re +from typing import Tuple, Dict, Any + +# Importar desde el registro de tipos (NO hardcodeado) +try: + from type_registry import get_registered_base_context + TYPE_REGISTRY_AVAILABLE = True +except ImportError: + TYPE_REGISTRY_AVAILABLE = False + +class UniversalTokenizer: + """Tokenizador que usa el registro de tipos para conversiones automáticas""" + + def __init__(self): + self.debug = False + self._update_fundamental_classes() + + def _update_fundamental_classes(self): + """Obtiene clases fundamentales desde el registro""" + if not TYPE_REGISTRY_AVAILABLE: + # Fallback básico + self.IntBase = None + self.FourBytes = None + return + + try: + # Obtener clases desde el registro automáticamente + registered_context = get_registered_base_context() + + self.IntBase = registered_context.get('IntBase') + self.FourBytes = registered_context.get('FourBytes') + + if self.debug: + print(f"🔧 Clases fundamentales cargadas desde registro:") + print(f" IntBase: {self.IntBase}") + print(f" FourBytes: {self.FourBytes}") + + except Exception as e: + if self.debug: + print(f"⚠️ Error cargando clases fundamentales: {e}") + self.IntBase = None + self.FourBytes = None + + def reload_fundamental_classes(self): + """Recarga clases fundamentales del registro""" + if self.debug: + print("🔄 Recargando clases fundamentales...") + self._update_fundamental_classes() + + def preprocess_tokens(self, expression): + """Convierte patrones específicos en objetos tipados""" + if not self.IntBase or not self.FourBytes: + if self.debug: + print("⚠️ Clases fundamentales no disponibles") + return expression + + # Fase 1: IntBase (mayor precedencia) + # 16#FF → IntBase('FF', 16) + def replace_intbase(match): + base = match.group(1) + value = match.group(2) + return f'IntBase("{value}", {base})' + + expression = re.sub( + r'(\d+)#([0-9A-Fa-fx]+)', + replace_intbase, + expression + ) + + # Fase 2: FourBytes + # 192.168.1.1 → FourBytes('192.168.1.1') + def replace_fourbytes(match): + dotted_value = match.group(1) + return f'FourBytes("{dotted_value}")' + + expression = re.sub( + r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b', + replace_fourbytes, + expression + ) + + if self.debug: + print(f"🔧 Tokenización: '{original}' → '{expression}'") + + return expression +``` + +### **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 diff --git a/.doc/refactoring/tokenizacion_ditribuida.md b/.doc/refactoring/tokenizacion_ditribuida.md new file mode 100644 index 0000000..b15e607 --- /dev/null +++ b/.doc/refactoring/tokenizacion_ditribuida.md @@ -0,0 +1,1262 @@ +# 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. \ No newline at end of file diff --git a/SISTEMA_TOKENIZACION_DISTRIBUIDA.md b/SISTEMA_TOKENIZACION_DISTRIBUIDA.md new file mode 100644 index 0000000..6a49088 --- /dev/null +++ b/SISTEMA_TOKENIZACION_DISTRIBUIDA.md @@ -0,0 +1,166 @@ +# Sistema de Tokenización Distribuida - IMPLEMENTADO + +## ✅ IMPLEMENTACIÓN COMPLETADA + +Se ha implementado exitosamente el sistema de tokenización distribuida según las especificaciones, reemplazando completamente el sistema de corchetes por un tokenizador universal que auto-descubre reglas. + +## 🏗️ Arquitectura Implementada + +### 1. **UniversalTokenizer** (en `tl_bracket_parser.py`) +- **Auto-descubrimiento**: Descubre automáticamente reglas de tokenización desde todas las clases registradas +- **Prioridad**: Ordena reglas por prioridad (menor número = mayor prioridad) +- **Aplicación secuencial**: Aplica reglas en orden de prioridad +- **Debug integrado**: Sistema completo de debug y estadísticas + +### 2. **TokenizationParser** (en `tl_bracket_parser.py`) +- **Integración completa**: Integra tokenización con el parser existente +- **Compatibilidad**: Mantiene interfaz compatible con BracketParser +- **Estadísticas**: Tracking de conversiones y rendimiento + +### 3. **Clases Base Universales** + +#### **IntBase** (en `custom_types/intbase_type.py`) +- **Patrón**: `(\d+)#([0-9A-Fa-fx]+)` +- **Prioridad**: 5 (ALTA - patrón específico) +- **Ejemplos**: `16#FF` → `IntBase("FF", 16)`, `2#1010` → `IntBase("1010", 2)` +- **Capacidades**: Aritmética nativa, símbolos algebraicos, conversión de bases + +#### **FourBytes** (en `custom_types/fourbytes_type.py`) +- **Patrón**: `(?> 24) & 0xFF}.{(value >> 16) & 0xFF}.{(value >> 8) & 0xFF}.{value & 0xFF}" + + # ========== OPERADORES ARITMÉTICOS NATIVOS (mantienen FourBytes) ========== + + def __add__(self, other): + """Suma nativa - mantiene como FourBytes cuando es posible""" + if self.has_symbols: + # Con símbolos, crear expresión simbólica pero mantener FourBytes + if isinstance(other, FourBytes): + if other.has_symbols: + new_expr = f"({self.original}) + ({other.original})" + else: + new_expr = f"({self.original}) + {other._numeric_value}" + elif isinstance(other, (int, float)): + new_expr = f"({self.original}) + {other}" + else: + # Para otros tipos, convertir a SymPy + return self.to_sympy() + other + + return FourBytes(new_expr) + + # Sin símbolos: aritmética numérica directa + if isinstance(other, FourBytes): + if other.has_symbols: + # El otro tiene símbolos, crear expresión + new_expr = f"{self._numeric_value} + ({other.original})" + return FourBytes(new_expr) + else: + # Ambos numéricos + result_value = (self._numeric_value + other._numeric_value) & 0xFFFFFFFF + elif isinstance(other, (int, float)): + result_value = (self._numeric_value + int(other)) & 0xFFFFFFFF + else: + # Para tipos complejos, convertir a SymPy + return self.to_sympy() + other + + # Convertir resultado de vuelta a formato dotted + result_str = self._int_to_fourbytes(result_value) + return FourBytes(result_str) + + def __sub__(self, other): + """Resta nativa""" + if self.has_symbols: + if isinstance(other, FourBytes): + if other.has_symbols: + new_expr = f"({self.original}) - ({other.original})" + else: + new_expr = f"({self.original}) - {other._numeric_value}" + elif isinstance(other, (int, float)): + new_expr = f"({self.original}) - {other}" + else: + return self.to_sympy() - other + + return FourBytes(new_expr) + + # Sin símbolos + if isinstance(other, FourBytes): + if other.has_symbols: + new_expr = f"{self._numeric_value} - ({other.original})" + return FourBytes(new_expr) + else: + result_value = (self._numeric_value - other._numeric_value) & 0xFFFFFFFF + elif isinstance(other, (int, float)): + result_value = (self._numeric_value - int(other)) & 0xFFFFFFFF + else: + return self.to_sympy() - other + + result_str = self._int_to_fourbytes(result_value) + return FourBytes(result_str) + + def ToBase(self, base): + """Convierte cada elemento a la base especificada""" + # Necesitamos importar IntBase desde el registro + try: + from type_registry import get_registered_base_context + context = get_registered_base_context() + IntBase = context.get('IntBase') + if not IntBase: + raise ImportError("IntBase no disponible en el registro") + except ImportError: + # Fallback: usar el método directo + def _convert_to_base_string(value, base): + if base == 2: + return bin(value)[2:] + elif base == 8: + return oct(value)[2:] + elif base == 16: + return hex(value)[2:].upper() + elif base == 10: + return str(value) + else: + digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + result = "" + while value > 0: + result = digits[value % base] + result + value //= base + return result or "0" + + if self.has_symbols: + # Con símbolos, mantener estructura simbólica + base_elements = [] + for elem in self.elements: + if elem.isdigit(): + num = int(elem) + base_str = _convert_to_base_string(num, base) + base_elements.append(f"{base}#{base_str}") + else: + base_elements.append(f"{base}#{elem}") + return '.'.join(base_elements) + else: + # Sin símbolos, conversión directa + base_elements = [] + for num in self._numeric_elements: + base_str = _convert_to_base_string(num, base) + base_elements.append(f"{base}#{base_str}") + return '.'.join(base_elements) + + # Versión con IntBase disponible + if self.has_symbols: + base_elements = [] + for elem in self.elements: + if elem.isdigit(): + num = int(elem) + base_str = IntBase._convert_to_base_string(num, base) + base_elements.append(f"{base}#{base_str}") + else: + base_elements.append(f"{base}#{elem}") + return '.'.join(base_elements) + else: + base_elements = [] + for num in self._numeric_elements: + base_str = IntBase._convert_to_base_string(num, base) + base_elements.append(f"{base}#{base_str}") + return '.'.join(base_elements) + + def ToCIDR(self, prefix_length): + """Convierte a notación CIDR""" + return f"{self.original}/{prefix_length}" + + def ToHex(self): + """Conversión a hexadecimal""" + return self.ToBase(16) + + def ToBinary(self): + """Conversión a binario""" + return self.ToBase(2) + + def ToOctal(self): + """Conversión a octal""" + return self.ToBase(8) + + @property + def value(self): + """Propiedad de acceso al valor - mantiene nativo""" + if self.has_symbols: + return self.original # Mantener como string simbólico + else: + return self._numeric_value # Valor numérico directo + + def __str__(self): + """Representación como cadena""" + return self.original + + def __repr__(self): + """Representación para debug""" + return f"FourBytes('{self.original}')" + + # ========== MÉTODOS PARA COMPATIBILIDAD CON SYMPY ========== + + def _sympystr(self, printer): + """Controla cómo SymPy representa este objeto""" + # Mantener representación nativa, no convertir a número + return f"FourBytes('{self.original}')" + + def __radd__(self, other): + """Suma reversa - cuando el otro operando es el primero""" + if isinstance(other, (int, float)): + return self.__add__(other) + else: + # Para otros tipos, dejar que SymPy maneje + return NotImplemented + + def __rsub__(self, other): + """Resta reversa""" + if isinstance(other, (int, float)): + # other - self + if self.has_symbols: + new_expr = f"{other} - ({self.original})" + return FourBytes(new_expr) + else: + result_value = (int(other) - self._numeric_value) & 0xFFFFFFFF + result_str = self._int_to_fourbytes(result_value) + return FourBytes(result_str) + else: + return NotImplemented + + @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"), + ] + + @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'(? 0: + result = digits[value % base] + result + value //= base + + return result + + def __str__(self): + """Representación como cadena""" + return f"{self.base}#{self.value_str}" + + def __repr__(self): + """Representación para debug""" + return f"IntBase('{self.value_str}', {self.base})" + + @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"), + ] + + @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.' + } + ] + +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" + }), + ] \ No newline at end of file diff --git a/custom_types/ip4_type.py b/custom_types/ip4_type.py index a6a5387..1a6f37c 100644 --- a/custom_types/ip4_type.py +++ b/custom_types/ip4_type.py @@ -3,11 +3,21 @@ Clase híbrida para direcciones IPv4 - ADAPTADA AL NUEVO SISTEMA Ahora usa FourBytes como clase base universal para direcciones IP """ from class_base import ClassBase -from sympy_Base import SympyClassBase, FourBytes, IntBase +from sympy_Base import SympyClassBase from typing import Optional, Union, List, Tuple import re import sympy +# Importación dinámica de clases base desde el registro +def get_base_classes(): + """Obtiene las clases base del registro de tipos""" + try: + from type_registry import get_registered_base_context + context = get_registered_base_context() + return context.get('IntBase'), context.get('FourBytes') + except ImportError: + return None, None + class IP4Mask(ClassBase): """ Helper class to manage IPv4 masks. @@ -16,8 +26,11 @@ class IP4Mask(ClassBase): _prefix: int _mask_int: int - def __init__(self, mask_input: Union[int, str, FourBytes, IntBase]): - if isinstance(mask_input, FourBytes): + def __init__(self, mask_input): + # Obtener clases dinámicamente + IntBase, FourBytes = get_base_classes() + + if FourBytes and isinstance(mask_input, FourBytes): # Máscara desde tokenización automática x.x.x.x if not mask_input.has_symbols: self._mask_int = mask_input._numeric_value @@ -26,7 +39,7 @@ class IP4Mask(ClassBase): # Mantener simbólico self._prefix = None self._mask_int = mask_input._symbolic_value - elif isinstance(mask_input, IntBase): + elif IntBase and isinstance(mask_input, IntBase): # Máscara desde hex u otra base (ej: 16#ffffff00) if not mask_input.has_symbols: self._mask_int = mask_input._numeric_value @@ -111,7 +124,24 @@ class IP4Mask(ClassBase): # Convertir a binario y verificar que sea una máscara válida binary_mask = bin(mask_int)[2:].zfill(32) if not re.fullmatch(r"1*0*", binary_mask): # Must be contiguous 1s followed by 0s - raise ValueError(f"Invalid mask: 0x{mask_int:08X} - bits not contiguous") + # Crear mensaje de error más informativo + mask_str = f"{(mask_int >> 24) & 0xFF}.{(mask_int >> 16) & 0xFF}.{(mask_int >> 8) & 0xFF}.{mask_int & 0xFF}" + binary_formatted = f"{binary_mask[:8]}.{binary_mask[8:16]}.{binary_mask[16:24]}.{binary_mask[24:32]}" + + raise ValueError(f""" +❌ Máscara inválida: {mask_str} (0x{mask_int:08X}) + +🔍 Análisis binario: {binary_formatted} + Los bits deben ser contiguos: todos los 1s seguidos de todos los 0s + +✅ Ejemplos de máscaras válidas: + /24 → 255.255.255.0 (11111111.11111111.11111111.00000000) + /20 → 255.240.0.0 (11111111.11110000.00000000.00000000) + /16 → 255.255.0.0 (11111111.11111111.00000000.00000000) + /12 → 255.240.0.0 (11111111.11110000.00000000.00000000) + +💡 ¿Quizás querías usar 255.240.0.0 en lugar de 255.240.0.3? +""") return binary_mask.count('1') @@ -328,8 +358,11 @@ class Class_IP4(SympyClassBase): mask: int, string, IP4Mask, FourBytes, o IntBase """ + # Obtener clases dinámicamente + IntBase, FourBytes = get_base_classes() + # Procesar dirección - if isinstance(address, FourBytes): + if FourBytes and isinstance(address, FourBytes): # address es FourBytes (ya tokenizado desde x.x.x.x) self.address = address self._ip_str = address.original @@ -358,8 +391,16 @@ class Class_IP4(SympyClassBase): else: self._ip_str = address.strip() - # Crear FourBytes manualmente para consistencia - self.address = FourBytes(self._ip_str) + # Crear FourBytes manualmente para consistencia si está disponible + if FourBytes: + self.address = FourBytes(self._ip_str) + else: + # Fallback: crear objeto simple + self.address = type('MockFourBytes', (), { + 'original': self._ip_str, + 'has_symbols': False, + '_numeric_value': self._parse_ip_to_int(self._ip_str) + })() if self.address.has_symbols: self._ip_int = self.address._symbolic_value self._has_symbols = True @@ -376,10 +417,10 @@ class Class_IP4(SympyClassBase): self._mask_obj = mask elif isinstance(mask, (int, str)): self._mask_obj = IP4Mask(mask) - elif isinstance(mask, FourBytes): + elif FourBytes and isinstance(mask, FourBytes): # Máscara desde tokenización (ej: 255.255.255.0) self._mask_obj = IP4Mask(mask) - elif isinstance(mask, IntBase): + elif IntBase and isinstance(mask, IntBase): # Máscara desde hex (ej: 16#ffffff00) self._mask_obj = IP4Mask(mask) else: @@ -393,6 +434,17 @@ class Class_IP4(SympyClassBase): else: super().__init__(self._ip_int, self._ip_str) + def _parse_ip_to_int(self, ip_str): + """Convierte string IP a entero (fallback)""" + try: + parts = ip_str.split('.') + if len(parts) == 4: + return (int(parts[0]) << 24 | int(parts[1]) << 16 | + int(parts[2]) << 8 | int(parts[3])) + except: + pass + return 0 + def __repr__(self): if self._mask_obj: return f"IP4({self._ip_str!r}, {self._mask_obj.get_prefix_int()})" diff --git a/debug_assignment_problem.py b/debug_assignment_problem.py new file mode 100644 index 0000000..8f0cc0d --- /dev/null +++ b/debug_assignment_problem.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +""" +Diagnóstico del problema de asignaciones +""" + +if __name__ == "__main__": + print("🔍 DIAGNOSTICANDO PROBLEMA DE ASIGNACIONES") + print("=" * 60) + + try: + from main_evaluation import HybridEvaluationEngine + from tl_bracket_parser import UniversalTokenizer + + # Crear motor de evaluación + engine = HybridEvaluationEngine() + engine.debug = True # Activar debug completo + + # Crear tokenizador para ver qué pasa + tokenizer = UniversalTokenizer() + tokenizer.debug = True + + # Casos de prueba exactos del usuario + test_cases = [ + "mask=255.240.0.3", + "mask", + "ip=10.1.1.1", + "10.1.1.1", + "ip" + ] + + print("\n🧪 Procesando casos de prueba del usuario:") + + for i, case in enumerate(test_cases, 1): + print(f"\n{'='*50}") + print(f"CASO {i}: '{case}'") + print(f"{'='*50}") + + # 1. Ver tokenización + tokenized = tokenizer.preprocess_tokens(case) + print(f"1. TOKENIZACIÓN: '{case}' → '{tokenized}'") + + # 2. Ver clasificación de línea + classification = engine._classify_line(tokenized) + print(f"2. CLASIFICACIÓN: {classification}") + + # 3. Evaluar con debug + print(f"3. EVALUACIÓN:") + result = engine.evaluate_line(case) + + print(f" RESULTADO:") + print(f" - Tipo de resultado: {result.result_type}") + print(f" - Es error: {result.is_error}") + if result.is_error: + print(f" - Error: {result.error}") + else: + print(f" - Valor: {result.result}") + print(f" - Tipo de valor: {type(result.result)}") + + # 4. Ver estado de variables + print(f"4. ESTADO DE VARIABLES:") + print(f" Variables en symbol_table: {list(engine.symbol_table.keys())}") + for var_name, var_value in engine.symbol_table.items(): + print(f" {var_name} = {var_value} (tipo: {type(var_value)})") + + # 5. Ver ecuaciones + print(f"5. ECUACIONES ALMACENADAS:") + print(f" Total ecuaciones: {len(engine.equations)}") + for j, eq in enumerate(engine.equations): + print(f" {j+1}. {eq}") + + print(f"\n{'_'*50}") + + # 6. Prueba manual de asignación directa + print(f"\n🔧 PRUEBA MANUAL DE ASIGNACIÓN DIRECTA:") + + try: + # Probar evaluación manual del lado derecho + print(f"6.1 Evaluando 'FourBytes(\"255.240.0.3\")' directamente:") + manual_result = engine._eval_in_context('FourBytes("255.240.0.3")') + print(f" Resultado: {manual_result} (tipo: {type(manual_result)})") + + # Probar asignación manual + print(f"6.2 Asignación manual 'test_var = FourBytes(\"255.240.0.3\")':") + exec_result = engine._eval_in_context('test_var = FourBytes("255.240.0.3")') + print(f" Resultado exec: {exec_result}") + + # Ver si se guardó + test_var_value = engine.get_variable('test_var') + print(f" test_var guardada: {test_var_value} (tipo: {type(test_var_value)})") + + except Exception as e: + print(f" Error en prueba manual: {e}") + import traceback + traceback.print_exc() + + except Exception as e: + print(f"Error general: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/debug_user_issues.py b/debug_user_issues.py new file mode 100644 index 0000000..f6974a9 --- /dev/null +++ b/debug_user_issues.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +""" +Diagnóstico de problemas específicos del usuario: +1. IP4Mask(mask) no funciona +2. 10.1.1.1 + 1 devuelve Integer en lugar de FourBytes +""" + +if __name__ == "__main__": + print("🔍 DIAGNOSTICANDO PROBLEMAS ESPECÍFICOS DEL USUARIO") + print("=" * 60) + + try: + from main_evaluation import HybridEvaluationEngine + + # Crear motor con debug + engine = HybridEvaluationEngine() + engine.debug = True + + print("📋 Reproduciendo secuencia del usuario:") + + # 1. Crear mask + print(f"\n1. Creando mask:") + result1 = engine.evaluate_line("mask=255.240.0.3") + print(f" mask asignada: {result1.result} (tipo: {type(result1.result)})") + + # Ver qué hay en mask + mask_value = engine.get_variable('mask') + print(f" mask en symbol_table: {mask_value} (tipo: {type(mask_value)})") + + print(f"\n2. Problema 1: IP4Mask(mask) no funciona") + print(f" Intentando: IP4Mask(mask)") + + # Probar directamente + try: + result2 = engine.evaluate_line("IP4Mask(mask)") + print(f" Resultado: {result2.result} (tipo: {type(result2.result)})") + if result2.is_error: + print(f" Error: {result2.error}") + except Exception as e: + print(f" Excepción: {e}") + + # Ver qué pasa cuando llamamos IP4Mask directamente con el valor + print(f"\n Probando IP4Mask directamente con el valor de mask:") + try: + # Obtener IP4Mask del contexto + IP4Mask = engine.base_context.get('IP4Mask') + if IP4Mask: + print(f" IP4Mask disponible: {IP4Mask}") + direct_result = IP4Mask(mask_value) + print(f" Resultado directo: {direct_result} (tipo: {type(direct_result)})") + else: + print(f" IP4Mask no encontrada en contexto") + except Exception as e: + print(f" Error en llamada directa: {e}") + import traceback + traceback.print_exc() + + print(f"\n3. Problema 2: 10.1.1.1 + 1 devuelve Integer") + print(f" Intentando: 10.1.1.1 + 1") + + result3 = engine.evaluate_line("10.1.1.1 + 1") + print(f" Resultado: {result3.result} (tipo: {type(result3.result)})") + + # Analizar paso a paso + print(f"\n Análisis paso a paso:") + + # Crear FourBytes manualmente para probar + try: + FourBytes = engine.base_context.get('FourBytes') + if FourBytes: + print(f" FourBytes disponible: {FourBytes}") + fb = FourBytes("10.1.1.1") + print(f" FourBytes creado: {fb} (tipo: {type(fb)})") + print(f" fb._numeric_value: {fb._numeric_value}") + + # Probar suma manual + sum_result = fb + 1 + print(f" fb + 1 = {sum_result} (tipo: {type(sum_result)})") + + # Ver el método __add__ + print(f" Método __add__ de FourBytes: {fb.__add__}") + + except Exception as e: + print(f" Error en análisis: {e}") + import traceback + traceback.print_exc() + + print(f"\n4. Verificando contexto:") + relevant_classes = ['FourBytes', 'IP4Mask', 'IP4'] + for cls_name in relevant_classes: + cls_obj = engine.base_context.get(cls_name) + if cls_obj: + print(f" ✅ {cls_name}: {cls_obj}") + else: + print(f" ❌ {cls_name}: No encontrada") + + except Exception as e: + print(f"Error general: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index dbc6382..8784f81 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -1,19 +1,10 @@ +mask=255.240.0.x +mask.ToHex() +ip=10.1.1.x +10.1.1.1 + 1 +ip -mm=255.240.0.x -IP4(mm,24) -j=IP4(mm,24) -x=5 -mm -j.substitute(x=1) -j.next_ip() - -hh=16#ff -Dec(hh) - -h=16#ff + 25 -h - -IP4Mask(255.240.0.0) -IP4(10.1.280.1) -IP4(10.1.x.1) \ No newline at end of file +IP4(10.1.1.4) +IP4Mask(mask) +IP4Mask(255.255.0.0) \ No newline at end of file diff --git a/hybrid_calc_settings.json b/hybrid_calc_settings.json index 36065f2..ec8e909 100644 --- a/hybrid_calc_settings.json +++ b/hybrid_calc_settings.json @@ -1,6 +1,6 @@ { - "window_geometry": "1020x700+236+1240", - "sash_pos_x": 360, + "window_geometry": "1020x700+356+1216", + "sash_pos_x": 359, "symbolic_mode": true, "show_numeric_approximation": true, "keep_symbolic_fractions": true, diff --git a/main_evaluation.py b/main_evaluation.py index 181c684..b786f3f 100644 --- a/main_evaluation.py +++ b/main_evaluation.py @@ -20,13 +20,8 @@ from type_registry import ( from tl_bracket_parser import BracketParser from tl_popup import PlotResult -# ========== NUEVO: Importar clases base para tokenización ========== -try: - from sympy_Base import IntBase, FourBytes - BASE_CLASSES_AVAILABLE = True -except ImportError: - BASE_CLASSES_AVAILABLE = False - print("⚠️ Clases base IntBase/FourBytes no disponibles") +# ========== ELIMINADO: Las clases base ahora se obtienen del registro ========== +# Las clases IntBase y FourBytes se cargan dinámicamente desde el registro de tipos class HybridEvaluationEngine: @@ -36,11 +31,11 @@ class HybridEvaluationEngine: """ def __init__(self, auto_discover_types: bool = True, types_directory: str = "custom_types"): - # ========== NUEVO: Inicializar parser con tokenización habilitada ========== - self.parser = BracketParser(use_type_registry=True) - self.parser.use_tokenizer = BASE_CLASSES_AVAILABLE # Habilitar tokenizador si clases disponibles - if self.parser.use_tokenizer: - self.parser.debug = False # Será habilitado por self.debug más adelante + # ========== NUEVO: Inicializar parser con tokenización distribuida ========== + self.parser = BracketParser(enable_tokenization=True, debug=False) + + # Debug mode (configurar antes que otros módulos) + self.debug = False self.symbol_table: Dict[str, Any] = {} self.equations: List[sympy.Eq] = [] @@ -54,9 +49,6 @@ class HybridEvaluationEngine: self.registered_types_info = {} self.helper_functions = [] - # Debug mode - self.debug = False - # NUEVA CONFIGURACIÓN: Modo simbólico self.symbolic_mode = True # Por defecto, mantener forma simbólica self.show_numeric_approximation = True # Mostrar aproximación numérica @@ -147,22 +139,15 @@ class HybridEvaluationEngine: 'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr), } - # ========== NUEVO: 4.5. CLASES BASE PARA TOKENIZACIÓN ========== - base_classes = {} - if BASE_CLASSES_AVAILABLE: - base_classes = { - 'IntBase': IntBase, - 'FourBytes': FourBytes, - } - if self.debug: - print("🔧 Clases base IntBase/FourBytes añadidas al contexto") + # ========== NUEVO: 4.5. CLASES BASE YA ESTÁN EN SPECIALIZED_CLASSES ========== + # Las clases IntBase y FourBytes ya están en specialized_classes desde el registro + # No necesitamos añadirlas por separado # 5. COMBINAR TODO EN EL CONTEXTO BASE self.base_context = { **math_functions, **specialized_classes, - **utility_functions, - **base_classes # ← NUEVO: Incluir clases base + **utility_functions } # 6. ACTUALIZAR HELPER FUNCTIONS @@ -178,17 +163,29 @@ class HybridEvaluationEngine: # ========== NUEVO: Sincronizar debug con parser ========== if hasattr(self.parser, 'debug'): self.parser.debug = self.debug + + # ========== NUEVO: Sincronizar debug con tokenizer ========== + if hasattr(self.parser, 'tokenizer') and hasattr(self.parser.tokenizer, 'debug'): + self.parser.tokenizer.debug = self.debug def _update_bracket_parser(self): """Actualiza el BracketParser con las clases descubiertas""" try: - # NUEVO: Llamar al método reload para actualizar dinámicamente - self.parser.reload_bracket_classes() - - if self.debug: - print(f"🔧 Bracket classes actualizadas: {self.parser.BRACKET_CLASSES}") + # NUEVO: Recargar reglas de tokenización del sistema distribuido + if hasattr(self.parser, 'reload_tokenization_rules'): + self.parser.reload_tokenization_rules() + + if self.debug: + tokenization_info = self.parser.get_tokenization_info() + print(f"🔧 Reglas de tokenización actualizadas: {len(tokenization_info['rules'])} reglas") + for rule in tokenization_info['rules']: + print(f" {rule['priority']:2d}: {rule['class']} - {rule['description']}") + else: + if self.debug: + print("⚠️ Parser sin soporte para tokenización distribuida") + except Exception as e: - print(f"⚠️ Error actualizando bracket parser: {e}") + print(f"⚠️ Error actualizando tokenización: {e}") def reload_types(self): """Recarga todos los tipos del directorio (útil para desarrollo)""" @@ -264,16 +261,18 @@ class HybridEvaluationEngine: NUEVA LÓGICA: Priorizar asignaciones, intentar ecuaciones silenciosamente """ try: - # 1. Parsear la línea - parsed_line, parse_info = self.parser.parse_line(line) + # 1. Aplicar tokenización distribuida + parsed_line = self.parser.process_expression(line) if self.debug: - print(f"Parse: '{line}' → '{parsed_line}' ({parse_info})") + print(f"Parse: '{line}' → '{parsed_line}'") - # 2. Manejar casos especiales - if parse_info == "comment": + # 2. Clasificar tipo de línea + line_type = self._classify_line(parsed_line) + + if line_type == "comment": return EvaluationResult(None, "comment", original_line=line) - elif parse_info == "assignment": + elif line_type == "assignment": # NUEVA LÓGICA: Para asignaciones, también intentar agregar como ecuación silenciosamente assignment_result = self._evaluate_assignment(parsed_line, line) @@ -285,11 +284,11 @@ class HybridEvaluationEngine: pass # Ignorar errores de ecuación return assignment_result - elif parse_info == "equation": + elif line_type == "equation": return self._evaluate_equation_addition(parsed_line, line) # 3. Evaluación SymPy - return self._evaluate_sympy_expression(parsed_line, parse_info, line) + return self._evaluate_sympy_expression(parsed_line, line_type, line) except Exception as e: return EvaluationResult( @@ -298,18 +297,94 @@ class HybridEvaluationEngine: original_line=line ) + def _classify_line(self, parsed_line: str) -> str: + """Clasifica el tipo de línea después del parsing""" + + # Simplificado: priorizar asignaciones, ser menos estricto + if self._is_assignment(parsed_line): + return "assignment" + elif self._is_standalone_equation(parsed_line): + return "equation" + elif not parsed_line or parsed_line.strip().startswith('#'): + return "comment" + else: + return "expression" + + def _is_assignment(self, line: str) -> bool: + """ + Detecta si una línea es una asignación de variable + NUEVA LÓGICA: Priorizar asignaciones, ser menos estricto + """ + try: + # Pattern: variable = expresión (que no sea comparación) + if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']): + # Verificar que sea una asignación válida de Python + parts = line.split('=', 1) + if len(parts) == 2: + var_part = parts[0].strip() + expr_part = parts[1].strip() + + # Verificar que la parte izquierda sea un identificador válido + if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part) and expr_part: + return True + + return False + except: + return False + + def _is_standalone_equation(self, line: str) -> bool: + """ + Determina si una línea es una ecuación standalone + NUEVA LÓGICA: Solo ecuaciones matemáticas obvias, no asignaciones + """ + try: + # Primero verificar si contiene '=' simple + if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']): + return False + + # NUEVA LÓGICA: Si ya fue clasificada como asignación, NO es ecuación + if self._is_assignment(line): + return False + + # Solo considerar como ecuación si tiene estructura matemática compleja + # Por ejemplo: expressions con funciones matemáticas, símbolos algebraicos, etc. + if any(pattern in line for pattern in ['sin(', 'cos(', 'log(', 'sqrt(', 'diff(', 'integrate(']): + return True + + # O si contiene múltiples variables en un formato algebraico + # Básicamente, ecuaciones que NO son asignaciones simples + return False + + except: + return False + def _evaluate_assignment(self, parsed_line: str, original_line: str) -> 'EvaluationResult': """Maneja la asignación de variables""" try: - # Ejecutar _assign_variable - result = self._eval_in_context(parsed_line) + # ARREGLO: Transformar asignación en llamada a _assign_variable + # parsed_line es algo como: mask=FourBytes("255.240.0.3") + # Necesitamos convertirlo en: _assign_variable("mask", FourBytes("255.240.0.3")) - # Extraer nombre de variable y valor del resultado - parts = original_line.split('=', 1) - var_name = parts[0].strip() + if '=' in parsed_line: + parts = parsed_line.split('=', 1) + var_name = parts[0].strip() + expr_part = parts[1].strip() + + # Crear llamada a _assign_variable + assign_call = f'_assign_variable("{var_name}", {expr_part})' + + if self.debug: + print(f"🔧 Transformando asignación: '{parsed_line}' → '{assign_call}'") + + # Ejecutar la asignación transformada + result = self._eval_in_context(assign_call) + + # Obtener el valor asignado + assigned_value = self.symbol_table.get(var_name) + else: + # Fallback: si no hay '=' algo está mal + raise ValueError(f"Línea de asignación sin '=': {parsed_line}") - # Obtener el valor asignado - assigned_value = self.symbol_table.get(var_name) # Generar evaluación numérica si está configurado para mostrarla numeric_result = None @@ -526,6 +601,11 @@ class HybridEvaluationEngine: # NUEVA LÓGICA: Priorizar SymPy en modo simbólico if self.symbolic_mode: try: + # INTERCEPTAR: Detectar operaciones aritméticas con objetos nativos + intercepted_result = self._intercept_native_operations(expression, context) + if intercepted_result is not None: + return intercepted_result + # Primero intentar con SymPy para mantener formas simbólicas result = sympify(expression, locals=context, rational=self.keep_symbolic_fractions) @@ -599,6 +679,45 @@ class HybridEvaluationEngine: except: raise syntax_error + def _intercept_native_operations(self, expression: str, context: Dict[str, Any]) -> Any: + """ + Intercepta operaciones aritméticas con objetos nativos para evitar + que SymPy las convierta prematuramente + """ + import re + + # Detectar patrones de aritmética con FourBytes, IntBase, etc. + # Patrones como: FourBytes(...) + número, número + FourBytes(...), etc. + + # Patrón 1: Constructor de tipo + operador + número/expresión + pattern1 = r'(\w+\([^)]+\))\s*([+\-*/])\s*(\d+|\w+)' + + # Patrón 2: número/expresión + operador + Constructor de tipo + pattern2 = r'(\d+|\w+)\s*([+\-*/])\s*(\w+\([^)]+\))' + + for pattern in [pattern1, pattern2]: + match = re.match(pattern, expression.strip()) + if match: + try: + # Evaluar usando eval para que los objetos manejen sus propias operaciones + result = eval(expression, {"__builtins__": {}}, context) + + # Verificar si el resultado es un objeto nativo que debe mantenerse + if hasattr(result, '__class__') and result.__class__.__module__ != 'sympy': + # Es un objeto de nuestras clases personalizadas + if hasattr(result, 'original') or hasattr(result, '_numeric_value'): + if self.debug: + print(f"🔧 Interceptando operación nativa: {expression} → {result} ({type(result).__name__})") + return result + + except Exception as e: + if self.debug: + print(f"🔧 Error en intercepción: {e}") + pass + + # Si no se detecta patrón específico, retornar None para continuar con SymPy + return None + def _get_full_context(self) -> Dict[str, Any]: """Obtiene el contexto completo para evaluación""" context = self.base_context.copy() diff --git a/sympy_Base.py b/sympy_Base.py index 0ec42f1..5d4a438 100644 --- a/sympy_Base.py +++ b/sympy_Base.py @@ -249,406 +249,58 @@ class SympyClassBase(ClassBase, sympy.Basic): return None -# ========== CLASES BASE UNIVERSALES PARA TOKENIZACIÓN ========== - -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 = str(value_str) - self.base = int(base) - - # Detección mejorada de símbolos: considerar la base - self.has_symbols = self._has_symbols_for_base() - - if self.has_symbols: - # Modo algebraico: mantener como expresión simbólica - self._symbolic_value = self._parse_symbolic_base() - self._numeric_value = None # No hay valor numérico - super().__init__(self._symbolic_value, f"{self.base}#{self.value_str}") - else: - # Modo numérico: convertir a entero - self._numeric_value = int(self.value_str, self.base) - self._symbolic_value = None # No hay valor simbólico - super().__init__(self._numeric_value, f"{self.base}#{self.value_str}") - - def _has_symbols_for_base(self): - """Detecta si hay símbolos considerando la base numérica""" - valid_digits = "0123456789ABCDEF"[:self.base] - - for char in self.value_str.upper(): - if char not in valid_digits: - # Es un símbolo (no un dígito válido para esta base) - return True - - return False - - 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 - 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 - - 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 - - @property - def value(self): - """Propiedad para acceder al valor (numérico o simbólico)""" - if self.has_symbols: - return self._symbolic_value - else: - return self._numeric_value - - # ========== 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) - - def __str__(self): - """Representación string""" - return f"{self.base}#{self.value_str}" - - def __repr__(self): - return f"IntBase('{self.value_str}', {self.base})" - - -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 = str(dotted_string) - self.elements = self.original.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)) - - # CORREGIDO: Crear expresión escalar en lugar de Matrix para compatibilidad con IP4 - # Convertir a representación de entero de 32 bits simbólico - self._symbolic_value = (self._symbolic_elements[0] * 2**24 + - self._symbolic_elements[1] * 2**16 + - self._symbolic_elements[2] * 2**8 + - self._symbolic_elements[3]) - 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: - # CORREGIDO: Ya tenemos la expresión escalar directamente - return self._symbolic_value - 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) - - def __str__(self): - """Representación string""" - return self.original - - def __repr__(self): - return f"FourBytes('{self.original}')" - - -# ========== TOKENIZADOR ALGEBRAICO ========== +# ========== FUNCIÓN DE TOKENIZACIÓN ACTUALIZADA ========== def preprocess_tokens(expression): """ - Tokenizador que convierte patrones específicos en objetos tipados - con precedencia correcta: IntBase (más específico) antes que FourBytes + Función de tokenización que usa el registro dinámico de tipos + NUEVA VERSIÓN: Obtiene clases desde custom_types/ automáticamente """ - # 1. MAYOR PRECEDENCIA: IntBase (patrón más específico) - # Pattern: base#valor - expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', r'IntBase("\2", \1)', expression) + # Intentar obtener las clases desde el registro + try: + from type_registry import get_registered_base_context + context = get_registered_base_context() + + IntBase = context.get('IntBase') + FourBytes = context.get('FourBytes') + + if not IntBase or not FourBytes: + # Fallback: tokenización básica sin clases + return _basic_tokenization(expression) + + except ImportError: + # Si el registro no está disponible, usar tokenización básica + return _basic_tokenization(expression) - # 2. MENOR PRECEDENCIA: FourBytes (patrón más general) - # Pattern: x.x.x.x pero evitar casos como obj.method - # Solo tokenizar si hay exactamente 4 elementos separados por puntos - # y no es parte de una cadena de métodos - pattern = r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b' + # Tokenización completa con clases disponibles + return _full_tokenization(expression, IntBase, FourBytes) + +def _full_tokenization(expression, IntBase, FourBytes): + """Tokenización completa usando las clases registradas""" + import re + + def replace_intbase(match): + base = match.group(1) + value = match.group(2) + return f'IntBase("{value}", {base})' def replace_fourbytes(match): - candidate = match.group(1) - # Verificar que realmente tiene 4 elementos - parts = candidate.split('.') - if len(parts) == 4: - return f'FourBytes("{candidate}")' - return candidate + pattern = match.group(1) + return f'FourBytes("{pattern}")' - expression = re.sub(pattern, replace_fourbytes, expression) + # PASO 1: IntBase - precedencia MAYOR (patrón más específico) + # Patrón: número#valor (ej: 16#FF, 2#1010) + expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', replace_intbase, expression) + # PASO 2: FourBytes - precedencia MENOR (patrón más general) + # Patrón: x.x.x.x donde x puede ser número o símbolo + # Usar \b para boundary, evitar conflictos con acceso a atributos + expression = re.sub(r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b', + replace_fourbytes, expression) + + return expression + +def _basic_tokenization(expression): + """Tokenización básica cuando las clases no están disponibles""" + # Solo hacer transformaciones básicas sin objetos return expression \ No newline at end of file diff --git a/test_distributed_tokenization.py b/test_distributed_tokenization.py new file mode 100644 index 0000000..43aada5 --- /dev/null +++ b/test_distributed_tokenization.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +""" +Script de prueba para el sistema de tokenización distribuida +""" + +def test_distributed_tokenization(): + """Prueba el sistema de tokenización distribuida completo""" + + print("🧪 PROBANDO SISTEMA DE TOKENIZACIÓN DISTRIBUIDA") + print("=" * 60) + + # Importar dependencias + try: + from tl_bracket_parser import TokenizationParser, preview_tokenization, test_tokenization_patterns + from type_registry import discover_and_register_types, get_registered_base_context + from main_evaluation import HybridEvaluationEngine + + print("✅ Todas las importaciones exitosas") + except ImportError as e: + print(f"❌ Error en importaciones: {e}") + return False + + # 1. Probar auto-descubrimiento de tipos + print("\n1️⃣ PROBANDO AUTO-DESCUBRIMIENTO DE TIPOS") + print("-" * 40) + + try: + registry_info = discover_and_register_types() + print(f"✅ Tipos descubiertos: {registry_info['class_count']} clases") + + registered_context = get_registered_base_context() + + # Verificar clases fundamentales + fundamental_classes = ['IntBase', 'FourBytes'] + for cls_name in fundamental_classes: + if cls_name in registered_context: + print(f" ✅ {cls_name} registrada") + + # Verificar si tiene tokenización + cls_obj = registered_context[cls_name] + if hasattr(cls_obj, 'get_tokenization_patterns'): + patterns = cls_obj.get_tokenization_patterns() + print(f" 🔧 Tiene {len(patterns)} reglas de tokenización") + for pattern in patterns: + print(f" 📋 Prioridad {pattern['priority']}: {pattern['description']}") + else: + print(f" ⚠️ Sin reglas de tokenización") + else: + print(f" ❌ {cls_name} NO registrada") + + except Exception as e: + print(f"❌ Error en auto-descubrimiento: {e}") + return False + + # 2. Probar tokenizador universal + print("\n2️⃣ PROBANDO TOKENIZADOR UNIVERSAL") + print("-" * 40) + + try: + parser = TokenizationParser(debug=True) + + # Casos de prueba + test_cases = [ + "16#FF", # IntBase + "2#1010", # IntBase + "192.168.1.1", # FourBytes + "10.x.1.y", # FourBytes simbólico + "0xFF", # Hex con prefijo + "0b1010", # Bin con prefijo + "16#FF + 192.168.1.1", # Mixto + "normal + variable", # Sin tokenización + ] + + print("🧪 Casos de prueba:") + results = parser.test_patterns(test_cases) + + print(f"\n📊 Resultados:") + print(f" ✅ Exitosos: {len(results['successful_conversions'])}") + print(f" ⚪ Sin cambios: {len(results['failed_conversions'])}") + + if results['successful_conversions']: + print(f"\n🎉 Conversiones exitosas:") + for expr in results['successful_conversions']: + result = results['tokenized_results'][expr] + print(f" '{expr}' → '{result}'") + + # Obtener información de reglas + tokenization_info = parser.get_tokenization_info() + print(f"\n📋 {len(tokenization_info['rules'])} reglas activas:") + for rule in tokenization_info['rules']: + print(f" {rule['priority']:2d}: {rule['class']} - {rule['description']}") + + except Exception as e: + print(f"❌ Error en tokenizador: {e}") + return False + + # 3. Probar integración con motor de evaluación + print("\n3️⃣ PROBANDO INTEGRACIÓN CON MOTOR DE EVALUACIÓN") + print("-" * 40) + + try: + engine = HybridEvaluationEngine(debug=True) + + # Probar expresiones tokenizadas + test_expressions = [ + "a = 16#FF", + "b = 192.168.1.1", + "c = a + 5", + "d = b + 256", + "16#10 + 2#1010", + ] + + print("🧪 Probando expresiones:") + for expr in test_expressions: + try: + result = engine.evaluate_line(expr) + if result.is_error: + print(f" ❌ '{expr}' → Error: {result.error}") + else: + print(f" ✅ '{expr}' → {result.result}") + except Exception as e: + print(f" ❌ '{expr}' → Excepción: {e}") + + except Exception as e: + print(f"❌ Error en motor de evaluación: {e}") + return False + + # 4. Prueba de casos específicos reportados por el usuario + print("\n4️⃣ PROBANDO CASOS ESPECÍFICOS DEL USUARIO") + print("-" * 40) + + try: + engine = HybridEvaluationEngine() + + # El caso específico que reportó el usuario + test_expression = "m=255.240.0.0 16#ff + 5" + + print(f"🎯 Probando: '{test_expression}'") + + # Primero ver cómo se tokeniza + preview = preview_tokenization(test_expression, debug=False) + print(f" 🔧 Tokenización: '{preview['original']}' → '{preview['tokenized']}'") + + # Ahora evaluar + result = engine.evaluate_line(test_expression) + if result.is_error: + print(f" ❌ Error: {result.error}") + else: + print(f" ✅ Resultado: {result.result}") + + except Exception as e: + print(f"❌ Error en caso específico: {e}") + return False + + print("\n🎉 TODAS LAS PRUEBAS COMPLETADAS") + return True + + +if __name__ == "__main__": + success = test_distributed_tokenization() + if success: + print("\n✅ Sistema de tokenización distribuida funcionando correctamente") + else: + print("\n❌ Hay problemas en el sistema de tokenización distribuida") \ No newline at end of file diff --git a/test_fixed_issues.py b/test_fixed_issues.py new file mode 100644 index 0000000..72d1406 --- /dev/null +++ b/test_fixed_issues.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Prueba de las correcciones: +1. FourBytes + int mantiene tipo FourBytes +2. IP4Mask con mejor mensaje de error para máscaras inválidas +""" + +if __name__ == "__main__": + print("🔧 PROBANDO CORRECCIONES") + print("=" * 50) + + try: + from main_evaluation import HybridEvaluationEngine + + # Crear motor + engine = HybridEvaluationEngine() + + print("📋 Reproduciendo casos del usuario:\n") + + # 1. Reproducir secuencia completa + print("1. mask=255.240.0.3") + result1 = engine.evaluate_line("mask=255.240.0.3") + print(f" → {result1.result} (tipo: {type(result1.result).__name__})") + + print(f"\n2. mask") + result2 = engine.evaluate_line("mask") + print(f" → {result2.result} (tipo: {type(result2.result).__name__})") + + print(f"\n3. ip=10.1.1.x") + result3 = engine.evaluate_line("ip=10.1.1.x") + print(f" → {result3.result} (tipo: {type(result3.result).__name__})") + + print(f"\n4. 10.1.1.1 + 1 (DEBE DEVOLVER FourBytes)") + result4 = engine.evaluate_line("10.1.1.1 + 1") + print(f" → {result4.result} (tipo: {type(result4.result).__name__})") + + if hasattr(result4.result, 'original'): + print(f" → Valor original: {result4.result.original}") + + print(f"\n5. ip") + result5 = engine.evaluate_line("ip") + print(f" → {result5.result} (tipo: {type(result5.result).__name__})") + + print(f"\n6. IP4(10.1.1.4)") + result6 = engine.evaluate_line("IP4(10.1.1.4)") + print(f" → {result6.result} (tipo: {type(result6.result).__name__})") + + print(f"\n7. IP4Mask(mask) - DEBE DAR ERROR INFORMATIVO") + result7 = engine.evaluate_line("IP4Mask(mask)") + if result7.is_error: + print(f" ❌ Error (como esperado): {result7.error}") + else: + print(f" → {result7.result} (tipo: {type(result7.result).__name__})") + + print(f"\n8. Probando con máscara válida: IP4Mask(255.255.0.0)") + result8 = engine.evaluate_line("IP4Mask(255.255.0.0)") + print(f" → {result8.result} (tipo: {type(result8.result).__name__})") + + print(f"\n🎯 RESUMEN DE RESULTADOS:") + print(f" ✅ mask asignada como FourBytes: {type(result1.result).__name__ == 'FourBytes'}") + print(f" ✅ 10.1.1.1 + 1 mantiene FourBytes: {type(result4.result).__name__ == 'FourBytes'}") + print(f" ✅ IP4Mask da error informativo para máscara inválida: {result7.is_error}") + print(f" ✅ IP4Mask funciona con máscara válida: {not result8.is_error}") + + # Verificación adicional de aritmética FourBytes + print(f"\n🔍 VERIFICACIÓN ADICIONAL DE ARITMÉTICA:") + + # Test más casos aritméticos + test_cases = [ + "192.168.1.1 + 5", + "10.0.0.0 + 256", + "255.255.255.255 - 1" + ] + + for case in test_cases: + result = engine.evaluate_line(case) + print(f" {case} → {result.result} ({type(result.result).__name__})") + + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/test_fourbytes_add.py b/test_fourbytes_add.py new file mode 100644 index 0000000..a2c4e3f --- /dev/null +++ b/test_fourbytes_add.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +""" +Prueba específica de FourBytes + int +""" + +if __name__ == "__main__": + print("🔍 PROBANDO FourBytes + int") + print("=" * 40) + + try: + from main_evaluation import HybridEvaluationEngine + + # Crear motor + engine = HybridEvaluationEngine() + + # 1. Crear FourBytes directamente + print("1. Creando FourBytes directamente:") + FourBytes = engine.base_context.get('FourBytes') + print(f" FourBytes: {FourBytes}") + + fb = FourBytes("10.1.1.1") + print(f" fb = {fb} (tipo: {type(fb)})") + print(f" fb._numeric_value = {fb._numeric_value}") + + # 2. Probar suma directa + print(f"\n2. Suma directa en Python:") + result_direct = fb + 1 + print(f" fb + 1 = {result_direct} (tipo: {type(result_direct)})") + + # 3. Probar a través del motor de evaluación + print(f"\n3. Evaluación a través del motor:") + engine.symbol_table['test_fb'] = fb + + result_engine = engine.evaluate_line("test_fb + 1") + print(f" test_fb + 1 = {result_engine.result} (tipo: {type(result_engine.result)})") + + # 4. Probar tokenización + evaluación + print(f"\n4. Tokenización completa:") + result_tokenized = engine.evaluate_line("10.1.1.1 + 1") + print(f" 10.1.1.1 + 1 = {result_tokenized.result} (tipo: {type(result_tokenized.result)})") + + # 5. Verificar paso a paso qué pasa en la tokenización + print(f"\n5. Análisis paso a paso:") + from tl_bracket_parser import UniversalTokenizer + tokenizer = UniversalTokenizer() + tokenizer.debug = True + + tokenized = tokenizer.preprocess_tokens("10.1.1.1 + 1") + print(f" Tokenizado: {tokenized}") + + # 6. Evaluar el tokenizado directamente + print(f"\n6. Evaluando tokenizado directamente:") + try: + direct_eval = engine._eval_in_context(tokenized) + print(f" Resultado directo: {direct_eval} (tipo: {type(direct_eval)})") + except Exception as e: + print(f" Error: {e}") + + # 7. Probar operación manual paso a paso + print(f"\n7. Operación manual paso a paso:") + try: + fb_manual = engine._eval_in_context('FourBytes("10.1.1.1")') + print(f" FourBytes manual: {fb_manual} (tipo: {type(fb_manual)})") + + add_result = engine._eval_in_context('FourBytes("10.1.1.1") + 1') + print(f" Suma manual: {add_result} (tipo: {type(add_result)})") + except Exception as e: + print(f" Error en operación manual: {e}") + import traceback + traceback.print_exc() + + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/test_quick_tokenization.py b/test_quick_tokenization.py new file mode 100644 index 0000000..c9404d9 --- /dev/null +++ b/test_quick_tokenization.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +""" +Script rápido para probar tokenización +""" + +if __name__ == "__main__": + print("🧪 Prueba rápida de tokenización") + + try: + # 1. Verificar registro de tipos + print("\n1. Verificando registro de tipos...") + from type_registry import discover_and_register_types, get_registered_base_context + + registry_info = discover_and_register_types() + print(f"Clases registradas: {registry_info['class_count']}") + + context = get_registered_base_context() + print(f"Clases en contexto: {list(context.keys())}") + + # 2. Verificar si las clases tienen tokenización + print("\n2. Verificando métodos de tokenización...") + for name, cls in context.items(): + if hasattr(cls, 'get_tokenization_patterns'): + patterns = cls.get_tokenization_patterns() + print(f"{name}: {len(patterns)} patrones") + for p in patterns: + print(f" - Prioridad {p['priority']}: {p['description']}") + else: + print(f"{name}: sin tokenización") + + # 3. Probar tokenizador + print("\n3. Probando tokenizador...") + from tl_bracket_parser import UniversalTokenizer + + tokenizer = UniversalTokenizer() + tokenizer.debug = True + print(f"Reglas cargadas: {len(tokenizer.tokenization_rules)}") + + test_cases = [ + "192.168.1.1", + "16#FF", + "0xFF", + "2#1010", + "10.x.1.y" + ] + + for case in test_cases: + result = tokenizer.preprocess_tokens(case) + print(f"'{case}' → '{result}'") + + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/test_real_usage.py b/test_real_usage.py new file mode 100644 index 0000000..dbc1457 --- /dev/null +++ b/test_real_usage.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +""" +Prueba de uso real del sistema corregido +""" + +if __name__ == "__main__": + print("🎯 PRUEBA DE USO REAL") + print("=" * 40) + + try: + from main_evaluation import HybridEvaluationEngine + + # Crear motor sin debug para simular uso real + engine = HybridEvaluationEngine() + engine.debug = False + + # Casos exactos del usuario + test_sequence = [ + "mask=255.240.0.3", + "mask", + "ip=10.1.1.1", + "10.1.1.1", + "ip" + ] + + print("Secuencia de comandos del usuario:") + print("-" * 40) + + for i, command in enumerate(test_sequence, 1): + print(f"\n{i}. {command}") + result = engine.evaluate_line(command) + + if result.is_error: + print(f" ❌ Error: {result.error}") + else: + print(f" ✅ {result.result}") + if hasattr(result.result, '__class__'): + print(f" [{result.result.__class__.__name__}]") + + print(f"\n" + "=" * 40) + print("ESTADO FINAL:") + print(f"Variables: {list(engine.symbol_table.keys())}") + for var, value in engine.symbol_table.items(): + print(f" {var} = {value}") + + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/test_user_case.py b/test_user_case.py new file mode 100644 index 0000000..b21fd6c --- /dev/null +++ b/test_user_case.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +""" +Prueba del caso específico del usuario: m=255.240.0.0 16#ff + 5 +""" + +if __name__ == "__main__": + print("🎯 Probando caso específico del usuario") + + try: + # Debug completo del sistema + from type_registry import discover_and_register_types, get_registered_base_context + from tl_bracket_parser import UniversalTokenizer + + print("\n🔍 DEBUG COMPLETO DEL SISTEMA:") + + # 1. Verificar registro de tipos + print("\n1. Verificando registro de tipos...") + registry_info = discover_and_register_types() + print(f"Clases registradas: {registry_info['class_count']}") + + context = get_registered_base_context() + fundamental_classes = ['IntBase', 'FourBytes'] + + for cls_name in fundamental_classes: + if cls_name in context: + print(f"✅ {cls_name} está registrada") + cls_obj = context[cls_name] + if hasattr(cls_obj, 'get_tokenization_patterns'): + patterns = cls_obj.get_tokenization_patterns() + print(f" 🔧 Tiene {len(patterns)} reglas de tokenización") + for i, pattern in enumerate(patterns): + print(f" {i+1}. Prioridad {pattern['priority']}: {pattern['description']}") + print(f" Patrón: {pattern['pattern']}") + else: + print(f" ❌ Sin reglas de tokenización") + else: + print(f"❌ {cls_name} NO está registrada") + + # 2. Crear tokenizador y verificar reglas + print("\n2. Creando tokenizador...") + tokenizer = UniversalTokenizer() + print(f"Reglas cargadas en tokenizador: {len(tokenizer.tokenization_rules)}") + + if len(tokenizer.tokenization_rules) > 0: + print("Reglas activas:") + for i, rule in enumerate(tokenizer.tokenization_rules): + print(f" {i+1}. {rule['class_name']} (prioridad {rule['priority']}): {rule['description']}") + else: + print("❌ No hay reglas cargadas en el tokenizador") + + # 3. Probar tokenización manual + print("\n3. Probando tokenización manual...") + tokenizer.debug = True + + test_simple = "192.168.1.1" + print(f"\nProbando: '{test_simple}'") + result = tokenizer.preprocess_tokens(test_simple) + print(f"Resultado: '{result}'") + + test_hex = "16#ff" + print(f"\nProbando: '{test_hex}'") + result = tokenizer.preprocess_tokens(test_hex) + print(f"Resultado: '{result}'") + + # 4. Verificar que las clases estén disponibles en motor + print("\n4. Verificando motor de evaluación...") + from main_evaluation import HybridEvaluationEngine + + engine = HybridEvaluationEngine() + engine.debug = True + + print(f"Contexto del motor tiene {len(engine.base_context)} entradas") + + # Verificar que IntBase y FourBytes estén en el contexto + for cls_name in ['IntBase', 'FourBytes']: + if cls_name in engine.base_context: + print(f"✅ {cls_name} está en contexto del motor") + else: + print(f"❌ {cls_name} NO está en contexto del motor") + + # 5. Probar evaluación directa + print("\n5. Probando evaluación directa...") + + # Probar evaluación manual sin tokenización + test_manual = "IntBase('ff', 16)" + print(f"Probando evaluación manual: {test_manual}") + try: + result = engine._eval_in_context(test_manual) + print(f"✅ Resultado: {result} (tipo: {type(result)})") + except Exception as e: + print(f"❌ Error: {e}") + + # Probar FourBytes manual + test_manual2 = "FourBytes('192.168.1.1')" + print(f"Probando evaluación manual: {test_manual2}") + try: + result = engine._eval_in_context(test_manual2) + print(f"✅ Resultado: {result} (tipo: {type(result)})") + except Exception as e: + print(f"❌ Error: {e}") + + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/tl_bracket_parser.py b/tl_bracket_parser.py index d45d50a..f38d04f 100644 --- a/tl_bracket_parser.py +++ b/tl_bracket_parser.py @@ -1,344 +1,257 @@ """ -Tokenization Parser - SISTEMA NUEVO que reemplaza los corchetes -Convierte automáticamente patrones específicos en objetos tipados +Tokenizador Universal con Auto-Descubrimiento de Reglas +Sistema de tokenización distribuida donde cada clase define sus propias reglas """ -import ast import re -from typing import Tuple, Optional, Set, Dict +from typing import List, Dict, Any, Callable, Tuple, Optional -# Importar tokenizador desde sympy_Base +# Importar desde el registro de tipos try: - from sympy_Base import preprocess_tokens - TOKENIZER_AVAILABLE = True + from type_registry import get_registered_base_context + TYPE_REGISTRY_AVAILABLE = True except ImportError: - TOKENIZER_AVAILABLE = False - print("⚠️ Tokenizador no disponible, usando parser básico") + 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 + conversion_stats = {} + + for rule in self.tokenization_rules: + try: + pattern = rule['pattern'] + replacement_func = rule['replacement'] + rule_name = f"{rule['class_name']}" + + # Contar matches antes de aplicar + matches_before = len(re.findall(pattern, result)) + + # Aplicar transformación + if callable(replacement_func): + result = re.sub(pattern, replacement_func, result) + else: + result = re.sub(pattern, replacement_func, result) + + # Contar conversiones + matches_after = len(re.findall(pattern, result)) + conversions = matches_before - matches_after + + if conversions > 0: + conversion_stats[rule_name] = conversion_stats.get(rule_name, 0) + conversions + + 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}'") + if conversion_stats: + for rule_name, count in conversion_stats.items(): + print(f" {rule_name}: {count} conversiones") + + 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 + ] + + def test_tokenization(self, test_expressions: List[str]) -> Dict[str, str]: + """Prueba tokenización en lista de expresiones""" + results = {} + for expr in test_expressions: + results[expr] = self.preprocess_tokens(expr) + return results class TokenizationParser: """ - Nuevo parser que reemplaza el sistema de corchetes - Convierte automáticamente patrones en objetos tipados + Parser que integra tokenización universal con BracketParser para compatibilidad """ - # Operadores de comparación que pueden formar ecuaciones - EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='} - - def __init__(self, use_tokenizer: bool = True): - self.debug = False - self.use_tokenizer = use_tokenizer and TOKENIZER_AVAILABLE + def __init__(self, enable_tokenization=True, debug=False): + self.enable_tokenization = enable_tokenization + self.debug = debug + self.tokenizer = UniversalTokenizer() + self.tokenizer.debug = debug - # Estadísticas de tokenización - self.stats = { - 'intbase_conversions': 0, - 'fourbytes_conversions': 0, - 'total_lines_processed': 0 + # Estadísticas + self.conversion_stats = {} + self.total_expressions_processed = 0 + + def process_expression(self, expression: str) -> str: + """Procesa una expresión aplicando tokenización si está habilitada""" + self.total_expressions_processed += 1 + + if not self.enable_tokenization: + return expression + + # Aplicar tokenización universal + tokenized = self.tokenizer.preprocess_tokens(expression) + + if self.debug and tokenized != expression: + print(f"🔧 Parser: '{expression}' → '{tokenized}'") + + return tokenized + + def reload_tokenization_rules(self): + """Recarga las reglas de tokenización""" + self.tokenizer.reload_tokenization_rules() + + def get_tokenization_info(self) -> Dict[str, Any]: + """Información completa del sistema de tokenización""" + return { + 'rules': self.tokenizer.get_tokenization_info(), + 'statistics': { + 'total_processed': self.total_expressions_processed, + 'tokenization_enabled': self.enable_tokenization, + 'rules_count': len(self.tokenizer.tokenization_rules) + } } - def get_tokenization_stats(self) -> Dict[str, int]: - """Retorna estadísticas de tokenización""" - return self.stats.copy() - - def reset_stats(self): - """Reinicia estadísticas""" - self.stats = {k: 0 for k in self.stats} - - def parse_line(self, code_line: str) -> Tuple[str, str]: - """ - Parsea una línea de código aplicando tokenización automática + def test_patterns(self, test_cases: List[str]) -> Dict[str, Any]: + """Prueba patrones de tokenización con casos de prueba""" + results = self.tokenizer.test_tokenization(test_cases) - Returns: - (transformed_code, parse_info): Código transformado e información de parsing - """ - original_line = code_line.strip() - if not original_line or original_line.startswith('#'): - return code_line, "comment" - - self.stats['total_lines_processed'] += 1 - - try: - # 1. Detectar y transformar atajo solve - transformed_line, has_solve_shortcut = self._transform_solve_shortcut(original_line) - if has_solve_shortcut: - return transformed_line, "solve_shortcut" - - # 2. Detectar asignaciones de variables - if self._is_assignment(original_line): - return self._transform_assignment(original_line), "assignment" - - # 3. Detectar ecuaciones standalone - if self._is_standalone_equation(original_line): - return f'_add_equation("{original_line}")', "equation" - - # 4. NUEVO: Aplicar tokenización automática - if self.use_tokenizer: - transformed_line = self._apply_tokenization(original_line) - - # Contar conversiones para estadísticas - intbase_count = transformed_line.count('IntBase(') - fourbytes_count = transformed_line.count('FourBytes(') - self.stats['intbase_conversions'] += intbase_count - self.stats['fourbytes_conversions'] += fourbytes_count - - if transformed_line != original_line: - if self.debug: - print(f"🔧 Tokenización: '{original_line}' → '{transformed_line}'") - return transformed_line, "tokenized" - - # 5. Si no hay transformaciones, devolver original - return original_line, "expression" - - except Exception as e: - if self.debug: - print(f"Error parsing line '{original_line}': {e}") - return code_line, "parse_error" - - def _apply_tokenization(self, line: str) -> str: - """ - Aplica tokenización automática usando el tokenizador de sympy_Base - """ - if not TOKENIZER_AVAILABLE: - return line - - try: - return preprocess_tokens(line) - except Exception as e: - if self.debug: - print(f"Error en tokenización: {e}") - return line - - def _transform_solve_shortcut(self, line: str) -> Tuple[str, bool]: - """ - Transforma 'variable=?' en '_solve_variable_in_system("variable")' - """ - # Pattern: variable_name = ? - pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\?$' - match = re.match(pattern, line.strip()) - - if match: - var_name = match.group(1) - return f'_solve_variable_in_system("{var_name}")', True - - return line, False - - def _is_assignment(self, line: str) -> bool: - """ - Detecta si una línea es una asignación de variable - NUEVA LÓGICA: Priorizar asignaciones, ser menos estricto - """ - try: - # Pattern: variable = expresión (que no sea comparación) - if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']): - # Verificar que sea una asignación válida de Python - parts = line.split('=', 1) - if len(parts) == 2: - var_part = parts[0].strip() - expr_part = parts[1].strip() - - # Verificar que la parte izquierda sea un identificador válido - if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part): - # NUEVA LÓGICA: Si tiene formato de asignación válida, asumir que ES asignación - # Las ecuaciones son solo para casos muy específicos como solve() - return True - return False - except: - return False - - def _transform_assignment(self, line: str) -> str: - """Transforma asignación a llamada de función especial""" - parts = line.split('=', 1) - var_name = parts[0].strip() - expression = parts[1].strip() - - # Aplicar tokenización a la expresión - if self.use_tokenizer: - expression = self._apply_tokenization(expression) - - return f'_assign_variable("{var_name}", {expression})' - - def _is_standalone_equation(self, line: str) -> bool: - """ - Determina si una línea es una ecuación standalone - NUEVA LÓGICA: Solo ecuaciones matemáticas obvias, no asignaciones - """ - try: - # Primero verificar si contiene '=' simple - if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']): - return False - - # NUEVA LÓGICA: Si ya fue clasificada como asignación, NO es ecuación - if self._is_assignment(line): - return False - - try: - tree = ast.parse(line.strip()) - - if not tree.body: - return False - - node = tree.body[0] - - # Si es una expresión (no asignación), verificar comparaciones - if isinstance(node, ast.Expr): - if isinstance(node.value, ast.Compare): - for op in node.value.ops: - if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)): - return True - - return False - - except SyntaxError: - # NUEVA LÓGICA: Solo tratar como ecuación si NO tiene formato de asignación válida - parts = line.split('=', 1) - if len(parts) == 2: - var_part = parts[0].strip() - # Si la parte izquierda no es un identificador válido, puede ser ecuación matemática - if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part): - if self.debug: - print(f"🔍 '{line}' error AST y no es asignación válida → Tratando como ECUACIÓN") - return True - return False - - except Exception as e: - if self.debug: - print(f"🚨 Error en _is_standalone_equation: {e}") - return False - - def preview_tokenization(self, text: str) -> str: - """ - Muestra preview de cómo se tokenizaría el texto sin ejecutar - Útil para debugging y testing - """ - if not TOKENIZER_AVAILABLE: - return "Tokenizador no disponible" - - lines = text.split('\n') - preview_lines = [] - - for i, line in enumerate(lines, 1): - original = line.strip() - if not original or original.startswith('#'): - preview_lines.append(f"{i:2}: {line}") - continue - - tokenized = self._apply_tokenization(original) - if tokenized != original: - preview_lines.append(f"{i:2}: {original}") - preview_lines.append(f" → {tokenized}") - else: - preview_lines.append(f"{i:2}: {line}") - - return '\n'.join(preview_lines) - - def test_patterns(self) -> str: - """ - Prueba los patrones de tokenización con ejemplos - """ - test_cases = [ - # IntBase patterns - "16#FF", - "2#1010", - "8#777", - "16#x0", - "2#101x", - - # FourBytes patterns - "192.168.1.1", - "255.255.0.0", - "10.1.x.2", - "a.b.c.d", - - # Mixed patterns - "16#FF + 192.168.1.1", - "2#1010 * 10.0.0.1", - - # Should NOT be tokenized - "obj.method.call()", - "x.y", # Only 2 elements - "a.b.c.d.e", # 5 elements - ] - - results = [] - for test in test_cases: - tokenized = self._apply_tokenization(test) if TOKENIZER_AVAILABLE else test - status = "✓ TOKENIZED" if tokenized != test else "○ NO CHANGE" - results.append(f"{status}: '{test}' → '{tokenized}'") - - return '\n'.join(results) + return { + 'input_expressions': test_cases, + 'tokenized_results': results, + 'successful_conversions': [expr for expr, result in results.items() if expr != result], + 'failed_conversions': [expr for expr, result in results.items() if expr == result] + } -# Mantener compatibilidad con el sistema anterior +# Mantener BracketParser como alias de compatibilidad class BracketParser(TokenizationParser): """ - Alias para compatibilidad con código existente - Ahora usa el nuevo sistema de tokenización + Alias de compatibilidad para el código existente """ - def __init__(self, use_type_registry: bool = True): - # Ignorar use_type_registry, ya no se usa - super().__init__(use_tokenizer=True) + def __init__(self, enable_tokenization=True, debug=False): + super().__init__(enable_tokenization, debug) + if debug: + print("🔄 BracketParser iniciado con tokenización universal") - def reload_bracket_classes(self): - """Método de compatibilidad - no hace nada en el nuevo sistema""" - if self.debug: - print("🔄 reload_bracket_classes() llamado - no necesario en nuevo sistema") - - def add_bracket_class(self, class_name: str): - """Método de compatibilidad - no hace nada en el nuevo sistema""" - if self.debug: - print(f"🔄 add_bracket_class({class_name}) llamado - no necesario en nuevo sistema") - - def remove_bracket_class(self, class_name: str): - """Método de compatibilidad - no hace nada en el nuevo sistema""" - if self.debug: - print(f"🔄 remove_bracket_class({class_name}) llamado - no necesario en nuevo sistema") - - def get_bracket_classes(self) -> Set[str]: - """Método de compatibilidad - retorna conjunto vacío""" - return set() - - def has_bracket_class(self, class_name: str) -> bool: - """Método de compatibilidad - siempre retorna False""" - return False + def parse_expression(self, expression: str) -> str: + """Método de compatibilidad que usa el nuevo sistema""" + return self.process_expression(expression) -# ========== FUNCIONES DE UTILIDAD ========== +# Funciones de utilidad para testing y preview +def preview_tokenization(expression: str, debug: bool = True) -> Dict[str, Any]: + """Vista previa de cómo se tokenizaría una expresión""" + tokenizer = UniversalTokenizer() + tokenizer.debug = debug + + original = expression + tokenized = tokenizer.preprocess_tokens(expression) + + return { + 'original': original, + 'tokenized': tokenized, + 'changed': original != tokenized, + 'rules_applied': tokenizer.get_tokenization_info() + } -def test_tokenization_system(): - """Función de testing completa del sistema de tokenización""" - print("🧪 TESTING SISTEMA DE TOKENIZACIÓN") + +def test_tokenization_patterns(test_cases: List[str]) -> None: + """Función de testing para validar patrones de tokenización""" + parser = TokenizationParser(debug=True) + + print("🧪 Probando patrones de tokenización...") print("=" * 50) - parser = TokenizationParser(use_tokenizer=True) - parser.debug = True + results = parser.test_patterns(test_cases) - # Test patterns - print("\n1. Test de patrones básicos:") - print(parser.test_patterns()) + print(f"\nResultados de {len(test_cases)} casos de prueba:") + print(f"✅ Exitosos: {len(results['successful_conversions'])}") + print(f"❌ Sin cambios: {len(results['failed_conversions'])}") - # Test de líneas completas - test_lines = [ - "ip = 192.168.1.1", - "mask = 16#ffffff00", - "net = 10.x.y.0", - "result = 16#FF + 2#1010", - "a.b.c.d + 255.255.255.0", - "x = solve(y + 2)", # No debería tokenizarse solve - ] + if results['successful_conversions']: + print("\n🎉 Conversiones exitosas:") + for expr in results['successful_conversions']: + result = results['tokenized_results'][expr] + print(f" '{expr}' → '{result}'") - print(f"\n2. Test de líneas completas:") - for line in test_lines: - transformed, info = parser.parse_line(line) - print(f" '{line}' → '{transformed}' [{info}]") + if results['failed_conversions']: + print("\n⚠️ Sin conversión:") + for expr in results['failed_conversions']: + print(f" '{expr}' (sin cambios)") - # Estadísticas - print(f"\n3. Estadísticas:") - stats = parser.get_tokenization_stats() - for key, value in stats.items(): - print(f" {key}: {value}") - - print("\n✅ Testing completado") - - -if __name__ == "__main__": - test_tokenization_system() \ No newline at end of file + print("\n📋 Reglas activas:") + info = parser.get_tokenization_info() + for rule in info['rules']: + print(f" {rule['priority']:2d}: {rule['class']} - {rule['description']}") \ No newline at end of file