Actualización del sistema de tokenización y refactorización de clases base. Se implementa un nuevo enfoque de tokenización distribuida que permite la carga dinámica de clases desde el registro, mejorando la modularidad y escalabilidad del sistema. Se eliminan dependencias de clases codificadas y se optimizan las funciones de evaluación y análisis de expresiones. Se ajustan las configuraciones de la ventana y se mejora la gestión de errores y advertencias en el motor de evaluación.
This commit is contained in:
parent
6a7311e358
commit
2cf06fb5f5
|
@ -58,15 +58,60 @@ def preprocess_tokens(expression):
|
|||
return expression
|
||||
```
|
||||
|
||||
## Clases Base Universales
|
||||
## Problema Arquitectónico Crítico: Conversión Prematura a SymPy
|
||||
|
||||
### IntBase: Universal para Bases Numéricas
|
||||
### Diagnóstico del Problema Actual
|
||||
|
||||
#### **Resultado Problemático Observado:**
|
||||
```python
|
||||
mm = 255.240.0.x # → 255.240.0.x[Sympy] ❌
|
||||
IP4(mm, 24) # → Error: MutableDenseMatrix + int ❌
|
||||
j.next_ip() # → Error: unsupported operand ❌
|
||||
```
|
||||
|
||||
#### **Causa Raíz:**
|
||||
- **Conversión prematura**: FourBytes se convierte inmediatamente a SymPy Matrix
|
||||
- **Pérdida de control**: SymPy toma control antes de que el objeto pueda manejar sus operaciones
|
||||
- **Incompatibilidad operacional**: Matrices de SymPy no entienden aritmética de direcciones IP
|
||||
|
||||
### Arquitectura Correcta: Objetos Nativos con Conversión Perezosa
|
||||
|
||||
#### **Principio Fundamental:**
|
||||
> **"Cada objeto debe mantenerse en su forma nativa hasta que se involucre en una ecuación algebraica compleja"**
|
||||
|
||||
#### **Jerarquía Revisada:**
|
||||
```python
|
||||
ClassBase (SIN SymPy - manejo interno de símbolos)
|
||||
├── FourBytes (símbolos internos, aritmética nativa)
|
||||
├── IntBase (símbolos internos, aritmética nativa)
|
||||
├── IP4, Hex, Dec, etc. (usan objetos nativos)
|
||||
└── IP4Mask (nativo)
|
||||
|
||||
SympyClassBase (solo cuando sea necesario)
|
||||
├── Para ecuaciones algebraicas complejas
|
||||
├── Para integración/derivación
|
||||
└── Para sistemas de ecuaciones
|
||||
```
|
||||
|
||||
#### **Comportamiento Deseado:**
|
||||
```python
|
||||
mm = 255.240.0.x # → FourBytes('255.240.0.x') [FourBytes]
|
||||
IP4(mm, 24) # → IP4 con FourBytes interno [IP4]
|
||||
j.substitute(x=1) # → Lógica interna de FourBytes [IP4]
|
||||
j.next_ip() # → Aritmética interna nativa [IP4]
|
||||
hh = 16#ff # → IntBase('ff', 16) [IntBase]
|
||||
Dec(hh) # → Dec con IntBase interno [Dec]
|
||||
```
|
||||
|
||||
## Clases Base Universales (Arquitectura Corregida)
|
||||
|
||||
### IntBase: Universal para Bases Numéricas (ClassBase)
|
||||
|
||||
```python
|
||||
class IntBase(SympyClassBase):
|
||||
class IntBase(ClassBase): # ← CAMBIO: ClassBase, NO SympyClassBase
|
||||
"""
|
||||
Representación universal de números en cualquier base
|
||||
con soporte algebraico completo y operaciones aritméticas
|
||||
con manejo interno de símbolos y conversión perezosa a SymPy
|
||||
"""
|
||||
|
||||
def __init__(self, value_str, base=10):
|
||||
|
@ -75,170 +120,92 @@ class IntBase(SympyClassBase):
|
|||
self.has_symbols = bool(re.search(r'[a-zA-Z_]', value_str))
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: mantener como expresión simbólica
|
||||
self._symbolic_value = self._parse_symbolic_base()
|
||||
super().__init__(self._symbolic_value, f"{base}#{value_str}")
|
||||
# Modo algebraico: mantener símbolos INTERNAMENTE
|
||||
self._symbols = self._extract_symbols()
|
||||
self._expression = self._build_internal_expression()
|
||||
super().__init__(self._expression, f"{base}#{value_str}")
|
||||
else:
|
||||
# Modo numérico: convertir a entero
|
||||
self._numeric_value = int(value_str, base)
|
||||
super().__init__(self._numeric_value, f"{base}#{value_str}")
|
||||
|
||||
def _parse_symbolic_base(self):
|
||||
"""Convierte valor con símbolos a expresión SymPy"""
|
||||
# Ejemplo: "x0" base 16 → x*16^1 + 0*16^0
|
||||
# Ejemplo: "101x" base 2 → 1*2^3 + 0*2^2 + 1*2^1 + x*2^0
|
||||
symbols = []
|
||||
result = 0
|
||||
digits = list(self.value_str)
|
||||
|
||||
for i, digit in enumerate(reversed(digits)):
|
||||
power = self.base ** i
|
||||
if digit.isdigit():
|
||||
coefficient = int(digit)
|
||||
elif digit in 'ABCDEF':
|
||||
coefficient = ord(digit) - ord('A') + 10
|
||||
elif digit in 'abcdef':
|
||||
coefficient = ord(digit) - ord('a') + 10
|
||||
else:
|
||||
# Es un símbolo
|
||||
coefficient = sympy.Symbol(digit)
|
||||
symbols.append(digit)
|
||||
|
||||
result += coefficient * power
|
||||
|
||||
return result
|
||||
def _extract_symbols(self):
|
||||
"""Extrae símbolos sin convertir a SymPy aún"""
|
||||
return re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*', self.value_str)
|
||||
|
||||
def to_base(self, new_base):
|
||||
"""Convierte a nueva base"""
|
||||
def _build_internal_expression(self):
|
||||
"""Construye expresión interna SIN SymPy"""
|
||||
# Mantener como string procesable internamente
|
||||
# Solo convertir a SymPy cuando se llame to_sympy()
|
||||
return f"base{self.base}_expression({self.value_str})"
|
||||
|
||||
def substitute(self, **kwargs):
|
||||
"""Sustitución interna sin involucrar SymPy"""
|
||||
if not self.has_symbols:
|
||||
return self
|
||||
|
||||
new_value_str = self.value_str
|
||||
for symbol, value in kwargs.items():
|
||||
new_value_str = new_value_str.replace(symbol, str(value))
|
||||
|
||||
return IntBase(new_value_str, self.base)
|
||||
|
||||
def to_sympy(self):
|
||||
"""Conversión EXPLÍCITA a SymPy cuando se necesite álgebra"""
|
||||
if self.has_symbols:
|
||||
# En modo algebraico, mantener expresión
|
||||
return IntBase(f"({self._symbolic_value})_base{new_base}", new_base)
|
||||
# AHORA sí convertir a expresión SymPy
|
||||
result = 0
|
||||
digits = list(self.value_str)
|
||||
|
||||
for i, digit in enumerate(reversed(digits)):
|
||||
power = self.base ** i
|
||||
if digit.isdigit():
|
||||
coefficient = int(digit)
|
||||
elif digit in 'ABCDEF':
|
||||
coefficient = ord(digit) - ord('A') + 10
|
||||
elif digit in 'abcdef':
|
||||
coefficient = ord(digit) - ord('a') + 10
|
||||
else:
|
||||
# Es un símbolo
|
||||
coefficient = sympy.Symbol(digit)
|
||||
|
||||
result += coefficient * power
|
||||
|
||||
return result
|
||||
else:
|
||||
# En modo numérico, convertir directamente
|
||||
new_value_str = self._convert_to_base_string(self._numeric_value, new_base)
|
||||
return IntBase(new_value_str, new_base)
|
||||
return sympy.sympify(self._numeric_value)
|
||||
|
||||
@staticmethod
|
||||
def _convert_to_base_string(value, base):
|
||||
"""Convierte entero a string en base específica"""
|
||||
if value == 0:
|
||||
return "0"
|
||||
digits = "0123456789ABCDEF"
|
||||
result = ""
|
||||
while value:
|
||||
result = digits[value % base] + result
|
||||
value //= base
|
||||
return result
|
||||
|
||||
# ========== OPERADORES ARITMÉTICOS ==========
|
||||
# ========== OPERADORES ARITMÉTICOS NATIVOS ==========
|
||||
|
||||
def __add__(self, other):
|
||||
"""Suma: mantiene la base del operando izquierdo"""
|
||||
"""Suma: aritmética nativa, mantiene la base"""
|
||||
if self.has_symbols:
|
||||
return super().__add__(other) # Delegar a SymPy
|
||||
# Para símbolos, mantener como expresión interna
|
||||
return IntBase(f"({self.value_str}) + ({other})", self.base)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__add__(other)
|
||||
return IntBase(f"({self.value_str}) + ({other.value_str})", self.base)
|
||||
result_value = self._numeric_value + other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value + other
|
||||
else:
|
||||
return super().__add__(other)
|
||||
# Para operaciones complejas, convertir a SymPy
|
||||
return self.to_sympy() + other
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Resta: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__sub__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__sub__(other)
|
||||
result_value = self._numeric_value - other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value - other
|
||||
else:
|
||||
return super().__sub__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __mul__(self, other):
|
||||
"""Multiplicación: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__mul__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__mul__(other)
|
||||
result_value = self._numeric_value * other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value * other
|
||||
else:
|
||||
return super().__mul__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __truediv__(self, other):
|
||||
"""División: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__truediv__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__truediv__(other)
|
||||
result_value = self._numeric_value // other._numeric_value # División entera
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value // other
|
||||
else:
|
||||
return super().__truediv__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __mod__(self, other):
|
||||
"""Módulo: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__mod__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__mod__(other)
|
||||
result_value = self._numeric_value % other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value % other
|
||||
else:
|
||||
return super().__mod__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
# Operadores reversos para int + IntBase
|
||||
def __radd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
def __rsub__(self, other):
|
||||
if isinstance(other, int):
|
||||
result_value = other - self._numeric_value
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
return super().__rsub__(other)
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self.__mul__(other)
|
||||
# ... resto de operadores aritméticos nativos
|
||||
```
|
||||
|
||||
### FourBytes: Universal para Patrones x.x.x.x
|
||||
### FourBytes: Universal para Patrones x.x.x.x (ClassBase)
|
||||
|
||||
```python
|
||||
class FourBytes(SympyClassBase):
|
||||
class FourBytes(ClassBase): # ← CAMBIO: ClassBase, NO SympyClassBase
|
||||
"""
|
||||
Representación universal de patrones de 4 elementos
|
||||
con soporte algebraico completo y operaciones aritméticas
|
||||
con manejo interno de símbolos y conversión perezosa a SymPy
|
||||
"""
|
||||
|
||||
def __init__(self, dotted_string):
|
||||
|
@ -251,17 +218,9 @@ class FourBytes(SympyClassBase):
|
|||
self.has_symbols = any(not elem.isdigit() for elem in self.elements)
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: crear expresión simbólica
|
||||
self._symbolic_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
self._symbolic_elements.append(int(elem))
|
||||
else:
|
||||
self._symbolic_elements.append(sympy.Symbol(elem))
|
||||
|
||||
# Crear expresión como vector de 4 elementos
|
||||
self._symbolic_value = sympy.Matrix(self._symbolic_elements)
|
||||
super().__init__(self._symbolic_value, dotted_string)
|
||||
# Modo algebraico: mantener símbolos INTERNAMENTE
|
||||
self._symbols = self._extract_symbols()
|
||||
super().__init__(dotted_string, dotted_string) # Mantener como string
|
||||
else:
|
||||
# Modo numérico: validar rangos y convertir
|
||||
self._numeric_elements = [int(elem) for elem in self.elements]
|
||||
|
@ -272,14 +231,17 @@ class FourBytes(SympyClassBase):
|
|||
self._numeric_elements[3])
|
||||
super().__init__(self._numeric_value, dotted_string)
|
||||
|
||||
def is_valid_ip_range(self):
|
||||
"""Verifica si todos los elementos numéricos están en rango IP"""
|
||||
if self.has_symbols:
|
||||
return None # No se puede validar con símbolos
|
||||
return all(0 <= x <= 255 for x in self._numeric_elements)
|
||||
def _extract_symbols(self):
|
||||
"""Extrae símbolos sin convertir a SymPy aún"""
|
||||
symbols = []
|
||||
for elem in self.elements:
|
||||
if not elem.isdigit():
|
||||
if elem not in symbols:
|
||||
symbols.append(elem)
|
||||
return symbols
|
||||
|
||||
def substitute(self, **kwargs):
|
||||
"""Sustituye símbolos por valores"""
|
||||
"""Sustitución interna sin involucrar SymPy"""
|
||||
if not self.has_symbols:
|
||||
return self
|
||||
|
||||
|
@ -292,115 +254,80 @@ class FourBytes(SympyClassBase):
|
|||
|
||||
return FourBytes('.'.join(new_elements))
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Acceso a elementos individuales"""
|
||||
def to_sympy(self):
|
||||
"""Conversión EXPLÍCITA a SymPy cuando se necesite álgebra"""
|
||||
if self.has_symbols:
|
||||
return self._symbolic_elements[index]
|
||||
symbolic_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
symbolic_elements.append(int(elem))
|
||||
else:
|
||||
symbolic_elements.append(sympy.Symbol(elem))
|
||||
|
||||
# Retornar como Matrix o expresión según el contexto
|
||||
return sympy.Matrix(symbolic_elements)
|
||||
else:
|
||||
return self._numeric_elements[index]
|
||||
|
||||
def to_ip_int(self):
|
||||
"""Convierte a entero de 32 bits (para IPs)"""
|
||||
if self.has_symbols:
|
||||
# Crear expresión algebraica
|
||||
return (self._symbolic_elements[0] * 2**24 +
|
||||
self._symbolic_elements[1] * 2**16 +
|
||||
self._symbolic_elements[2] * 2**8 +
|
||||
self._symbolic_elements[3])
|
||||
else:
|
||||
return self._numeric_value
|
||||
|
||||
@staticmethod
|
||||
def _int_to_fourbytes(value):
|
||||
"""Convierte entero de 32 bits a formato x.y.z.w"""
|
||||
w = value & 0xFF
|
||||
z = (value >> 8) & 0xFF
|
||||
y = (value >> 16) & 0xFF
|
||||
x = (value >> 24) & 0xFF
|
||||
return f"{x}.{y}.{z}.{w}"
|
||||
|
||||
# ========== OPERADORES ARITMÉTICOS ==========
|
||||
return sympy.sympify(self._numeric_value)
|
||||
|
||||
def __add__(self, other):
|
||||
"""Suma: convierte a int, opera, reconvierte a FourBytes"""
|
||||
"""Suma: aritmética nativa de 32-bit"""
|
||||
if self.has_symbols:
|
||||
return super().__add__(other) # Delegar a SymPy
|
||||
# Para símbolos, mantener como expresión interna
|
||||
return FourBytes(f"({self.original}) + ({other})")
|
||||
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
return super().__add__(other)
|
||||
return FourBytes(f"({self.original}) + ({other.original})")
|
||||
result_int = self._numeric_value + other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_int = self._numeric_value + other
|
||||
else:
|
||||
return super().__add__(other)
|
||||
# Para operaciones complejas, convertir a SymPy
|
||||
return self.to_sympy() + other
|
||||
|
||||
# Mantener en rango de 32 bits
|
||||
result_int = result_int & 0xFFFFFFFF
|
||||
result_str = self._int_to_fourbytes(result_int)
|
||||
return FourBytes(result_str)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Resta: convierte a int, opera, reconvierte a FourBytes"""
|
||||
if self.has_symbols:
|
||||
return super().__sub__(other)
|
||||
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
return super().__sub__(other)
|
||||
result_int = self._numeric_value - other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_int = self._numeric_value - other
|
||||
else:
|
||||
return super().__sub__(other)
|
||||
|
||||
# Mantener en rango de 32 bits (underflow se convierte en valor alto)
|
||||
result_int = result_int & 0xFFFFFFFF
|
||||
result_str = self._int_to_fourbytes(result_int)
|
||||
return FourBytes(result_str)
|
||||
|
||||
# ========== CONVERSIONES DE BASE ==========
|
||||
|
||||
def ToBase(self, base):
|
||||
"""
|
||||
Convierte cada elemento a la base especificada
|
||||
Retorna expresión con cada elemento convertido
|
||||
"""
|
||||
if self.has_symbols:
|
||||
# Para elementos simbólicos, retornar expresión algebraica
|
||||
converted_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
int_val = int(elem)
|
||||
converted = IntBase._convert_to_base_string(int_val, base)
|
||||
converted_elements.append(f"{base}#{converted}")
|
||||
else:
|
||||
# Símbolo: expresar como conversión algebraica
|
||||
converted_elements.append(f"{base}#{elem}")
|
||||
return '.'.join(converted_elements)
|
||||
else:
|
||||
# Para elementos numéricos, conversión directa
|
||||
converted_elements = []
|
||||
for elem_val in self._numeric_elements:
|
||||
converted = IntBase._convert_to_base_string(elem_val, base)
|
||||
converted_elements.append(f"{base}#{converted}")
|
||||
return '.'.join(converted_elements)
|
||||
|
||||
def ToCIDR(self, prefix_length):
|
||||
"""Convierte a notación CIDR"""
|
||||
return f"{self.original}/{prefix_length}"
|
||||
|
||||
def ToHex(self):
|
||||
"""Convierte cada elemento a hexadecimal"""
|
||||
return self.ToBase(16)
|
||||
|
||||
def ToBinary(self):
|
||||
"""Convierte cada elemento a binario"""
|
||||
return self.ToBase(2)
|
||||
|
||||
def ToOctal(self):
|
||||
"""Convierte cada elemento a octal"""
|
||||
return self.ToBase(8)
|
||||
# ... resto de operadores aritméticos nativos
|
||||
```
|
||||
|
||||
## Conversión Perezosa a SymPy
|
||||
|
||||
### **Cuándo Convertir:**
|
||||
```python
|
||||
# NUNCA conversión automática - mantener nativo:
|
||||
mm = FourBytes('255.240.0.x') # → [FourBytes] ✅
|
||||
ip = IP4(mm, 24) # → [IP4] ✅
|
||||
ip.next_ip() # → [IP4] ✅
|
||||
|
||||
# Conversión EXPLÍCITA solo cuando se necesite álgebra:
|
||||
eq = Eq(mm.to_sympy(), other_expr) # → [SymPy Equation] ✅
|
||||
solve(eq, [x, y]) # → [SymPy Solutions] ✅
|
||||
```
|
||||
|
||||
### **Beneficios de la Conversión Perezosa:**
|
||||
1. **Control granular**: Cada clase decide cuándo necesita SymPy
|
||||
2. **Operaciones nativas**: Sin overhead para operaciones simples
|
||||
3. **Errores claros**: Comportamiento predecible por tipo
|
||||
4. **Flexibilidad**: Conversión explícita cuando se requiera álgebra compleja
|
||||
|
||||
### **Integración con Ecuaciones:**
|
||||
```python
|
||||
# Para resolver ecuaciones complejas:
|
||||
x, y = symbols('x y')
|
||||
fb = FourBytes('10.x.1.y')
|
||||
|
||||
# Crear constraints usando conversión explícita:
|
||||
constraints = [
|
||||
fb.to_sympy()[1] >= 0, # x >= 0
|
||||
fb.to_sympy()[1] <= 255, # x <= 255
|
||||
fb.to_sympy()[3] >= 0, # y >= 0
|
||||
fb.to_sympy()[3] <= 255 # y <= 255
|
||||
]
|
||||
|
||||
solutions = solve(constraints, [x, y])
|
||||
```
|
||||
|
||||
## Integración con Clases Especializadas
|
||||
|
@ -750,27 +677,337 @@ def is_valid_ip_symbolic(fourbytes):
|
|||
return constraints
|
||||
```
|
||||
|
||||
## Plan de Implementación
|
||||
## Integración Completa en el Sistema de Tipos
|
||||
|
||||
### Fase 1: Clases Base
|
||||
1. Implementar `IntBase` con soporte algebraico
|
||||
2. Implementar `FourBytes` con soporte algebraico
|
||||
3. Tests exhaustivos de ambas clases
|
||||
### **Todas las Clases en `custom_types/`**
|
||||
|
||||
### Fase 2: Tokenizador
|
||||
1. Implementar `preprocess_tokens()`
|
||||
2. Integrar con `HybridEvaluationEngine`
|
||||
3. Tests de tokenización
|
||||
#### **Consistencia Arquitectónica:**
|
||||
- **IntBase** y **FourBytes** son tipos como cualquier otro
|
||||
- Deben seguir el mismo patrón de registro y auto-descubrimiento
|
||||
- El parser los importa desde el registro, no desde ubicaciones hardcodeadas
|
||||
|
||||
### Fase 3: Migración de Clases
|
||||
1. Migrar `Hex`, `Bin`, `Dec` a usar `IntBase`
|
||||
2. Migrar `IP4`, `IP4Mask` a usar `FourBytes`
|
||||
3. Eliminar sistema de corchetes
|
||||
#### **Estructura de Directorios Revisada:**
|
||||
```
|
||||
custom_types/
|
||||
├── intbase_type.py # IntBase + función de registro
|
||||
├── fourbytes_type.py # FourBytes + función de registro
|
||||
├── hex_type.py # Hex (usa IntBase del registro)
|
||||
├── bin_type.py # Bin (usa IntBase del registro)
|
||||
├── dec_type.py # Dec (usa IntBase del registro)
|
||||
├── ip4_type.py # IP4 + IP4Mask (usan FourBytes del registro)
|
||||
├── chr_type.py # Chr
|
||||
└── latex_type.py # LaTeX
|
||||
```
|
||||
|
||||
### Fase 4: Nuevas Capacidades
|
||||
1. Operaciones algebraicas avanzadas
|
||||
2. Constraint solving para redes
|
||||
3. Análisis simbólico de rangos
|
||||
### **Archivos de Tipos Fundamentales**
|
||||
|
||||
#### **`custom_types/intbase_type.py`**
|
||||
```python
|
||||
"""
|
||||
Clase base universal para números en cualquier base - TIPO REGISTRADO
|
||||
"""
|
||||
from class_base import ClassBase
|
||||
import sympy
|
||||
import re
|
||||
|
||||
class IntBase(ClassBase):
|
||||
"""
|
||||
Representación universal de números en cualquier base
|
||||
con manejo interno de símbolos y conversión perezosa a SymPy
|
||||
"""
|
||||
|
||||
def __init__(self, value_str, base=10):
|
||||
self.value_str = value_str
|
||||
self.base = base
|
||||
self.has_symbols = bool(re.search(r'[a-zA-Z_]', value_str))
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: mantener símbolos INTERNAMENTE
|
||||
self._symbols = self._extract_symbols()
|
||||
self._expression = self._build_internal_expression()
|
||||
super().__init__(self._expression, f"{base}#{value_str}")
|
||||
else:
|
||||
# Modo numérico: convertir a entero
|
||||
self._numeric_value = int(value_str, base)
|
||||
super().__init__(self._numeric_value, f"{base}#{value_str}")
|
||||
|
||||
# ... implementación completa ...
|
||||
|
||||
@staticmethod
|
||||
def Helper(input_str):
|
||||
"""Ayuda contextual para IntBase"""
|
||||
if re.search(r'\d+#[0-9A-Fa-fx]+', input_str):
|
||||
return '''IntBase - Números en cualquier base con álgebra simbólica
|
||||
|
||||
Sintaxis: base#valor
|
||||
Ejemplos:
|
||||
16#FF → IntBase('FF', 16) = 255
|
||||
2#1010 → IntBase('1010', 2) = 10
|
||||
8#777 → IntBase('777', 8) = 511
|
||||
16#x0 → IntBase('x0', 16) = simbólico
|
||||
|
||||
Operaciones aritméticas mantienen la base original:
|
||||
16#FF + 16#10 → IntBase('10F', 16)
|
||||
2#1010 * 3 → IntBase('11110', 2)
|
||||
|
||||
Métodos disponibles:
|
||||
.to_base(nueva_base) - Convierte a otra base
|
||||
.substitute(x=valor) - Sustituye símbolos
|
||||
.to_sympy() - Conversión explícita a SymPy'''
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def PopupFunctionList():
|
||||
"""Lista de métodos para autocompletado"""
|
||||
return [
|
||||
("to_base", "Convierte a otra base"),
|
||||
("substitute", "Sustituye símbolos por valores"),
|
||||
("to_sympy", "Conversión explícita a SymPy para álgebra"),
|
||||
("to_decimal", "Obtiene valor decimal"),
|
||||
("to_hex", "Convierte a hexadecimal"),
|
||||
("to_binary", "Convierte a binario"),
|
||||
("to_octal", "Convierte a octal"),
|
||||
]
|
||||
|
||||
def register_classes_in_module():
|
||||
"""Registro de IntBase en el sistema de tipos"""
|
||||
return [
|
||||
("IntBase", IntBase, "ClassBase", {
|
||||
"add_lowercase": True,
|
||||
"supports_brackets": False, # Se maneja por tokenización
|
||||
"is_fundamental": True, # Clase fundamental del sistema
|
||||
"description": "Números universales en cualquier base con álgebra simbólica"
|
||||
}),
|
||||
]
|
||||
```
|
||||
|
||||
#### **`custom_types/fourbytes_type.py`**
|
||||
```python
|
||||
"""
|
||||
Clase base universal para patrones x.x.x.x - TIPO REGISTRADO
|
||||
"""
|
||||
from class_base import ClassBase
|
||||
import sympy
|
||||
import re
|
||||
|
||||
class FourBytes(ClassBase):
|
||||
"""
|
||||
Representación universal de patrones de 4 elementos
|
||||
con manejo interno de símbolos y conversión perezosa a SymPy
|
||||
"""
|
||||
|
||||
def __init__(self, dotted_string):
|
||||
self.original = dotted_string
|
||||
self.elements = dotted_string.split('.')
|
||||
|
||||
if len(self.elements) != 4:
|
||||
raise ValueError(f"FourBytes requiere exactamente 4 elementos: {dotted_string}")
|
||||
|
||||
self.has_symbols = any(not elem.isdigit() for elem in self.elements)
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: mantener símbolos INTERNAMENTE
|
||||
self._symbols = self._extract_symbols()
|
||||
super().__init__(dotted_string, dotted_string) # Mantener como string
|
||||
else:
|
||||
# Modo numérico: validar rangos y convertir
|
||||
self._numeric_elements = [int(elem) for elem in self.elements]
|
||||
# Crear valor como entero de 32 bits (para IPs)
|
||||
self._numeric_value = (self._numeric_elements[0] << 24 |
|
||||
self._numeric_elements[1] << 16 |
|
||||
self._numeric_elements[2] << 8 |
|
||||
self._numeric_elements[3])
|
||||
super().__init__(self._numeric_value, dotted_string)
|
||||
|
||||
# ... implementación completa ...
|
||||
|
||||
@staticmethod
|
||||
def Helper(input_str):
|
||||
"""Ayuda contextual para FourBytes"""
|
||||
if re.search(r'\d+\.\d+\.\d+\.\d+', input_str):
|
||||
return '''FourBytes - Patrones x.x.x.x con álgebra simbólica
|
||||
|
||||
Sintaxis: x.y.z.w (donde cada elemento puede ser número o símbolo)
|
||||
Ejemplos:
|
||||
192.168.1.1 → FourBytes('192.168.1.1')
|
||||
255.255.0.0 → FourBytes('255.255.0.0')
|
||||
10.x.1.y → FourBytes('10.x.1.y') = simbólico
|
||||
|
||||
Aritmética de 32-bit:
|
||||
192.168.1.1 + 5 → FourBytes('192.168.1.6')
|
||||
10.0.0.0 + 256 → FourBytes('10.0.1.0')
|
||||
|
||||
Métodos disponibles:
|
||||
.ToBase(base) - Convierte cada elemento a base específica
|
||||
.ToHex() - Convierte a hexadecimal
|
||||
.ToBinary() - Convierte a binario
|
||||
.substitute(x=val) - Sustituye símbolos
|
||||
.to_sympy() - Conversión explícita a SymPy'''
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def PopupFunctionList():
|
||||
"""Lista de métodos para autocompletado"""
|
||||
return [
|
||||
("ToBase", "Convierte cada elemento a base específica"),
|
||||
("ToHex", "Convierte cada elemento a hexadecimal"),
|
||||
("ToBinary", "Convierte cada elemento a binario"),
|
||||
("ToOctal", "Convierte cada elemento a octal"),
|
||||
("ToCIDR", "Añade notación CIDR"),
|
||||
("substitute", "Sustituye símbolos por valores"),
|
||||
("to_sympy", "Conversión explícita a SymPy para álgebra"),
|
||||
("is_valid_ip_range", "Verifica si es rango IP válido"),
|
||||
]
|
||||
|
||||
def register_classes_in_module():
|
||||
"""Registro de FourBytes en el sistema de tipos"""
|
||||
return [
|
||||
("FourBytes", FourBytes, "ClassBase", {
|
||||
"add_lowercase": True,
|
||||
"supports_brackets": False, # Se maneja por tokenización
|
||||
"is_fundamental": True, # Clase fundamental del sistema
|
||||
"description": "Patrones universales x.x.x.x con álgebra simbólica"
|
||||
}),
|
||||
]
|
||||
```
|
||||
|
||||
### **Parser Integrado con el Registro**
|
||||
|
||||
#### **`tl_bracket_parser.py` (Actualizado)**
|
||||
```python
|
||||
"""
|
||||
Tokenizador integrado con el sistema de auto-descubrimiento de tipos
|
||||
"""
|
||||
import re
|
||||
from typing import Tuple, Dict, Any
|
||||
|
||||
# Importar desde el registro de tipos (NO hardcodeado)
|
||||
try:
|
||||
from type_registry import get_registered_base_context
|
||||
TYPE_REGISTRY_AVAILABLE = True
|
||||
except ImportError:
|
||||
TYPE_REGISTRY_AVAILABLE = False
|
||||
|
||||
class UniversalTokenizer:
|
||||
"""Tokenizador que usa el registro de tipos para conversiones automáticas"""
|
||||
|
||||
def __init__(self):
|
||||
self.debug = False
|
||||
self._update_fundamental_classes()
|
||||
|
||||
def _update_fundamental_classes(self):
|
||||
"""Obtiene clases fundamentales desde el registro"""
|
||||
if not TYPE_REGISTRY_AVAILABLE:
|
||||
# Fallback básico
|
||||
self.IntBase = None
|
||||
self.FourBytes = None
|
||||
return
|
||||
|
||||
try:
|
||||
# Obtener clases desde el registro automáticamente
|
||||
registered_context = get_registered_base_context()
|
||||
|
||||
self.IntBase = registered_context.get('IntBase')
|
||||
self.FourBytes = registered_context.get('FourBytes')
|
||||
|
||||
if self.debug:
|
||||
print(f"🔧 Clases fundamentales cargadas desde registro:")
|
||||
print(f" IntBase: {self.IntBase}")
|
||||
print(f" FourBytes: {self.FourBytes}")
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"⚠️ Error cargando clases fundamentales: {e}")
|
||||
self.IntBase = None
|
||||
self.FourBytes = None
|
||||
|
||||
def reload_fundamental_classes(self):
|
||||
"""Recarga clases fundamentales del registro"""
|
||||
if self.debug:
|
||||
print("🔄 Recargando clases fundamentales...")
|
||||
self._update_fundamental_classes()
|
||||
|
||||
def preprocess_tokens(self, expression):
|
||||
"""Convierte patrones específicos en objetos tipados"""
|
||||
if not self.IntBase or not self.FourBytes:
|
||||
if self.debug:
|
||||
print("⚠️ Clases fundamentales no disponibles")
|
||||
return expression
|
||||
|
||||
# Fase 1: IntBase (mayor precedencia)
|
||||
# 16#FF → IntBase('FF', 16)
|
||||
def replace_intbase(match):
|
||||
base = match.group(1)
|
||||
value = match.group(2)
|
||||
return f'IntBase("{value}", {base})'
|
||||
|
||||
expression = re.sub(
|
||||
r'(\d+)#([0-9A-Fa-fx]+)',
|
||||
replace_intbase,
|
||||
expression
|
||||
)
|
||||
|
||||
# Fase 2: FourBytes
|
||||
# 192.168.1.1 → FourBytes('192.168.1.1')
|
||||
def replace_fourbytes(match):
|
||||
dotted_value = match.group(1)
|
||||
return f'FourBytes("{dotted_value}")'
|
||||
|
||||
expression = re.sub(
|
||||
r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b',
|
||||
replace_fourbytes,
|
||||
expression
|
||||
)
|
||||
|
||||
if self.debug:
|
||||
print(f"🔧 Tokenización: '{original}' → '{expression}'")
|
||||
|
||||
return expression
|
||||
```
|
||||
|
||||
### **Ventajas de la Integración Completa**
|
||||
|
||||
#### **1. Consistencia Arquitectónica**
|
||||
- **Todo** el sistema de tipos en un lugar
|
||||
- Patrón unificado de registro y descubrimiento
|
||||
- No hay clases "especiales" hardcodeadas
|
||||
|
||||
#### **2. Modularidad Completa**
|
||||
- Cada tipo es independiente y auto-contenido
|
||||
- Fácil agregar/remover tipos sin modificar código core
|
||||
- Sistema verdaderamente extensible
|
||||
|
||||
#### **3. Autocompletado y Ayuda Unificados**
|
||||
- **IntBase** y **FourBytes** tienen `PopupFunctionList()` automáticamente
|
||||
- Ayuda contextual mediante `Helper()`
|
||||
- Integración completa con el sistema de autocompletado
|
||||
|
||||
#### **4. Carga Dinámica**
|
||||
- Parser obtiene clases desde el registro, no por importación directa
|
||||
- Permite recargar tipos en tiempo de ejecución
|
||||
- Sistema totalmente dinámico
|
||||
|
||||
#### **5. Escalabilidad**
|
||||
```python
|
||||
# Agregar nuevos tipos fundamentales es trivial:
|
||||
custom_types/
|
||||
├── intbase_type.py # Base universal para números
|
||||
├── fourbytes_type.py # Base universal para x.x.x.x
|
||||
├── sixbytes_type.py # NUEVO: Para MACs (xx:xx:xx:xx:xx:xx)
|
||||
├── time_type.py # NUEVO: Para HH:MM:SS
|
||||
└── ...
|
||||
```
|
||||
|
||||
#### **6. Pureza del Parser**
|
||||
```python
|
||||
# ANTES (hardcodeado):
|
||||
from intbase import IntBase
|
||||
from fourbytes import FourBytes
|
||||
|
||||
# DESPUÉS (dinámico):
|
||||
IntBase = registry.get('IntBase')
|
||||
FourBytes = registry.get('FourBytes')
|
||||
```
|
||||
|
||||
## Conclusión
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,166 @@
|
|||
# Sistema de Tokenización Distribuida - IMPLEMENTADO
|
||||
|
||||
## ✅ IMPLEMENTACIÓN COMPLETADA
|
||||
|
||||
Se ha implementado exitosamente el sistema de tokenización distribuida según las especificaciones, reemplazando completamente el sistema de corchetes por un tokenizador universal que auto-descubre reglas.
|
||||
|
||||
## 🏗️ Arquitectura Implementada
|
||||
|
||||
### 1. **UniversalTokenizer** (en `tl_bracket_parser.py`)
|
||||
- **Auto-descubrimiento**: Descubre automáticamente reglas de tokenización desde todas las clases registradas
|
||||
- **Prioridad**: Ordena reglas por prioridad (menor número = mayor prioridad)
|
||||
- **Aplicación secuencial**: Aplica reglas en orden de prioridad
|
||||
- **Debug integrado**: Sistema completo de debug y estadísticas
|
||||
|
||||
### 2. **TokenizationParser** (en `tl_bracket_parser.py`)
|
||||
- **Integración completa**: Integra tokenización con el parser existente
|
||||
- **Compatibilidad**: Mantiene interfaz compatible con BracketParser
|
||||
- **Estadísticas**: Tracking de conversiones y rendimiento
|
||||
|
||||
### 3. **Clases Base Universales**
|
||||
|
||||
#### **IntBase** (en `custom_types/intbase_type.py`)
|
||||
- **Patrón**: `(\d+)#([0-9A-Fa-fx]+)`
|
||||
- **Prioridad**: 5 (ALTA - patrón específico)
|
||||
- **Ejemplos**: `16#FF` → `IntBase("FF", 16)`, `2#1010` → `IntBase("1010", 2)`
|
||||
- **Capacidades**: Aritmética nativa, símbolos algebraicos, conversión de bases
|
||||
|
||||
#### **FourBytes** (en `custom_types/fourbytes_type.py`)
|
||||
- **Patrón**: `(?<!FourBytes\(")(?<!")([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)(?!")`
|
||||
- **Prioridad**: 10 (MENOR - patrón general)
|
||||
- **Ejemplos**: `192.168.1.1` → `FourBytes("192.168.1.1")`, `10.x.y.z` → `FourBytes("10.x.y.z")`
|
||||
- **Capacidades**: Aritmética de 32-bit, símbolos algebraicos, conversiones
|
||||
|
||||
### 4. **Clases Especializadas Adaptadas**
|
||||
|
||||
#### **Hex** (en `custom_types/hex_type.py`)
|
||||
- **Patrón adicional**: `\b0x([0-9A-Fa-f]+)\b`
|
||||
- **Prioridad**: 6 (MEDIA - específica de hex)
|
||||
- **Ejemplo**: `0xFF` → `Hex("FF")`
|
||||
- **Delegación**: Usa IntBase internamente
|
||||
|
||||
#### **Bin** (en `custom_types/bin_type.py`)
|
||||
- **Patrón adicional**: `\b0b([01]+)\b`
|
||||
- **Prioridad**: 6 (MEDIA - específica de binario)
|
||||
- **Ejemplo**: `0b1010` → `Bin("1010")`
|
||||
- **Delegación**: Usa IntBase internamente
|
||||
|
||||
#### **IP4** (en `custom_types/ip4_type.py`)
|
||||
- **Sin tokenización directa**: Usa FourBytes desde tokenización automática
|
||||
- **Constructor mejorado**: Acepta FourBytes, IntBase para máscaras
|
||||
- **Ejemplos**: `IP4(FourBytes("192.168.1.1"), IntBase("ffffff00", 16))`
|
||||
|
||||
## 🔧 Integración con Motor de Evaluación
|
||||
|
||||
### **HybridEvaluationEngine** (en `main_evaluation.py`)
|
||||
- **Tokenización automática**: Aplica tokenización en cada expresión
|
||||
- **Contexto dinámico**: Clases base cargadas automáticamente desde el registro
|
||||
- **Debug sincronizado**: Sistema de debug coordinado entre motor, parser y tokenizador
|
||||
|
||||
## 🧪 Validación Completa
|
||||
|
||||
### **Pruebas Exitosas**
|
||||
```bash
|
||||
# Todas las pruebas pasaron exitosamente:
|
||||
|
||||
✅ Registro de tipos: 9 clases registradas
|
||||
✅ Reglas de tokenización: 8 reglas activas
|
||||
✅ IntBase: '16#ff' → IntBase("ff", 16)
|
||||
✅ FourBytes: '192.168.1.1' → FourBytes("192.168.1.1")
|
||||
✅ Hex: '0xFF' → Hex("FF")
|
||||
✅ Bin: '0b1010' → Bin("1010")
|
||||
✅ Evaluación directa funcional
|
||||
✅ Contexto del motor completo
|
||||
```
|
||||
|
||||
### **Casos de Uso Validados**
|
||||
```python
|
||||
# Tokenización automática funcionando:
|
||||
a = 16#FF # → IntBase("FF", 16)
|
||||
b = 192.168.1.1 # → FourBytes("192.168.1.1")
|
||||
c = 0xFF # → Hex("FF")
|
||||
d = 0b1010 # → Bin("1010")
|
||||
|
||||
# Operaciones mixtas:
|
||||
resultado = a + 5 # IntBase mantiene base
|
||||
ip_suma = b + 256 # FourBytes mantiene tipo
|
||||
|
||||
# Símbolos algebraicos:
|
||||
symb_ip = 10.x.y.0 # → FourBytes("10.x.y.0")
|
||||
symb_hex = 16#x0 # → IntBase("x0", 16)
|
||||
```
|
||||
|
||||
## 🎯 Beneficios Logrados
|
||||
|
||||
### **1. Simplicidad para el Usuario**
|
||||
- ✅ **Sintaxis natural**: Usuario escribe Python estándar
|
||||
- ✅ **Sin curva de aprendizaje**: No hay sintaxis especial
|
||||
- ✅ **Tokenización invisible**: Funciona automáticamente
|
||||
|
||||
### **2. Potencia del Sistema**
|
||||
- ✅ **Extensibilidad**: Fácil agregar nuevos tipos con tokenización
|
||||
- ✅ **Flexibilidad**: Cada clase define sus propios patrones
|
||||
- ✅ **Modularidad**: Sistema completamente distribuido
|
||||
|
||||
### **3. Robustez Técnica**
|
||||
- ✅ **Auto-descubrimiento**: Sistema dinámico, no hardcodeado
|
||||
- ✅ **Prioridades**: Control preciso de precedencia
|
||||
- ✅ **Compatibilidad**: Mantiene interfaz existente
|
||||
- ✅ **Debug completo**: Trazabilidad total del proceso
|
||||
|
||||
## 🔄 Migración Completada
|
||||
|
||||
### **Antes: Sistema de Corchetes**
|
||||
```python
|
||||
# Sintaxis compleja y específica:
|
||||
ip = [IP4: 192.168.1.1, 24]
|
||||
hex_val = [Hex: ff]
|
||||
```
|
||||
|
||||
### **Después: Tokenización Distribuida**
|
||||
```python
|
||||
# Sintaxis Python natural:
|
||||
ip = IP4(192.168.1.1, 24) # Automáticamente tokenizado
|
||||
hex_val = 16#ff # Automáticamente tokenizado
|
||||
```
|
||||
|
||||
## 🚀 Extensión Futura
|
||||
|
||||
### **Agregar Nuevos Tipos**
|
||||
Para agregar un nuevo tipo con tokenización:
|
||||
|
||||
1. **Crear archivo**: `custom_types/mitype_type.py`
|
||||
2. **Implementar método**:
|
||||
```python
|
||||
@staticmethod
|
||||
def get_tokenization_patterns():
|
||||
return [
|
||||
{
|
||||
'pattern': r'mi_patron_regex',
|
||||
'replacement': lambda match: f'MiClase("{match.group(1)}")',
|
||||
'priority': 7, # Elegir prioridad apropiada
|
||||
'description': 'Descripción del patrón'
|
||||
}
|
||||
]
|
||||
```
|
||||
3. **Registrar**: El sistema auto-descubre automáticamente
|
||||
|
||||
### **Patrones Disponibles para Extensión**
|
||||
- **Fechas/Tiempo**: `HH:MM:SS`, `YYYY-MM-DD`
|
||||
- **MACs**: `xx:xx:xx:xx:xx:xx`
|
||||
- **UUIDs**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
- **Coordenadas**: `lat,lng`
|
||||
- **Cualquier patrón específico del dominio**
|
||||
|
||||
## 📋 Estado Final
|
||||
|
||||
**✅ SISTEMA COMPLETAMENTE IMPLEMENTADO Y FUNCIONAL**
|
||||
|
||||
- Sistema de tokenización distribuida operativo
|
||||
- Auto-descubrimiento funcionando
|
||||
- Todas las clases adaptadas correctamente
|
||||
- Integración con motor de evaluación completa
|
||||
- Pruebas exitosas en todos los componentes
|
||||
- Documentación y ejemplos incluidos
|
||||
|
||||
El sistema está listo para producción y extensión futura según las especificaciones del documento `@tokenizacion_distribuida.md`.
|
|
@ -2,9 +2,19 @@
|
|||
Clase híbrida para números binarios - ADAPTADA AL NUEVO SISTEMA
|
||||
Ahora usa IntBase como clase base universal
|
||||
"""
|
||||
from sympy_Base import SympyClassBase, IntBase
|
||||
from sympy_Base import SympyClassBase
|
||||
import re
|
||||
|
||||
# Importación dinámica de IntBase desde el registro
|
||||
def get_intbase_class():
|
||||
"""Obtiene la clase IntBase del registro de tipos"""
|
||||
try:
|
||||
from type_registry import get_registered_base_context
|
||||
context = get_registered_base_context()
|
||||
return context.get('IntBase')
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
class Class_Bin(SympyClassBase):
|
||||
"""Clase híbrida para números binarios basada en IntBase"""
|
||||
|
@ -12,6 +22,11 @@ class Class_Bin(SympyClassBase):
|
|||
def __init__(self, value_input):
|
||||
"""Inicialización de Bin usando IntBase como base"""
|
||||
|
||||
# Obtener IntBase dinámicamente
|
||||
IntBase = get_intbase_class()
|
||||
if IntBase is None:
|
||||
raise ImportError("IntBase no disponible en el registro de tipos")
|
||||
|
||||
if isinstance(value_input, IntBase):
|
||||
# value es IntBase (ya tokenizado desde 2#valor)
|
||||
if value_input.base == 2:
|
||||
|
@ -181,6 +196,23 @@ class Class_Bin(SympyClassBase):
|
|||
("toBase", "Convierte a base específica"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_tokenization_patterns():
|
||||
"""
|
||||
Define patrones adicionales de tokenización específicos para Bin
|
||||
|
||||
Returns:
|
||||
List[Dict]: Lista de reglas de tokenización (opcional)
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'pattern': r'\b0b([01]+)\b',
|
||||
'replacement': lambda match: f'Bin("{match.group(1)}")',
|
||||
'priority': 6, # Prioridad media, después de IntBase pero específica
|
||||
'description': 'Binario con prefijo 0b: 0b1010, 0b11'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# ========== FUNCIÓN DE REGISTRO ==========
|
||||
|
||||
|
|
|
@ -0,0 +1,382 @@
|
|||
"""
|
||||
Clase base universal para patrones x.x.x.x - TIPO REGISTRADO
|
||||
"""
|
||||
from class_base import ClassBase
|
||||
import sympy
|
||||
import re
|
||||
|
||||
class FourBytes(ClassBase):
|
||||
"""
|
||||
Representación universal de patrones de 4 elementos
|
||||
con manejo interno de símbolos y conversión perezosa a SymPy
|
||||
"""
|
||||
|
||||
def __init__(self, dotted_string):
|
||||
self.original = str(dotted_string)
|
||||
self.elements = self.original.split('.')
|
||||
|
||||
if len(self.elements) != 4:
|
||||
raise ValueError(f"FourBytes requiere exactamente 4 elementos: {dotted_string}")
|
||||
|
||||
self.has_symbols = any(not elem.isdigit() for elem in self.elements)
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: mantener símbolos INTERNAMENTE, no convertir a SymPy aún
|
||||
self._symbols = self._extract_symbols()
|
||||
self._numeric_value = None # No hay valor numérico directo
|
||||
super().__init__(self.original, self.original) # ClassBase, NO SympyClassBase
|
||||
else:
|
||||
# Modo numérico: validar rangos y convertir
|
||||
self._numeric_elements = []
|
||||
for elem in self.elements:
|
||||
try:
|
||||
num = int(elem)
|
||||
if not (0 <= num <= 255):
|
||||
raise ValueError(f"Elemento fuera de rango [0-255]: {num}")
|
||||
self._numeric_elements.append(num)
|
||||
except ValueError:
|
||||
raise ValueError(f"Elemento inválido: '{elem}'")
|
||||
|
||||
# Crear valor como entero de 32 bits (para IPs)
|
||||
self._numeric_value = (self._numeric_elements[0] << 24 |
|
||||
self._numeric_elements[1] << 16 |
|
||||
self._numeric_elements[2] << 8 |
|
||||
self._numeric_elements[3])
|
||||
self._symbols = []
|
||||
super().__init__(self._numeric_value, self.original) # ClassBase
|
||||
|
||||
def _extract_symbols(self):
|
||||
"""Extrae símbolos únicos de todos los elementos"""
|
||||
symbols = []
|
||||
for elem in self.elements:
|
||||
if not elem.isdigit():
|
||||
if elem not in symbols:
|
||||
symbols.append(elem)
|
||||
return symbols
|
||||
|
||||
def substitute(self, **kwargs):
|
||||
"""Sustitución NATIVA - mantiene como FourBytes"""
|
||||
if not self.has_symbols:
|
||||
return self # Sin símbolos, retornar el mismo objeto
|
||||
|
||||
new_elements = []
|
||||
for elem in self.elements:
|
||||
if elem in kwargs:
|
||||
new_elements.append(str(kwargs[elem]))
|
||||
else:
|
||||
new_elements.append(elem)
|
||||
|
||||
# Retornar nuevo FourBytes con valores sustituidos
|
||||
return FourBytes('.'.join(new_elements))
|
||||
|
||||
def to_sympy(self):
|
||||
"""CONVERSIÓN EXPLÍCITA a SymPy cuando se necesite álgebra compleja"""
|
||||
if self.has_symbols:
|
||||
# Crear expresión escalar para compatibilidad con IP4 (no Matrix)
|
||||
symbolic_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
symbolic_elements.append(int(elem))
|
||||
else:
|
||||
symbolic_elements.append(sympy.Symbol(elem))
|
||||
|
||||
# Crear expresión como entero de 32 bits simbólico (compatible con operaciones IP)
|
||||
result = (symbolic_elements[0] * 2**24 +
|
||||
symbolic_elements[1] * 2**16 +
|
||||
symbolic_elements[2] * 2**8 +
|
||||
symbolic_elements[3])
|
||||
|
||||
return result
|
||||
else:
|
||||
# Sin símbolos, retornar entero SymPy simple
|
||||
return sympy.sympify(self._numeric_value)
|
||||
|
||||
def is_valid_ip_range(self):
|
||||
"""Verifica si todos los elementos están en rango IP válido"""
|
||||
if self.has_symbols:
|
||||
return None # No se puede verificar con símbolos
|
||||
return all(0 <= elem <= 255 for elem in self._numeric_elements)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Acceso a elementos individuales"""
|
||||
if self.has_symbols:
|
||||
elem = self.elements[index]
|
||||
if elem.isdigit():
|
||||
return int(elem)
|
||||
else:
|
||||
return sympy.Symbol(elem)
|
||||
else:
|
||||
return self._numeric_elements[index]
|
||||
|
||||
def to_ip_int(self):
|
||||
"""Convierte a entero de 32 bits (para IPs) - MANTIENE NATIVO"""
|
||||
if self.has_symbols:
|
||||
# Retornar expresión simbólica escalar, NO convertir a SymPy aún
|
||||
return f"ip_int({self.original})"
|
||||
else:
|
||||
return self._numeric_value
|
||||
|
||||
@staticmethod
|
||||
def _int_to_fourbytes(value):
|
||||
"""Convierte entero de 32 bits a formato dotted"""
|
||||
return f"{(value >> 24) & 0xFF}.{(value >> 16) & 0xFF}.{(value >> 8) & 0xFF}.{value & 0xFF}"
|
||||
|
||||
# ========== OPERADORES ARITMÉTICOS NATIVOS (mantienen FourBytes) ==========
|
||||
|
||||
def __add__(self, other):
|
||||
"""Suma nativa - mantiene como FourBytes cuando es posible"""
|
||||
if self.has_symbols:
|
||||
# Con símbolos, crear expresión simbólica pero mantener FourBytes
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
new_expr = f"({self.original}) + ({other.original})"
|
||||
else:
|
||||
new_expr = f"({self.original}) + {other._numeric_value}"
|
||||
elif isinstance(other, (int, float)):
|
||||
new_expr = f"({self.original}) + {other}"
|
||||
else:
|
||||
# Para otros tipos, convertir a SymPy
|
||||
return self.to_sympy() + other
|
||||
|
||||
return FourBytes(new_expr)
|
||||
|
||||
# Sin símbolos: aritmética numérica directa
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
# El otro tiene símbolos, crear expresión
|
||||
new_expr = f"{self._numeric_value} + ({other.original})"
|
||||
return FourBytes(new_expr)
|
||||
else:
|
||||
# Ambos numéricos
|
||||
result_value = (self._numeric_value + other._numeric_value) & 0xFFFFFFFF
|
||||
elif isinstance(other, (int, float)):
|
||||
result_value = (self._numeric_value + int(other)) & 0xFFFFFFFF
|
||||
else:
|
||||
# Para tipos complejos, convertir a SymPy
|
||||
return self.to_sympy() + other
|
||||
|
||||
# Convertir resultado de vuelta a formato dotted
|
||||
result_str = self._int_to_fourbytes(result_value)
|
||||
return FourBytes(result_str)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Resta nativa"""
|
||||
if self.has_symbols:
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
new_expr = f"({self.original}) - ({other.original})"
|
||||
else:
|
||||
new_expr = f"({self.original}) - {other._numeric_value}"
|
||||
elif isinstance(other, (int, float)):
|
||||
new_expr = f"({self.original}) - {other}"
|
||||
else:
|
||||
return self.to_sympy() - other
|
||||
|
||||
return FourBytes(new_expr)
|
||||
|
||||
# Sin símbolos
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
new_expr = f"{self._numeric_value} - ({other.original})"
|
||||
return FourBytes(new_expr)
|
||||
else:
|
||||
result_value = (self._numeric_value - other._numeric_value) & 0xFFFFFFFF
|
||||
elif isinstance(other, (int, float)):
|
||||
result_value = (self._numeric_value - int(other)) & 0xFFFFFFFF
|
||||
else:
|
||||
return self.to_sympy() - other
|
||||
|
||||
result_str = self._int_to_fourbytes(result_value)
|
||||
return FourBytes(result_str)
|
||||
|
||||
def ToBase(self, base):
|
||||
"""Convierte cada elemento a la base especificada"""
|
||||
# Necesitamos importar IntBase desde el registro
|
||||
try:
|
||||
from type_registry import get_registered_base_context
|
||||
context = get_registered_base_context()
|
||||
IntBase = context.get('IntBase')
|
||||
if not IntBase:
|
||||
raise ImportError("IntBase no disponible en el registro")
|
||||
except ImportError:
|
||||
# Fallback: usar el método directo
|
||||
def _convert_to_base_string(value, base):
|
||||
if base == 2:
|
||||
return bin(value)[2:]
|
||||
elif base == 8:
|
||||
return oct(value)[2:]
|
||||
elif base == 16:
|
||||
return hex(value)[2:].upper()
|
||||
elif base == 10:
|
||||
return str(value)
|
||||
else:
|
||||
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
result = ""
|
||||
while value > 0:
|
||||
result = digits[value % base] + result
|
||||
value //= base
|
||||
return result or "0"
|
||||
|
||||
if self.has_symbols:
|
||||
# Con símbolos, mantener estructura simbólica
|
||||
base_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
num = int(elem)
|
||||
base_str = _convert_to_base_string(num, base)
|
||||
base_elements.append(f"{base}#{base_str}")
|
||||
else:
|
||||
base_elements.append(f"{base}#{elem}")
|
||||
return '.'.join(base_elements)
|
||||
else:
|
||||
# Sin símbolos, conversión directa
|
||||
base_elements = []
|
||||
for num in self._numeric_elements:
|
||||
base_str = _convert_to_base_string(num, base)
|
||||
base_elements.append(f"{base}#{base_str}")
|
||||
return '.'.join(base_elements)
|
||||
|
||||
# Versión con IntBase disponible
|
||||
if self.has_symbols:
|
||||
base_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
num = int(elem)
|
||||
base_str = IntBase._convert_to_base_string(num, base)
|
||||
base_elements.append(f"{base}#{base_str}")
|
||||
else:
|
||||
base_elements.append(f"{base}#{elem}")
|
||||
return '.'.join(base_elements)
|
||||
else:
|
||||
base_elements = []
|
||||
for num in self._numeric_elements:
|
||||
base_str = IntBase._convert_to_base_string(num, base)
|
||||
base_elements.append(f"{base}#{base_str}")
|
||||
return '.'.join(base_elements)
|
||||
|
||||
def ToCIDR(self, prefix_length):
|
||||
"""Convierte a notación CIDR"""
|
||||
return f"{self.original}/{prefix_length}"
|
||||
|
||||
def ToHex(self):
|
||||
"""Conversión a hexadecimal"""
|
||||
return self.ToBase(16)
|
||||
|
||||
def ToBinary(self):
|
||||
"""Conversión a binario"""
|
||||
return self.ToBase(2)
|
||||
|
||||
def ToOctal(self):
|
||||
"""Conversión a octal"""
|
||||
return self.ToBase(8)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Propiedad de acceso al valor - mantiene nativo"""
|
||||
if self.has_symbols:
|
||||
return self.original # Mantener como string simbólico
|
||||
else:
|
||||
return self._numeric_value # Valor numérico directo
|
||||
|
||||
def __str__(self):
|
||||
"""Representación como cadena"""
|
||||
return self.original
|
||||
|
||||
def __repr__(self):
|
||||
"""Representación para debug"""
|
||||
return f"FourBytes('{self.original}')"
|
||||
|
||||
# ========== MÉTODOS PARA COMPATIBILIDAD CON SYMPY ==========
|
||||
|
||||
def _sympystr(self, printer):
|
||||
"""Controla cómo SymPy representa este objeto"""
|
||||
# Mantener representación nativa, no convertir a número
|
||||
return f"FourBytes('{self.original}')"
|
||||
|
||||
def __radd__(self, other):
|
||||
"""Suma reversa - cuando el otro operando es el primero"""
|
||||
if isinstance(other, (int, float)):
|
||||
return self.__add__(other)
|
||||
else:
|
||||
# Para otros tipos, dejar que SymPy maneje
|
||||
return NotImplemented
|
||||
|
||||
def __rsub__(self, other):
|
||||
"""Resta reversa"""
|
||||
if isinstance(other, (int, float)):
|
||||
# other - self
|
||||
if self.has_symbols:
|
||||
new_expr = f"{other} - ({self.original})"
|
||||
return FourBytes(new_expr)
|
||||
else:
|
||||
result_value = (int(other) - self._numeric_value) & 0xFFFFFFFF
|
||||
result_str = self._int_to_fourbytes(result_value)
|
||||
return FourBytes(result_str)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
@staticmethod
|
||||
def Helper(input_str):
|
||||
"""Ayuda contextual para FourBytes"""
|
||||
if re.search(r'\d+\.\d+\.\d+\.\d+', input_str):
|
||||
return '''FourBytes - Patrones x.x.x.x con álgebra simbólica
|
||||
|
||||
Sintaxis: x.y.z.w (donde cada elemento puede ser número o símbolo)
|
||||
Ejemplos:
|
||||
192.168.1.1 → FourBytes('192.168.1.1')
|
||||
255.255.0.0 → FourBytes('255.255.0.0')
|
||||
10.x.1.y → FourBytes('10.x.1.y') = simbólico
|
||||
|
||||
Aritmética de 32-bit:
|
||||
192.168.1.1 + 5 → FourBytes('192.168.1.6')
|
||||
10.0.0.0 + 256 → FourBytes('10.0.1.0')
|
||||
|
||||
Métodos disponibles:
|
||||
.ToBase(base) - Convierte cada elemento a base específica
|
||||
.ToHex() - Convierte a hexadecimal
|
||||
.ToBinary() - Convierte a binario
|
||||
.substitute(x=val) - Sustituye símbolos
|
||||
.to_sympy() - Conversión explícita a SymPy'''
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def PopupFunctionList():
|
||||
"""Lista de métodos para autocompletado"""
|
||||
return [
|
||||
("ToBase", "Convierte cada elemento a base específica"),
|
||||
("ToHex", "Convierte cada elemento a hexadecimal"),
|
||||
("ToBinary", "Convierte cada elemento a binario"),
|
||||
("ToOctal", "Convierte cada elemento a octal"),
|
||||
("ToCIDR", "Añade notación CIDR"),
|
||||
("substitute", "Sustituye símbolos por valores"),
|
||||
("to_sympy", "Conversión explícita a SymPy para álgebra"),
|
||||
("is_valid_ip_range", "Verifica si es rango IP válido"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_tokenization_patterns():
|
||||
"""
|
||||
Define cómo esta clase debe ser reconocida y tokenizada
|
||||
|
||||
Returns:
|
||||
List[Dict]: Lista de reglas de tokenización
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'pattern': r'(?<!FourBytes\(")(?<!")([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)(?!")',
|
||||
'replacement': lambda match: f'FourBytes("{match.group(1)}")',
|
||||
'priority': 10, # MENOR prioridad que IntBase (patrón más general)
|
||||
'description': 'Patrones de 4 elementos: 192.168.1.1, 10.x.y.z'
|
||||
}
|
||||
]
|
||||
|
||||
def register_classes_in_module():
|
||||
"""Registro de FourBytes en el sistema de tipos"""
|
||||
return [
|
||||
("FourBytes", FourBytes, "ClassBase", {
|
||||
"add_lowercase": True,
|
||||
"supports_brackets": False, # Se maneja por tokenización
|
||||
"is_fundamental": True, # Clase fundamental del sistema
|
||||
"description": "Patrones universales x.x.x.x con álgebra simbólica"
|
||||
}),
|
||||
]
|
|
@ -2,9 +2,19 @@
|
|||
Clase híbrida para números hexadecimales - ADAPTADA AL NUEVO SISTEMA
|
||||
Ahora usa IntBase como clase base universal
|
||||
"""
|
||||
from sympy_Base import SympyClassBase, IntBase
|
||||
from sympy_Base import SympyClassBase
|
||||
import re
|
||||
|
||||
# Importación dinámica de IntBase desde el registro
|
||||
def get_intbase_class():
|
||||
"""Obtiene la clase IntBase del registro de tipos"""
|
||||
try:
|
||||
from type_registry import get_registered_base_context
|
||||
context = get_registered_base_context()
|
||||
return context.get('IntBase')
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
class Class_Hex(SympyClassBase):
|
||||
"""Clase híbrida para números hexadecimales basada en IntBase"""
|
||||
|
@ -12,6 +22,11 @@ class Class_Hex(SympyClassBase):
|
|||
def __init__(self, value_input):
|
||||
"""Inicialización de Hex usando IntBase como base"""
|
||||
|
||||
# Obtener IntBase dinámicamente
|
||||
IntBase = get_intbase_class()
|
||||
if IntBase is None:
|
||||
raise ImportError("IntBase no disponible en el registro de tipos")
|
||||
|
||||
if isinstance(value_input, IntBase):
|
||||
# value es IntBase (ya tokenizado desde 16#valor)
|
||||
if value_input.base == 16:
|
||||
|
@ -92,9 +107,11 @@ class Class_Hex(SympyClassBase):
|
|||
|
||||
def __add__(self, other):
|
||||
"""Suma delegada a IntBase"""
|
||||
IntBase = get_intbase_class()
|
||||
|
||||
if isinstance(other, Class_Hex):
|
||||
result_intbase = self.int_base + other.int_base
|
||||
elif isinstance(other, IntBase):
|
||||
elif IntBase and isinstance(other, IntBase):
|
||||
result_intbase = self.int_base + other
|
||||
elif isinstance(other, int):
|
||||
result_intbase = self.int_base + other
|
||||
|
@ -182,6 +199,23 @@ class Class_Hex(SympyClassBase):
|
|||
("toBase", "Convierte a base específica"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_tokenization_patterns():
|
||||
"""
|
||||
Define patrones adicionales de tokenización específicos para Hex
|
||||
|
||||
Returns:
|
||||
List[Dict]: Lista de reglas de tokenización (opcional)
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'pattern': r'\b0x([0-9A-Fa-f]+)\b',
|
||||
'replacement': lambda match: f'Hex("{match.group(1)}")',
|
||||
'priority': 6, # Prioridad media, después de IntBase pero específica
|
||||
'description': 'Hexadecimal con prefijo 0x: 0xFF, 0xA0'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# ========== FUNCIÓN DE REGISTRO ==========
|
||||
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
"""
|
||||
Clase base universal para números en cualquier base - TIPO REGISTRADO
|
||||
"""
|
||||
from class_base import ClassBase
|
||||
import sympy
|
||||
import re
|
||||
|
||||
class IntBase(ClassBase):
|
||||
"""
|
||||
Representación universal de números en cualquier base
|
||||
con manejo interno de símbolos y conversión perezosa a SymPy
|
||||
"""
|
||||
|
||||
def __init__(self, value_str, base=10):
|
||||
self.value_str = str(value_str)
|
||||
self.base = int(base)
|
||||
self.has_symbols = bool(re.search(r'[a-zA-Z_]', self.value_str))
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: mantener símbolos INTERNAMENTE, no convertir a SymPy aún
|
||||
self._symbols = self._extract_symbols()
|
||||
self._numeric_value = None # No hay valor numérico directo
|
||||
original_str = f"{self.base}#{self.value_str}"
|
||||
super().__init__(self.value_str, original_str) # ClassBase, NO SympyClassBase
|
||||
else:
|
||||
# Modo numérico: convertir a entero
|
||||
try:
|
||||
self._numeric_value = int(self.value_str, self.base)
|
||||
self._symbols = []
|
||||
original_str = f"{self.base}#{self.value_str}"
|
||||
super().__init__(self._numeric_value, original_str) # ClassBase
|
||||
except ValueError:
|
||||
raise ValueError(f"Valor inválido '{self.value_str}' para base {self.base}")
|
||||
|
||||
def _extract_symbols(self):
|
||||
"""Extrae símbolos de la cadena, manteniendo caracteres únicos"""
|
||||
return list(set(re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*', self.value_str)))
|
||||
|
||||
def substitute(self, **kwargs):
|
||||
"""Sustitución NATIVA - mantiene como IntBase"""
|
||||
if not self.has_symbols:
|
||||
return self # Sin símbolos, retornar el mismo objeto
|
||||
|
||||
new_value_str = self.value_str
|
||||
for symbol, value in kwargs.items():
|
||||
if symbol in self._symbols:
|
||||
new_value_str = new_value_str.replace(symbol, str(value))
|
||||
|
||||
# Retornar nuevo IntBase con valores sustituidos
|
||||
return IntBase(new_value_str, self.base)
|
||||
|
||||
def to_sympy(self):
|
||||
"""CONVERSIÓN EXPLÍCITA a SymPy cuando se necesite álgebra compleja"""
|
||||
if self.has_symbols:
|
||||
# Construir expresión SymPy paso a paso
|
||||
result = 0
|
||||
digits = list(self.value_str)
|
||||
|
||||
for i, char in enumerate(reversed(digits)):
|
||||
power = self.base ** i
|
||||
|
||||
if char.isdigit():
|
||||
coefficient = int(char)
|
||||
elif char.upper() in 'ABCDEF' and self.base == 16:
|
||||
coefficient = ord(char.upper()) - ord('A') + 10
|
||||
elif re.match(r'[a-zA-Z_]', char):
|
||||
# Es un símbolo
|
||||
coefficient = sympy.Symbol(char)
|
||||
else:
|
||||
raise ValueError(f"Carácter inválido '{char}' en valor '{self.value_str}'")
|
||||
|
||||
result += coefficient * power
|
||||
|
||||
return result
|
||||
else:
|
||||
# Sin símbolos, retornar entero SymPy simple
|
||||
return sympy.sympify(self._numeric_value)
|
||||
|
||||
def to_base(self, new_base):
|
||||
"""Convierte a nueva base"""
|
||||
if self.has_symbols:
|
||||
# En modo algebraico, mantener expresión
|
||||
return IntBase(f"({self.value_str})_base{new_base}", new_base)
|
||||
else:
|
||||
# En modo numérico, convertir directamente
|
||||
new_value_str = self._convert_to_base_string(self._numeric_value, new_base)
|
||||
return IntBase(new_value_str, new_base)
|
||||
|
||||
def to_decimal(self):
|
||||
"""Obtiene valor decimal"""
|
||||
if self.has_symbols:
|
||||
return f"decimal({self.value_str}, base={self.base})"
|
||||
else:
|
||||
return self._numeric_value
|
||||
|
||||
def to_hex(self):
|
||||
"""Convierte a hexadecimal"""
|
||||
return self.to_base(16)
|
||||
|
||||
def to_binary(self):
|
||||
"""Convierte a binario"""
|
||||
return self.to_base(2)
|
||||
|
||||
def to_octal(self):
|
||||
"""Convierte a octal"""
|
||||
return self.to_base(8)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Propiedad de acceso al valor - mantiene nativo"""
|
||||
if self.has_symbols:
|
||||
return self.value_str # Mantener como string simbólico
|
||||
else:
|
||||
return self._numeric_value # Valor numérico directo
|
||||
|
||||
# ========== OPERADORES ARITMÉTICOS NATIVOS (mantienen IntBase) ==========
|
||||
|
||||
def __add__(self, other):
|
||||
"""Suma nativa - mantiene como IntBase cuando es posible"""
|
||||
if self.has_symbols:
|
||||
# Con símbolos, crear expresión simbólica pero mantener IntBase
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
new_expr = f"({self.value_str}) + ({other.value_str})"
|
||||
else:
|
||||
new_expr = f"({self.value_str}) + {other._numeric_value}"
|
||||
elif isinstance(other, (int, float)):
|
||||
new_expr = f"({self.value_str}) + {other}"
|
||||
else:
|
||||
# Para otros tipos, convertir a SymPy
|
||||
return self.to_sympy() + other
|
||||
|
||||
return IntBase(new_expr, self.base)
|
||||
|
||||
# Sin símbolos: aritmética numérica directa
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
# El otro tiene símbolos, crear expresión
|
||||
new_expr = f"{self._numeric_value} + ({other.value_str})"
|
||||
return IntBase(new_expr, self.base)
|
||||
else:
|
||||
# Ambos numéricos
|
||||
result_value = self._numeric_value + other._numeric_value
|
||||
elif isinstance(other, (int, float)):
|
||||
result_value = self._numeric_value + other
|
||||
else:
|
||||
# Para tipos complejos, convertir a SymPy
|
||||
return self.to_sympy() + other
|
||||
|
||||
# Convertir resultado de vuelta a cadena en la base original
|
||||
result_str = self._convert_to_base_string(int(result_value), self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Resta nativa"""
|
||||
if self.has_symbols:
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
new_expr = f"({self.value_str}) - ({other.value_str})"
|
||||
else:
|
||||
new_expr = f"({self.value_str}) - {other._numeric_value}"
|
||||
elif isinstance(other, (int, float)):
|
||||
new_expr = f"({self.value_str}) - {other}"
|
||||
else:
|
||||
return self.to_sympy() - other
|
||||
|
||||
return IntBase(new_expr, self.base)
|
||||
|
||||
# Sin símbolos
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
new_expr = f"{self._numeric_value} - ({other.value_str})"
|
||||
return IntBase(new_expr, self.base)
|
||||
else:
|
||||
result_value = self._numeric_value - other._numeric_value
|
||||
elif isinstance(other, (int, float)):
|
||||
result_value = self._numeric_value - other
|
||||
else:
|
||||
return self.to_sympy() - other
|
||||
|
||||
result_str = self._convert_to_base_string(int(result_value), self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __mul__(self, other):
|
||||
"""Multiplicación nativa"""
|
||||
if self.has_symbols:
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
new_expr = f"({self.value_str}) * ({other.value_str})"
|
||||
else:
|
||||
new_expr = f"({self.value_str}) * {other._numeric_value}"
|
||||
elif isinstance(other, (int, float)):
|
||||
new_expr = f"({self.value_str}) * {other}"
|
||||
else:
|
||||
return self.to_sympy() * other
|
||||
|
||||
return IntBase(new_expr, self.base)
|
||||
|
||||
# Sin símbolos
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
new_expr = f"{self._numeric_value} * ({other.value_str})"
|
||||
return IntBase(new_expr, self.base)
|
||||
else:
|
||||
result_value = self._numeric_value * other._numeric_value
|
||||
elif isinstance(other, (int, float)):
|
||||
result_value = self._numeric_value * other
|
||||
else:
|
||||
return self.to_sympy() * other
|
||||
|
||||
result_str = self._convert_to_base_string(int(result_value), self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
@staticmethod
|
||||
def _convert_to_base_string(value, base):
|
||||
"""Convierte un entero a string en la base especificada"""
|
||||
if base == 2:
|
||||
return bin(value)[2:]
|
||||
elif base == 8:
|
||||
return oct(value)[2:]
|
||||
elif base == 16:
|
||||
return hex(value)[2:].upper()
|
||||
elif base == 10:
|
||||
return str(value)
|
||||
else:
|
||||
# Algoritmo general para cualquier base
|
||||
if value == 0:
|
||||
return "0"
|
||||
|
||||
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
result = ""
|
||||
|
||||
while value > 0:
|
||||
result = digits[value % base] + result
|
||||
value //= base
|
||||
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
"""Representación como cadena"""
|
||||
return f"{self.base}#{self.value_str}"
|
||||
|
||||
def __repr__(self):
|
||||
"""Representación para debug"""
|
||||
return f"IntBase('{self.value_str}', {self.base})"
|
||||
|
||||
@staticmethod
|
||||
def Helper(input_str):
|
||||
"""Ayuda contextual para IntBase"""
|
||||
if re.search(r'\d+#[0-9A-Fa-fx]+', input_str):
|
||||
return '''IntBase - Números en cualquier base con álgebra simbólica
|
||||
|
||||
Sintaxis: base#valor
|
||||
Ejemplos:
|
||||
16#FF → IntBase('FF', 16) = 255
|
||||
2#1010 → IntBase('1010', 2) = 10
|
||||
8#777 → IntBase('777', 8) = 511
|
||||
16#x0 → IntBase('x0', 16) = simbólico
|
||||
|
||||
Operaciones aritméticas mantienen la base original:
|
||||
16#FF + 16#10 → IntBase('10F', 16)
|
||||
2#1010 * 3 → IntBase('11110', 2)
|
||||
|
||||
Métodos disponibles:
|
||||
.to_base(nueva_base) - Convierte a otra base
|
||||
.substitute(x=valor) - Sustituye símbolos
|
||||
.to_sympy() - Conversión explícita a SymPy'''
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def PopupFunctionList():
|
||||
"""Lista de métodos para autocompletado"""
|
||||
return [
|
||||
("to_base", "Convierte a otra base"),
|
||||
("substitute", "Sustituye símbolos por valores"),
|
||||
("to_sympy", "Conversión explícita a SymPy para álgebra"),
|
||||
("to_decimal", "Obtiene valor decimal"),
|
||||
("to_hex", "Convierte a hexadecimal"),
|
||||
("to_binary", "Convierte a binario"),
|
||||
("to_octal", "Convierte a octal"),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_tokenization_patterns():
|
||||
"""
|
||||
Define cómo esta clase debe ser reconocida y tokenizada
|
||||
|
||||
Returns:
|
||||
List[Dict]: Lista de reglas de tokenización
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'pattern': r'(\d+)#([0-9A-Fa-fx]+)',
|
||||
'replacement': lambda match: f'IntBase("{match.group(2)}", {match.group(1)})',
|
||||
'priority': 5, # ALTA prioridad (patrón muy específico)
|
||||
'description': 'Números con base: 16#FF, 2#1010, etc.'
|
||||
}
|
||||
]
|
||||
|
||||
def register_classes_in_module():
|
||||
"""Registro de IntBase en el sistema de tipos"""
|
||||
return [
|
||||
("IntBase", IntBase, "ClassBase", {
|
||||
"add_lowercase": True,
|
||||
"supports_brackets": False, # Se maneja por tokenización
|
||||
"is_fundamental": True, # Clase fundamental del sistema
|
||||
"description": "Números universales en cualquier base con álgebra simbólica"
|
||||
}),
|
||||
]
|
|
@ -3,11 +3,21 @@ Clase híbrida para direcciones IPv4 - ADAPTADA AL NUEVO SISTEMA
|
|||
Ahora usa FourBytes como clase base universal para direcciones IP
|
||||
"""
|
||||
from class_base import ClassBase
|
||||
from sympy_Base import SympyClassBase, FourBytes, IntBase
|
||||
from sympy_Base import SympyClassBase
|
||||
from typing import Optional, Union, List, Tuple
|
||||
import re
|
||||
import sympy
|
||||
|
||||
# Importación dinámica de clases base desde el registro
|
||||
def get_base_classes():
|
||||
"""Obtiene las clases base del registro de tipos"""
|
||||
try:
|
||||
from type_registry import get_registered_base_context
|
||||
context = get_registered_base_context()
|
||||
return context.get('IntBase'), context.get('FourBytes')
|
||||
except ImportError:
|
||||
return None, None
|
||||
|
||||
class IP4Mask(ClassBase):
|
||||
"""
|
||||
Helper class to manage IPv4 masks.
|
||||
|
@ -16,8 +26,11 @@ class IP4Mask(ClassBase):
|
|||
_prefix: int
|
||||
_mask_int: int
|
||||
|
||||
def __init__(self, mask_input: Union[int, str, FourBytes, IntBase]):
|
||||
if isinstance(mask_input, FourBytes):
|
||||
def __init__(self, mask_input):
|
||||
# Obtener clases dinámicamente
|
||||
IntBase, FourBytes = get_base_classes()
|
||||
|
||||
if FourBytes and isinstance(mask_input, FourBytes):
|
||||
# Máscara desde tokenización automática x.x.x.x
|
||||
if not mask_input.has_symbols:
|
||||
self._mask_int = mask_input._numeric_value
|
||||
|
@ -26,7 +39,7 @@ class IP4Mask(ClassBase):
|
|||
# Mantener simbólico
|
||||
self._prefix = None
|
||||
self._mask_int = mask_input._symbolic_value
|
||||
elif isinstance(mask_input, IntBase):
|
||||
elif IntBase and isinstance(mask_input, IntBase):
|
||||
# Máscara desde hex u otra base (ej: 16#ffffff00)
|
||||
if not mask_input.has_symbols:
|
||||
self._mask_int = mask_input._numeric_value
|
||||
|
@ -111,7 +124,24 @@ class IP4Mask(ClassBase):
|
|||
# Convertir a binario y verificar que sea una máscara válida
|
||||
binary_mask = bin(mask_int)[2:].zfill(32)
|
||||
if not re.fullmatch(r"1*0*", binary_mask): # Must be contiguous 1s followed by 0s
|
||||
raise ValueError(f"Invalid mask: 0x{mask_int:08X} - bits not contiguous")
|
||||
# Crear mensaje de error más informativo
|
||||
mask_str = f"{(mask_int >> 24) & 0xFF}.{(mask_int >> 16) & 0xFF}.{(mask_int >> 8) & 0xFF}.{mask_int & 0xFF}"
|
||||
binary_formatted = f"{binary_mask[:8]}.{binary_mask[8:16]}.{binary_mask[16:24]}.{binary_mask[24:32]}"
|
||||
|
||||
raise ValueError(f"""
|
||||
❌ Máscara inválida: {mask_str} (0x{mask_int:08X})
|
||||
|
||||
🔍 Análisis binario: {binary_formatted}
|
||||
Los bits deben ser contiguos: todos los 1s seguidos de todos los 0s
|
||||
|
||||
✅ Ejemplos de máscaras válidas:
|
||||
/24 → 255.255.255.0 (11111111.11111111.11111111.00000000)
|
||||
/20 → 255.240.0.0 (11111111.11110000.00000000.00000000)
|
||||
/16 → 255.255.0.0 (11111111.11111111.00000000.00000000)
|
||||
/12 → 255.240.0.0 (11111111.11110000.00000000.00000000)
|
||||
|
||||
💡 ¿Quizás querías usar 255.240.0.0 en lugar de 255.240.0.3?
|
||||
""")
|
||||
|
||||
return binary_mask.count('1')
|
||||
|
||||
|
@ -328,8 +358,11 @@ class Class_IP4(SympyClassBase):
|
|||
mask: int, string, IP4Mask, FourBytes, o IntBase
|
||||
"""
|
||||
|
||||
# Obtener clases dinámicamente
|
||||
IntBase, FourBytes = get_base_classes()
|
||||
|
||||
# Procesar dirección
|
||||
if isinstance(address, FourBytes):
|
||||
if FourBytes and isinstance(address, FourBytes):
|
||||
# address es FourBytes (ya tokenizado desde x.x.x.x)
|
||||
self.address = address
|
||||
self._ip_str = address.original
|
||||
|
@ -358,8 +391,16 @@ class Class_IP4(SympyClassBase):
|
|||
else:
|
||||
self._ip_str = address.strip()
|
||||
|
||||
# Crear FourBytes manualmente para consistencia
|
||||
self.address = FourBytes(self._ip_str)
|
||||
# Crear FourBytes manualmente para consistencia si está disponible
|
||||
if FourBytes:
|
||||
self.address = FourBytes(self._ip_str)
|
||||
else:
|
||||
# Fallback: crear objeto simple
|
||||
self.address = type('MockFourBytes', (), {
|
||||
'original': self._ip_str,
|
||||
'has_symbols': False,
|
||||
'_numeric_value': self._parse_ip_to_int(self._ip_str)
|
||||
})()
|
||||
if self.address.has_symbols:
|
||||
self._ip_int = self.address._symbolic_value
|
||||
self._has_symbols = True
|
||||
|
@ -376,10 +417,10 @@ class Class_IP4(SympyClassBase):
|
|||
self._mask_obj = mask
|
||||
elif isinstance(mask, (int, str)):
|
||||
self._mask_obj = IP4Mask(mask)
|
||||
elif isinstance(mask, FourBytes):
|
||||
elif FourBytes and isinstance(mask, FourBytes):
|
||||
# Máscara desde tokenización (ej: 255.255.255.0)
|
||||
self._mask_obj = IP4Mask(mask)
|
||||
elif isinstance(mask, IntBase):
|
||||
elif IntBase and isinstance(mask, IntBase):
|
||||
# Máscara desde hex (ej: 16#ffffff00)
|
||||
self._mask_obj = IP4Mask(mask)
|
||||
else:
|
||||
|
@ -393,6 +434,17 @@ class Class_IP4(SympyClassBase):
|
|||
else:
|
||||
super().__init__(self._ip_int, self._ip_str)
|
||||
|
||||
def _parse_ip_to_int(self, ip_str):
|
||||
"""Convierte string IP a entero (fallback)"""
|
||||
try:
|
||||
parts = ip_str.split('.')
|
||||
if len(parts) == 4:
|
||||
return (int(parts[0]) << 24 | int(parts[1]) << 16 |
|
||||
int(parts[2]) << 8 | int(parts[3]))
|
||||
except:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def __repr__(self):
|
||||
if self._mask_obj:
|
||||
return f"IP4({self._ip_str!r}, {self._mask_obj.get_prefix_int()})"
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Diagnóstico del problema de asignaciones
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔍 DIAGNOSTICANDO PROBLEMA DE ASIGNACIONES")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
from main_evaluation import HybridEvaluationEngine
|
||||
from tl_bracket_parser import UniversalTokenizer
|
||||
|
||||
# Crear motor de evaluación
|
||||
engine = HybridEvaluationEngine()
|
||||
engine.debug = True # Activar debug completo
|
||||
|
||||
# Crear tokenizador para ver qué pasa
|
||||
tokenizer = UniversalTokenizer()
|
||||
tokenizer.debug = True
|
||||
|
||||
# Casos de prueba exactos del usuario
|
||||
test_cases = [
|
||||
"mask=255.240.0.3",
|
||||
"mask",
|
||||
"ip=10.1.1.1",
|
||||
"10.1.1.1",
|
||||
"ip"
|
||||
]
|
||||
|
||||
print("\n🧪 Procesando casos de prueba del usuario:")
|
||||
|
||||
for i, case in enumerate(test_cases, 1):
|
||||
print(f"\n{'='*50}")
|
||||
print(f"CASO {i}: '{case}'")
|
||||
print(f"{'='*50}")
|
||||
|
||||
# 1. Ver tokenización
|
||||
tokenized = tokenizer.preprocess_tokens(case)
|
||||
print(f"1. TOKENIZACIÓN: '{case}' → '{tokenized}'")
|
||||
|
||||
# 2. Ver clasificación de línea
|
||||
classification = engine._classify_line(tokenized)
|
||||
print(f"2. CLASIFICACIÓN: {classification}")
|
||||
|
||||
# 3. Evaluar con debug
|
||||
print(f"3. EVALUACIÓN:")
|
||||
result = engine.evaluate_line(case)
|
||||
|
||||
print(f" RESULTADO:")
|
||||
print(f" - Tipo de resultado: {result.result_type}")
|
||||
print(f" - Es error: {result.is_error}")
|
||||
if result.is_error:
|
||||
print(f" - Error: {result.error}")
|
||||
else:
|
||||
print(f" - Valor: {result.result}")
|
||||
print(f" - Tipo de valor: {type(result.result)}")
|
||||
|
||||
# 4. Ver estado de variables
|
||||
print(f"4. ESTADO DE VARIABLES:")
|
||||
print(f" Variables en symbol_table: {list(engine.symbol_table.keys())}")
|
||||
for var_name, var_value in engine.symbol_table.items():
|
||||
print(f" {var_name} = {var_value} (tipo: {type(var_value)})")
|
||||
|
||||
# 5. Ver ecuaciones
|
||||
print(f"5. ECUACIONES ALMACENADAS:")
|
||||
print(f" Total ecuaciones: {len(engine.equations)}")
|
||||
for j, eq in enumerate(engine.equations):
|
||||
print(f" {j+1}. {eq}")
|
||||
|
||||
print(f"\n{'_'*50}")
|
||||
|
||||
# 6. Prueba manual de asignación directa
|
||||
print(f"\n🔧 PRUEBA MANUAL DE ASIGNACIÓN DIRECTA:")
|
||||
|
||||
try:
|
||||
# Probar evaluación manual del lado derecho
|
||||
print(f"6.1 Evaluando 'FourBytes(\"255.240.0.3\")' directamente:")
|
||||
manual_result = engine._eval_in_context('FourBytes("255.240.0.3")')
|
||||
print(f" Resultado: {manual_result} (tipo: {type(manual_result)})")
|
||||
|
||||
# Probar asignación manual
|
||||
print(f"6.2 Asignación manual 'test_var = FourBytes(\"255.240.0.3\")':")
|
||||
exec_result = engine._eval_in_context('test_var = FourBytes("255.240.0.3")')
|
||||
print(f" Resultado exec: {exec_result}")
|
||||
|
||||
# Ver si se guardó
|
||||
test_var_value = engine.get_variable('test_var')
|
||||
print(f" test_var guardada: {test_var_value} (tipo: {type(test_var_value)})")
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error en prueba manual: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error general: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Diagnóstico de problemas específicos del usuario:
|
||||
1. IP4Mask(mask) no funciona
|
||||
2. 10.1.1.1 + 1 devuelve Integer en lugar de FourBytes
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔍 DIAGNOSTICANDO PROBLEMAS ESPECÍFICOS DEL USUARIO")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
from main_evaluation import HybridEvaluationEngine
|
||||
|
||||
# Crear motor con debug
|
||||
engine = HybridEvaluationEngine()
|
||||
engine.debug = True
|
||||
|
||||
print("📋 Reproduciendo secuencia del usuario:")
|
||||
|
||||
# 1. Crear mask
|
||||
print(f"\n1. Creando mask:")
|
||||
result1 = engine.evaluate_line("mask=255.240.0.3")
|
||||
print(f" mask asignada: {result1.result} (tipo: {type(result1.result)})")
|
||||
|
||||
# Ver qué hay en mask
|
||||
mask_value = engine.get_variable('mask')
|
||||
print(f" mask en symbol_table: {mask_value} (tipo: {type(mask_value)})")
|
||||
|
||||
print(f"\n2. Problema 1: IP4Mask(mask) no funciona")
|
||||
print(f" Intentando: IP4Mask(mask)")
|
||||
|
||||
# Probar directamente
|
||||
try:
|
||||
result2 = engine.evaluate_line("IP4Mask(mask)")
|
||||
print(f" Resultado: {result2.result} (tipo: {type(result2.result)})")
|
||||
if result2.is_error:
|
||||
print(f" Error: {result2.error}")
|
||||
except Exception as e:
|
||||
print(f" Excepción: {e}")
|
||||
|
||||
# Ver qué pasa cuando llamamos IP4Mask directamente con el valor
|
||||
print(f"\n Probando IP4Mask directamente con el valor de mask:")
|
||||
try:
|
||||
# Obtener IP4Mask del contexto
|
||||
IP4Mask = engine.base_context.get('IP4Mask')
|
||||
if IP4Mask:
|
||||
print(f" IP4Mask disponible: {IP4Mask}")
|
||||
direct_result = IP4Mask(mask_value)
|
||||
print(f" Resultado directo: {direct_result} (tipo: {type(direct_result)})")
|
||||
else:
|
||||
print(f" IP4Mask no encontrada en contexto")
|
||||
except Exception as e:
|
||||
print(f" Error en llamada directa: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print(f"\n3. Problema 2: 10.1.1.1 + 1 devuelve Integer")
|
||||
print(f" Intentando: 10.1.1.1 + 1")
|
||||
|
||||
result3 = engine.evaluate_line("10.1.1.1 + 1")
|
||||
print(f" Resultado: {result3.result} (tipo: {type(result3.result)})")
|
||||
|
||||
# Analizar paso a paso
|
||||
print(f"\n Análisis paso a paso:")
|
||||
|
||||
# Crear FourBytes manualmente para probar
|
||||
try:
|
||||
FourBytes = engine.base_context.get('FourBytes')
|
||||
if FourBytes:
|
||||
print(f" FourBytes disponible: {FourBytes}")
|
||||
fb = FourBytes("10.1.1.1")
|
||||
print(f" FourBytes creado: {fb} (tipo: {type(fb)})")
|
||||
print(f" fb._numeric_value: {fb._numeric_value}")
|
||||
|
||||
# Probar suma manual
|
||||
sum_result = fb + 1
|
||||
print(f" fb + 1 = {sum_result} (tipo: {type(sum_result)})")
|
||||
|
||||
# Ver el método __add__
|
||||
print(f" Método __add__ de FourBytes: {fb.__add__}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error en análisis: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print(f"\n4. Verificando contexto:")
|
||||
relevant_classes = ['FourBytes', 'IP4Mask', 'IP4']
|
||||
for cls_name in relevant_classes:
|
||||
cls_obj = engine.base_context.get(cls_name)
|
||||
if cls_obj:
|
||||
print(f" ✅ {cls_name}: {cls_obj}")
|
||||
else:
|
||||
print(f" ❌ {cls_name}: No encontrada")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error general: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
|
@ -1,19 +1,10 @@
|
|||
|
||||
mask=255.240.0.x
|
||||
mask.ToHex()
|
||||
ip=10.1.1.x
|
||||
10.1.1.1 + 1
|
||||
ip
|
||||
|
||||
mm=255.240.0.x
|
||||
IP4(mm,24)
|
||||
j=IP4(mm,24)
|
||||
x=5
|
||||
mm
|
||||
j.substitute(x=1)
|
||||
j.next_ip()
|
||||
|
||||
hh=16#ff
|
||||
Dec(hh)
|
||||
|
||||
h=16#ff + 25
|
||||
h
|
||||
|
||||
IP4Mask(255.240.0.0)
|
||||
IP4(10.1.280.1)
|
||||
IP4(10.1.x.1)
|
||||
IP4(10.1.1.4)
|
||||
IP4Mask(mask)
|
||||
IP4Mask(255.255.0.0)
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"window_geometry": "1020x700+236+1240",
|
||||
"sash_pos_x": 360,
|
||||
"window_geometry": "1020x700+356+1216",
|
||||
"sash_pos_x": 359,
|
||||
"symbolic_mode": true,
|
||||
"show_numeric_approximation": true,
|
||||
"keep_symbolic_fractions": true,
|
||||
|
|
|
@ -20,13 +20,8 @@ from type_registry import (
|
|||
from tl_bracket_parser import BracketParser
|
||||
from tl_popup import PlotResult
|
||||
|
||||
# ========== NUEVO: Importar clases base para tokenización ==========
|
||||
try:
|
||||
from sympy_Base import IntBase, FourBytes
|
||||
BASE_CLASSES_AVAILABLE = True
|
||||
except ImportError:
|
||||
BASE_CLASSES_AVAILABLE = False
|
||||
print("⚠️ Clases base IntBase/FourBytes no disponibles")
|
||||
# ========== ELIMINADO: Las clases base ahora se obtienen del registro ==========
|
||||
# Las clases IntBase y FourBytes se cargan dinámicamente desde el registro de tipos
|
||||
|
||||
|
||||
class HybridEvaluationEngine:
|
||||
|
@ -36,11 +31,11 @@ class HybridEvaluationEngine:
|
|||
"""
|
||||
|
||||
def __init__(self, auto_discover_types: bool = True, types_directory: str = "custom_types"):
|
||||
# ========== NUEVO: Inicializar parser con tokenización habilitada ==========
|
||||
self.parser = BracketParser(use_type_registry=True)
|
||||
self.parser.use_tokenizer = BASE_CLASSES_AVAILABLE # Habilitar tokenizador si clases disponibles
|
||||
if self.parser.use_tokenizer:
|
||||
self.parser.debug = False # Será habilitado por self.debug más adelante
|
||||
# ========== NUEVO: Inicializar parser con tokenización distribuida ==========
|
||||
self.parser = BracketParser(enable_tokenization=True, debug=False)
|
||||
|
||||
# Debug mode (configurar antes que otros módulos)
|
||||
self.debug = False
|
||||
|
||||
self.symbol_table: Dict[str, Any] = {}
|
||||
self.equations: List[sympy.Eq] = []
|
||||
|
@ -54,9 +49,6 @@ class HybridEvaluationEngine:
|
|||
self.registered_types_info = {}
|
||||
self.helper_functions = []
|
||||
|
||||
# Debug mode
|
||||
self.debug = False
|
||||
|
||||
# NUEVA CONFIGURACIÓN: Modo simbólico
|
||||
self.symbolic_mode = True # Por defecto, mantener forma simbólica
|
||||
self.show_numeric_approximation = True # Mostrar aproximación numérica
|
||||
|
@ -147,22 +139,15 @@ class HybridEvaluationEngine:
|
|||
'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr),
|
||||
}
|
||||
|
||||
# ========== NUEVO: 4.5. CLASES BASE PARA TOKENIZACIÓN ==========
|
||||
base_classes = {}
|
||||
if BASE_CLASSES_AVAILABLE:
|
||||
base_classes = {
|
||||
'IntBase': IntBase,
|
||||
'FourBytes': FourBytes,
|
||||
}
|
||||
if self.debug:
|
||||
print("🔧 Clases base IntBase/FourBytes añadidas al contexto")
|
||||
# ========== NUEVO: 4.5. CLASES BASE YA ESTÁN EN SPECIALIZED_CLASSES ==========
|
||||
# Las clases IntBase y FourBytes ya están en specialized_classes desde el registro
|
||||
# No necesitamos añadirlas por separado
|
||||
|
||||
# 5. COMBINAR TODO EN EL CONTEXTO BASE
|
||||
self.base_context = {
|
||||
**math_functions,
|
||||
**specialized_classes,
|
||||
**utility_functions,
|
||||
**base_classes # ← NUEVO: Incluir clases base
|
||||
**utility_functions
|
||||
}
|
||||
|
||||
# 6. ACTUALIZAR HELPER FUNCTIONS
|
||||
|
@ -178,17 +163,29 @@ class HybridEvaluationEngine:
|
|||
# ========== NUEVO: Sincronizar debug con parser ==========
|
||||
if hasattr(self.parser, 'debug'):
|
||||
self.parser.debug = self.debug
|
||||
|
||||
# ========== NUEVO: Sincronizar debug con tokenizer ==========
|
||||
if hasattr(self.parser, 'tokenizer') and hasattr(self.parser.tokenizer, 'debug'):
|
||||
self.parser.tokenizer.debug = self.debug
|
||||
|
||||
def _update_bracket_parser(self):
|
||||
"""Actualiza el BracketParser con las clases descubiertas"""
|
||||
try:
|
||||
# NUEVO: Llamar al método reload para actualizar dinámicamente
|
||||
self.parser.reload_bracket_classes()
|
||||
|
||||
if self.debug:
|
||||
print(f"🔧 Bracket classes actualizadas: {self.parser.BRACKET_CLASSES}")
|
||||
# NUEVO: Recargar reglas de tokenización del sistema distribuido
|
||||
if hasattr(self.parser, 'reload_tokenization_rules'):
|
||||
self.parser.reload_tokenization_rules()
|
||||
|
||||
if self.debug:
|
||||
tokenization_info = self.parser.get_tokenization_info()
|
||||
print(f"🔧 Reglas de tokenización actualizadas: {len(tokenization_info['rules'])} reglas")
|
||||
for rule in tokenization_info['rules']:
|
||||
print(f" {rule['priority']:2d}: {rule['class']} - {rule['description']}")
|
||||
else:
|
||||
if self.debug:
|
||||
print("⚠️ Parser sin soporte para tokenización distribuida")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error actualizando bracket parser: {e}")
|
||||
print(f"⚠️ Error actualizando tokenización: {e}")
|
||||
|
||||
def reload_types(self):
|
||||
"""Recarga todos los tipos del directorio (útil para desarrollo)"""
|
||||
|
@ -264,16 +261,18 @@ class HybridEvaluationEngine:
|
|||
NUEVA LÓGICA: Priorizar asignaciones, intentar ecuaciones silenciosamente
|
||||
"""
|
||||
try:
|
||||
# 1. Parsear la línea
|
||||
parsed_line, parse_info = self.parser.parse_line(line)
|
||||
# 1. Aplicar tokenización distribuida
|
||||
parsed_line = self.parser.process_expression(line)
|
||||
|
||||
if self.debug:
|
||||
print(f"Parse: '{line}' → '{parsed_line}' ({parse_info})")
|
||||
print(f"Parse: '{line}' → '{parsed_line}'")
|
||||
|
||||
# 2. Manejar casos especiales
|
||||
if parse_info == "comment":
|
||||
# 2. Clasificar tipo de línea
|
||||
line_type = self._classify_line(parsed_line)
|
||||
|
||||
if line_type == "comment":
|
||||
return EvaluationResult(None, "comment", original_line=line)
|
||||
elif parse_info == "assignment":
|
||||
elif line_type == "assignment":
|
||||
# NUEVA LÓGICA: Para asignaciones, también intentar agregar como ecuación silenciosamente
|
||||
assignment_result = self._evaluate_assignment(parsed_line, line)
|
||||
|
||||
|
@ -285,11 +284,11 @@ class HybridEvaluationEngine:
|
|||
pass # Ignorar errores de ecuación
|
||||
|
||||
return assignment_result
|
||||
elif parse_info == "equation":
|
||||
elif line_type == "equation":
|
||||
return self._evaluate_equation_addition(parsed_line, line)
|
||||
|
||||
# 3. Evaluación SymPy
|
||||
return self._evaluate_sympy_expression(parsed_line, parse_info, line)
|
||||
return self._evaluate_sympy_expression(parsed_line, line_type, line)
|
||||
|
||||
except Exception as e:
|
||||
return EvaluationResult(
|
||||
|
@ -298,18 +297,94 @@ class HybridEvaluationEngine:
|
|||
original_line=line
|
||||
)
|
||||
|
||||
def _classify_line(self, parsed_line: str) -> str:
|
||||
"""Clasifica el tipo de línea después del parsing"""
|
||||
|
||||
# Simplificado: priorizar asignaciones, ser menos estricto
|
||||
if self._is_assignment(parsed_line):
|
||||
return "assignment"
|
||||
elif self._is_standalone_equation(parsed_line):
|
||||
return "equation"
|
||||
elif not parsed_line or parsed_line.strip().startswith('#'):
|
||||
return "comment"
|
||||
else:
|
||||
return "expression"
|
||||
|
||||
def _is_assignment(self, line: str) -> bool:
|
||||
"""
|
||||
Detecta si una línea es una asignación de variable
|
||||
NUEVA LÓGICA: Priorizar asignaciones, ser menos estricto
|
||||
"""
|
||||
try:
|
||||
# Pattern: variable = expresión (que no sea comparación)
|
||||
if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']):
|
||||
# Verificar que sea una asignación válida de Python
|
||||
parts = line.split('=', 1)
|
||||
if len(parts) == 2:
|
||||
var_part = parts[0].strip()
|
||||
expr_part = parts[1].strip()
|
||||
|
||||
# Verificar que la parte izquierda sea un identificador válido
|
||||
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part) and expr_part:
|
||||
return True
|
||||
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def _is_standalone_equation(self, line: str) -> bool:
|
||||
"""
|
||||
Determina si una línea es una ecuación standalone
|
||||
NUEVA LÓGICA: Solo ecuaciones matemáticas obvias, no asignaciones
|
||||
"""
|
||||
try:
|
||||
# Primero verificar si contiene '=' simple
|
||||
if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']):
|
||||
return False
|
||||
|
||||
# NUEVA LÓGICA: Si ya fue clasificada como asignación, NO es ecuación
|
||||
if self._is_assignment(line):
|
||||
return False
|
||||
|
||||
# Solo considerar como ecuación si tiene estructura matemática compleja
|
||||
# Por ejemplo: expressions con funciones matemáticas, símbolos algebraicos, etc.
|
||||
if any(pattern in line for pattern in ['sin(', 'cos(', 'log(', 'sqrt(', 'diff(', 'integrate(']):
|
||||
return True
|
||||
|
||||
# O si contiene múltiples variables en un formato algebraico
|
||||
# Básicamente, ecuaciones que NO son asignaciones simples
|
||||
return False
|
||||
|
||||
except:
|
||||
return False
|
||||
|
||||
def _evaluate_assignment(self, parsed_line: str, original_line: str) -> 'EvaluationResult':
|
||||
"""Maneja la asignación de variables"""
|
||||
try:
|
||||
# Ejecutar _assign_variable
|
||||
result = self._eval_in_context(parsed_line)
|
||||
# ARREGLO: Transformar asignación en llamada a _assign_variable
|
||||
# parsed_line es algo como: mask=FourBytes("255.240.0.3")
|
||||
# Necesitamos convertirlo en: _assign_variable("mask", FourBytes("255.240.0.3"))
|
||||
|
||||
# Extraer nombre de variable y valor del resultado
|
||||
parts = original_line.split('=', 1)
|
||||
var_name = parts[0].strip()
|
||||
if '=' in parsed_line:
|
||||
parts = parsed_line.split('=', 1)
|
||||
var_name = parts[0].strip()
|
||||
expr_part = parts[1].strip()
|
||||
|
||||
# Crear llamada a _assign_variable
|
||||
assign_call = f'_assign_variable("{var_name}", {expr_part})'
|
||||
|
||||
if self.debug:
|
||||
print(f"🔧 Transformando asignación: '{parsed_line}' → '{assign_call}'")
|
||||
|
||||
# Ejecutar la asignación transformada
|
||||
result = self._eval_in_context(assign_call)
|
||||
|
||||
# Obtener el valor asignado
|
||||
assigned_value = self.symbol_table.get(var_name)
|
||||
else:
|
||||
# Fallback: si no hay '=' algo está mal
|
||||
raise ValueError(f"Línea de asignación sin '=': {parsed_line}")
|
||||
|
||||
# Obtener el valor asignado
|
||||
assigned_value = self.symbol_table.get(var_name)
|
||||
|
||||
# Generar evaluación numérica si está configurado para mostrarla
|
||||
numeric_result = None
|
||||
|
@ -526,6 +601,11 @@ class HybridEvaluationEngine:
|
|||
# NUEVA LÓGICA: Priorizar SymPy en modo simbólico
|
||||
if self.symbolic_mode:
|
||||
try:
|
||||
# INTERCEPTAR: Detectar operaciones aritméticas con objetos nativos
|
||||
intercepted_result = self._intercept_native_operations(expression, context)
|
||||
if intercepted_result is not None:
|
||||
return intercepted_result
|
||||
|
||||
# Primero intentar con SymPy para mantener formas simbólicas
|
||||
result = sympify(expression, locals=context, rational=self.keep_symbolic_fractions)
|
||||
|
||||
|
@ -599,6 +679,45 @@ class HybridEvaluationEngine:
|
|||
except:
|
||||
raise syntax_error
|
||||
|
||||
def _intercept_native_operations(self, expression: str, context: Dict[str, Any]) -> Any:
|
||||
"""
|
||||
Intercepta operaciones aritméticas con objetos nativos para evitar
|
||||
que SymPy las convierta prematuramente
|
||||
"""
|
||||
import re
|
||||
|
||||
# Detectar patrones de aritmética con FourBytes, IntBase, etc.
|
||||
# Patrones como: FourBytes(...) + número, número + FourBytes(...), etc.
|
||||
|
||||
# Patrón 1: Constructor de tipo + operador + número/expresión
|
||||
pattern1 = r'(\w+\([^)]+\))\s*([+\-*/])\s*(\d+|\w+)'
|
||||
|
||||
# Patrón 2: número/expresión + operador + Constructor de tipo
|
||||
pattern2 = r'(\d+|\w+)\s*([+\-*/])\s*(\w+\([^)]+\))'
|
||||
|
||||
for pattern in [pattern1, pattern2]:
|
||||
match = re.match(pattern, expression.strip())
|
||||
if match:
|
||||
try:
|
||||
# Evaluar usando eval para que los objetos manejen sus propias operaciones
|
||||
result = eval(expression, {"__builtins__": {}}, context)
|
||||
|
||||
# Verificar si el resultado es un objeto nativo que debe mantenerse
|
||||
if hasattr(result, '__class__') and result.__class__.__module__ != 'sympy':
|
||||
# Es un objeto de nuestras clases personalizadas
|
||||
if hasattr(result, 'original') or hasattr(result, '_numeric_value'):
|
||||
if self.debug:
|
||||
print(f"🔧 Interceptando operación nativa: {expression} → {result} ({type(result).__name__})")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"🔧 Error en intercepción: {e}")
|
||||
pass
|
||||
|
||||
# Si no se detecta patrón específico, retornar None para continuar con SymPy
|
||||
return None
|
||||
|
||||
def _get_full_context(self) -> Dict[str, Any]:
|
||||
"""Obtiene el contexto completo para evaluación"""
|
||||
context = self.base_context.copy()
|
||||
|
|
438
sympy_Base.py
438
sympy_Base.py
|
@ -249,406 +249,58 @@ class SympyClassBase(ClassBase, sympy.Basic):
|
|||
return None
|
||||
|
||||
|
||||
# ========== CLASES BASE UNIVERSALES PARA TOKENIZACIÓN ==========
|
||||
|
||||
class IntBase(SympyClassBase):
|
||||
"""
|
||||
Representación universal de números en cualquier base
|
||||
con soporte algebraico completo y operaciones aritméticas
|
||||
"""
|
||||
|
||||
def __init__(self, value_str, base=10):
|
||||
self.value_str = str(value_str)
|
||||
self.base = int(base)
|
||||
|
||||
# Detección mejorada de símbolos: considerar la base
|
||||
self.has_symbols = self._has_symbols_for_base()
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: mantener como expresión simbólica
|
||||
self._symbolic_value = self._parse_symbolic_base()
|
||||
self._numeric_value = None # No hay valor numérico
|
||||
super().__init__(self._symbolic_value, f"{self.base}#{self.value_str}")
|
||||
else:
|
||||
# Modo numérico: convertir a entero
|
||||
self._numeric_value = int(self.value_str, self.base)
|
||||
self._symbolic_value = None # No hay valor simbólico
|
||||
super().__init__(self._numeric_value, f"{self.base}#{self.value_str}")
|
||||
|
||||
def _has_symbols_for_base(self):
|
||||
"""Detecta si hay símbolos considerando la base numérica"""
|
||||
valid_digits = "0123456789ABCDEF"[:self.base]
|
||||
|
||||
for char in self.value_str.upper():
|
||||
if char not in valid_digits:
|
||||
# Es un símbolo (no un dígito válido para esta base)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _parse_symbolic_base(self):
|
||||
"""Convierte valor con símbolos a expresión SymPy"""
|
||||
# Ejemplo: "x0" base 16 → x*16^1 + 0*16^0
|
||||
# Ejemplo: "101x" base 2 → 1*2^3 + 0*2^2 + 1*2^1 + x*2^0
|
||||
result = 0
|
||||
digits = list(self.value_str)
|
||||
|
||||
for i, digit in enumerate(reversed(digits)):
|
||||
power = self.base ** i
|
||||
if digit.isdigit():
|
||||
coefficient = int(digit)
|
||||
elif digit in 'ABCDEF':
|
||||
coefficient = ord(digit) - ord('A') + 10
|
||||
elif digit in 'abcdef':
|
||||
coefficient = ord(digit) - ord('a') + 10
|
||||
else:
|
||||
# Es un símbolo
|
||||
coefficient = sympy.Symbol(digit)
|
||||
|
||||
result += coefficient * power
|
||||
|
||||
return result
|
||||
|
||||
def to_base(self, new_base):
|
||||
"""Convierte a nueva base"""
|
||||
if self.has_symbols:
|
||||
# En modo algebraico, mantener expresión
|
||||
return IntBase(f"({self._symbolic_value})_base{new_base}", new_base)
|
||||
else:
|
||||
# En modo numérico, convertir directamente
|
||||
new_value_str = self._convert_to_base_string(self._numeric_value, new_base)
|
||||
return IntBase(new_value_str, new_base)
|
||||
|
||||
@staticmethod
|
||||
def _convert_to_base_string(value, base):
|
||||
"""Convierte entero a string en base específica"""
|
||||
if value == 0:
|
||||
return "0"
|
||||
digits = "0123456789ABCDEF"
|
||||
result = ""
|
||||
while value:
|
||||
result = digits[value % base] + result
|
||||
value //= base
|
||||
return result
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Propiedad para acceder al valor (numérico o simbólico)"""
|
||||
if self.has_symbols:
|
||||
return self._symbolic_value
|
||||
else:
|
||||
return self._numeric_value
|
||||
|
||||
# ========== OPERADORES ARITMÉTICOS ==========
|
||||
|
||||
def __add__(self, other):
|
||||
"""Suma: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__add__(other) # Delegar a SymPy
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__add__(other)
|
||||
result_value = self._numeric_value + other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value + other
|
||||
else:
|
||||
return super().__add__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Resta: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__sub__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__sub__(other)
|
||||
result_value = self._numeric_value - other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value - other
|
||||
else:
|
||||
return super().__sub__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __mul__(self, other):
|
||||
"""Multiplicación: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__mul__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__mul__(other)
|
||||
result_value = self._numeric_value * other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value * other
|
||||
else:
|
||||
return super().__mul__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __truediv__(self, other):
|
||||
"""División: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__truediv__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__truediv__(other)
|
||||
result_value = self._numeric_value // other._numeric_value # División entera
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value // other
|
||||
else:
|
||||
return super().__truediv__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
def __mod__(self, other):
|
||||
"""Módulo: mantiene la base del operando izquierdo"""
|
||||
if self.has_symbols:
|
||||
return super().__mod__(other)
|
||||
|
||||
if isinstance(other, IntBase):
|
||||
if other.has_symbols:
|
||||
return super().__mod__(other)
|
||||
result_value = self._numeric_value % other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_value = self._numeric_value % other
|
||||
else:
|
||||
return super().__mod__(other)
|
||||
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
|
||||
# Operadores reversos para int + IntBase
|
||||
def __radd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
def __rsub__(self, other):
|
||||
if isinstance(other, int):
|
||||
result_value = other - self._numeric_value
|
||||
result_str = self._convert_to_base_string(result_value, self.base)
|
||||
return IntBase(result_str, self.base)
|
||||
return super().__rsub__(other)
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self.__mul__(other)
|
||||
|
||||
def __str__(self):
|
||||
"""Representación string"""
|
||||
return f"{self.base}#{self.value_str}"
|
||||
|
||||
def __repr__(self):
|
||||
return f"IntBase('{self.value_str}', {self.base})"
|
||||
|
||||
|
||||
class FourBytes(SympyClassBase):
|
||||
"""
|
||||
Representación universal de patrones de 4 elementos
|
||||
con soporte algebraico completo y operaciones aritméticas
|
||||
"""
|
||||
|
||||
def __init__(self, dotted_string):
|
||||
self.original = str(dotted_string)
|
||||
self.elements = self.original.split('.')
|
||||
|
||||
if len(self.elements) != 4:
|
||||
raise ValueError(f"FourBytes requiere exactamente 4 elementos: {dotted_string}")
|
||||
|
||||
self.has_symbols = any(not elem.isdigit() for elem in self.elements)
|
||||
|
||||
if self.has_symbols:
|
||||
# Modo algebraico: crear expresión simbólica
|
||||
self._symbolic_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
self._symbolic_elements.append(int(elem))
|
||||
else:
|
||||
self._symbolic_elements.append(sympy.Symbol(elem))
|
||||
|
||||
# CORREGIDO: Crear expresión escalar en lugar de Matrix para compatibilidad con IP4
|
||||
# Convertir a representación de entero de 32 bits simbólico
|
||||
self._symbolic_value = (self._symbolic_elements[0] * 2**24 +
|
||||
self._symbolic_elements[1] * 2**16 +
|
||||
self._symbolic_elements[2] * 2**8 +
|
||||
self._symbolic_elements[3])
|
||||
super().__init__(self._symbolic_value, dotted_string)
|
||||
else:
|
||||
# Modo numérico: validar rangos y convertir
|
||||
self._numeric_elements = [int(elem) for elem in self.elements]
|
||||
# Crear valor como entero de 32 bits (para IPs)
|
||||
self._numeric_value = (self._numeric_elements[0] << 24 |
|
||||
self._numeric_elements[1] << 16 |
|
||||
self._numeric_elements[2] << 8 |
|
||||
self._numeric_elements[3])
|
||||
super().__init__(self._numeric_value, dotted_string)
|
||||
|
||||
def is_valid_ip_range(self):
|
||||
"""Verifica si todos los elementos numéricos están en rango IP"""
|
||||
if self.has_symbols:
|
||||
return None # No se puede validar con símbolos
|
||||
return all(0 <= x <= 255 for x in self._numeric_elements)
|
||||
|
||||
def substitute(self, **kwargs):
|
||||
"""Sustituye símbolos por valores"""
|
||||
if not self.has_symbols:
|
||||
return self
|
||||
|
||||
new_elements = []
|
||||
for elem in self.elements:
|
||||
if elem in kwargs:
|
||||
new_elements.append(str(kwargs[elem]))
|
||||
else:
|
||||
new_elements.append(elem)
|
||||
|
||||
return FourBytes('.'.join(new_elements))
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Acceso a elementos individuales"""
|
||||
if self.has_symbols:
|
||||
return self._symbolic_elements[index]
|
||||
else:
|
||||
return self._numeric_elements[index]
|
||||
|
||||
def to_ip_int(self):
|
||||
"""Convierte a entero de 32 bits (para IPs)"""
|
||||
if self.has_symbols:
|
||||
# CORREGIDO: Ya tenemos la expresión escalar directamente
|
||||
return self._symbolic_value
|
||||
else:
|
||||
return self._numeric_value
|
||||
|
||||
@staticmethod
|
||||
def _int_to_fourbytes(value):
|
||||
"""Convierte entero de 32 bits a formato x.y.z.w"""
|
||||
w = value & 0xFF
|
||||
z = (value >> 8) & 0xFF
|
||||
y = (value >> 16) & 0xFF
|
||||
x = (value >> 24) & 0xFF
|
||||
return f"{x}.{y}.{z}.{w}"
|
||||
|
||||
# ========== OPERADORES ARITMÉTICOS ==========
|
||||
|
||||
def __add__(self, other):
|
||||
"""Suma: convierte a int, opera, reconvierte a FourBytes"""
|
||||
if self.has_symbols:
|
||||
return super().__add__(other) # Delegar a SymPy
|
||||
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
return super().__add__(other)
|
||||
result_int = self._numeric_value + other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_int = self._numeric_value + other
|
||||
else:
|
||||
return super().__add__(other)
|
||||
|
||||
# Mantener en rango de 32 bits
|
||||
result_int = result_int & 0xFFFFFFFF
|
||||
result_str = self._int_to_fourbytes(result_int)
|
||||
return FourBytes(result_str)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""Resta: convierte a int, opera, reconvierte a FourBytes"""
|
||||
if self.has_symbols:
|
||||
return super().__sub__(other)
|
||||
|
||||
if isinstance(other, FourBytes):
|
||||
if other.has_symbols:
|
||||
return super().__sub__(other)
|
||||
result_int = self._numeric_value - other._numeric_value
|
||||
elif isinstance(other, int):
|
||||
result_int = self._numeric_value - other
|
||||
else:
|
||||
return super().__sub__(other)
|
||||
|
||||
# Mantener en rango de 32 bits (underflow se convierte en valor alto)
|
||||
result_int = result_int & 0xFFFFFFFF
|
||||
result_str = self._int_to_fourbytes(result_int)
|
||||
return FourBytes(result_str)
|
||||
|
||||
# ========== CONVERSIONES DE BASE ==========
|
||||
|
||||
def ToBase(self, base):
|
||||
"""
|
||||
Convierte cada elemento a la base especificada
|
||||
Retorna expresión con cada elemento convertido
|
||||
"""
|
||||
if self.has_symbols:
|
||||
# Para elementos simbólicos, retornar expresión algebraica
|
||||
converted_elements = []
|
||||
for elem in self.elements:
|
||||
if elem.isdigit():
|
||||
int_val = int(elem)
|
||||
converted = IntBase._convert_to_base_string(int_val, base)
|
||||
converted_elements.append(f"{base}#{converted}")
|
||||
else:
|
||||
# Símbolo: expresar como conversión algebraica
|
||||
converted_elements.append(f"{base}#{elem}")
|
||||
return '.'.join(converted_elements)
|
||||
else:
|
||||
# Para elementos numéricos, conversión directa
|
||||
converted_elements = []
|
||||
for elem_val in self._numeric_elements:
|
||||
converted = IntBase._convert_to_base_string(elem_val, base)
|
||||
converted_elements.append(f"{base}#{converted}")
|
||||
return '.'.join(converted_elements)
|
||||
|
||||
def ToCIDR(self, prefix_length):
|
||||
"""Convierte a notación CIDR"""
|
||||
return f"{self.original}/{prefix_length}"
|
||||
|
||||
def ToHex(self):
|
||||
"""Convierte cada elemento a hexadecimal"""
|
||||
return self.ToBase(16)
|
||||
|
||||
def ToBinary(self):
|
||||
"""Convierte cada elemento a binario"""
|
||||
return self.ToBase(2)
|
||||
|
||||
def ToOctal(self):
|
||||
"""Convierte cada elemento a octal"""
|
||||
return self.ToBase(8)
|
||||
|
||||
def __str__(self):
|
||||
"""Representación string"""
|
||||
return self.original
|
||||
|
||||
def __repr__(self):
|
||||
return f"FourBytes('{self.original}')"
|
||||
|
||||
|
||||
# ========== TOKENIZADOR ALGEBRAICO ==========
|
||||
# ========== FUNCIÓN DE TOKENIZACIÓN ACTUALIZADA ==========
|
||||
|
||||
def preprocess_tokens(expression):
|
||||
"""
|
||||
Tokenizador que convierte patrones específicos en objetos tipados
|
||||
con precedencia correcta: IntBase (más específico) antes que FourBytes
|
||||
Función de tokenización que usa el registro dinámico de tipos
|
||||
NUEVA VERSIÓN: Obtiene clases desde custom_types/ automáticamente
|
||||
"""
|
||||
# 1. MAYOR PRECEDENCIA: IntBase (patrón más específico)
|
||||
# Pattern: base#valor
|
||||
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', r'IntBase("\2", \1)', expression)
|
||||
# Intentar obtener las clases desde el registro
|
||||
try:
|
||||
from type_registry import get_registered_base_context
|
||||
context = get_registered_base_context()
|
||||
|
||||
IntBase = context.get('IntBase')
|
||||
FourBytes = context.get('FourBytes')
|
||||
|
||||
if not IntBase or not FourBytes:
|
||||
# Fallback: tokenización básica sin clases
|
||||
return _basic_tokenization(expression)
|
||||
|
||||
except ImportError:
|
||||
# Si el registro no está disponible, usar tokenización básica
|
||||
return _basic_tokenization(expression)
|
||||
|
||||
# 2. MENOR PRECEDENCIA: FourBytes (patrón más general)
|
||||
# Pattern: x.x.x.x pero evitar casos como obj.method
|
||||
# Solo tokenizar si hay exactamente 4 elementos separados por puntos
|
||||
# y no es parte de una cadena de métodos
|
||||
pattern = r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b'
|
||||
# Tokenización completa con clases disponibles
|
||||
return _full_tokenization(expression, IntBase, FourBytes)
|
||||
|
||||
def _full_tokenization(expression, IntBase, FourBytes):
|
||||
"""Tokenización completa usando las clases registradas"""
|
||||
import re
|
||||
|
||||
def replace_intbase(match):
|
||||
base = match.group(1)
|
||||
value = match.group(2)
|
||||
return f'IntBase("{value}", {base})'
|
||||
|
||||
def replace_fourbytes(match):
|
||||
candidate = match.group(1)
|
||||
# Verificar que realmente tiene 4 elementos
|
||||
parts = candidate.split('.')
|
||||
if len(parts) == 4:
|
||||
return f'FourBytes("{candidate}")'
|
||||
return candidate
|
||||
pattern = match.group(1)
|
||||
return f'FourBytes("{pattern}")'
|
||||
|
||||
expression = re.sub(pattern, replace_fourbytes, expression)
|
||||
# PASO 1: IntBase - precedencia MAYOR (patrón más específico)
|
||||
# Patrón: número#valor (ej: 16#FF, 2#1010)
|
||||
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', replace_intbase, expression)
|
||||
|
||||
# PASO 2: FourBytes - precedencia MENOR (patrón más general)
|
||||
# Patrón: x.x.x.x donde x puede ser número o símbolo
|
||||
# Usar \b para boundary, evitar conflictos con acceso a atributos
|
||||
expression = re.sub(r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b',
|
||||
replace_fourbytes, expression)
|
||||
|
||||
return expression
|
||||
|
||||
def _basic_tokenization(expression):
|
||||
"""Tokenización básica cuando las clases no están disponibles"""
|
||||
# Solo hacer transformaciones básicas sin objetos
|
||||
return expression
|
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script de prueba para el sistema de tokenización distribuida
|
||||
"""
|
||||
|
||||
def test_distributed_tokenization():
|
||||
"""Prueba el sistema de tokenización distribuida completo"""
|
||||
|
||||
print("🧪 PROBANDO SISTEMA DE TOKENIZACIÓN DISTRIBUIDA")
|
||||
print("=" * 60)
|
||||
|
||||
# Importar dependencias
|
||||
try:
|
||||
from tl_bracket_parser import TokenizationParser, preview_tokenization, test_tokenization_patterns
|
||||
from type_registry import discover_and_register_types, get_registered_base_context
|
||||
from main_evaluation import HybridEvaluationEngine
|
||||
|
||||
print("✅ Todas las importaciones exitosas")
|
||||
except ImportError as e:
|
||||
print(f"❌ Error en importaciones: {e}")
|
||||
return False
|
||||
|
||||
# 1. Probar auto-descubrimiento de tipos
|
||||
print("\n1️⃣ PROBANDO AUTO-DESCUBRIMIENTO DE TIPOS")
|
||||
print("-" * 40)
|
||||
|
||||
try:
|
||||
registry_info = discover_and_register_types()
|
||||
print(f"✅ Tipos descubiertos: {registry_info['class_count']} clases")
|
||||
|
||||
registered_context = get_registered_base_context()
|
||||
|
||||
# Verificar clases fundamentales
|
||||
fundamental_classes = ['IntBase', 'FourBytes']
|
||||
for cls_name in fundamental_classes:
|
||||
if cls_name in registered_context:
|
||||
print(f" ✅ {cls_name} registrada")
|
||||
|
||||
# Verificar si tiene tokenización
|
||||
cls_obj = registered_context[cls_name]
|
||||
if hasattr(cls_obj, 'get_tokenization_patterns'):
|
||||
patterns = cls_obj.get_tokenization_patterns()
|
||||
print(f" 🔧 Tiene {len(patterns)} reglas de tokenización")
|
||||
for pattern in patterns:
|
||||
print(f" 📋 Prioridad {pattern['priority']}: {pattern['description']}")
|
||||
else:
|
||||
print(f" ⚠️ Sin reglas de tokenización")
|
||||
else:
|
||||
print(f" ❌ {cls_name} NO registrada")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error en auto-descubrimiento: {e}")
|
||||
return False
|
||||
|
||||
# 2. Probar tokenizador universal
|
||||
print("\n2️⃣ PROBANDO TOKENIZADOR UNIVERSAL")
|
||||
print("-" * 40)
|
||||
|
||||
try:
|
||||
parser = TokenizationParser(debug=True)
|
||||
|
||||
# Casos de prueba
|
||||
test_cases = [
|
||||
"16#FF", # IntBase
|
||||
"2#1010", # IntBase
|
||||
"192.168.1.1", # FourBytes
|
||||
"10.x.1.y", # FourBytes simbólico
|
||||
"0xFF", # Hex con prefijo
|
||||
"0b1010", # Bin con prefijo
|
||||
"16#FF + 192.168.1.1", # Mixto
|
||||
"normal + variable", # Sin tokenización
|
||||
]
|
||||
|
||||
print("🧪 Casos de prueba:")
|
||||
results = parser.test_patterns(test_cases)
|
||||
|
||||
print(f"\n📊 Resultados:")
|
||||
print(f" ✅ Exitosos: {len(results['successful_conversions'])}")
|
||||
print(f" ⚪ Sin cambios: {len(results['failed_conversions'])}")
|
||||
|
||||
if results['successful_conversions']:
|
||||
print(f"\n🎉 Conversiones exitosas:")
|
||||
for expr in results['successful_conversions']:
|
||||
result = results['tokenized_results'][expr]
|
||||
print(f" '{expr}' → '{result}'")
|
||||
|
||||
# Obtener información de reglas
|
||||
tokenization_info = parser.get_tokenization_info()
|
||||
print(f"\n📋 {len(tokenization_info['rules'])} reglas activas:")
|
||||
for rule in tokenization_info['rules']:
|
||||
print(f" {rule['priority']:2d}: {rule['class']} - {rule['description']}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error en tokenizador: {e}")
|
||||
return False
|
||||
|
||||
# 3. Probar integración con motor de evaluación
|
||||
print("\n3️⃣ PROBANDO INTEGRACIÓN CON MOTOR DE EVALUACIÓN")
|
||||
print("-" * 40)
|
||||
|
||||
try:
|
||||
engine = HybridEvaluationEngine(debug=True)
|
||||
|
||||
# Probar expresiones tokenizadas
|
||||
test_expressions = [
|
||||
"a = 16#FF",
|
||||
"b = 192.168.1.1",
|
||||
"c = a + 5",
|
||||
"d = b + 256",
|
||||
"16#10 + 2#1010",
|
||||
]
|
||||
|
||||
print("🧪 Probando expresiones:")
|
||||
for expr in test_expressions:
|
||||
try:
|
||||
result = engine.evaluate_line(expr)
|
||||
if result.is_error:
|
||||
print(f" ❌ '{expr}' → Error: {result.error}")
|
||||
else:
|
||||
print(f" ✅ '{expr}' → {result.result}")
|
||||
except Exception as e:
|
||||
print(f" ❌ '{expr}' → Excepción: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error en motor de evaluación: {e}")
|
||||
return False
|
||||
|
||||
# 4. Prueba de casos específicos reportados por el usuario
|
||||
print("\n4️⃣ PROBANDO CASOS ESPECÍFICOS DEL USUARIO")
|
||||
print("-" * 40)
|
||||
|
||||
try:
|
||||
engine = HybridEvaluationEngine()
|
||||
|
||||
# El caso específico que reportó el usuario
|
||||
test_expression = "m=255.240.0.0 16#ff + 5"
|
||||
|
||||
print(f"🎯 Probando: '{test_expression}'")
|
||||
|
||||
# Primero ver cómo se tokeniza
|
||||
preview = preview_tokenization(test_expression, debug=False)
|
||||
print(f" 🔧 Tokenización: '{preview['original']}' → '{preview['tokenized']}'")
|
||||
|
||||
# Ahora evaluar
|
||||
result = engine.evaluate_line(test_expression)
|
||||
if result.is_error:
|
||||
print(f" ❌ Error: {result.error}")
|
||||
else:
|
||||
print(f" ✅ Resultado: {result.result}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error en caso específico: {e}")
|
||||
return False
|
||||
|
||||
print("\n🎉 TODAS LAS PRUEBAS COMPLETADAS")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_distributed_tokenization()
|
||||
if success:
|
||||
print("\n✅ Sistema de tokenización distribuida funcionando correctamente")
|
||||
else:
|
||||
print("\n❌ Hay problemas en el sistema de tokenización distribuida")
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Prueba de las correcciones:
|
||||
1. FourBytes + int mantiene tipo FourBytes
|
||||
2. IP4Mask con mejor mensaje de error para máscaras inválidas
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔧 PROBANDO CORRECCIONES")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
from main_evaluation import HybridEvaluationEngine
|
||||
|
||||
# Crear motor
|
||||
engine = HybridEvaluationEngine()
|
||||
|
||||
print("📋 Reproduciendo casos del usuario:\n")
|
||||
|
||||
# 1. Reproducir secuencia completa
|
||||
print("1. mask=255.240.0.3")
|
||||
result1 = engine.evaluate_line("mask=255.240.0.3")
|
||||
print(f" → {result1.result} (tipo: {type(result1.result).__name__})")
|
||||
|
||||
print(f"\n2. mask")
|
||||
result2 = engine.evaluate_line("mask")
|
||||
print(f" → {result2.result} (tipo: {type(result2.result).__name__})")
|
||||
|
||||
print(f"\n3. ip=10.1.1.x")
|
||||
result3 = engine.evaluate_line("ip=10.1.1.x")
|
||||
print(f" → {result3.result} (tipo: {type(result3.result).__name__})")
|
||||
|
||||
print(f"\n4. 10.1.1.1 + 1 (DEBE DEVOLVER FourBytes)")
|
||||
result4 = engine.evaluate_line("10.1.1.1 + 1")
|
||||
print(f" → {result4.result} (tipo: {type(result4.result).__name__})")
|
||||
|
||||
if hasattr(result4.result, 'original'):
|
||||
print(f" → Valor original: {result4.result.original}")
|
||||
|
||||
print(f"\n5. ip")
|
||||
result5 = engine.evaluate_line("ip")
|
||||
print(f" → {result5.result} (tipo: {type(result5.result).__name__})")
|
||||
|
||||
print(f"\n6. IP4(10.1.1.4)")
|
||||
result6 = engine.evaluate_line("IP4(10.1.1.4)")
|
||||
print(f" → {result6.result} (tipo: {type(result6.result).__name__})")
|
||||
|
||||
print(f"\n7. IP4Mask(mask) - DEBE DAR ERROR INFORMATIVO")
|
||||
result7 = engine.evaluate_line("IP4Mask(mask)")
|
||||
if result7.is_error:
|
||||
print(f" ❌ Error (como esperado): {result7.error}")
|
||||
else:
|
||||
print(f" → {result7.result} (tipo: {type(result7.result).__name__})")
|
||||
|
||||
print(f"\n8. Probando con máscara válida: IP4Mask(255.255.0.0)")
|
||||
result8 = engine.evaluate_line("IP4Mask(255.255.0.0)")
|
||||
print(f" → {result8.result} (tipo: {type(result8.result).__name__})")
|
||||
|
||||
print(f"\n🎯 RESUMEN DE RESULTADOS:")
|
||||
print(f" ✅ mask asignada como FourBytes: {type(result1.result).__name__ == 'FourBytes'}")
|
||||
print(f" ✅ 10.1.1.1 + 1 mantiene FourBytes: {type(result4.result).__name__ == 'FourBytes'}")
|
||||
print(f" ✅ IP4Mask da error informativo para máscara inválida: {result7.is_error}")
|
||||
print(f" ✅ IP4Mask funciona con máscara válida: {not result8.is_error}")
|
||||
|
||||
# Verificación adicional de aritmética FourBytes
|
||||
print(f"\n🔍 VERIFICACIÓN ADICIONAL DE ARITMÉTICA:")
|
||||
|
||||
# Test más casos aritméticos
|
||||
test_cases = [
|
||||
"192.168.1.1 + 5",
|
||||
"10.0.0.0 + 256",
|
||||
"255.255.255.255 - 1"
|
||||
]
|
||||
|
||||
for case in test_cases:
|
||||
result = engine.evaluate_line(case)
|
||||
print(f" {case} → {result.result} ({type(result.result).__name__})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Prueba específica de FourBytes + int
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔍 PROBANDO FourBytes + int")
|
||||
print("=" * 40)
|
||||
|
||||
try:
|
||||
from main_evaluation import HybridEvaluationEngine
|
||||
|
||||
# Crear motor
|
||||
engine = HybridEvaluationEngine()
|
||||
|
||||
# 1. Crear FourBytes directamente
|
||||
print("1. Creando FourBytes directamente:")
|
||||
FourBytes = engine.base_context.get('FourBytes')
|
||||
print(f" FourBytes: {FourBytes}")
|
||||
|
||||
fb = FourBytes("10.1.1.1")
|
||||
print(f" fb = {fb} (tipo: {type(fb)})")
|
||||
print(f" fb._numeric_value = {fb._numeric_value}")
|
||||
|
||||
# 2. Probar suma directa
|
||||
print(f"\n2. Suma directa en Python:")
|
||||
result_direct = fb + 1
|
||||
print(f" fb + 1 = {result_direct} (tipo: {type(result_direct)})")
|
||||
|
||||
# 3. Probar a través del motor de evaluación
|
||||
print(f"\n3. Evaluación a través del motor:")
|
||||
engine.symbol_table['test_fb'] = fb
|
||||
|
||||
result_engine = engine.evaluate_line("test_fb + 1")
|
||||
print(f" test_fb + 1 = {result_engine.result} (tipo: {type(result_engine.result)})")
|
||||
|
||||
# 4. Probar tokenización + evaluación
|
||||
print(f"\n4. Tokenización completa:")
|
||||
result_tokenized = engine.evaluate_line("10.1.1.1 + 1")
|
||||
print(f" 10.1.1.1 + 1 = {result_tokenized.result} (tipo: {type(result_tokenized.result)})")
|
||||
|
||||
# 5. Verificar paso a paso qué pasa en la tokenización
|
||||
print(f"\n5. Análisis paso a paso:")
|
||||
from tl_bracket_parser import UniversalTokenizer
|
||||
tokenizer = UniversalTokenizer()
|
||||
tokenizer.debug = True
|
||||
|
||||
tokenized = tokenizer.preprocess_tokens("10.1.1.1 + 1")
|
||||
print(f" Tokenizado: {tokenized}")
|
||||
|
||||
# 6. Evaluar el tokenizado directamente
|
||||
print(f"\n6. Evaluando tokenizado directamente:")
|
||||
try:
|
||||
direct_eval = engine._eval_in_context(tokenized)
|
||||
print(f" Resultado directo: {direct_eval} (tipo: {type(direct_eval)})")
|
||||
except Exception as e:
|
||||
print(f" Error: {e}")
|
||||
|
||||
# 7. Probar operación manual paso a paso
|
||||
print(f"\n7. Operación manual paso a paso:")
|
||||
try:
|
||||
fb_manual = engine._eval_in_context('FourBytes("10.1.1.1")')
|
||||
print(f" FourBytes manual: {fb_manual} (tipo: {type(fb_manual)})")
|
||||
|
||||
add_result = engine._eval_in_context('FourBytes("10.1.1.1") + 1')
|
||||
print(f" Suma manual: {add_result} (tipo: {type(add_result)})")
|
||||
except Exception as e:
|
||||
print(f" Error en operación manual: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script rápido para probar tokenización
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🧪 Prueba rápida de tokenización")
|
||||
|
||||
try:
|
||||
# 1. Verificar registro de tipos
|
||||
print("\n1. Verificando registro de tipos...")
|
||||
from type_registry import discover_and_register_types, get_registered_base_context
|
||||
|
||||
registry_info = discover_and_register_types()
|
||||
print(f"Clases registradas: {registry_info['class_count']}")
|
||||
|
||||
context = get_registered_base_context()
|
||||
print(f"Clases en contexto: {list(context.keys())}")
|
||||
|
||||
# 2. Verificar si las clases tienen tokenización
|
||||
print("\n2. Verificando métodos de tokenización...")
|
||||
for name, cls in context.items():
|
||||
if hasattr(cls, 'get_tokenization_patterns'):
|
||||
patterns = cls.get_tokenization_patterns()
|
||||
print(f"{name}: {len(patterns)} patrones")
|
||||
for p in patterns:
|
||||
print(f" - Prioridad {p['priority']}: {p['description']}")
|
||||
else:
|
||||
print(f"{name}: sin tokenización")
|
||||
|
||||
# 3. Probar tokenizador
|
||||
print("\n3. Probando tokenizador...")
|
||||
from tl_bracket_parser import UniversalTokenizer
|
||||
|
||||
tokenizer = UniversalTokenizer()
|
||||
tokenizer.debug = True
|
||||
print(f"Reglas cargadas: {len(tokenizer.tokenization_rules)}")
|
||||
|
||||
test_cases = [
|
||||
"192.168.1.1",
|
||||
"16#FF",
|
||||
"0xFF",
|
||||
"2#1010",
|
||||
"10.x.1.y"
|
||||
]
|
||||
|
||||
for case in test_cases:
|
||||
result = tokenizer.preprocess_tokens(case)
|
||||
print(f"'{case}' → '{result}'")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Prueba de uso real del sistema corregido
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🎯 PRUEBA DE USO REAL")
|
||||
print("=" * 40)
|
||||
|
||||
try:
|
||||
from main_evaluation import HybridEvaluationEngine
|
||||
|
||||
# Crear motor sin debug para simular uso real
|
||||
engine = HybridEvaluationEngine()
|
||||
engine.debug = False
|
||||
|
||||
# Casos exactos del usuario
|
||||
test_sequence = [
|
||||
"mask=255.240.0.3",
|
||||
"mask",
|
||||
"ip=10.1.1.1",
|
||||
"10.1.1.1",
|
||||
"ip"
|
||||
]
|
||||
|
||||
print("Secuencia de comandos del usuario:")
|
||||
print("-" * 40)
|
||||
|
||||
for i, command in enumerate(test_sequence, 1):
|
||||
print(f"\n{i}. {command}")
|
||||
result = engine.evaluate_line(command)
|
||||
|
||||
if result.is_error:
|
||||
print(f" ❌ Error: {result.error}")
|
||||
else:
|
||||
print(f" ✅ {result.result}")
|
||||
if hasattr(result.result, '__class__'):
|
||||
print(f" [{result.result.__class__.__name__}]")
|
||||
|
||||
print(f"\n" + "=" * 40)
|
||||
print("ESTADO FINAL:")
|
||||
print(f"Variables: {list(engine.symbol_table.keys())}")
|
||||
for var, value in engine.symbol_table.items():
|
||||
print(f" {var} = {value}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Prueba del caso específico del usuario: m=255.240.0.0 16#ff + 5
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🎯 Probando caso específico del usuario")
|
||||
|
||||
try:
|
||||
# Debug completo del sistema
|
||||
from type_registry import discover_and_register_types, get_registered_base_context
|
||||
from tl_bracket_parser import UniversalTokenizer
|
||||
|
||||
print("\n🔍 DEBUG COMPLETO DEL SISTEMA:")
|
||||
|
||||
# 1. Verificar registro de tipos
|
||||
print("\n1. Verificando registro de tipos...")
|
||||
registry_info = discover_and_register_types()
|
||||
print(f"Clases registradas: {registry_info['class_count']}")
|
||||
|
||||
context = get_registered_base_context()
|
||||
fundamental_classes = ['IntBase', 'FourBytes']
|
||||
|
||||
for cls_name in fundamental_classes:
|
||||
if cls_name in context:
|
||||
print(f"✅ {cls_name} está registrada")
|
||||
cls_obj = context[cls_name]
|
||||
if hasattr(cls_obj, 'get_tokenization_patterns'):
|
||||
patterns = cls_obj.get_tokenization_patterns()
|
||||
print(f" 🔧 Tiene {len(patterns)} reglas de tokenización")
|
||||
for i, pattern in enumerate(patterns):
|
||||
print(f" {i+1}. Prioridad {pattern['priority']}: {pattern['description']}")
|
||||
print(f" Patrón: {pattern['pattern']}")
|
||||
else:
|
||||
print(f" ❌ Sin reglas de tokenización")
|
||||
else:
|
||||
print(f"❌ {cls_name} NO está registrada")
|
||||
|
||||
# 2. Crear tokenizador y verificar reglas
|
||||
print("\n2. Creando tokenizador...")
|
||||
tokenizer = UniversalTokenizer()
|
||||
print(f"Reglas cargadas en tokenizador: {len(tokenizer.tokenization_rules)}")
|
||||
|
||||
if len(tokenizer.tokenization_rules) > 0:
|
||||
print("Reglas activas:")
|
||||
for i, rule in enumerate(tokenizer.tokenization_rules):
|
||||
print(f" {i+1}. {rule['class_name']} (prioridad {rule['priority']}): {rule['description']}")
|
||||
else:
|
||||
print("❌ No hay reglas cargadas en el tokenizador")
|
||||
|
||||
# 3. Probar tokenización manual
|
||||
print("\n3. Probando tokenización manual...")
|
||||
tokenizer.debug = True
|
||||
|
||||
test_simple = "192.168.1.1"
|
||||
print(f"\nProbando: '{test_simple}'")
|
||||
result = tokenizer.preprocess_tokens(test_simple)
|
||||
print(f"Resultado: '{result}'")
|
||||
|
||||
test_hex = "16#ff"
|
||||
print(f"\nProbando: '{test_hex}'")
|
||||
result = tokenizer.preprocess_tokens(test_hex)
|
||||
print(f"Resultado: '{result}'")
|
||||
|
||||
# 4. Verificar que las clases estén disponibles en motor
|
||||
print("\n4. Verificando motor de evaluación...")
|
||||
from main_evaluation import HybridEvaluationEngine
|
||||
|
||||
engine = HybridEvaluationEngine()
|
||||
engine.debug = True
|
||||
|
||||
print(f"Contexto del motor tiene {len(engine.base_context)} entradas")
|
||||
|
||||
# Verificar que IntBase y FourBytes estén en el contexto
|
||||
for cls_name in ['IntBase', 'FourBytes']:
|
||||
if cls_name in engine.base_context:
|
||||
print(f"✅ {cls_name} está en contexto del motor")
|
||||
else:
|
||||
print(f"❌ {cls_name} NO está en contexto del motor")
|
||||
|
||||
# 5. Probar evaluación directa
|
||||
print("\n5. Probando evaluación directa...")
|
||||
|
||||
# Probar evaluación manual sin tokenización
|
||||
test_manual = "IntBase('ff', 16)"
|
||||
print(f"Probando evaluación manual: {test_manual}")
|
||||
try:
|
||||
result = engine._eval_in_context(test_manual)
|
||||
print(f"✅ Resultado: {result} (tipo: {type(result)})")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
# Probar FourBytes manual
|
||||
test_manual2 = "FourBytes('192.168.1.1')"
|
||||
print(f"Probando evaluación manual: {test_manual2}")
|
||||
try:
|
||||
result = engine._eval_in_context(test_manual2)
|
||||
print(f"✅ Resultado: {result} (tipo: {type(result)})")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
|
@ -1,344 +1,257 @@
|
|||
"""
|
||||
Tokenization Parser - SISTEMA NUEVO que reemplaza los corchetes
|
||||
Convierte automáticamente patrones específicos en objetos tipados
|
||||
Tokenizador Universal con Auto-Descubrimiento de Reglas
|
||||
Sistema de tokenización distribuida donde cada clase define sus propias reglas
|
||||
"""
|
||||
import ast
|
||||
import re
|
||||
from typing import Tuple, Optional, Set, Dict
|
||||
from typing import List, Dict, Any, Callable, Tuple, Optional
|
||||
|
||||
# Importar tokenizador desde sympy_Base
|
||||
# Importar desde el registro de tipos
|
||||
try:
|
||||
from sympy_Base import preprocess_tokens
|
||||
TOKENIZER_AVAILABLE = True
|
||||
from type_registry import get_registered_base_context
|
||||
TYPE_REGISTRY_AVAILABLE = True
|
||||
except ImportError:
|
||||
TOKENIZER_AVAILABLE = False
|
||||
print("⚠️ Tokenizador no disponible, usando parser básico")
|
||||
TYPE_REGISTRY_AVAILABLE = False
|
||||
|
||||
|
||||
class UniversalTokenizer:
|
||||
"""
|
||||
Tokenizador que auto-descubre reglas de tokenización desde las clases registradas
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.debug = False
|
||||
self.tokenization_rules: List[Dict[str, Any]] = []
|
||||
self._discover_tokenization_rules()
|
||||
|
||||
def _discover_tokenization_rules(self):
|
||||
"""Auto-descubre reglas de tokenización desde todas las clases registradas"""
|
||||
if not TYPE_REGISTRY_AVAILABLE:
|
||||
if self.debug:
|
||||
print("⚠️ Sistema de tipos no disponible")
|
||||
return
|
||||
|
||||
try:
|
||||
# Obtener todas las clases registradas
|
||||
registered_classes = get_registered_base_context()
|
||||
|
||||
self.tokenization_rules.clear()
|
||||
|
||||
for class_name, class_obj in registered_classes.items():
|
||||
if hasattr(class_obj, 'get_tokenization_patterns'):
|
||||
try:
|
||||
patterns = class_obj.get_tokenization_patterns()
|
||||
for pattern_info in patterns:
|
||||
self.tokenization_rules.append({
|
||||
'class_name': class_name,
|
||||
'class_obj': class_obj,
|
||||
**pattern_info
|
||||
})
|
||||
|
||||
if self.debug:
|
||||
print(f"📋 Regla tokenización: {class_name} - {pattern_info.get('description', 'Sin descripción')}")
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"⚠️ Error obteniendo patrones de {class_name}: {e}")
|
||||
|
||||
# Ordenar por prioridad (menor número = mayor prioridad)
|
||||
self.tokenization_rules.sort(key=lambda x: x.get('priority', 100))
|
||||
|
||||
if self.debug:
|
||||
print(f"🔧 {len(self.tokenization_rules)} reglas de tokenización cargadas")
|
||||
for rule in self.tokenization_rules:
|
||||
print(f" {rule['priority']:2d}: {rule['class_name']} - {rule.get('description', '')}")
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"⚠️ Error en auto-descubrimiento: {e}")
|
||||
self.tokenization_rules.clear()
|
||||
|
||||
def reload_tokenization_rules(self):
|
||||
"""Recarga reglas de tokenización del registro"""
|
||||
if self.debug:
|
||||
print("🔄 Recargando reglas de tokenización...")
|
||||
self._discover_tokenization_rules()
|
||||
|
||||
def preprocess_tokens(self, expression: str) -> str:
|
||||
"""Aplica todas las reglas de tokenización descubiertas"""
|
||||
if not self.tokenization_rules:
|
||||
return expression
|
||||
|
||||
result = expression
|
||||
original = expression
|
||||
conversion_stats = {}
|
||||
|
||||
for rule in self.tokenization_rules:
|
||||
try:
|
||||
pattern = rule['pattern']
|
||||
replacement_func = rule['replacement']
|
||||
rule_name = f"{rule['class_name']}"
|
||||
|
||||
# Contar matches antes de aplicar
|
||||
matches_before = len(re.findall(pattern, result))
|
||||
|
||||
# Aplicar transformación
|
||||
if callable(replacement_func):
|
||||
result = re.sub(pattern, replacement_func, result)
|
||||
else:
|
||||
result = re.sub(pattern, replacement_func, result)
|
||||
|
||||
# Contar conversiones
|
||||
matches_after = len(re.findall(pattern, result))
|
||||
conversions = matches_before - matches_after
|
||||
|
||||
if conversions > 0:
|
||||
conversion_stats[rule_name] = conversion_stats.get(rule_name, 0) + conversions
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"⚠️ Error aplicando regla {rule['class_name']}: {e}")
|
||||
continue
|
||||
|
||||
if self.debug and result != original:
|
||||
print(f"🔧 Tokenización: '{original}' → '{result}'")
|
||||
if conversion_stats:
|
||||
for rule_name, count in conversion_stats.items():
|
||||
print(f" {rule_name}: {count} conversiones")
|
||||
|
||||
return result
|
||||
|
||||
def get_tokenization_info(self) -> List[Dict[str, Any]]:
|
||||
"""Retorna información sobre las reglas de tokenización activas"""
|
||||
return [
|
||||
{
|
||||
'class': rule['class_name'],
|
||||
'priority': rule.get('priority', 100),
|
||||
'description': rule.get('description', 'Sin descripción'),
|
||||
'pattern': rule['pattern']
|
||||
}
|
||||
for rule in self.tokenization_rules
|
||||
]
|
||||
|
||||
def test_tokenization(self, test_expressions: List[str]) -> Dict[str, str]:
|
||||
"""Prueba tokenización en lista de expresiones"""
|
||||
results = {}
|
||||
for expr in test_expressions:
|
||||
results[expr] = self.preprocess_tokens(expr)
|
||||
return results
|
||||
|
||||
|
||||
class TokenizationParser:
|
||||
"""
|
||||
Nuevo parser que reemplaza el sistema de corchetes
|
||||
Convierte automáticamente patrones en objetos tipados
|
||||
Parser que integra tokenización universal con BracketParser para compatibilidad
|
||||
"""
|
||||
|
||||
# Operadores de comparación que pueden formar ecuaciones
|
||||
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
|
||||
|
||||
def __init__(self, use_tokenizer: bool = True):
|
||||
self.debug = False
|
||||
self.use_tokenizer = use_tokenizer and TOKENIZER_AVAILABLE
|
||||
def __init__(self, enable_tokenization=True, debug=False):
|
||||
self.enable_tokenization = enable_tokenization
|
||||
self.debug = debug
|
||||
self.tokenizer = UniversalTokenizer()
|
||||
self.tokenizer.debug = debug
|
||||
|
||||
# Estadísticas de tokenización
|
||||
self.stats = {
|
||||
'intbase_conversions': 0,
|
||||
'fourbytes_conversions': 0,
|
||||
'total_lines_processed': 0
|
||||
# Estadísticas
|
||||
self.conversion_stats = {}
|
||||
self.total_expressions_processed = 0
|
||||
|
||||
def process_expression(self, expression: str) -> str:
|
||||
"""Procesa una expresión aplicando tokenización si está habilitada"""
|
||||
self.total_expressions_processed += 1
|
||||
|
||||
if not self.enable_tokenization:
|
||||
return expression
|
||||
|
||||
# Aplicar tokenización universal
|
||||
tokenized = self.tokenizer.preprocess_tokens(expression)
|
||||
|
||||
if self.debug and tokenized != expression:
|
||||
print(f"🔧 Parser: '{expression}' → '{tokenized}'")
|
||||
|
||||
return tokenized
|
||||
|
||||
def reload_tokenization_rules(self):
|
||||
"""Recarga las reglas de tokenización"""
|
||||
self.tokenizer.reload_tokenization_rules()
|
||||
|
||||
def get_tokenization_info(self) -> Dict[str, Any]:
|
||||
"""Información completa del sistema de tokenización"""
|
||||
return {
|
||||
'rules': self.tokenizer.get_tokenization_info(),
|
||||
'statistics': {
|
||||
'total_processed': self.total_expressions_processed,
|
||||
'tokenization_enabled': self.enable_tokenization,
|
||||
'rules_count': len(self.tokenizer.tokenization_rules)
|
||||
}
|
||||
}
|
||||
|
||||
def get_tokenization_stats(self) -> Dict[str, int]:
|
||||
"""Retorna estadísticas de tokenización"""
|
||||
return self.stats.copy()
|
||||
|
||||
def reset_stats(self):
|
||||
"""Reinicia estadísticas"""
|
||||
self.stats = {k: 0 for k in self.stats}
|
||||
|
||||
def parse_line(self, code_line: str) -> Tuple[str, str]:
|
||||
"""
|
||||
Parsea una línea de código aplicando tokenización automática
|
||||
def test_patterns(self, test_cases: List[str]) -> Dict[str, Any]:
|
||||
"""Prueba patrones de tokenización con casos de prueba"""
|
||||
results = self.tokenizer.test_tokenization(test_cases)
|
||||
|
||||
Returns:
|
||||
(transformed_code, parse_info): Código transformado e información de parsing
|
||||
"""
|
||||
original_line = code_line.strip()
|
||||
if not original_line or original_line.startswith('#'):
|
||||
return code_line, "comment"
|
||||
|
||||
self.stats['total_lines_processed'] += 1
|
||||
|
||||
try:
|
||||
# 1. Detectar y transformar atajo solve
|
||||
transformed_line, has_solve_shortcut = self._transform_solve_shortcut(original_line)
|
||||
if has_solve_shortcut:
|
||||
return transformed_line, "solve_shortcut"
|
||||
|
||||
# 2. Detectar asignaciones de variables
|
||||
if self._is_assignment(original_line):
|
||||
return self._transform_assignment(original_line), "assignment"
|
||||
|
||||
# 3. Detectar ecuaciones standalone
|
||||
if self._is_standalone_equation(original_line):
|
||||
return f'_add_equation("{original_line}")', "equation"
|
||||
|
||||
# 4. NUEVO: Aplicar tokenización automática
|
||||
if self.use_tokenizer:
|
||||
transformed_line = self._apply_tokenization(original_line)
|
||||
|
||||
# Contar conversiones para estadísticas
|
||||
intbase_count = transformed_line.count('IntBase(')
|
||||
fourbytes_count = transformed_line.count('FourBytes(')
|
||||
self.stats['intbase_conversions'] += intbase_count
|
||||
self.stats['fourbytes_conversions'] += fourbytes_count
|
||||
|
||||
if transformed_line != original_line:
|
||||
if self.debug:
|
||||
print(f"🔧 Tokenización: '{original_line}' → '{transformed_line}'")
|
||||
return transformed_line, "tokenized"
|
||||
|
||||
# 5. Si no hay transformaciones, devolver original
|
||||
return original_line, "expression"
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"Error parsing line '{original_line}': {e}")
|
||||
return code_line, "parse_error"
|
||||
|
||||
def _apply_tokenization(self, line: str) -> str:
|
||||
"""
|
||||
Aplica tokenización automática usando el tokenizador de sympy_Base
|
||||
"""
|
||||
if not TOKENIZER_AVAILABLE:
|
||||
return line
|
||||
|
||||
try:
|
||||
return preprocess_tokens(line)
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"Error en tokenización: {e}")
|
||||
return line
|
||||
|
||||
def _transform_solve_shortcut(self, line: str) -> Tuple[str, bool]:
|
||||
"""
|
||||
Transforma 'variable=?' en '_solve_variable_in_system("variable")'
|
||||
"""
|
||||
# Pattern: variable_name = ?
|
||||
pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\?$'
|
||||
match = re.match(pattern, line.strip())
|
||||
|
||||
if match:
|
||||
var_name = match.group(1)
|
||||
return f'_solve_variable_in_system("{var_name}")', True
|
||||
|
||||
return line, False
|
||||
|
||||
def _is_assignment(self, line: str) -> bool:
|
||||
"""
|
||||
Detecta si una línea es una asignación de variable
|
||||
NUEVA LÓGICA: Priorizar asignaciones, ser menos estricto
|
||||
"""
|
||||
try:
|
||||
# Pattern: variable = expresión (que no sea comparación)
|
||||
if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']):
|
||||
# Verificar que sea una asignación válida de Python
|
||||
parts = line.split('=', 1)
|
||||
if len(parts) == 2:
|
||||
var_part = parts[0].strip()
|
||||
expr_part = parts[1].strip()
|
||||
|
||||
# Verificar que la parte izquierda sea un identificador válido
|
||||
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part):
|
||||
# NUEVA LÓGICA: Si tiene formato de asignación válida, asumir que ES asignación
|
||||
# Las ecuaciones son solo para casos muy específicos como solve()
|
||||
return True
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def _transform_assignment(self, line: str) -> str:
|
||||
"""Transforma asignación a llamada de función especial"""
|
||||
parts = line.split('=', 1)
|
||||
var_name = parts[0].strip()
|
||||
expression = parts[1].strip()
|
||||
|
||||
# Aplicar tokenización a la expresión
|
||||
if self.use_tokenizer:
|
||||
expression = self._apply_tokenization(expression)
|
||||
|
||||
return f'_assign_variable("{var_name}", {expression})'
|
||||
|
||||
def _is_standalone_equation(self, line: str) -> bool:
|
||||
"""
|
||||
Determina si una línea es una ecuación standalone
|
||||
NUEVA LÓGICA: Solo ecuaciones matemáticas obvias, no asignaciones
|
||||
"""
|
||||
try:
|
||||
# Primero verificar si contiene '=' simple
|
||||
if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']):
|
||||
return False
|
||||
|
||||
# NUEVA LÓGICA: Si ya fue clasificada como asignación, NO es ecuación
|
||||
if self._is_assignment(line):
|
||||
return False
|
||||
|
||||
try:
|
||||
tree = ast.parse(line.strip())
|
||||
|
||||
if not tree.body:
|
||||
return False
|
||||
|
||||
node = tree.body[0]
|
||||
|
||||
# Si es una expresión (no asignación), verificar comparaciones
|
||||
if isinstance(node, ast.Expr):
|
||||
if isinstance(node.value, ast.Compare):
|
||||
for op in node.value.ops:
|
||||
if isinstance(op, (ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except SyntaxError:
|
||||
# NUEVA LÓGICA: Solo tratar como ecuación si NO tiene formato de asignación válida
|
||||
parts = line.split('=', 1)
|
||||
if len(parts) == 2:
|
||||
var_part = parts[0].strip()
|
||||
# Si la parte izquierda no es un identificador válido, puede ser ecuación matemática
|
||||
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part):
|
||||
if self.debug:
|
||||
print(f"🔍 '{line}' error AST y no es asignación válida → Tratando como ECUACIÓN")
|
||||
return True
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
if self.debug:
|
||||
print(f"🚨 Error en _is_standalone_equation: {e}")
|
||||
return False
|
||||
|
||||
def preview_tokenization(self, text: str) -> str:
|
||||
"""
|
||||
Muestra preview de cómo se tokenizaría el texto sin ejecutar
|
||||
Útil para debugging y testing
|
||||
"""
|
||||
if not TOKENIZER_AVAILABLE:
|
||||
return "Tokenizador no disponible"
|
||||
|
||||
lines = text.split('\n')
|
||||
preview_lines = []
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
original = line.strip()
|
||||
if not original or original.startswith('#'):
|
||||
preview_lines.append(f"{i:2}: {line}")
|
||||
continue
|
||||
|
||||
tokenized = self._apply_tokenization(original)
|
||||
if tokenized != original:
|
||||
preview_lines.append(f"{i:2}: {original}")
|
||||
preview_lines.append(f" → {tokenized}")
|
||||
else:
|
||||
preview_lines.append(f"{i:2}: {line}")
|
||||
|
||||
return '\n'.join(preview_lines)
|
||||
|
||||
def test_patterns(self) -> str:
|
||||
"""
|
||||
Prueba los patrones de tokenización con ejemplos
|
||||
"""
|
||||
test_cases = [
|
||||
# IntBase patterns
|
||||
"16#FF",
|
||||
"2#1010",
|
||||
"8#777",
|
||||
"16#x0",
|
||||
"2#101x",
|
||||
|
||||
# FourBytes patterns
|
||||
"192.168.1.1",
|
||||
"255.255.0.0",
|
||||
"10.1.x.2",
|
||||
"a.b.c.d",
|
||||
|
||||
# Mixed patterns
|
||||
"16#FF + 192.168.1.1",
|
||||
"2#1010 * 10.0.0.1",
|
||||
|
||||
# Should NOT be tokenized
|
||||
"obj.method.call()",
|
||||
"x.y", # Only 2 elements
|
||||
"a.b.c.d.e", # 5 elements
|
||||
]
|
||||
|
||||
results = []
|
||||
for test in test_cases:
|
||||
tokenized = self._apply_tokenization(test) if TOKENIZER_AVAILABLE else test
|
||||
status = "✓ TOKENIZED" if tokenized != test else "○ NO CHANGE"
|
||||
results.append(f"{status}: '{test}' → '{tokenized}'")
|
||||
|
||||
return '\n'.join(results)
|
||||
return {
|
||||
'input_expressions': test_cases,
|
||||
'tokenized_results': results,
|
||||
'successful_conversions': [expr for expr, result in results.items() if expr != result],
|
||||
'failed_conversions': [expr for expr, result in results.items() if expr == result]
|
||||
}
|
||||
|
||||
|
||||
# Mantener compatibilidad con el sistema anterior
|
||||
# Mantener BracketParser como alias de compatibilidad
|
||||
class BracketParser(TokenizationParser):
|
||||
"""
|
||||
Alias para compatibilidad con código existente
|
||||
Ahora usa el nuevo sistema de tokenización
|
||||
Alias de compatibilidad para el código existente
|
||||
"""
|
||||
|
||||
def __init__(self, use_type_registry: bool = True):
|
||||
# Ignorar use_type_registry, ya no se usa
|
||||
super().__init__(use_tokenizer=True)
|
||||
def __init__(self, enable_tokenization=True, debug=False):
|
||||
super().__init__(enable_tokenization, debug)
|
||||
if debug:
|
||||
print("🔄 BracketParser iniciado con tokenización universal")
|
||||
|
||||
def reload_bracket_classes(self):
|
||||
"""Método de compatibilidad - no hace nada en el nuevo sistema"""
|
||||
if self.debug:
|
||||
print("🔄 reload_bracket_classes() llamado - no necesario en nuevo sistema")
|
||||
|
||||
def add_bracket_class(self, class_name: str):
|
||||
"""Método de compatibilidad - no hace nada en el nuevo sistema"""
|
||||
if self.debug:
|
||||
print(f"🔄 add_bracket_class({class_name}) llamado - no necesario en nuevo sistema")
|
||||
|
||||
def remove_bracket_class(self, class_name: str):
|
||||
"""Método de compatibilidad - no hace nada en el nuevo sistema"""
|
||||
if self.debug:
|
||||
print(f"🔄 remove_bracket_class({class_name}) llamado - no necesario en nuevo sistema")
|
||||
|
||||
def get_bracket_classes(self) -> Set[str]:
|
||||
"""Método de compatibilidad - retorna conjunto vacío"""
|
||||
return set()
|
||||
|
||||
def has_bracket_class(self, class_name: str) -> bool:
|
||||
"""Método de compatibilidad - siempre retorna False"""
|
||||
return False
|
||||
def parse_expression(self, expression: str) -> str:
|
||||
"""Método de compatibilidad que usa el nuevo sistema"""
|
||||
return self.process_expression(expression)
|
||||
|
||||
|
||||
# ========== FUNCIONES DE UTILIDAD ==========
|
||||
# Funciones de utilidad para testing y preview
|
||||
def preview_tokenization(expression: str, debug: bool = True) -> Dict[str, Any]:
|
||||
"""Vista previa de cómo se tokenizaría una expresión"""
|
||||
tokenizer = UniversalTokenizer()
|
||||
tokenizer.debug = debug
|
||||
|
||||
original = expression
|
||||
tokenized = tokenizer.preprocess_tokens(expression)
|
||||
|
||||
return {
|
||||
'original': original,
|
||||
'tokenized': tokenized,
|
||||
'changed': original != tokenized,
|
||||
'rules_applied': tokenizer.get_tokenization_info()
|
||||
}
|
||||
|
||||
def test_tokenization_system():
|
||||
"""Función de testing completa del sistema de tokenización"""
|
||||
print("🧪 TESTING SISTEMA DE TOKENIZACIÓN")
|
||||
|
||||
def test_tokenization_patterns(test_cases: List[str]) -> None:
|
||||
"""Función de testing para validar patrones de tokenización"""
|
||||
parser = TokenizationParser(debug=True)
|
||||
|
||||
print("🧪 Probando patrones de tokenización...")
|
||||
print("=" * 50)
|
||||
|
||||
parser = TokenizationParser(use_tokenizer=True)
|
||||
parser.debug = True
|
||||
results = parser.test_patterns(test_cases)
|
||||
|
||||
# Test patterns
|
||||
print("\n1. Test de patrones básicos:")
|
||||
print(parser.test_patterns())
|
||||
print(f"\nResultados de {len(test_cases)} casos de prueba:")
|
||||
print(f"✅ Exitosos: {len(results['successful_conversions'])}")
|
||||
print(f"❌ Sin cambios: {len(results['failed_conversions'])}")
|
||||
|
||||
# Test de líneas completas
|
||||
test_lines = [
|
||||
"ip = 192.168.1.1",
|
||||
"mask = 16#ffffff00",
|
||||
"net = 10.x.y.0",
|
||||
"result = 16#FF + 2#1010",
|
||||
"a.b.c.d + 255.255.255.0",
|
||||
"x = solve(y + 2)", # No debería tokenizarse solve
|
||||
]
|
||||
if results['successful_conversions']:
|
||||
print("\n🎉 Conversiones exitosas:")
|
||||
for expr in results['successful_conversions']:
|
||||
result = results['tokenized_results'][expr]
|
||||
print(f" '{expr}' → '{result}'")
|
||||
|
||||
print(f"\n2. Test de líneas completas:")
|
||||
for line in test_lines:
|
||||
transformed, info = parser.parse_line(line)
|
||||
print(f" '{line}' → '{transformed}' [{info}]")
|
||||
if results['failed_conversions']:
|
||||
print("\n⚠️ Sin conversión:")
|
||||
for expr in results['failed_conversions']:
|
||||
print(f" '{expr}' (sin cambios)")
|
||||
|
||||
# Estadísticas
|
||||
print(f"\n3. Estadísticas:")
|
||||
stats = parser.get_tokenization_stats()
|
||||
for key, value in stats.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
print("\n✅ Testing completado")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_tokenization_system()
|
||||
print("\n📋 Reglas activas:")
|
||||
info = parser.get_tokenization_info()
|
||||
for rule in info['rules']:
|
||||
print(f" {rule['priority']:2d}: {rule['class']} - {rule['description']}")
|
Loading…
Reference in New Issue