Refactorizado de archivos y nombres

This commit is contained in:
Miguel 2025-06-02 10:39:59 +02:00
parent 3aadae3dc6
commit 261b20df5c
15 changed files with 884 additions and 626 deletions

View File

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

View File

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

52
bin_type.py Normal file
View File

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

63
chr_type.py Normal file
View File

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

52
dec_type.py Normal file
View File

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

52
hex_type.py Normal file
View File

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

246
hybrid_base.py Normal file
View File

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

View File

@ -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()

View File

@ -1,2 +1,5 @@
Hex[FF]
ip=IP4[192.168.1.100/24]
ip=IP4[192.168.1.100/24]
500/25

180
ip4_type.py Normal file
View File

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

View File

@ -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()

View File

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

View File

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