diff --git a/.doc/quick_start_readme.md b/.doc/quick_start_readme.md index af495a2..e0c4493 100644 --- a/.doc/quick_start_readme.md +++ b/.doc/quick_start_readme.md @@ -1,6 +1,67 @@ # Calculadora MAV - CAS Híbrido -Sistema de Álgebra Computacional híbrido que combina SymPy con clases especializadas. +## Estructura del Proyecto + +``` +📁 Calcv2/ +├── main_calc.py # 🚀 Launcher principal +├── main_calc_app.py # 🖥️ Interfaz gráfica +├── main_evaluation.py # 🧮 Motor CAS +├── hybrid_base.py # 🏗️ Clase base +├── ip4_type.py # 🌐 Clase IP4 +├── hex_type.py # 🔢 Clase Hex +├── bin_type.py # 🔢 Clase Bin +├── dec_type.py # 🔢 Clase Dec +├── chr_type.py # 🔤 Clase Chr +├── tl_bracket_parser.py # 📝 Parser sintaxis +└── tl_popup.py # 📊 Resultados clickeables +``` + +## Inicio Rápido + +1. Instalar dependencias: +```bash +pip install sympy matplotlib numpy +``` + +2. Ejecutar la aplicación: +```bash +python main_calc.py +``` + +3. Ejemplos de uso: +```python +# Tipos especializados +Hex[FF] + 1 +IP4[192.168.1.100/24].NetworkAddress[] +Bin[1010] * 2 + +# Matemáticas simbólicas +x + 2*y +diff(x**2 + sin(x), x) +integrate(x**2, x) + +# Ecuaciones +x**2 + 2*x - 8 = 0 +solve(x**2 + 2*x - 8, x) +``` + +## Características + +- ✅ Motor algebraico completo (SymPy) +- ✅ Sintaxis simplificada con corchetes +- ✅ Detección automática de ecuaciones +- ✅ Resultados interactivos clickeables +- ✅ Tipos especializados (IP4, Hex, Bin, etc.) +- ✅ Variables SymPy puras +- ✅ Plotting integrado + +## Comandos Útiles + +- `python main_calc.py --help` - Muestra ayuda +- `python main_calc.py --test` - Ejecuta tests +- `python main_calc.py --setup` - Instala dependencias +- `python main_calc.py --debug` - Activa logging detallado ## 🚀 Inicio Rápido diff --git a/.doc/refactoring_guide.md b/.doc/refactoring_guide.md index ec93b1e..d0c7c58 100644 --- a/.doc/refactoring_guide.md +++ b/.doc/refactoring_guide.md @@ -298,6 +298,153 @@ solve([x + y == 10, x - y == 2], [x, y]) # Resultado: "📋 Ver Soluciones" - **Ejemplos interactivos**: Templates para operaciones comunes - **Cheat sheet**: Referencia rápida de sintaxis nueva vs antigua + +## Sistema de Autocompletado y Ayuda Contextual (Helpers) + +### Objetivo + +Proveer al usuario de una experiencia de ayuda y autocompletado inteligente, no invasiva y extensible, que facilite el uso de funciones avanzadas y objetos personalizados, integrando tanto métodos propios como todas las funciones de SymPy. + +--- + +### 1. **Helpers: Ayuda Contextual por Tipo** + +Cada tipo de objeto (por ejemplo, `IP4`, `Hex`, etc.) debe definir una función `Helper(input_str)` que: + +- Recibe el string de entrada del usuario. +- Decide, usando su propia lógica (por ejemplo, expresiones regulares), si puede ofrecer una ayuda relevante. +- Devuelve un string de ayuda (ejemplo de uso, sintaxis, sugerencia) o `None` si no aplica. + +**Ejemplo:** +```python +# En ip4_type.py +def Helper(input_str): + if re.match(r"^\s*IP4(\b|\s*\[.*)?", input_str, re.IGNORECASE): + return 'Ej: IP4[192.168.1.1/24], IP4[10.0.0.1, 8], o IP4[172.16.0.5, 255.255.0.0]' + return None +``` + +**Ventajas:** +- Cada Helper es autocontenido y puede evolucionar de forma independiente. +- Permite personalizar la ayuda para cada tipo de objeto o función. + +--- + +### 2. **Helper para SymPy** + +Dado que ahora todas las funciones de SymPy están disponibles, se debe agregar un Helper general para SymPy que: + +- Detecte si el usuario está comenzando a escribir el nombre de una función de SymPy (por ejemplo, `diff`, `integrate`, `solve`, `limit`, etc.). +- Sugiera la sintaxis básica y ejemplos de uso para la función detectada. +- Puede usar una lista de funciones comunes de SymPy y sus descripciones. + +**Ejemplo:** +```python +# En sympy_helper.py +def Helper(input_str): + sympy_funcs = { + "diff": "Derivada: diff(expr, var). Ej: diff(sin(x), x)", + "integrate": "Integral: integrate(expr, var). Ej: integrate(x**2, x)", + "solve": "Resolver ecuaciones: solve(expr, var). Ej: solve(x**2-1, x)", + "limit": "Límite: limit(expr, var, valor). Ej: limit(sin(x)/x, x, 0)", + # ...agregar más funciones comunes + } + for func, ayuda in sympy_funcs.items(): + if input_str.strip().startswith(func): + return ayuda + return None +``` + +--- + +### 3. **Manejo Centralizado de Helpers** + +En el motor principal de la aplicación, se debe mantener una lista de todas las funciones Helper disponibles (incluyendo la de SymPy). +Al evaluar la línea de entrada: + +- Se llama a cada Helper en orden. +- Si alguna Helper retorna una ayuda, se muestra esa ayuda al usuario (en la línea de resultado, tooltip, etc.). +- Si ninguna Helper ayuda, se muestra el mensaje de error real. + +**Ejemplo:** +```python +HELPERS = [ + IP4.Helper, + Hex.Helper, + SympyHelper.Helper, + # ...otros helpers +] + +def obtener_ayuda(input_str): + for helper in HELPERS: + ayuda = helper(input_str) + if ayuda: + return ayuda + return None +``` + +--- + +### 4. **Autocompletado de Métodos y Funciones (Popup tras el punto)** + +- Cuando el usuario escribe un punto (`.`) después de un objeto válido, se evalúa el objeto y se obtiene la lista de métodos disponibles. +- Se muestra un popup de autocompletado con los métodos relevantes (filtrando los no útiles). +- El usuario puede seleccionar un método con el teclado o mouse, y se inserta automáticamente (con paréntesis si corresponde). +- El popup solo aparece tras el punto, no en cada pulsación de tecla, para no ser invasivo. + +**Puntos clave:** +- Priorizar métodos útiles y públicos. +- Permitir que cada tipo defina una lista de métodos sugeridos. +- Cerrar el popup fácilmente (Escape, clic fuera, etc.). + +--- + +### 5. **Flujo de Evaluación y Ayuda** + +1. El usuario escribe una línea. +2. Si presiona `.`, se muestra el popup de autocompletado de métodos. +3. Si la línea tiene error: + - Se consulta a los Helpers para mostrar ayuda contextual. + - Si ninguna Helper ayuda, se muestra el mensaje de error real. +4. Si la línea es válida, se evalúa normalmente. + +--- + +### 6. **Extensibilidad** + +- Para agregar ayuda para un nuevo tipo, solo se debe definir un nuevo Helper y registrarlo en la lista central. +- El Helper de SymPy puede expandirse para cubrir más funciones y ejemplos. +- El sistema de autocompletado puede ampliarse para sugerir funciones de SymPy al escribir el nombre de la función (no solo tras el punto). + +--- + +### 7. **Resumen de Beneficios** + +- **No invasivo:** El autocompletado solo aparece cuando el usuario lo solicita (tras el punto). +- **Ayuda contextual:** Los Helpers ofrecen ayuda específica y relevante según el contexto. +- **Extensible:** Fácil de agregar nuevos tipos y funciones de ayuda. +- **Consistente:** El usuario recibe ayuda o autocompletado solo cuando es útil, no en cada pulsación. + +--- + +### 8. **Ejemplo de Integración** + +```python +# En el motor principal: +ayuda = obtener_ayuda(linea_usuario) +if ayuda: + mostrar_ayuda(ayuda) +elif error: + mostrar_error(error) +else: + mostrar_resultado(resultado) +``` + +--- + +**Este sistema permite que la ayuda y el autocompletado evolucionen de forma incremental, mejorando la experiencia del usuario sin ser molestos ni invasivos.** + + ### **Gestión de Variables Puras SymPy** - **Solo símbolos SymPy**: Todas las variables son `Symbol()` automáticamente - **Sin variables Python**: Eliminación de `eval()` como mecanismo por defecto diff --git a/bin_type.py b/bin_type.py new file mode 100644 index 0000000..89c0095 --- /dev/null +++ b/bin_type.py @@ -0,0 +1,52 @@ +""" +Clase híbrida para números binarios +""" +from hybrid_base import HybridCalcType + + +class HybridBin(HybridCalcType): + """Clase híbrida para números binarios""" + + def __new__(cls, value_input): + """Crear objeto SymPy válido""" + obj = HybridCalcType.__new__(cls) + return obj + + 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) + + # Llamar al constructor base + super().__init__(value, f"0b{value:08b}") + + def __str__(self): + """Representación string para display""" + return f"0b{self._value:08b}" + + def _sympystr(self, printer): + """Representación SymPy string""" + return str(self) + + @staticmethod + def Helper(input_str): + """Ayuda contextual para Bin""" + return """ + Formato Bin: + - Con prefijo: 0b1010 + - Sin prefijo: 1010 + + Conversiones: + - toDecimal(): Convierte a decimal + """ + + def toDecimal(self): + """Convierte a decimal""" + return self._value \ No newline at end of file diff --git a/chr_type.py b/chr_type.py new file mode 100644 index 0000000..fe8d373 --- /dev/null +++ b/chr_type.py @@ -0,0 +1,63 @@ +""" +Clase híbrida para caracteres +""" +from hybrid_base import HybridCalcType + + +class HybridChr(HybridCalcType): + """Clase híbrida para caracteres""" + + def __new__(cls, str_input): + """Crear objeto SymPy válido""" + obj = HybridCalcType.__new__(cls) + return obj + + def __init__(self, str_input: str): + """Inicialización de Chr""" + # Convertir input a entero (código ASCII) + if isinstance(str_input, str): + if len(str_input) == 1: + value = ord(str_input) + else: + raise ValueError("Input must be a single character") + else: + value = int(str_input) + if not 0 <= value <= 255: + raise ValueError("Value must be between 0 and 255") + + # Llamar al constructor base + super().__init__(value, chr(value)) + + def __str__(self): + """Representación string para display""" + return f"'{self._original_str}' ({self._value})" + + def _sympystr(self, printer): + """Representación SymPy string""" + return str(self) + + @staticmethod + def Helper(input_str): + """Ayuda contextual para Chr""" + return """ + Formato Chr: + - Carácter: 'A' + - Código ASCII: 65 + + Conversiones: + - toDecimal(): Obtiene código ASCII + - toHex(): Convierte a hexadecimal + - toBin(): Convierte a binario + """ + + def toDecimal(self): + """Obtiene código ASCII""" + return self._value + + def toHex(self): + """Convierte a hexadecimal""" + return f"0x{self._value:02x}" + + def toBin(self): + """Convierte a binario""" + return f"0b{self._value:08b}" \ No newline at end of file diff --git a/dec_type.py b/dec_type.py new file mode 100644 index 0000000..5cc8d2c --- /dev/null +++ b/dec_type.py @@ -0,0 +1,52 @@ +""" +Clase híbrida para números decimales +""" +from hybrid_base import HybridCalcType + + +class HybridDec(HybridCalcType): + """Clase híbrida para números decimales""" + + def __new__(cls, value_input): + """Crear objeto SymPy válido""" + obj = HybridCalcType.__new__(cls) + return obj + + def __init__(self, value_input): + """Inicialización de Dec""" + # Convertir input a entero + if isinstance(value_input, str): + value = int(value_input) + else: + value = int(value_input) + + # Llamar al constructor base + super().__init__(value, str(value)) + + def __str__(self): + """Representación string para display""" + return str(self._value) + + def _sympystr(self, printer): + """Representación SymPy string""" + return str(self) + + @staticmethod + def Helper(input_str): + """Ayuda contextual para Dec""" + return """ + Formato Dec: + - Número entero: 42 + + Conversiones: + - toHex(): Convierte a hexadecimal + - toBin(): Convierte a binario + """ + + def toHex(self): + """Convierte a hexadecimal""" + return f"0x{self._value:02x}" + + def toBin(self): + """Convierte a binario""" + return f"0b{self._value:08b}" \ No newline at end of file diff --git a/hex_type.py b/hex_type.py new file mode 100644 index 0000000..c6375c2 --- /dev/null +++ b/hex_type.py @@ -0,0 +1,52 @@ +""" +Clase híbrida para números hexadecimales +""" +from hybrid_base import HybridCalcType + + +class HybridHex(HybridCalcType): + """Clase híbrida para números hexadecimales""" + + def __new__(cls, value_input): + """Crear objeto SymPy válido""" + obj = HybridCalcType.__new__(cls) + return obj + + 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) + + # Llamar al constructor base + super().__init__(value, f"0x{value:02x}") + + def __str__(self): + """Representación string para display""" + return f"0x{self._value:02x}" + + def _sympystr(self, printer): + """Representación SymPy string""" + return str(self) + + @staticmethod + def Helper(input_str): + """Ayuda contextual para Hex""" + return """ + Formato Hex: + - Con prefijo: 0x1A + - Sin prefijo: 1A + + Conversiones: + - toDecimal(): Convierte a decimal + """ + + def toDecimal(self): + """Convierte a decimal""" + return self._value \ No newline at end of file diff --git a/hybrid_base.py b/hybrid_base.py new file mode 100644 index 0000000..bf1274f --- /dev/null +++ b/hybrid_base.py @@ -0,0 +1,246 @@ +""" +Clase base híbrida que combina SymPy con funcionalidad especializada +""" +import sympy +from sympy import Basic, Symbol, sympify +from typing import Any, Optional, Dict +import re + + +class HybridCalcType(Basic): + """ + Clase base híbrida que combina SymPy Basic con funcionalidad de calculadora + Todas las clases especializadas deben heredar de esta + """ + + def __new__(cls, *args, **kwargs): + """Crear objeto SymPy válido""" + obj = Basic.__new__(cls) + return obj + + def __init__(self, value, original_str=""): + """Inicialización de funcionalidad especializada""" + self._value = value + self._original_str = original_str + self._init_specialized() + + def _init_specialized(self): + """Override en subclases para inicialización especializada""" + pass + + @property + def value(self): + """Acceso al valor interno""" + return self._value + + @property + def original_str(self): + """String original de entrada""" + return self._original_str + + # Propiedades requeridas por SymPy + @property + def args(self): + """Argumentos para SymPy - retorna valor como argumento""" + return (sympify(self._value),) + + @property + def func(self): + """Función constructora para SymPy""" + return self.__class__ + + def _sympystr(self, printer): + """Representación SymPy string""" + return f"{self.__class__.__name__}({self._original_str})" + + def _latex(self, printer): + """Representación LaTeX""" + return self._sympystr(printer) + + def __str__(self): + """Representación string para display""" + return str(self._value) + + def __repr__(self): + """Representación para debugging""" + return f"{self.__class__.__name__}('{self._original_str}')" + + def evalf(self, n=15, **options): + """Evaluación numérica si es posible""" + if isinstance(self._value, (int, float, complex)): + return sympy.Float(self._value) + return self + + def _eval_evalf(self, prec): + """Evaluación numérica para SymPy""" + if isinstance(self._value, (int, float, complex)): + return sympy.Float(self._value, prec) + return None + + def __dec__(self): + """Conversión a decimal para compatibilidad""" + if isinstance(self._value, (int, float, complex)): + return self._value + raise TypeError(f"Cannot convert {self.__class__.__name__} to decimal") + + # Métodos requeridos por SymPy para manipulación algebraica + def as_coeff_Mul(self, rational=True): + """Descomponer como coeficiente * términos""" + if isinstance(self._value, (int, float)): + return sympify(self._value), sympify(1) + return sympify(1), self + + def as_coeff_Add(self, rational=True): + """Descomponer como coeficiente + términos""" + if isinstance(self._value, (int, float)): + return sympify(self._value), sympify(0) + return sympify(0), self + + def as_base_exp(self): + """Descomponer como base^exponente""" + return self, sympify(1) + + def as_numer_denom(self): + """Descomponer como numerador/denominador""" + return self, sympify(1) + + # Propiedades de tipo para SymPy + @property + def is_number(self): + """Indica si es un número""" + return isinstance(self._value, (int, float, complex)) + + @property + def is_real(self): + """Indica si es real""" + return isinstance(self._value, (int, float)) and not isinstance(self._value, complex) + + @property + def is_integer(self): + """Indica si es entero""" + return isinstance(self._value, int) + + @property + def is_rational(self): + """Indica si es racional""" + return isinstance(self._value, (int, float)) + + @property + def is_irrational(self): + """Indica si es irracional""" + return False + + @property + def is_positive(self): + """Indica si es positivo""" + if isinstance(self._value, (int, float)): + return self._value > 0 + return None + + @property + def is_negative(self): + """Indica si es negativo""" + if isinstance(self._value, (int, float)): + return self._value < 0 + return None + + @property + def is_zero(self): + """Indica si es cero""" + if isinstance(self._value, (int, float)): + return self._value == 0 + return None + + @property + def is_finite(self): + """Indica si es finito""" + return isinstance(self._value, (int, float, complex)) + + @property + def is_infinite(self): + """Indica si es infinito""" + return False + + @property + def is_commutative(self): + """Indica si es conmutativo""" + return True + + # Operaciones aritméticas simples que retornan el valor numérico para integración con SymPy + def __add__(self, other): + """Suma que integra con SymPy""" + if isinstance(other, (int, float, complex)): + # Retornar valor numérico para que SymPy maneje la operación + return sympify(self._value + other) + elif hasattr(other, '_value'): + return sympify(self._value + other._value) + else: + # Dejar que SymPy maneje la operación + return sympify(self._value) + sympify(other) + + def __radd__(self, other): + """Suma reversa""" + return sympify(other) + sympify(self._value) + + def __mul__(self, other): + """Multiplicación que integra con SymPy""" + if isinstance(other, (int, float, complex)): + return sympify(self._value * other) + elif hasattr(other, '_value'): + return sympify(self._value * other._value) + else: + return sympify(self._value) * sympify(other) + + def __rmul__(self, other): + """Multiplicación reversa""" + return sympify(other) * sympify(self._value) + + def __sub__(self, other): + """Resta""" + if isinstance(other, (int, float, complex)): + return sympify(self._value - other) + elif hasattr(other, '_value'): + return sympify(self._value - other._value) + else: + return sympify(self._value) - sympify(other) + + def __rsub__(self, other): + """Resta reversa""" + return sympify(other) - sympify(self._value) + + def __truediv__(self, other): + """División""" + if isinstance(other, (int, float, complex)): + if other == 0: + raise ZeroDivisionError("Division by zero") + return sympify(self._value / other) + elif hasattr(other, '_value'): + if other._value == 0: + raise ZeroDivisionError("Division by zero") + return sympify(self._value / other._value) + else: + return sympify(self._value) / sympify(other) + + def __rtruediv__(self, other): + """División reversa""" + if self._value == 0: + raise ZeroDivisionError("Division by zero") + return sympify(other) / sympify(self._value) + + def __pow__(self, other): + """Potencia""" + if isinstance(other, (int, float, complex)): + return sympify(self._value ** other) + elif hasattr(other, '_value'): + return sympify(self._value ** other._value) + else: + return sympify(self._value) ** sympify(other) + + def __rpow__(self, other): + """Potencia reversa""" + return sympify(other) ** sympify(self._value) + + @staticmethod + def Helper(input_str): + """Override en subclases para ayuda contextual""" + return None \ No newline at end of file diff --git a/hybrid_base_types.py b/hybrid_base_types.py deleted file mode 100644 index 511afbe..0000000 --- a/hybrid_base_types.py +++ /dev/null @@ -1,606 +0,0 @@ -""" -Clases base híbridas que combinan SymPy con funcionalidad especializada -""" -import sympy -from sympy import Basic, Symbol, sympify -from typing import Any, Optional, Dict -import re - - -class HybridCalcType(Basic): - """ - Clase base híbrida que combina SymPy Basic con funcionalidad de calculadora - Todas las clases especializadas deben heredar de esta - """ - - def __new__(cls, *args, **kwargs): - """Crear objeto SymPy válido""" - obj = Basic.__new__(cls) - return obj - - def __init__(self, value, original_str=""): - """Inicialización de funcionalidad especializada""" - self._value = value - self._original_str = original_str - self._init_specialized() - - def _init_specialized(self): - """Override en subclases para inicialización especializada""" - pass - - @property - def value(self): - """Acceso al valor interno""" - return self._value - - @property - def original_str(self): - """String original de entrada""" - return self._original_str - - # Propiedades requeridas por SymPy - @property - def args(self): - """Argumentos para SymPy - retorna valor como argumento""" - return (sympify(self._value),) - - @property - def func(self): - """Función constructora para SymPy""" - return self.__class__ - - def _sympystr(self, printer): - """Representación SymPy string""" - return f"{self.__class__.__name__}({self._original_str})" - - def _latex(self, printer): - """Representación LaTeX""" - return self._sympystr(printer) - - def __str__(self): - """Representación string para display""" - return str(self._value) - - def __repr__(self): - """Representación para debugging""" - return f"{self.__class__.__name__}('{self._original_str}')" - - def evalf(self, n=15, **options): - """Evaluación numérica si es posible""" - if isinstance(self._value, (int, float, complex)): - return sympy.Float(self._value) - return self - - def _eval_evalf(self, prec): - """Evaluación numérica para SymPy""" - if isinstance(self._value, (int, float, complex)): - return sympy.Float(self._value, prec) - return None - - def __dec__(self): - """Conversión a decimal para compatibilidad""" - if isinstance(self._value, (int, float, complex)): - return self._value - raise TypeError(f"Cannot convert {self.__class__.__name__} to decimal") - - # Métodos requeridos por SymPy para manipulación algebraica - def as_coeff_Mul(self, rational=True): - """Descomponer como coeficiente * términos""" - if isinstance(self._value, (int, float)): - return sympify(self._value), sympify(1) - return sympify(1), self - - def as_coeff_Add(self, rational=True): - """Descomponer como coeficiente + términos""" - if isinstance(self._value, (int, float)): - return sympify(self._value), sympify(0) - return sympify(0), self - - def as_base_exp(self): - """Descomponer como base^exponente""" - return self, sympify(1) - - def as_numer_denom(self): - """Descomponer como numerador/denominador""" - return self, sympify(1) - - # Propiedades de tipo para SymPy - @property - def is_number(self): - """Indica si es un número""" - return isinstance(self._value, (int, float, complex)) - - @property - def is_real(self): - """Indica si es real""" - return isinstance(self._value, (int, float)) and not isinstance(self._value, complex) - - @property - def is_integer(self): - """Indica si es entero""" - return isinstance(self._value, int) - - @property - def is_rational(self): - """Indica si es racional""" - return isinstance(self._value, (int, float)) - - @property - def is_irrational(self): - """Indica si es irracional""" - return False - - @property - def is_positive(self): - """Indica si es positivo""" - if isinstance(self._value, (int, float)): - return self._value > 0 - return None - - @property - def is_negative(self): - """Indica si es negativo""" - if isinstance(self._value, (int, float)): - return self._value < 0 - return None - - @property - def is_zero(self): - """Indica si es cero""" - if isinstance(self._value, (int, float)): - return self._value == 0 - return None - - @property - def is_finite(self): - """Indica si es finito""" - return isinstance(self._value, (int, float, complex)) - - @property - def is_infinite(self): - """Indica si es infinito""" - return False - - @property - def is_commutative(self): - """Indica si es conmutativo""" - return True - - # Operaciones aritméticas simples que retornan el valor numérico para integración con SymPy - def __add__(self, other): - """Suma que integra con SymPy""" - if isinstance(other, (int, float, complex)): - # Retornar valor numérico para que SymPy maneje la operación - return sympify(self._value + other) - elif hasattr(other, '_value'): - return sympify(self._value + other._value) - else: - # Dejar que SymPy maneje la operación - return sympify(self._value) + sympify(other) - - def __radd__(self, other): - """Suma reversa""" - return sympify(other) + sympify(self._value) - - def __mul__(self, other): - """Multiplicación que integra con SymPy""" - if isinstance(other, (int, float, complex)): - return sympify(self._value * other) - elif hasattr(other, '_value'): - return sympify(self._value * other._value) - else: - return sympify(self._value) * sympify(other) - - def __rmul__(self, other): - """Multiplicación reversa""" - return sympify(other) * sympify(self._value) - - def __sub__(self, other): - """Resta""" - if isinstance(other, (int, float, complex)): - return sympify(self._value - other) - elif hasattr(other, '_value'): - return sympify(self._value - other._value) - else: - return sympify(self._value) - sympify(other) - - def __rsub__(self, other): - """Resta reversa""" - return sympify(other) - sympify(self._value) - - def __truediv__(self, other): - """División""" - if isinstance(other, (int, float, complex)): - if other == 0: - raise ZeroDivisionError("Division by zero") - return sympify(self._value / other) - elif hasattr(other, '_value'): - if other._value == 0: - raise ZeroDivisionError("Division by zero") - return sympify(self._value / other._value) - else: - return sympify(self._value) / sympify(other) - - def __rtruediv__(self, other): - """División reversa""" - if self._value == 0: - raise ZeroDivisionError("Division by zero") - return sympify(other) / sympify(self._value) - - def __pow__(self, other): - """Potencia""" - if isinstance(other, (int, float, complex)): - return sympify(self._value ** other) - elif hasattr(other, '_value'): - return sympify(self._value ** other._value) - else: - return sympify(self._value) ** sympify(other) - - def __rpow__(self, other): - """Potencia reversa""" - return sympify(other) ** sympify(self._value) - - @staticmethod - def Helper(input_str): - """Override en subclases para ayuda contextual""" - return None - - -class HybridHex(HybridCalcType): - """Clase híbrida para números hexadecimales""" - - def __new__(cls, value_input): - obj = HybridCalcType.__new__(cls) - return obj - - def __init__(self, value_input): - processed_value = None - original_str = str(value_input) - - if isinstance(value_input, HybridCalcType): - processed_value = int(value_input.__dec__()) - elif isinstance(value_input, (int, float)): - processed_value = int(value_input) - else: - # String input - str_input = str(value_input).lower() - if str_input.startswith("0x"): - processed_value = int(str_input, 16) - else: - processed_value = int(str_input, 16) - - super().__init__(processed_value, original_str) - - def __str__(self): - return f"0x{self._value:X}" - - def _sympystr(self, printer): - return f"Hex({self._value})" - - @staticmethod - def Helper(input_str): - if re.match(r"^\s*Hex(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): - return 'Ej: Hex[FF] o Hex[255]' - return None - - def toDecimal(self): - """Convertir a decimal""" - return self._value - - -class HybridBin(HybridCalcType): - """Clase híbrida para números binarios""" - - def __new__(cls, value_input): - obj = HybridCalcType.__new__(cls) - return obj - - def __init__(self, value_input): - processed_value = None - original_str = str(value_input) - - if isinstance(value_input, HybridCalcType): - processed_value = int(value_input.__dec__()) - elif isinstance(value_input, (int, float)): - processed_value = int(value_input) - else: - # String input - str_input = str(value_input).lower() - if str_input.startswith("0b"): - processed_value = int(str_input, 2) - else: - processed_value = int(str_input, 2) - - super().__init__(processed_value, original_str) - - def __str__(self): - return f"0b{bin(self._value)[2:]}" - - def _sympystr(self, printer): - return f"Bin({self._value})" - - @staticmethod - def Helper(input_str): - if re.match(r"^\s*Bin(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): - return 'Ej: Bin[1011] o Bin[11]' - return None - - -class HybridDec(HybridCalcType): - """Clase híbrida para números decimales""" - - def __new__(cls, value_input): - obj = HybridCalcType.__new__(cls) - return obj - - def __init__(self, value_input): - processed_value = None - original_str = str(value_input) - - if isinstance(value_input, HybridCalcType): - processed_value = float(value_input.__dec__()) - elif isinstance(value_input, (int, float)): - processed_value = float(value_input) - else: - try: - processed_value = float(str(value_input)) - except (ValueError, TypeError) as e: - raise ValueError(f"Invalid decimal value: '{value_input}'") from e - - super().__init__(processed_value, original_str) - - def __str__(self): - if self._value == int(self._value): - return str(int(self._value)) - return str(self._value) - - def _sympystr(self, printer): - return f"Dec({self._value})" - - @staticmethod - def Helper(input_str): - if re.match(r"^\s*Dec(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): - return 'Ej: Dec[10.5] o Dec[10]' - return None - - -class HybridIP4(HybridCalcType): - """Clase híbrida para direcciones IPv4""" - - def __new__(cls, *args): - obj = HybridCalcType.__new__(cls) - return obj - - def __init__(self, *args): - import socket - import struct - - if not args: - raise ValueError("IP4() constructor requires at least one argument.") - - ip_str = None - prefix = None - - if len(args) == 1: - arg = args[0] - if not isinstance(arg, str): - raise ValueError(f"IP4 single argument must be a string, got '{arg}'") - - if "/" in arg: # CIDR notation - parts = arg.split('/', 1) - ip_str = parts[0] - if len(parts) == 2 and parts[1].isdigit(): - prefix_val = int(parts[1]) - if not self._is_valid_prefix(prefix_val): - raise ValueError(f"Invalid CIDR prefix '{parts[1]}' in '{arg}'") - prefix = prefix_val - else: - raise ValueError(f"Invalid CIDR format: '{arg}'") - else: - ip_str = arg - - elif len(args) == 2: - ip_arg, mask_arg = args[0], args[1] - if not isinstance(ip_arg, str): - raise ValueError(f"IP4 first argument must be an IP string") - ip_str = ip_arg - - if isinstance(mask_arg, int): - if not self._is_valid_prefix(mask_arg): - raise ValueError(f"Invalid prefix length: {mask_arg}") - prefix = mask_arg - elif isinstance(mask_arg, str): - if mask_arg.isdigit(): - prefix_val = int(mask_arg) - if self._is_valid_prefix(prefix_val): - prefix = prefix_val - else: - parsed_prefix = self._netmask_str_to_prefix(mask_arg) - if parsed_prefix is None: - raise ValueError(f"Invalid mask string: '{mask_arg}'") - prefix = parsed_prefix - else: - parsed_prefix = self._netmask_str_to_prefix(mask_arg) - if parsed_prefix is None: - raise ValueError(f"Invalid netmask string: '{mask_arg}'") - prefix = parsed_prefix - else: - raise ValueError(f"IP4() constructor takes 1 or 2 arguments, got {len(args)}") - - if not self._is_valid_ip_string(ip_str): - raise ValueError(f"Invalid IP address string: '{ip_str}'") - - self.ip_address = ip_str - self.prefix = prefix - - # Valor para SymPy - usar representación numérica de la IP - ip_as_int = struct.unpack("!I", socket.inet_aton(ip_str))[0] - canonical_repr = f"{ip_str}/{prefix}" if prefix is not None else ip_str - - super().__init__(ip_as_int, canonical_repr) - - def _is_valid_ip_string(self, ip_str: str) -> bool: - """Verifica si es una dirección IP válida""" - import socket - try: - socket.inet_aton(ip_str) - parts = ip_str.split(".") - return len(parts) == 4 and all(0 <= int(part) <= 255 for part in parts) - except (socket.error, ValueError, TypeError): - return False - - def _is_valid_prefix(self, prefix: int) -> bool: - """Verifica si es un prefijo CIDR válido""" - return isinstance(prefix, int) and 0 <= prefix <= 32 - - def _netmask_str_to_prefix(self, netmask_str: str) -> Optional[int]: - """Convierte máscara decimal a prefijo CIDR""" - import socket - import struct - - if not self._is_valid_ip_string(netmask_str): - return None - try: - mask_int = struct.unpack("!I", socket.inet_aton(netmask_str))[0] - except socket.error: - return None - - binary_mask = bin(mask_int)[2:].zfill(32) - if '0' in binary_mask and '1' in binary_mask[binary_mask.find('0'):]: - return None - - return binary_mask.count('1') - - def __str__(self): - if self.prefix is not None: - return f"{self.ip_address}/{self.prefix}" - return self.ip_address - - def _sympystr(self, printer): - return f"IP4({self.__str__()})" - - @staticmethod - def Helper(input_str): - if re.match(r"^\s*IP4(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): - return 'Ej: IP4[192.168.1.1/24] o IP4[10.0.0.1]' - return None - - def NetworkAddress(self): - """Retorna la dirección de red""" - import socket - import struct - - if self.prefix is None: - raise ValueError("Mask not set, cannot calculate network address.") - - ip_int = struct.unpack("!I", socket.inet_aton(self.ip_address))[0] - mask_int = (0xFFFFFFFF << (32 - self.prefix)) & 0xFFFFFFFF if self.prefix > 0 else 0 - net_addr_int = ip_int & mask_int - net_addr_str = socket.inet_ntoa(struct.pack("!I", net_addr_int)) - return HybridIP4(net_addr_str, self.prefix) - - def BroadcastAddress(self): - """Retorna la dirección de broadcast""" - import socket - import struct - - if self.prefix is None: - raise ValueError("Mask not set, cannot calculate broadcast address.") - - net_addr_obj = self.NetworkAddress() - net_addr_int = struct.unpack("!I", socket.inet_aton(net_addr_obj.ip_address))[0] - wildcard_mask = ((1 << (32 - self.prefix)) - 1) if self.prefix < 32 else 0 - bcast_addr_int = net_addr_int | wildcard_mask - bcast_addr_str = socket.inet_ntoa(struct.pack("!I", bcast_addr_int)) - return HybridIP4(bcast_addr_str, self.prefix) - - def Nodes(self): - """Retorna el número de hosts disponibles""" - if self.prefix is None: - raise ValueError("Mask not set, cannot calculate number of nodes.") - - host_bits = 32 - self.prefix - if host_bits < 2: - return 0 - return (1 << host_bits) - 2 - - -class HybridChr(HybridCalcType): - """Clase híbrida para caracteres ASCII""" - - def __new__(cls, str_input): - obj = HybridCalcType.__new__(cls) - return obj - - def __init__(self, str_input: str): - if not isinstance(str_input, str): - raise TypeError(f"Chr input must be a string, got {type(str_input).__name__}") - if not str_input: - raise ValueError("Chr input string cannot be empty.") - - if len(str_input) == 1: - processed_value = ord(str_input) - else: - processed_value = [ord(c) for c in str_input] - - super().__init__(processed_value, str_input) - - def __str__(self): - if isinstance(self._value, int): - return chr(self._value) - elif isinstance(self._value, list): - return "".join(map(chr, self._value)) - return super().__str__() - - def _sympystr(self, printer): - return f"Chr({self._original_str})" - - @staticmethod - def Helper(input_str): - if re.match(r"^\s*Chr(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE): - return 'Ej: Chr[A] o Chr[Hello]. Representa caracteres ASCII.' - return None - - -# Alias para compatibilidad -Hex = HybridHex -Bin = HybridBin -Dec = HybridDec -IP4 = HybridIP4 -Chr = HybridChr - - -# Funciones de testing -def test_hybrid_classes(): - """Test de las clases híbridas""" - print("=== Test Clases Híbridas ===") - - # Test Hex - h = Hex("FF") - print(f"Hex('FF'): {h} (type: {type(h)})") - print(f" SymPy str: {h._sympystr(None)}") - print(f" Is SymPy Basic: {isinstance(h, sympy.Basic)}") - print(f" Value: {h._value}") - - # Test operación simple - try: - result = h + 1 - print(f" Hex + 1: {result} (type: {type(result)})") - except Exception as e: - print(f" Error en Hex + 1: {e}") - - # Test IP4 - ip = IP4("192.168.1.100/24") - print(f"IP4('192.168.1.100/24'): {ip}") - print(f" NetworkAddress: {ip.NetworkAddress()}") - print(f" Is SymPy Basic: {isinstance(ip, sympy.Basic)}") - - # Test Chr - c = Chr("A") - print(f"Chr('A'): {c}") - print(f" ASCII value: {c._value}") - print(f" Is SymPy Basic: {isinstance(c, sympy.Basic)}") - - -if __name__ == "__main__": - test_hybrid_classes() diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index c99e54b..d1005b4 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -1,2 +1,5 @@ Hex[FF] -ip=IP4[192.168.1.100/24] \ No newline at end of file +ip=IP4[192.168.1.100/24] + + +500/25 \ No newline at end of file diff --git a/ip4_type.py b/ip4_type.py new file mode 100644 index 0000000..15c31fc --- /dev/null +++ b/ip4_type.py @@ -0,0 +1,180 @@ +""" +Clase híbrida para direcciones IPv4 +""" +from hybrid_base import HybridCalcType +from typing import Optional, Tuple +import re + + +class HybridIP4(HybridCalcType): + """Clase híbrida para direcciones IPv4""" + + def __new__(cls, *args): + """Crear objeto SymPy válido""" + obj = HybridCalcType.__new__(cls) + return obj + + def __init__(self, *args): + """Inicialización de IP4""" + if len(args) == 1: + # Formato: "192.168.1.1/24" o "192.168.1.1 255.255.255.0" + input_str = args[0] + if '/' in input_str: + # Formato CIDR + ip_str, prefix_str = input_str.split('/') + prefix = int(prefix_str) + else: + # Formato con máscara + parts = input_str.split() + if len(parts) == 2: + ip_str, netmask_str = parts + prefix = self._netmask_str_to_prefix(netmask_str) + else: + ip_str = input_str + prefix = None + else: + # Formato: ("192.168.1.1", 24) o ("192.168.1.1", "255.255.255.0") + ip_str = args[0] + if len(args) > 1: + if isinstance(args[1], int): + prefix = args[1] + else: + prefix = self._netmask_str_to_prefix(args[1]) + else: + prefix = None + + if not self._is_valid_ip_string(ip_str): + raise ValueError(f"Invalid IP address format: {ip_str}") + + if prefix is not None and not self._is_valid_prefix(prefix): + raise ValueError(f"Invalid prefix length: {prefix}") + + # Convertir IP a entero para almacenamiento + ip_parts = [int(x) for x in ip_str.split('.')] + ip_int = (ip_parts[0] << 24) + (ip_parts[1] << 16) + (ip_parts[2] << 8) + ip_parts[3] + + # Almacenar valores + self._ip_int = ip_int + self._prefix = prefix + self._ip_str = ip_str + + # Llamar al constructor base + super().__init__(ip_int, input_str) + + def _is_valid_ip_string(self, ip_str: str) -> bool: + """Verifica si el string es una IP válida""" + pattern = r'^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$' + match = re.match(pattern, ip_str) + if not match: + return False + + # Verificar que cada octeto esté en rango + for octet in match.groups(): + if not 0 <= int(octet) <= 255: + return False + + return True + + def _is_valid_prefix(self, prefix: int) -> bool: + """Verifica si el prefijo es válido""" + return 0 <= prefix <= 32 + + def _netmask_str_to_prefix(self, netmask_str: str) -> Optional[int]: + """Convierte máscara de red a longitud de prefijo""" + if not self._is_valid_ip_string(netmask_str): + return None + + # Convertir máscara a binario + parts = [int(x) for x in netmask_str.split('.')] + binary = ''.join(f'{x:08b}' for x in parts) + + # Contar 1's consecutivos desde la izquierda + prefix = 0 + for bit in binary: + if bit == '1': + prefix += 1 + else: + break + + # Verificar que el resto sean 0's + if '1' in binary[prefix:]: + return None + + return prefix + + def __str__(self): + """Representación string para display""" + if self._prefix is not None: + return f"{self._ip_str}/{self._prefix}" + return self._ip_str + + def _sympystr(self, printer): + """Representación SymPy string""" + return str(self) + + @staticmethod + def Helper(input_str): + """Ayuda contextual para IP4""" + return """ + Formato IP4: + - CIDR: 192.168.1.1/24 + - Con máscara: 192.168.1.1 255.255.255.0 + - Solo IP: 192.168.1.1 + + Métodos disponibles: + - NetworkAddress(): Obtiene dirección de red + - BroadcastAddress(): Obtiene dirección de broadcast + - Nodes(): Obtiene número de nodos disponibles + """ + + def NetworkAddress(self): + """Obtiene la dirección de red""" + if self._prefix is None: + raise ValueError("No prefix/mask defined") + + # Calcular máscara de red + mask = (0xffffffff >> (32 - self._prefix)) << (32 - self._prefix) + + # Aplicar máscara + network = self._ip_int & mask + + # Convertir a string + parts = [ + (network >> 24) & 0xff, + (network >> 16) & 0xff, + (network >> 8) & 0xff, + network & 0xff + ] + network_str = '.'.join(str(x) for x in parts) + + return HybridIP4(f"{network_str}/{self._prefix}") + + def BroadcastAddress(self): + """Obtiene la dirección de broadcast""" + if self._prefix is None: + raise ValueError("No prefix/mask defined") + + # Calcular máscara de red + mask = (0xffffffff >> (32 - self._prefix)) << (32 - self._prefix) + + # Calcular broadcast + broadcast = self._ip_int | (~mask & 0xffffffff) + + # Convertir a string + parts = [ + (broadcast >> 24) & 0xff, + (broadcast >> 16) & 0xff, + (broadcast >> 8) & 0xff, + broadcast & 0xff + ] + broadcast_str = '.'.join(str(x) for x in parts) + + return HybridIP4(f"{broadcast_str}/{self._prefix}") + + def Nodes(self): + """Obtiene el número de nodos disponibles""" + if self._prefix is None: + raise ValueError("No prefix/mask defined") + + # 2^(32-prefix) - 2 (red y broadcast) + return 2 ** (32 - self._prefix) - 2 \ No newline at end of file diff --git a/main_launcher.py b/main_calc.py similarity index 98% rename from main_launcher.py rename to main_calc.py index da7c843..3294791 100644 --- a/main_launcher.py +++ b/main_calc.py @@ -334,11 +334,16 @@ def check_app_files(): logger.info("Verificando archivos de la aplicación...") required_files = [ - 'bracket_parser.py', - 'hybrid_base_types.py', - 'hybrid_evaluation_engine.py', - 'interactive_results.py', - 'hybrid_calc_app.py' + 'tl_bracket_parser.py', + 'hybrid_base.py', + 'ip4_type.py', + 'hex_type.py', + 'bin_type.py', + 'dec_type.py', + 'chr_type.py', + 'main_evaluation.py', + 'tl_popup.py', + 'main_calc_app.py' ] current_dir = Path(__file__).parent @@ -386,7 +391,7 @@ def launch_application(): try: # Importar y ejecutar la aplicación logger.info("Importando módulo principal...") - from hybrid_calc_app import HybridCalculatorApp + from main_calc_app import HybridCalculatorApp logger.info("Creando ventana principal...") root = tk.Tk() diff --git a/hybrid_calc_app.py b/main_calc_app.py similarity index 98% rename from hybrid_calc_app.py rename to main_calc_app.py index 4f39f6c..70e7b79 100644 --- a/hybrid_calc_app.py +++ b/main_calc_app.py @@ -10,9 +10,13 @@ import threading from typing import List, Dict, Any, Optional # Importar componentes del CAS híbrido -from hybrid_evaluation_engine import HybridEvaluationEngine, EvaluationResult -from interactive_results import InteractiveResultManager -from hybrid_base_types import Hex, Bin, Dec, IP4, Chr +from main_evaluation import HybridEvaluationEngine, EvaluationResult +from tl_popup import InteractiveResultManager +from ip4_type import HybridIP4 as IP4 +from hex_type import HybridHex as Hex +from bin_type import HybridBin as Bin +from dec_type import HybridDec as Dec +from chr_type import HybridChr as Chr import sympy diff --git a/hybrid_evaluation_engine.py b/main_evaluation.py similarity index 98% rename from hybrid_evaluation_engine.py rename to main_evaluation.py index 8625118..46541d1 100644 --- a/hybrid_evaluation_engine.py +++ b/main_evaluation.py @@ -8,12 +8,14 @@ import ast import re from contextlib import contextmanager -from bracket_parser import BracketParser -from interactive_results import PlotResult # 🔧 IMPORTACIÓN CORREGIDA -from hybrid_base_types import ( - HybridCalcType, HybridHex, HybridBin, HybridDec, - HybridIP4, HybridChr, Hex, Bin, Dec, IP4, Chr -) +from tl_bracket_parser import BracketParser +from tl_popup import PlotResult +from hybrid_base import HybridCalcType +from ip4_type import HybridIP4 as IP4 +from hex_type import HybridHex as Hex +from bin_type import HybridBin as Bin +from dec_type import HybridDec as Dec +from chr_type import HybridChr as Chr class HybridEvaluationEngine: @@ -472,9 +474,6 @@ class EvaluationResult: @property def is_interactive(self) -> bool: """Determina si el resultado requiere interactividad""" - # 🔧 CORRECCIÓN: Importar PlotResult desde el lugar correcto - from interactive_results import PlotResult - return isinstance(self.result, (PlotResult, sympy.Matrix)) or \ (isinstance(self.result, list) and len(self.result) > 3) diff --git a/bracket_parser.py b/tl_bracket_parser.py similarity index 100% rename from bracket_parser.py rename to tl_bracket_parser.py diff --git a/interactive_results.py b/tl_popup.py similarity index 100% rename from interactive_results.py rename to tl_popup.py