Refactorizado de archivos y nombres
This commit is contained in:
parent
3aadae3dc6
commit
261b20df5c
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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}"
|
|
@ -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}"
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -1,2 +1,5 @@
|
|||
Hex[FF]
|
||||
ip=IP4[192.168.1.100/24]
|
||||
ip=IP4[192.168.1.100/24]
|
||||
|
||||
|
||||
500/25
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
|
Loading…
Reference in New Issue