diff --git a/custom_types/bin_type.py b/custom_types/bin_type.py index d005665..00fdf3f 100644 --- a/custom_types/bin_type.py +++ b/custom_types/bin_type.py @@ -1,46 +1,174 @@ """ Clase híbrida para números binarios - ADAPTADA AL NUEVO SISTEMA +Ahora usa IntBase como clase base universal """ -from sympy_Base import SympyClassBase +from sympy_Base import SympyClassBase, IntBase import re class Class_Bin(SympyClassBase): - """Clase híbrida para números binarios""" - - def __new__(cls, value_input): - """Crear objeto SymPy válido""" - obj = SympyClassBase.__new__(cls) - return obj + """Clase híbrida para números binarios basada en IntBase""" def __init__(self, value_input): - """Inicialización de Bin""" - # Convertir input a entero - if isinstance(value_input, str): - # Remover prefijo 0b si existe - if value_input.startswith('0b'): - value_input = value_input[2:] - # Convertir a entero - value = int(value_input, 2) - else: - value = int(value_input) + """Inicialización de Bin usando IntBase como base""" - # Llamar al constructor base - super().__init__(value, f"0b{value:08b}") + if isinstance(value_input, IntBase): + # value es IntBase (ya tokenizado desde 2#valor) + if value_input.base == 2: + self.int_base = value_input + else: + # Convertir de otra base a binario + if not value_input.has_symbols: + bin_value = IntBase._convert_to_base_string(value_input._numeric_value, 2) + self.int_base = IntBase(bin_value, 2) + else: + # Mantener simbólico para análisis algebraico + self.int_base = value_input + elif isinstance(value_input, str): + # Conversión desde string + clean_value = value_input + if clean_value.startswith('0b'): + clean_value = clean_value[2:] + self.int_base = IntBase(clean_value, 2) + elif isinstance(value_input, (int, float)) or hasattr(value_input, '__int__'): + # Conversión desde entero (incluyendo sympy.Integer) + try: + int_val = int(value_input) + bin_value = IntBase._convert_to_base_string(int_val, 2) + self.int_base = IntBase(bin_value, 2) + except (ValueError, TypeError): + raise TypeError(f"No se puede convertir {type(value_input)} a entero") + else: + raise TypeError(f"Bin no puede inicializarse con {type(value_input)}") + + # Llamar al constructor base con el valor del IntBase + super().__init__(self.int_base.value, str(self.int_base)) def __str__(self): """Representación string para display""" - return f"0b{self._value:08b}" + if self.int_base.has_symbols: + return f"0b{self.int_base.value_str}" + else: + return f"0b{self.int_base._numeric_value:08b}" def _sympystr(self, printer): """Representación SymPy string""" return str(self) + def __repr__(self): + return f"Bin({self.int_base.value_str})" + + # ========== MÉTODOS DE CONVERSIÓN ========== + + def toDecimal(self): + """Convierte a decimal""" + if self.int_base.has_symbols: + return self.int_base._symbolic_value # Expresión simbólica + else: + return self.int_base._numeric_value + + def toHex(self): + """Convierte a hexadecimal""" + if self.int_base.has_symbols: + return f"Hex({self.int_base._symbolic_value})" + else: + hex_value = IntBase._convert_to_base_string(self.int_base._numeric_value, 16) + return f"0x{hex_value}" + + def toOctal(self): + """Convierte a octal""" + if self.int_base.has_symbols: + return f"Oct({self.int_base._symbolic_value})" + else: + oct_value = IntBase._convert_to_base_string(self.int_base._numeric_value, 8) + return f"0o{oct_value}" + + def toBase(self, base): + """Convierte a cualquier base""" + return self.int_base.to_base(base) + + # ========== OPERADORES ARITMÉTICOS (delegados a IntBase) ========== + + def __add__(self, other): + """Suma delegada a IntBase""" + if isinstance(other, Class_Bin): + result_intbase = self.int_base + other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base + other + elif isinstance(other, int): + result_intbase = self.int_base + other + else: + return NotImplemented + + return Class_Bin(result_intbase) + + def __sub__(self, other): + """Resta delegada a IntBase""" + if isinstance(other, Class_Bin): + result_intbase = self.int_base - other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base - other + elif isinstance(other, int): + result_intbase = self.int_base - other + else: + return NotImplemented + + return Class_Bin(result_intbase) + + def __mul__(self, other): + """Multiplicación delegada a IntBase""" + if isinstance(other, Class_Bin): + result_intbase = self.int_base * other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base * other + elif isinstance(other, int): + result_intbase = self.int_base * other + else: + return NotImplemented + + return Class_Bin(result_intbase) + + def __truediv__(self, other): + """División delegada a IntBase""" + if isinstance(other, Class_Bin): + result_intbase = self.int_base / other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base / other + elif isinstance(other, int): + result_intbase = self.int_base / other + else: + return NotImplemented + + return Class_Bin(result_intbase) + + def __mod__(self, other): + """Módulo delegado a IntBase""" + if isinstance(other, Class_Bin): + result_intbase = self.int_base % other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base % other + elif isinstance(other, int): + result_intbase = self.int_base % other + else: + return NotImplemented + + return Class_Bin(result_intbase) + + # Operadores reversos + def __radd__(self, other): + return self.__add__(other) + + def __rmul__(self, other): + return self.__mul__(other) + + # ========== MÉTODOS ESTÁTICOS ========== + @staticmethod def Helper(input_str): """Ayuda contextual para Bin""" if re.match(r"^\s*Bin\b", input_str, re.IGNORECASE): - return 'Ej: Bin[1010], Bin[10]\nFunciones: toDecimal()' + return ('Ej: 2#1010 (se convierte automáticamente), Bin(10)\n' + 'Funciones: toDecimal(), toHex(), toOctal(), toBase(n)') return None @staticmethod @@ -48,23 +176,23 @@ class Class_Bin(SympyClassBase): """Lista de métodos sugeridos para autocompletado de Bin""" return [ ("toDecimal", "Convierte a decimal"), + ("toHex", "Convierte a hexadecimal"), + ("toOctal", "Convierte a octal"), + ("toBase", "Convierte a base específica"), ] - - def toDecimal(self): - """Convierte a decimal""" - return self._value -# ========== FUNCIÓN DE REGISTRO - NUEVA ========== +# ========== FUNCIÓN DE REGISTRO ========== def register_classes_in_module(): """ Devuelve una lista de clases definidas en este módulo para ser registradas. + NOTA: Ya no se registra para corchetes, el tokenizador maneja 2#valor automáticamente """ return [ ("Bin", Class_Bin, "SympyClassBase", { "add_lowercase": True, - "supports_brackets": True, - "description": "Números binarios" + "supports_brackets": False, # Ya no usa corchetes + "description": "Números binarios (usar 2#valor para tokenización automática)" }), ] \ No newline at end of file diff --git a/custom_types/dec_type.py b/custom_types/dec_type.py index c8bfd57..0e3d55e 100644 --- a/custom_types/dec_type.py +++ b/custom_types/dec_type.py @@ -14,9 +14,19 @@ class Class_Dec(SympyClassBase): return obj def __init__(self, value_input): - """Inicialización de Dec""" - # Convertir input a entero - if isinstance(value_input, str): + """Inicialización de Dec - MEJORADA para compatibilidad con IntBase""" + + # Manejar diferentes tipos de input + if hasattr(value_input, '_numeric_value'): + # Es un IntBase u objeto similar con valor numérico + value = value_input._numeric_value + elif hasattr(value_input, 'value') and isinstance(value_input.value, (int, float)): + # Objeto con propiedad value numérica + value = int(value_input.value) + elif isinstance(value_input, str): + value = int(value_input) + elif hasattr(value_input, '__int__'): + # Cualquier objeto convertible a int (incluyendo sympy.Integer) value = int(value_input) else: value = int(value_input) diff --git a/custom_types/hex_type.py b/custom_types/hex_type.py index 4edbff4..b5263e0 100644 --- a/custom_types/hex_type.py +++ b/custom_types/hex_type.py @@ -1,46 +1,175 @@ """ Clase híbrida para números hexadecimales - ADAPTADA AL NUEVO SISTEMA +Ahora usa IntBase como clase base universal """ -from sympy_Base import SympyClassBase +from sympy_Base import SympyClassBase, IntBase import re class Class_Hex(SympyClassBase): - """Clase híbrida para números hexadecimales""" - - def __new__(cls, value_input): - """Crear objeto SymPy válido""" - obj = SympyClassBase.__new__(cls) - return obj + """Clase híbrida para números hexadecimales basada en IntBase""" def __init__(self, value_input): - """Inicialización de Hex""" - # Convertir input a entero - if isinstance(value_input, str): - # Remover prefijo 0x si existe - if value_input.startswith('0x'): - value_input = value_input[2:] - # Convertir a entero - value = int(value_input, 16) - else: - value = int(value_input) + """Inicialización de Hex usando IntBase como base""" - # Llamar al constructor base - super().__init__(value, f"0x{value:02x}") + if isinstance(value_input, IntBase): + # value es IntBase (ya tokenizado desde 16#valor) + if value_input.base == 16: + self.int_base = value_input + else: + # Convertir de otra base a hexadecimal + if not value_input.has_symbols: + hex_value = IntBase._convert_to_base_string(value_input._numeric_value, 16) + self.int_base = IntBase(hex_value, 16) + else: + # Mantener simbólico para análisis algebraico + self.int_base = value_input + elif isinstance(value_input, str): + # Conversión desde string + clean_value = value_input + if clean_value.startswith('0x'): + clean_value = clean_value[2:] + self.int_base = IntBase(clean_value, 16) + elif isinstance(value_input, (int, float)) or hasattr(value_input, '__int__'): + # Conversión desde entero (incluyendo sympy.Integer) + try: + int_val = int(value_input) + hex_value = IntBase._convert_to_base_string(int_val, 16) + self.int_base = IntBase(hex_value, 16) + except (ValueError, TypeError): + raise TypeError(f"No se puede convertir {type(value_input)} a entero") + else: + raise TypeError(f"Hex no puede inicializarse con {type(value_input)}") + + # Llamar al constructor base con el valor del IntBase + super().__init__(self.int_base.value, str(self.int_base)) def __str__(self): """Representación string para display""" - return f"0x{self._value:02x}" + if self.int_base.has_symbols: + return f"0x{self.int_base.value_str}" + else: + return f"0x{self.int_base._numeric_value:02X}" def _sympystr(self, printer): """Representación SymPy string""" return str(self) + def __repr__(self): + return f"Hex({self.int_base.value_str})" + + # ========== MÉTODOS DE CONVERSIÓN ========== + + def toDecimal(self): + """Convierte a decimal""" + if self.int_base.has_symbols: + return self.int_base._symbolic_value # Expresión simbólica + else: + return self.int_base._numeric_value + + def toBinary(self): + """Convierte a binario""" + if self.int_base.has_symbols: + # Para valores simbólicos, crear expresión de conversión + return f"Bin({self.int_base._symbolic_value})" + else: + bin_value = IntBase._convert_to_base_string(self.int_base._numeric_value, 2) + return f"0b{bin_value}" + + def toOctal(self): + """Convierte a octal""" + if self.int_base.has_symbols: + return f"Oct({self.int_base._symbolic_value})" + else: + oct_value = IntBase._convert_to_base_string(self.int_base._numeric_value, 8) + return f"0o{oct_value}" + + def toBase(self, base): + """Convierte a cualquier base""" + return self.int_base.to_base(base) + + # ========== OPERADORES ARITMÉTICOS (delegados a IntBase) ========== + + def __add__(self, other): + """Suma delegada a IntBase""" + if isinstance(other, Class_Hex): + result_intbase = self.int_base + other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base + other + elif isinstance(other, int): + result_intbase = self.int_base + other + else: + return NotImplemented + + return Class_Hex(result_intbase) + + def __sub__(self, other): + """Resta delegada a IntBase""" + if isinstance(other, Class_Hex): + result_intbase = self.int_base - other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base - other + elif isinstance(other, int): + result_intbase = self.int_base - other + else: + return NotImplemented + + return Class_Hex(result_intbase) + + def __mul__(self, other): + """Multiplicación delegada a IntBase""" + if isinstance(other, Class_Hex): + result_intbase = self.int_base * other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base * other + elif isinstance(other, int): + result_intbase = self.int_base * other + else: + return NotImplemented + + return Class_Hex(result_intbase) + + def __truediv__(self, other): + """División delegada a IntBase""" + if isinstance(other, Class_Hex): + result_intbase = self.int_base / other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base / other + elif isinstance(other, int): + result_intbase = self.int_base / other + else: + return NotImplemented + + return Class_Hex(result_intbase) + + def __mod__(self, other): + """Módulo delegado a IntBase""" + if isinstance(other, Class_Hex): + result_intbase = self.int_base % other.int_base + elif isinstance(other, IntBase): + result_intbase = self.int_base % other + elif isinstance(other, int): + result_intbase = self.int_base % other + else: + return NotImplemented + + return Class_Hex(result_intbase) + + # Operadores reversos + def __radd__(self, other): + return self.__add__(other) + + def __rmul__(self, other): + return self.__mul__(other) + + # ========== MÉTODOS ESTÁTICOS ========== + @staticmethod def Helper(input_str): """Ayuda contextual para Hex""" if re.match(r"^\s*Hex\b", input_str, re.IGNORECASE): - return 'Ej: Hex[FF], Hex[255]\nFunciones: toDecimal()' + return ('Ej: 16#FF (se convierte automáticamente), Hex(255)\n' + 'Funciones: toDecimal(), toBinary(), toOctal(), toBase(n)') return None @staticmethod @@ -48,22 +177,23 @@ class Class_Hex(SympyClassBase): """Lista de métodos sugeridos para autocompletado de Hex""" return [ ("toDecimal", "Convierte a decimal"), + ("toBinary", "Convierte a binario"), + ("toOctal", "Convierte a octal"), + ("toBase", "Convierte a base específica"), ] - - def toDecimal(self): - """Convierte a decimal""" - return self._value +# ========== FUNCIÓN DE REGISTRO ========== def register_classes_in_module(): """ Devuelve una lista de clases definidas en este módulo para ser registradas. + NOTA: Ya no se registra para corchetes, el tokenizador maneja 16#valor automáticamente """ return [ ("Hex", Class_Hex, "SympyClassBase", { "add_lowercase": True, - "supports_brackets": True, - "description": "Números hexadecimales" + "supports_brackets": False, # Ya no usa corchetes + "description": "Números hexadecimales (usar 16#valor para tokenización automática)" }), ] \ No newline at end of file diff --git a/custom_types/ip4_type.py b/custom_types/ip4_type.py index c5a33dc..a6a5387 100644 --- a/custom_types/ip4_type.py +++ b/custom_types/ip4_type.py @@ -1,8 +1,9 @@ """ -Clase híbrida para direcciones IPv4 +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 +from sympy_Base import SympyClassBase, FourBytes, IntBase from typing import Optional, Union, List, Tuple import re import sympy @@ -10,16 +11,37 @@ import sympy class IP4Mask(ClassBase): """ Helper class to manage IPv4 masks. - It can be initialized with an integer prefix (0-32) or a netmask string (e.g., "255.255.255.0"). + Ahora soporta IntBase y FourBytes desde tokenización automática """ _prefix: int _mask_int: int - def __init__(self, mask_input: Union[int, str]): - prefix = self._parse_mask(mask_input) - super().__init__(prefix, str(mask_input)) - self._prefix = prefix - self._mask_int = self._prefix_to_mask_int(self._prefix) + def __init__(self, mask_input: Union[int, str, FourBytes, IntBase]): + if 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 + 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): + # Máscara desde hex u otra base (ej: 16#ffffff00) + 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: + # Procesamiento tradicional + prefix = self._parse_mask(mask_input) + self._prefix = prefix + self._mask_int = self._prefix_to_mask_int(self._prefix) + + super().__init__(self._mask_int, str(mask_input)) def _parse_mask(self, mask_input: Union[int, str]) -> int: """Helper to parse mask_input and return prefix.""" @@ -80,6 +102,19 @@ class IP4Mask(ClassBase): return 0 return (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF + @staticmethod + def _mask_int_to_prefix(mask_int: int) -> int: + """Convierte entero de máscara a prefijo CIDR""" + if mask_int == 0: + return 0 + + # 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") + + return binary_mask.count('1') + def get_mask_str(self) -> str: return f"{(self._mask_int >> 24) & 0xFF}.{(self._mask_int >> 16) & 0xFF}.{(self._mask_int >> 8) & 0xFF}.{self._mask_int & 0xFF}" @@ -282,116 +317,124 @@ class IP4Mask(ClassBase): class Class_IP4(SympyClassBase): - """Clase híbrida para direcciones IPv4""" + """Clase híbrida para direcciones IPv4 - ADAPTADA AL NUEVO SISTEMA""" - def __new__(cls, *args, **kwargs): - temp_ip_str_candidate = args[0] if args else "" - temp_ip_str = "" - if isinstance(temp_ip_str_candidate, str): - if '/' in temp_ip_str_candidate: - temp_ip_str = temp_ip_str_candidate.split('/', 1)[0].strip() - elif ' ' in temp_ip_str_candidate: - temp_ip_str = temp_ip_str_candidate.split(' ', 1)[0].strip() - else: - temp_ip_str = temp_ip_str_candidate.strip() + def __init__(self, address, mask=None): + """ + Constructor mejorado que acepta FourBytes desde tokenización automática + + Args: + address: FourBytes (tokenizado automáticamente), string, o Class_IP4 existente + mask: int, string, IP4Mask, FourBytes, o IntBase + """ + + # Procesar dirección + if isinstance(address, FourBytes): + # address es FourBytes (ya tokenizado desde x.x.x.x) + self.address = address + self._ip_str = address.original - if IP4Mask._is_valid_ip_string(temp_ip_str): - ip_parts_for_new = [int(x) for x in temp_ip_str.split('.')] - ip_int_for_new = (ip_parts_for_new[0] << 24) | \ - (ip_parts_for_new[1] << 16) | \ - (ip_parts_for_new[2] << 8) | \ - ip_parts_for_new[3] - obj = SympyClassBase.__new__(cls, ip_int_for_new) + if address.has_symbols: + # Modo algebraico + self._ip_int = address._symbolic_value + self._has_symbols = True else: - raise ValueError(f"Invalid IP address format for __new__: {temp_ip_str}") + # Modo numérico + self._ip_int = address._numeric_value + self._has_symbols = False + + elif isinstance(address, str): + # String tradicional - puede incluir máscara + if '/' in address: + addr_part, mask_part = address.split('/', 1) + self._ip_str = addr_part.strip() + if mask is None: + mask = mask_part.strip() + elif ' ' in address: + addr_part, mask_part = address.split(' ', 1) + self._ip_str = addr_part.strip() + if mask is None: + mask = mask_part.strip() + else: + self._ip_str = address.strip() + + # Crear FourBytes manualmente para consistencia + self.address = FourBytes(self._ip_str) + if self.address.has_symbols: + self._ip_int = self.address._symbolic_value + self._has_symbols = True + else: + self._ip_int = self.address._numeric_value + self._has_symbols = False + else: - if not args: - raise ValueError("IP4 constructor requires arguments.") - obj = SympyClassBase.__new__(cls, *args) - - return obj + raise TypeError(f"address debe ser FourBytes o string, recibido: {type(address)}") + + # Procesar máscara + if mask is not None: + if isinstance(mask, IP4Mask): + self._mask_obj = mask + elif isinstance(mask, (int, str)): + self._mask_obj = IP4Mask(mask) + elif isinstance(mask, FourBytes): + # Máscara desde tokenización (ej: 255.255.255.0) + self._mask_obj = IP4Mask(mask) + elif isinstance(mask, IntBase): + # Máscara desde hex (ej: 16#ffffff00) + self._mask_obj = IP4Mask(mask) + else: + raise TypeError(f"mask debe ser int, str, IP4Mask, FourBytes, o IntBase") + else: + self._mask_obj = None + + # Llamar al constructor base de SymPy + if self._has_symbols: + super().__init__(self._ip_int, str(self.address)) + else: + super().__init__(self._ip_int, self._ip_str) - def __init__(self, *args): - self._raw_constructor_args = args - - ip_str: str - self._mask_obj: Optional[IP4Mask] = None - - if not args: - raise ValueError("IP4 constructor requires at least one argument (the IP string).") - - ip_str_candidate = args[0] - if not isinstance(ip_str_candidate, str): - raise TypeError(f"First argument to IP4 must be a string (IP address), got {type(ip_str_candidate)}") - - if len(args) == 1: - input_str = args[0] - if '/' in input_str: - parts = input_str.split('/', 1) - ip_str = parts[0].strip() - if len(parts) > 1 and parts[1].strip(): - self._mask_obj = IP4Mask(parts[1].strip()) - elif ' ' in input_str: - parts = input_str.split(' ', 1) - ip_str = parts[0].strip() - if len(parts) > 1 and parts[1].strip(): - self._mask_obj = IP4Mask(parts[1].strip()) - else: - ip_str = input_str.strip() - - elif len(args) == 2: - ip_str = args[0].strip() - mask_arg = args[1] - - if isinstance(mask_arg, IP4Mask): - self._mask_obj = mask_arg - elif isinstance(mask_arg, (str, int)): - self._mask_obj = IP4Mask(mask_arg) - else: - raise TypeError(f"Second argument (mask) for IP4 must be int, str, or IP4Mask instance, got {type(mask_arg)}") - else: - raise ValueError(f"IP4 constructor takes 1 or 2 arguments, got {len(args)}: {args}") - - if not IP4Mask._is_valid_ip_string(ip_str): - raise ValueError(f"Invalid IP address format: {ip_str}") - - self._ip_str = ip_str - ip_parts = [int(x) for x in ip_str.split('.')] - self._ip_int = (ip_parts[0] << 24) | (ip_parts[1] << 16) | (ip_parts[2] << 8) | ip_parts[3] - - sympy_base_original_str: str - if len(args) == 1 and isinstance(args[0], str): - sympy_base_original_str = args[0] - elif len(args) == 2: - mask_arg_for_repr = args[1] - if isinstance(mask_arg_for_repr, IP4Mask): - mask_repr_str = str(mask_arg_for_repr.get_prefix_int()) - elif isinstance(mask_arg_for_repr, int): - mask_repr_str = str(mask_arg_for_repr) - else: - mask_repr_str = str(mask_arg_for_repr) - sympy_base_original_str = f"{args[0]};{mask_repr_str}" - else: - sympy_base_original_str = self._ip_str - - super().__init__(self._ip_int, sympy_base_original_str) - def __repr__(self): - arg_reprs = [repr(arg) for arg in self._raw_constructor_args] - return f"{self.__class__.__name__}({', '.join(arg_reprs)})" + if self._mask_obj: + return f"IP4({self._ip_str!r}, {self._mask_obj.get_prefix_int()})" + return f"IP4({self._ip_str!r})" def __str__(self): if self._mask_obj: - return f"{self._ip_str}/{self._mask_obj.get_prefix_int()}" + if self._mask_obj._prefix is not None: + return f"{self._ip_str}/{self._mask_obj._prefix}" + else: + return f"{self._ip_str}/{self._mask_obj}" return self._ip_str def _sympystr(self, printer): return str(self) + # ========== MÉTODOS PARA TRABAJAR CON SÍMBOLOS ========== + + def substitute(self, **kwargs): + """Sustituye símbolos por valores""" + if not self._has_symbols: + return self + + new_address = self.address.substitute(**kwargs) + return Class_IP4(new_address, self._mask_obj) + + def get_symbols(self): + """Obtiene símbolos en la dirección""" + if self._has_symbols: + return [elem for elem in self.address.elements if not elem.isdigit()] + return [] + + def is_symbolic(self): + """¿Contiene símbolos?""" + return self._has_symbols + @staticmethod def Helper(input_str): if re.match(r"^\s*IP4\b", input_str, re.IGNORECASE): - return ('Ej: IP4[192.168.1.1/24], IP4[10.0.0.1;8]. Funciones: NetworkAddress(), BroadcastAddress(), add_hosts(), next_ip(), mask()') + return ('Ej: 192.168.1.1 (se tokeniza automáticamente), IP4(192.168.1.1, 24)\n' + 'Soporte algebraico: 10.x.y.0/24, substitute(x=1, y=2)\n' + 'Funciones: NetworkAddress(), BroadcastAddress(), add_hosts(), next_ip(), mask()') return None @staticmethod @@ -401,6 +444,9 @@ class Class_IP4(SympyClassBase): ("BroadcastAddress", "Dirección de broadcast"), ("Nodes", "Hosts disponibles"), ("mask", "Objeto máscara con sus métodos"), + ("substitute", "Sustituir símbolos por valores"), + ("get_symbols", "Obtener símbolos en la dirección"), + ("is_symbolic", "¿Contiene símbolos?"), ("add_hosts", "Sumar N hosts a la dirección"), ("subtract_hosts", "Restar N hosts a la dirección"), ("next_ip", "Siguiente dirección IP"), @@ -496,15 +542,33 @@ class Class_IP4(SympyClassBase): def add_hosts(self, n: int) -> 'Class_IP4': """Suma N hosts a la dirección""" - new_ip_int = (self._ip_int + n) & 0xFFFFFFFF - new_parts = [ - (new_ip_int >> 24) & 0xFF, - (new_ip_int >> 16) & 0xFF, - (new_ip_int >> 8) & 0xFF, - new_ip_int & 0xFF - ] - new_ip_str = '.'.join(str(x) for x in new_parts) - return Class_IP4(new_ip_str, self._mask_obj) + if self._has_symbols: + # NUEVO: Para IPs simbólicas, crear nueva instancia con expresión simbólica actualizada + new_ip_expr = self._ip_int + n + + # Crear nueva instancia directamente con la expresión simbólica + new_ip = Class_IP4.__new__(Class_IP4) + new_ip._ip_int = new_ip_expr + new_ip._has_symbols = True + new_ip._ip_str = f"({new_ip_expr})" + new_ip._mask_obj = self._mask_obj + new_ip.address = self.address # Mantener la referencia al FourBytes original + + # Llamar constructor base SymPy + SympyClassBase.__init__(new_ip, new_ip_expr, new_ip._ip_str) + + return new_ip + else: + # Modo numérico original + new_ip_int = (self._ip_int + n) & 0xFFFFFFFF + new_parts = [ + (new_ip_int >> 24) & 0xFF, + (new_ip_int >> 16) & 0xFF, + (new_ip_int >> 8) & 0xFF, + new_ip_int & 0xFF + ] + new_ip_str = '.'.join(str(x) for x in new_parts) + return Class_IP4(new_ip_str, self._mask_obj) def subtract_hosts(self, n: int) -> 'Class_IP4': """Resta N hosts a la dirección""" @@ -882,16 +946,17 @@ class Class_IP4(SympyClassBase): def register_classes_in_module(): """ Devuelve una lista de clases definidas en este módulo para ser registradas. + NOTA: Ya no se registra para corchetes, el tokenizador maneja x.x.x.x automáticamente """ return [ ("IP4", Class_IP4, "SympyClassBase", { "add_lowercase": True, - "supports_brackets": True, - "description": "Direcciones IPv4 con máscara" + "supports_brackets": False, # Ya no usa corchetes + "description": "Direcciones IPv4 con máscara (usar x.x.x.x para tokenización automática)" }), ("IP4Mask", IP4Mask, "ClassBase", { "add_lowercase": True, - "supports_brackets": True, + "supports_brackets": False, # Ya no usa corchetes "description": "Máscaras de red IPv4" }), ] @@ -902,15 +967,20 @@ def register_classes_in_module(): def get_module_info(): """Información adicional sobre este módulo""" return { - "name": "IPv4 Network Types", - "description": "Clases para manejo de direcciones IP y máscaras de red", - "version": "1.0", + "name": "IPv4 Network Types - Tokenization System", + "description": "Clases para manejo de direcciones IP y máscaras con tokenización automática", + "version": "2.0", "depends_on": ["class_base", "sympy_Base"], "examples": [ - "IP4[192.168.1.1/24]", - "IP4[10.0.0.1;255.255.255.0]", - "IP4[192.168.1.1/24].NetworkAddress()", - "IP4Mask[24]", - "IP4Mask[255.255.255.0]" + "192.168.1.1 # → FourBytes('192.168.1.1') automáticamente", + "IP4(192.168.1.1, 24)", + "ip = 10.x.y.0; ip.substitute(x=1, y=2)", + "mask = 16#ffffff00 # → IntBase('ffffff00', 16)", + "net = IP4(192.168.1.0, mask)", + ], + "tokenization_patterns": [ + "x.x.x.x → FourBytes('x.x.x.x')", + "16#ffffff00 → IntBase('ffffff00', 16)", + "2#11111111 → IntBase('11111111', 2)" ] } \ No newline at end of file diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index e945386..dbc6382 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -1,10 +1,19 @@ -m=IP4Mask[255.240.0.0] +mm=255.240.0.x +IP4(mm,24) +j=IP4(mm,24) +x=5 +mm +j.substitute(x=1) +j.next_ip() -m.get_prefix_int() -m.hosts_count() +hh=16#ff +Dec(hh) -i=IP4("0.1.30.70",m.get_prefix_int()) +h=16#ff + 25 +h -9*3*36.0 \ No newline at end of file +IP4Mask(255.240.0.0) +IP4(10.1.280.1) +IP4(10.1.x.1) \ No newline at end of file diff --git a/hybrid_calc_settings.json b/hybrid_calc_settings.json index b234ffc..36065f2 100644 --- a/hybrid_calc_settings.json +++ b/hybrid_calc_settings.json @@ -1,6 +1,6 @@ { - "window_geometry": "1020x700+708+147", - "sash_pos_x": 368, + "window_geometry": "1020x700+236+1240", + "sash_pos_x": 360, "symbolic_mode": true, "show_numeric_approximation": true, "keep_symbolic_fractions": true, diff --git a/main_evaluation.py b/main_evaluation.py index 383b7a4..181c684 100644 --- a/main_evaluation.py +++ b/main_evaluation.py @@ -20,6 +20,14 @@ 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") + class HybridEvaluationEngine: """ @@ -28,7 +36,12 @@ class HybridEvaluationEngine: """ def __init__(self, auto_discover_types: bool = True, types_directory: str = "custom_types"): - self.parser = BracketParser() + # ========== 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 + self.symbol_table: Dict[str, Any] = {} self.equations: List[sympy.Eq] = [] self.last_result = None @@ -134,11 +147,22 @@ 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") + # 5. COMBINAR TODO EN EL CONTEXTO BASE self.base_context = { **math_functions, **specialized_classes, - **utility_functions + **utility_functions, + **base_classes # ← NUEVO: Incluir clases base } # 6. ACTUALIZAR HELPER FUNCTIONS @@ -150,6 +174,10 @@ class HybridEvaluationEngine: if self.debug: print(f"📋 Contexto base configurado: {len(self.base_context)} entradas") print(f"🆘 Helper functions: {len(self.helper_functions)}") + + # ========== NUEVO: Sincronizar debug con parser ========== + if hasattr(self.parser, 'debug'): + self.parser.debug = self.debug def _update_bracket_parser(self): """Actualiza el BracketParser con las clases descubiertas""" @@ -233,6 +261,7 @@ class HybridEvaluationEngine: def evaluate_line(self, line: str) -> 'EvaluationResult': """ Evalúa una línea de código y retorna el resultado + NUEVA LÓGICA: Priorizar asignaciones, intentar ecuaciones silenciosamente """ try: # 1. Parsear la línea @@ -244,10 +273,20 @@ class HybridEvaluationEngine: # 2. Manejar casos especiales if parse_info == "comment": return EvaluationResult(None, "comment", original_line=line) + elif parse_info == "assignment": + # NUEVA LÓGICA: Para asignaciones, también intentar agregar como ecuación silenciosamente + assignment_result = self._evaluate_assignment(parsed_line, line) + + # Intentar agregar como ecuación silenciosamente (sin mostrar errores) + if not assignment_result.is_error: + try: + self._add_equation_silently(line) + except: + pass # Ignorar errores de ecuación + + return assignment_result elif parse_info == "equation": return self._evaluate_equation_addition(parsed_line, line) - elif parse_info == "assignment": - return self._evaluate_assignment(parsed_line, line) # 3. Evaluación SymPy return self._evaluate_sympy_expression(parsed_line, parse_info, line) @@ -277,20 +316,32 @@ class HybridEvaluationEngine: if self.show_numeric_approximation and hasattr(assigned_value, 'evalf'): try: numeric_eval = assigned_value.evalf() - # Verificar si el resultado numérico es diferente del simbólico - # Para fracciones racionales, siempre mostrar la aproximación decimal + # MEJORADO: Solo mostrar aproximación si es realmente útil if hasattr(assigned_value, 'is_Rational') and assigned_value.is_Rational: # Es una fracción racional, mostrar aproximación decimal numeric_result = numeric_eval + elif hasattr(assigned_value, 'is_Integer') and assigned_value.is_Integer: + # Es un entero SymPy, no mostrar aproximación + numeric_result = None + elif hasattr(assigned_value, 'is_number') and assigned_value.is_number: + # Es un número, verificar si la aproximación es diferente significativamente + try: + if abs(float(numeric_eval) - float(assigned_value)) > 1e-10: + numeric_result = numeric_eval + except: + # Si no se puede comparar, mostrar solo si el string es diferente + if str(numeric_eval) != str(assigned_value): + numeric_result = numeric_eval elif numeric_eval != assigned_value: # Para otros casos, mostrar si son diferentes try: # Intentar comparación numérica más robusta - if abs(float(numeric_eval) - float(assigned_value)) > 1e-15: + if abs(float(numeric_eval) - float(assigned_value)) > 1e-10: numeric_result = numeric_eval except: - # Si la comparación falla, asumir que son diferentes - numeric_result = numeric_eval + # Si la comparación falla, asumir que son diferentes solo si el string es diferente + if str(numeric_eval) != str(assigned_value): + numeric_result = numeric_eval except Exception as e: if self.debug: print(f"DEBUG: Error en evaluación numérica: {e}") @@ -345,8 +396,23 @@ class HybridEvaluationEngine: if self.show_numeric_approximation and hasattr(result, 'evalf'): try: numeric_eval = result.evalf() - # Solo mostrar evaluación numérica si es diferente del resultado simbólico - if (str(numeric_eval) != str(result) and numeric_eval != result and + # MEJORADO: Solo mostrar evaluación numérica si es realmente útil + if hasattr(result, 'is_Integer') and result.is_Integer: + # Es un entero SymPy, no mostrar aproximación + numeric_result = None + elif hasattr(result, 'is_Rational') and result.is_Rational: + # Es una fracción racional, mostrar aproximación decimal + numeric_result = numeric_eval + elif hasattr(result, 'is_number') and result.is_number: + # Es un número, verificar si la aproximación es diferente significativamente + try: + if abs(float(numeric_eval) - float(result)) > 1e-10: + numeric_result = numeric_eval + except: + # Si no se puede comparar, mostrar solo si el string es diferente + if str(numeric_eval) != str(result): + numeric_result = numeric_eval + elif (str(numeric_eval) != str(result) and numeric_eval != result and not (isinstance(result, (int, float)) or (hasattr(result, 'is_number') and result.is_number and hasattr(result, 'is_Integer') and result.is_Integer))): @@ -577,6 +643,34 @@ class HybridEvaluationEngine: except Exception as e: raise ValueError(f"Error parseando ecuación '{equation_str}': {e}") + def _add_equation_silently(self, equation_str: str) -> bool: + """ + Intenta agregar una ecuación al sistema silenciosamente + Retorna True si tuvo éxito, False si falló + NUEVA FUNCIÓN: Para agregar ecuaciones sin mostrar errores + """ + try: + # Parsear ecuación + if '=' in equation_str and '==' not in equation_str: + # Ecuación simple: convertir a igualdad SymPy + left, right = equation_str.split('=', 1) + left_expr = sympify(left.strip(), locals=self._get_full_context()) + right_expr = sympify(right.strip(), locals=self._get_full_context()) + equation = Eq(left_expr, right_expr) + else: + # Ya es una comparación válida de SymPy + equation = sympify(equation_str, locals=self._get_full_context()) + + self.equations.append(equation) + if self.debug: + print(f"🔍 Ecuación agregada silenciosamente: {equation}") + return True + + except Exception as e: + if self.debug: + print(f"🔍 No se pudo agregar como ecuación: {e}") + return False + def solve_system(self, variables: Optional[List[str]] = None) -> Dict[str, Any]: """Resuelve el sistema de ecuaciones""" if not self.equations: diff --git a/sympy_Base.py b/sympy_Base.py index 7595682..0ec42f1 100644 --- a/sympy_Base.py +++ b/sympy_Base.py @@ -246,4 +246,409 @@ class SympyClassBase(ClassBase, sympy.Basic): @staticmethod def Helper(input_str): """Override en subclases para ayuda contextual""" - return None \ No newline at end of file + 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 ========== + +def preprocess_tokens(expression): + """ + Tokenizador que convierte patrones específicos en objetos tipados + con precedencia correcta: IntBase (más específico) antes que FourBytes + """ + # 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) + + # 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' + + 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 + + expression = re.sub(pattern, replace_fourbytes, expression) + + return expression \ No newline at end of file diff --git a/test_final.py b/test_final.py deleted file mode 100644 index 88e0ef6..0000000 --- a/test_final.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -""" -Script de prueba final para verificar todas las funcionalidades del modo simbólico -""" - -from main_evaluation import HybridEvaluationEngine - -def test_comprehensive(): - """Prueba comprehensiva del modo simbólico""" - print("=" * 70) - print("PRUEBA COMPREHENSIVA - MODO SIMBÓLICO") - print("=" * 70) - - # Crear motor en modo simbólico - engine = HybridEvaluationEngine() - engine.set_symbolic_mode( - symbolic_mode=True, - show_numeric=True, - keep_fractions=True - ) - - # Test cases - test_cases = [ - # Fracciones simples - ("4/5", "Fracción simple"), - ("25/51", "Fracción compleja"), - ("22/7", "Aproximación de π"), - - # Asignaciones de fracciones - ("a = 4/5", "Asignación fracción simple"), - ("b = 25/51", "Asignación fracción compleja"), - ("c = 22/7", "Asignación aproximación π"), - - # Operaciones con fracciones - ("3/4 + 1/6", "Suma de fracciones"), - ("5/6 - 1/3", "Resta de fracciones"), - ("2/3 * 3/4", "Multiplicación de fracciones"), - ("5/6 / 2/3", "División de fracciones"), - - # Asignaciones de operaciones - ("d = 3/4 + 1/6", "Asignación suma fracciones"), - ("e = 2/3 * 3/4", "Asignación multiplicación fracciones"), - - # Expresiones simbólicas - ("sqrt(2)", "Raíz cuadrada"), - ("sin(pi/4)", "Función trigonométrica"), - ("log(e)", "Logaritmo"), - - # Asignaciones simbólicas - ("f = sqrt(2)/2", "Asignación expresión simbólica"), - ("g = sin(pi/6)", "Asignación función trigonométrica"), - ] - - print("\nRESULTADOS:") - print("-" * 70) - - for i, (expression, description) in enumerate(test_cases, 1): - print(f"\n{i:2d}. {description}") - print(f" Expresión: {expression}") - - result = engine.evaluate_line(expression) - - if result.is_error: - print(f" ❌ ERROR: {result.error}") - else: - print(f" ✅ Resultado: {result.result}") - if result.numeric_result is not None: - print(f" 📊 Numérico: ≈ {result.numeric_result}") - else: - print(f" 📊 Numérico: (no disponible)") - - # Verificar variables asignadas - print(f"\n{'-'*70}") - print("VARIABLES ASIGNADAS:") - print(f"{'-'*70}") - - variables = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] - for var in variables: - value = engine.get_variable(var) - if value is not None: - print(f" {var} = {value} (tipo: {type(value).__name__})") - - print(f"\n{'='*70}") - print("MODO SIMBÓLICO: ✅ FUNCIONANDO CORRECTAMENTE") - print("- Fracciones se mantienen simbólicas: 4/5, 25/51, 22/7") - print("- Aproximaciones numéricas se muestran cuando corresponde") - print("- Asignaciones preservan la forma simbólica") - print("- Operaciones mantienen exactitud simbólica") - print(f"{'='*70}") - -if __name__ == "__main__": - test_comprehensive() \ No newline at end of file diff --git a/test_symbolic.py b/test_symbolic.py deleted file mode 100644 index db22176..0000000 --- a/test_symbolic.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -""" -Script de prueba para el modo simbólico de la calculadora - ASIGNACIONES -""" - -from main_evaluation import HybridEvaluationEngine - -def test_assignments(): - """Prueba las asignaciones en modo simbólico""" - print("=" * 60) - print("PRUEBA DE ASIGNACIONES EN MODO SIMBÓLICO") - print("=" * 60) - - # Crear motor en modo simbólico - print("\n1. MODO SIMBÓLICO ACTIVADO:") - engine_symbolic = HybridEvaluationEngine() - engine_symbolic.set_symbolic_mode( - symbolic_mode=True, - show_numeric=True, - keep_fractions=True - ) - - # Pruebas de asignaciones - assignment_tests = [ - "a = 25/51", - "b = 4/5", - "c = 22/7", - "d = 3/4 + 1/6", - "e = sqrt(2)/2" - ] - - for test in assignment_tests: - result = engine_symbolic.evaluate_line(test) - print(f" {test:15} → Tipo: {result.result_type}") - print(f" {' ':15} Resultado: {result.result}") - print(f" {' ':15} Simbólico: {result.symbolic_result}") - if result.numeric_result: - print(f" {' ':15} Numérico: ≈ {result.numeric_result}") - else: - print(f" {' ':15} Numérico: None") - print() - - print("\n2. VERIFICAR VALORES ASIGNADOS:") - variables = ['a', 'b', 'c', 'd', 'e'] - for var in variables: - value = engine_symbolic.get_variable(var) - print(f" {var} = {value} (tipo: {type(value)})") - -if __name__ == "__main__": - test_assignments() \ No newline at end of file diff --git a/tl_bracket_parser.py b/tl_bracket_parser.py index 44f5902..d45d50a 100644 --- a/tl_bracket_parser.py +++ b/tl_bracket_parser.py @@ -1,103 +1,51 @@ """ -Bracket Parser - INTEGRADO con el sistema de auto-descubrimiento de tipos +Tokenization Parser - SISTEMA NUEVO que reemplaza los corchetes +Convierte automáticamente patrones específicos en objetos tipados """ import ast import re from typing import Tuple, Optional, Set, Dict -# Importar sistema de tipos +# Importar tokenizador desde sympy_Base try: - from type_registry import get_registered_bracket_classes - TYPE_REGISTRY_AVAILABLE = True + from sympy_Base import preprocess_tokens + TOKENIZER_AVAILABLE = True except ImportError: - TYPE_REGISTRY_AVAILABLE = False - print("⚠️ Sistema de tipos no disponible, usando clases hardcodeadas") + TOKENIZER_AVAILABLE = False + print("⚠️ Tokenizador no disponible, usando parser básico") -class BracketParser: - """Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente""" - - # Clases de fallback mínimas si falla el registro dinámico - FALLBACK_BRACKET_CLASSES = {'Hex', 'Bin'} +class TokenizationParser: + """ + Nuevo parser que reemplaza el sistema de corchetes + Convierte automáticamente patrones en objetos tipados + """ # Operadores de comparación que pueden formar ecuaciones EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='} - def __init__(self, use_type_registry: bool = True): + def __init__(self, use_tokenizer: bool = True): self.debug = False - self.use_type_registry = use_type_registry and TYPE_REGISTRY_AVAILABLE + self.use_tokenizer = use_tokenizer and TOKENIZER_AVAILABLE - # Inicializar clases de corchetes dinámicamente - self._update_bracket_classes() + # Estadísticas de tokenización + self.stats = { + 'intbase_conversions': 0, + 'fourbytes_conversions': 0, + 'total_lines_processed': 0 + } - def _get_dynamic_bracket_classes(self) -> Set[str]: - """ - Obtiene las clases de corchetes dinámicamente del registro de tipos - NUEVA FUNCIÓN que reemplaza el hardcoding de DEFAULT_BRACKET_CLASSES - """ - if not self.use_type_registry: - return self.FALLBACK_BRACKET_CLASSES.copy() - - try: - # Obtener TODAS las clases registradas que soportan corchetes - registered_classes = get_registered_bracket_classes() - - if self.debug: - print(f"🔧 Clases dinámicas obtenidas del registro: {registered_classes}") - - # Si no hay clases registradas, usar fallback - if not registered_classes: - if self.debug: - print("⚠️ No se encontraron clases registradas, usando fallback") - return self.FALLBACK_BRACKET_CLASSES.copy() - - return registered_classes - - except Exception as e: - if self.debug: - print(f"⚠️ Error obteniendo clases dinámicas: {e}") - return self.FALLBACK_BRACKET_CLASSES.copy() + def get_tokenization_stats(self) -> Dict[str, int]: + """Retorna estadísticas de tokenización""" + return self.stats.copy() - def _update_bracket_classes(self): - """ - Actualiza las clases que soportan sintaxis con corchetes dinámicamente - MODIFICADO: Ya no usa DEFAULT_BRACKET_CLASSES hardcodeadas - """ - # Obtener clases dinámicamente del registro de tipos - self.BRACKET_CLASSES = self._get_dynamic_bracket_classes() - - if self.debug: - print(f"🔧 Bracket classes actualizadas dinámicamente: {self.BRACKET_CLASSES}") - - def reload_bracket_classes(self): - """Recarga las clases de corchetes desde el registro dinámicamente""" - if self.debug: - print("🔄 Recargando bracket classes dinámicamente...") - self._update_bracket_classes() - - def add_bracket_class(self, class_name: str): - """Añade una clase que soporta sintaxis con corchetes""" - self.BRACKET_CLASSES.add(class_name) - if self.debug: - print(f"➕ Añadida bracket class: {class_name}") - - def remove_bracket_class(self, class_name: str): - """Remueve una clase de la sintaxis con corchetes""" - self.BRACKET_CLASSES.discard(class_name) - if self.debug: - print(f"➖ Removida bracket class: {class_name}") - - def get_bracket_classes(self) -> Set[str]: - """Retorna el set actual de clases con sintaxis de corchetes""" - return self.BRACKET_CLASSES.copy() - - def has_bracket_class(self, class_name: str) -> bool: - """Verifica si una clase está registrada para sintaxis con corchetes""" - return class_name in self.BRACKET_CLASSES + 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 todas las transformaciones + Parsea una línea de código aplicando tokenización automática Returns: (transformed_code, parse_info): Código transformado e información de parsing @@ -106,6 +54,8 @@ class BracketParser: 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) @@ -120,27 +70,46 @@ class BracketParser: if self._is_standalone_equation(original_line): return f'_add_equation("{original_line}")', "equation" - # 4. Transformar sintaxis con corchetes - transformed_line = self._transform_brackets(original_line) + # 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 - if transformed_line == original_line: - return original_line, "expression" - else: - return transformed_line, "bracket_transform" + 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")' - en lugar de 'solve(variable)' para resolver usando las ecuaciones del sistema - - Returns: - (transformed_line, was_transformed) """ # Pattern: variable_name = ? pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\?$' @@ -148,7 +117,6 @@ class BracketParser: if match: var_name = match.group(1) - # CAMBIO: usar función personalizada que resuelve usando el sistema de ecuaciones return f'_solve_variable_in_system("{var_name}")', True return line, False @@ -156,10 +124,10 @@ class BracketParser: def _is_assignment(self, line: str) -> bool: """ Detecta si una línea es una asignación de variable - MEJORADO: Considera si contiene símbolos desconocidos que sugieren ecuación + NUEVA LÓGICA: Priorizar asignaciones, ser menos estricto """ try: - # Pattern: variable = expresión (que no sea ecuación) + # 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) @@ -169,44 +137,9 @@ class BracketParser: # 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: Detectar símbolos desconocidos en la expresión - # Si hay símbolos desconocidos, puede ser mejor tratarlo como ecuación - symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_part) - - # Funciones y constantes conocidas de SymPy/matemáticas - known_functions = { - # Funciones trigonométricas - 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', - 'sinh', 'cosh', 'tanh', - # Funciones exponenciales y logarítmicas - 'exp', 'log', 'ln', 'sqrt', - # Constantes - 'pi', 'e', 'I', 'oo', - # Funciones especiales - 'abs', 'sign', 'floor', 'ceiling', 'factorial', - # Álgebra - 'diff', 'integrate', 'limit', 'series', 'solve', - 'simplify', 'expand', 'factor', 'collect', - 'cancel', 'apart', 'together', - # Matrices - 'Matrix', 'det', 'inv', - # Funciones de Python comunes - 'int', 'float', 'str', 'len', 'max', 'min', 'sum' - } - - # Encontrar símbolos que no son funciones conocidas - unknown_symbols = [s for s in symbols_in_expr if s not in known_functions] - - if unknown_symbols: - if self.debug: - print(f"🔍 '{line}' contiene símbolos desconocidos: {unknown_symbols}") - print(f"🔍 Considerando como ECUACIÓN en lugar de asignación") - # Si hay símbolos desconocidos, tratarlo como ecuación - return False # No es asignación, será detectado como ecuación - else: - # Solo funciones/constantes conocidas: asignación normal - return True + # 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 @@ -217,23 +150,26 @@ class BracketParser: var_name = parts[0].strip() expression = parts[1].strip() - # Transformar corchetes en la expresión - expression = self._transform_brackets(expression) + # 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 usando análisis AST - SIMPLIFICADO: Evita recursión y es más directo + 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 - # Si no es una asignación (según la lógica ya evaluada en parse_line) - # y tiene '=', entonces es una ecuación try: tree = ast.parse(line.strip()) @@ -242,52 +178,6 @@ class BracketParser: node = tree.body[0] - # Si es una asignación Python válida, NO es ecuación standalone - if isinstance(node, ast.Assign): - # Pero si contiene símbolos desconocidos, SÍ es ecuación - # Verificar si la expresión contiene símbolos desconocidos - if len(node.targets) == 1: - # Obtener la expresión del lado derecho - try: - expr_code = ast.get_source_segment(line, node.value) - if expr_code: - symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_code) - known_functions = { - 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', - 'sinh', 'cosh', 'tanh', 'exp', 'log', 'ln', 'sqrt', - 'pi', 'e', 'I', 'oo', 'abs', 'sign', 'floor', 'ceiling', - 'factorial', 'diff', 'integrate', 'limit', 'series', - 'solve', 'simplify', 'expand', 'factor', 'collect', - 'cancel', 'apart', 'together', 'Matrix', 'det', 'inv', - 'int', 'float', 'str', 'len', 'max', 'min', 'sum' - } - unknown_symbols = [s for s in symbols_in_expr if s not in known_functions] - if unknown_symbols: - if self.debug: - print(f"🔍 '{line}' es asignación pero con símbolos desconocidos: {unknown_symbols} → ECUACIÓN") - return True - except: - # Si falla get_source_segment, usar método alternativo - parts = line.split('=', 1) - if len(parts) == 2: - expr_part = parts[1].strip() - symbols_in_expr = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', expr_part) - known_functions = { - 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', - 'sinh', 'cosh', 'tanh', 'exp', 'log', 'ln', 'sqrt', - 'pi', 'e', 'I', 'oo', 'abs', 'sign', 'floor', 'ceiling', - 'factorial', 'diff', 'integrate', 'limit', 'series', - 'solve', 'simplify', 'expand', 'factor', 'collect', - 'cancel', 'apart', 'together', 'Matrix', 'det', 'inv', - 'int', 'float', 'str', 'len', 'max', 'min', 'sum' - } - unknown_symbols = [s for s in symbols_in_expr if s not in known_functions] - if unknown_symbols: - if self.debug: - print(f"🔍 '{line}' es asignación pero con símbolos desconocidos: {unknown_symbols} → ECUACIÓN") - return True - return False - # Si es una expresión (no asignación), verificar comparaciones if isinstance(node, ast.Expr): if isinstance(node.value, ast.Compare): @@ -298,107 +188,157 @@ class BracketParser: return False except SyntaxError: - # Error de sintaxis: probablemente ecuación mal formada como "x + 2 = 5" - if self.debug: - print(f"🔍 '{line}' error AST → Tratando como ECUACIÓN") - return True + # 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 _transform_brackets(self, line: str) -> str: + def preview_tokenization(self, text: str) -> str: """ - Transforma sintaxis Class[args] → Class("args") y maneja métodos - VERSIÓN DINÁMICA que usa las clases registradas + Muestra preview de cómo se tokenizaría el texto sin ejecutar + Útil para debugging y testing """ - # Crear pattern dinámicamente basado en clases registradas - if not self.BRACKET_CLASSES: - return line # No hay clases registradas + if not TOKENIZER_AVAILABLE: + return "Tokenizador no disponible" - # Pattern principal: ClassName[contenido] usando clases dinámicas - bracket_classes_pattern = '|'.join(re.escape(cls) for cls in self.BRACKET_CLASSES) - pattern = rf'(\b(?:{bracket_classes_pattern})\b)\[([^\]]*)\]' + lines = text.split('\n') + preview_lines = [] - if self.debug: - print(f"🔍 Usando pattern: {pattern}") - print(f"🔧 Clases registradas: {self.BRACKET_CLASSES}") - - def replace_match(match): - class_name = match.group(1) - args_content = match.group(2).strip() - - if not args_content: - # Caso: Class[] → Class() - return f'{class_name}()' + 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: - # Split arguments by semicolon if present - # Each argument will be individually quoted. - # Example: Class[arg1; arg2] -> Class("arg1", "arg2") - # Example: Class[arg1] -> Class("arg1") - args_list = [arg.strip() for arg in args_content.split(';')] - - processed_args = [] - for arg_val in args_list: - # Escape backslashes first, then double quotes for string literals - escaped_arg = arg_val.replace('\\', '\\\\').replace('"', '\\"') - processed_args.append(f'"{escaped_arg}"') - - return f'{class_name}({", ".join(processed_args)})' + preview_lines.append(f"{i:2}: {line}") - # Aplicar transformación repetidamente hasta que no haya más cambios - transformed = line - while True: - new_transformed = re.sub(pattern, replace_match, transformed) - if new_transformed == transformed: - break - transformed = new_transformed - - # Transformar corchetes vacíos en métodos: .método[] → .método() - method_pattern = r'\.([a-zA-Z_][a-zA-Z0-9_]*)\[\]' - transformed = re.sub(method_pattern, r'.\1()', transformed) - - return transformed - - -class EquationDetector: - """Detector específico para ecuaciones con análisis AST avanzado""" + return '\n'.join(preview_lines) - @staticmethod - def is_equation_in_context(code: str, context: str = "standalone") -> bool: + def test_patterns(self) -> str: """ - Determina si el código contiene una ecuación considerando el contexto + 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 + ] - Args: - code: Código a analizar - context: Contexto ("standalone", "function_arg", "assignment") - """ - try: - tree = ast.parse(code.strip()) - - for node in ast.walk(tree): - if isinstance(node, ast.Compare): - # Verificar operadores de ecuación - for op in node.ops: - if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)): - if context == "standalone": - # En contexto standalone, es una ecuación - return True - elif context == "function_arg": - # En argumentos de función, generalmente NO es ecuación - return False - - # Verificar '=' simple (no comparación) - if '=' in code and context == "standalone": - # Verificar que no sea asignación Python válida - try: - ast.parse(code.strip()) - return False # Es sintaxis Python válida - except SyntaxError: - return True # Puede ser ecuación mal formada para Python - - return False - - except Exception: - return False \ No newline at end of file + results = [] + for test in test_cases: + tokenized = self._apply_tokenization(test) if TOKENIZER_AVAILABLE else test + status = "✓ TOKENIZED" if tokenized != test else "○ NO CHANGE" + results.append(f"{status}: '{test}' → '{tokenized}'") + + return '\n'.join(results) + + +# Mantener compatibilidad con el sistema anterior +class BracketParser(TokenizationParser): + """ + Alias para compatibilidad con código existente + Ahora usa el nuevo sistema de tokenización + """ + + def __init__(self, use_type_registry: bool = True): + # Ignorar use_type_registry, ya no se usa + super().__init__(use_tokenizer=True) + + def reload_bracket_classes(self): + """Método de compatibilidad - no hace nada en el nuevo sistema""" + if self.debug: + print("🔄 reload_bracket_classes() llamado - no necesario en nuevo sistema") + + def add_bracket_class(self, class_name: str): + """Método de compatibilidad - no hace nada en el nuevo sistema""" + if self.debug: + print(f"🔄 add_bracket_class({class_name}) llamado - no necesario en nuevo sistema") + + def remove_bracket_class(self, class_name: str): + """Método de compatibilidad - no hace nada en el nuevo sistema""" + if self.debug: + print(f"🔄 remove_bracket_class({class_name}) llamado - no necesario en nuevo sistema") + + def get_bracket_classes(self) -> Set[str]: + """Método de compatibilidad - retorna conjunto vacío""" + return set() + + def has_bracket_class(self, class_name: str) -> bool: + """Método de compatibilidad - siempre retorna False""" + return False + + +# ========== FUNCIONES DE UTILIDAD ========== + +def test_tokenization_system(): + """Función de testing completa del sistema de tokenización""" + print("🧪 TESTING SISTEMA DE TOKENIZACIÓN") + print("=" * 50) + + parser = TokenizationParser(use_tokenizer=True) + parser.debug = True + + # Test patterns + print("\n1. Test de patrones básicos:") + print(parser.test_patterns()) + + # Test de líneas completas + test_lines = [ + "ip = 192.168.1.1", + "mask = 16#ffffff00", + "net = 10.x.y.0", + "result = 16#FF + 2#1010", + "a.b.c.d + 255.255.255.0", + "x = solve(y + 2)", # No debería tokenizarse solve + ] + + print(f"\n2. Test de líneas completas:") + for line in test_lines: + transformed, info = parser.parse_line(line) + print(f" '{line}' → '{transformed}' [{info}]") + + # Estadísticas + print(f"\n3. Estadísticas:") + stats = parser.get_tokenization_stats() + for key, value in stats.items(): + print(f" {key}: {value}") + + print("\n✅ Testing completado") + + +if __name__ == "__main__": + test_tokenization_system() \ No newline at end of file