654 lines
23 KiB
Python
654 lines
23 KiB
Python
"""
|
|
Clase base híbrida que combina SymPy con funcionalidad especializada
|
|
"""
|
|
import sympy
|
|
from sympy import Basic, Symbol, sympify
|
|
from typing import Any, Optional, Dict
|
|
import re
|
|
from class_base import ClassBase
|
|
|
|
|
|
class SympyClassBase(ClassBase, sympy.Basic):
|
|
"""Para clases que necesitan álgebra completa"""
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
# La subclase (ej. Class_IP4) es responsable de pasar los argumentos correctos
|
|
# que sympy.Basic.__new__ espera.
|
|
obj = sympy.Basic.__new__(cls, *args, **kwargs)
|
|
return obj
|
|
|
|
def __init__(self, value, original_str=""):
|
|
ClassBase.__init__(self, value, original_str)
|
|
# La inicialización de sympy.Basic se maneja principalmente a través de __new__.
|
|
# Atributos adicionales de sympy como self.args son establecidos por sympy.Basic
|
|
# basado en los argumentos pasados a __new__.
|
|
|
|
def _sympystr(self, printer):
|
|
# Las subclases deben implementar esto para una representación SymPy adecuada.
|
|
# Por defecto, usa el __str__ de ClassBase, que es str(self.value).
|
|
# Si self.value es un objeto Sympy, str(self.value) ya dará una buena representación.
|
|
# Si self.value no es un objeto Sympy pero se desea una forma SymPy específica,
|
|
# esto debería ser sobreescrito.
|
|
return str(self.value)
|
|
|
|
def _init_specialized(self):
|
|
"""Override en subclases para inicialización especializada"""
|
|
pass
|
|
|
|
@property
|
|
def value(self):
|
|
"""Acceso al valor interno"""
|
|
return self._value
|
|
|
|
@property
|
|
def original_str(self):
|
|
"""String original de entrada"""
|
|
return self._original_str
|
|
|
|
# Propiedades requeridas por SymPy
|
|
@property
|
|
def args(self):
|
|
"""Argumentos para SymPy - retorna valor como argumento"""
|
|
return (sympify(self._value),)
|
|
|
|
@property
|
|
def func(self):
|
|
"""Función constructora para SymPy"""
|
|
return self.__class__
|
|
|
|
def _latex(self, printer):
|
|
"""Representación LaTeX"""
|
|
return self._sympystr(printer)
|
|
|
|
def __str__(self):
|
|
"""Representación string para display"""
|
|
return str(self._value)
|
|
|
|
def __repr__(self):
|
|
"""Representación para debugging"""
|
|
return f"{self.__class__.__name__}('{self._original_str}')"
|
|
|
|
def evalf(self, n=15, **options):
|
|
"""Evaluación numérica si es posible"""
|
|
if isinstance(self._value, (int, float, complex)):
|
|
return sympy.Float(self._value)
|
|
return self
|
|
|
|
def _eval_evalf(self, prec):
|
|
"""Evaluación numérica para SymPy"""
|
|
if isinstance(self._value, (int, float, complex)):
|
|
return sympy.Float(self._value, prec)
|
|
return None
|
|
|
|
def __dec__(self):
|
|
"""Conversión a decimal para compatibilidad"""
|
|
if isinstance(self._value, (int, float, complex)):
|
|
return self._value
|
|
raise TypeError(f"Cannot convert {self.__class__.__name__} to decimal")
|
|
|
|
# Métodos requeridos por SymPy para manipulación algebraica
|
|
def as_coeff_Mul(self, rational=True):
|
|
"""Descomponer como coeficiente * términos"""
|
|
if isinstance(self._value, (int, float)):
|
|
return sympify(self._value), sympify(1)
|
|
return sympify(1), self
|
|
|
|
def as_coeff_Add(self, rational=True):
|
|
"""Descomponer como coeficiente + términos"""
|
|
if isinstance(self._value, (int, float)):
|
|
return sympify(self._value), sympify(0)
|
|
return sympify(0), self
|
|
|
|
def as_base_exp(self):
|
|
"""Descomponer como base^exponente"""
|
|
return self, sympify(1)
|
|
|
|
def as_numer_denom(self):
|
|
"""Descomponer como numerador/denominador"""
|
|
return self, sympify(1)
|
|
|
|
# Propiedades de tipo para SymPy
|
|
@property
|
|
def is_number(self):
|
|
"""Indica si es un número"""
|
|
return isinstance(self._value, (int, float, complex))
|
|
|
|
@property
|
|
def is_real(self):
|
|
"""Indica si es real"""
|
|
return isinstance(self._value, (int, float)) and not isinstance(self._value, complex)
|
|
|
|
@property
|
|
def is_integer(self):
|
|
"""Indica si es entero"""
|
|
return isinstance(self._value, int)
|
|
|
|
@property
|
|
def is_rational(self):
|
|
"""Indica si es racional"""
|
|
return isinstance(self._value, (int, float))
|
|
|
|
@property
|
|
def is_irrational(self):
|
|
"""Indica si es irracional"""
|
|
return False
|
|
|
|
@property
|
|
def is_positive(self):
|
|
"""Indica si es positivo"""
|
|
if isinstance(self._value, (int, float)):
|
|
return self._value > 0
|
|
return None
|
|
|
|
@property
|
|
def is_negative(self):
|
|
"""Indica si es negativo"""
|
|
if isinstance(self._value, (int, float)):
|
|
return self._value < 0
|
|
return None
|
|
|
|
@property
|
|
def is_zero(self):
|
|
"""Indica si es cero"""
|
|
if isinstance(self._value, (int, float)):
|
|
return self._value == 0
|
|
return None
|
|
|
|
@property
|
|
def is_finite(self):
|
|
"""Indica si es finito"""
|
|
return isinstance(self._value, (int, float, complex))
|
|
|
|
@property
|
|
def is_infinite(self):
|
|
"""Indica si es infinito"""
|
|
return False
|
|
|
|
@property
|
|
def is_commutative(self):
|
|
"""Indica si es conmutativo"""
|
|
return True
|
|
|
|
# Operaciones aritméticas simples que retornan el valor numérico para integración con SymPy
|
|
def __add__(self, other):
|
|
"""Suma que integra con SymPy"""
|
|
if isinstance(other, (int, float, complex)):
|
|
# Retornar valor numérico para que SymPy maneje la operación
|
|
return sympify(self._value + other)
|
|
elif hasattr(other, '_value'):
|
|
return sympify(self._value + other._value)
|
|
else:
|
|
# Dejar que SymPy maneje la operación
|
|
return sympify(self._value) + sympify(other)
|
|
|
|
def __radd__(self, other):
|
|
"""Suma reversa"""
|
|
return sympify(other) + sympify(self._value)
|
|
|
|
def __mul__(self, other):
|
|
"""Multiplicación que integra con SymPy"""
|
|
if isinstance(other, (int, float, complex)):
|
|
return sympify(self._value * other)
|
|
elif hasattr(other, '_value'):
|
|
return sympify(self._value * other._value)
|
|
else:
|
|
return sympify(self._value) * sympify(other)
|
|
|
|
def __rmul__(self, other):
|
|
"""Multiplicación reversa"""
|
|
return sympify(other) * sympify(self._value)
|
|
|
|
def __sub__(self, other):
|
|
"""Resta"""
|
|
if isinstance(other, (int, float, complex)):
|
|
return sympify(self._value - other)
|
|
elif hasattr(other, '_value'):
|
|
return sympify(self._value - other._value)
|
|
else:
|
|
return sympify(self._value) - sympify(other)
|
|
|
|
def __rsub__(self, other):
|
|
"""Resta reversa"""
|
|
return sympify(other) - sympify(self._value)
|
|
|
|
def __truediv__(self, other):
|
|
"""División"""
|
|
if isinstance(other, (int, float, complex)):
|
|
if other == 0:
|
|
raise ZeroDivisionError("Division by zero")
|
|
return sympify(self._value / other)
|
|
elif hasattr(other, '_value'):
|
|
if other._value == 0:
|
|
raise ZeroDivisionError("Division by zero")
|
|
return sympify(self._value / other._value)
|
|
else:
|
|
return sympify(self._value) / sympify(other)
|
|
|
|
def __rtruediv__(self, other):
|
|
"""División reversa"""
|
|
if self._value == 0:
|
|
raise ZeroDivisionError("Division by zero")
|
|
return sympify(other) / sympify(self._value)
|
|
|
|
def __pow__(self, other):
|
|
"""Potencia"""
|
|
if isinstance(other, (int, float, complex)):
|
|
return sympify(self._value ** other)
|
|
elif hasattr(other, '_value'):
|
|
return sympify(self._value ** other._value)
|
|
else:
|
|
return sympify(self._value) ** sympify(other)
|
|
|
|
def __rpow__(self, other):
|
|
"""Potencia reversa"""
|
|
return sympify(other) ** sympify(self._value)
|
|
|
|
@staticmethod
|
|
def Helper(input_str):
|
|
"""Override en subclases para ayuda contextual"""
|
|
return None
|
|
|
|
|
|
# ========== CLASES BASE UNIVERSALES PARA TOKENIZACIÓN ==========
|
|
|
|
class IntBase(SympyClassBase):
|
|
"""
|
|
Representación universal de números en cualquier base
|
|
con soporte algebraico completo y operaciones aritméticas
|
|
"""
|
|
|
|
def __init__(self, value_str, base=10):
|
|
self.value_str = str(value_str)
|
|
self.base = int(base)
|
|
|
|
# Detección mejorada de símbolos: considerar la base
|
|
self.has_symbols = self._has_symbols_for_base()
|
|
|
|
if self.has_symbols:
|
|
# Modo algebraico: mantener como expresión simbólica
|
|
self._symbolic_value = self._parse_symbolic_base()
|
|
self._numeric_value = None # No hay valor numérico
|
|
super().__init__(self._symbolic_value, f"{self.base}#{self.value_str}")
|
|
else:
|
|
# Modo numérico: convertir a entero
|
|
self._numeric_value = int(self.value_str, self.base)
|
|
self._symbolic_value = None # No hay valor simbólico
|
|
super().__init__(self._numeric_value, f"{self.base}#{self.value_str}")
|
|
|
|
def _has_symbols_for_base(self):
|
|
"""Detecta si hay símbolos considerando la base numérica"""
|
|
valid_digits = "0123456789ABCDEF"[:self.base]
|
|
|
|
for char in self.value_str.upper():
|
|
if char not in valid_digits:
|
|
# Es un símbolo (no un dígito válido para esta base)
|
|
return True
|
|
|
|
return False
|
|
|
|
def _parse_symbolic_base(self):
|
|
"""Convierte valor con símbolos a expresión SymPy"""
|
|
# Ejemplo: "x0" base 16 → x*16^1 + 0*16^0
|
|
# Ejemplo: "101x" base 2 → 1*2^3 + 0*2^2 + 1*2^1 + x*2^0
|
|
result = 0
|
|
digits = list(self.value_str)
|
|
|
|
for i, digit in enumerate(reversed(digits)):
|
|
power = self.base ** i
|
|
if digit.isdigit():
|
|
coefficient = int(digit)
|
|
elif digit in 'ABCDEF':
|
|
coefficient = ord(digit) - ord('A') + 10
|
|
elif digit in 'abcdef':
|
|
coefficient = ord(digit) - ord('a') + 10
|
|
else:
|
|
# Es un símbolo
|
|
coefficient = sympy.Symbol(digit)
|
|
|
|
result += coefficient * power
|
|
|
|
return result
|
|
|
|
def to_base(self, new_base):
|
|
"""Convierte a nueva base"""
|
|
if self.has_symbols:
|
|
# En modo algebraico, mantener expresión
|
|
return IntBase(f"({self._symbolic_value})_base{new_base}", new_base)
|
|
else:
|
|
# En modo numérico, convertir directamente
|
|
new_value_str = self._convert_to_base_string(self._numeric_value, new_base)
|
|
return IntBase(new_value_str, new_base)
|
|
|
|
@staticmethod
|
|
def _convert_to_base_string(value, base):
|
|
"""Convierte entero a string en base específica"""
|
|
if value == 0:
|
|
return "0"
|
|
digits = "0123456789ABCDEF"
|
|
result = ""
|
|
while value:
|
|
result = digits[value % base] + result
|
|
value //= base
|
|
return result
|
|
|
|
@property
|
|
def value(self):
|
|
"""Propiedad para acceder al valor (numérico o simbólico)"""
|
|
if self.has_symbols:
|
|
return self._symbolic_value
|
|
else:
|
|
return self._numeric_value
|
|
|
|
# ========== OPERADORES ARITMÉTICOS ==========
|
|
|
|
def __add__(self, other):
|
|
"""Suma: mantiene la base del operando izquierdo"""
|
|
if self.has_symbols:
|
|
return super().__add__(other) # Delegar a SymPy
|
|
|
|
if isinstance(other, IntBase):
|
|
if other.has_symbols:
|
|
return super().__add__(other)
|
|
result_value = self._numeric_value + other._numeric_value
|
|
elif isinstance(other, int):
|
|
result_value = self._numeric_value + other
|
|
else:
|
|
return super().__add__(other)
|
|
|
|
result_str = self._convert_to_base_string(result_value, self.base)
|
|
return IntBase(result_str, self.base)
|
|
|
|
def __sub__(self, other):
|
|
"""Resta: mantiene la base del operando izquierdo"""
|
|
if self.has_symbols:
|
|
return super().__sub__(other)
|
|
|
|
if isinstance(other, IntBase):
|
|
if other.has_symbols:
|
|
return super().__sub__(other)
|
|
result_value = self._numeric_value - other._numeric_value
|
|
elif isinstance(other, int):
|
|
result_value = self._numeric_value - other
|
|
else:
|
|
return super().__sub__(other)
|
|
|
|
result_str = self._convert_to_base_string(result_value, self.base)
|
|
return IntBase(result_str, self.base)
|
|
|
|
def __mul__(self, other):
|
|
"""Multiplicación: mantiene la base del operando izquierdo"""
|
|
if self.has_symbols:
|
|
return super().__mul__(other)
|
|
|
|
if isinstance(other, IntBase):
|
|
if other.has_symbols:
|
|
return super().__mul__(other)
|
|
result_value = self._numeric_value * other._numeric_value
|
|
elif isinstance(other, int):
|
|
result_value = self._numeric_value * other
|
|
else:
|
|
return super().__mul__(other)
|
|
|
|
result_str = self._convert_to_base_string(result_value, self.base)
|
|
return IntBase(result_str, self.base)
|
|
|
|
def __truediv__(self, other):
|
|
"""División: mantiene la base del operando izquierdo"""
|
|
if self.has_symbols:
|
|
return super().__truediv__(other)
|
|
|
|
if isinstance(other, IntBase):
|
|
if other.has_symbols:
|
|
return super().__truediv__(other)
|
|
result_value = self._numeric_value // other._numeric_value # División entera
|
|
elif isinstance(other, int):
|
|
result_value = self._numeric_value // other
|
|
else:
|
|
return super().__truediv__(other)
|
|
|
|
result_str = self._convert_to_base_string(result_value, self.base)
|
|
return IntBase(result_str, self.base)
|
|
|
|
def __mod__(self, other):
|
|
"""Módulo: mantiene la base del operando izquierdo"""
|
|
if self.has_symbols:
|
|
return super().__mod__(other)
|
|
|
|
if isinstance(other, IntBase):
|
|
if other.has_symbols:
|
|
return super().__mod__(other)
|
|
result_value = self._numeric_value % other._numeric_value
|
|
elif isinstance(other, int):
|
|
result_value = self._numeric_value % other
|
|
else:
|
|
return super().__mod__(other)
|
|
|
|
result_str = self._convert_to_base_string(result_value, self.base)
|
|
return IntBase(result_str, self.base)
|
|
|
|
# Operadores reversos para int + IntBase
|
|
def __radd__(self, other):
|
|
return self.__add__(other)
|
|
|
|
def __rsub__(self, other):
|
|
if isinstance(other, int):
|
|
result_value = other - self._numeric_value
|
|
result_str = self._convert_to_base_string(result_value, self.base)
|
|
return IntBase(result_str, self.base)
|
|
return super().__rsub__(other)
|
|
|
|
def __rmul__(self, other):
|
|
return self.__mul__(other)
|
|
|
|
def __str__(self):
|
|
"""Representación string"""
|
|
return f"{self.base}#{self.value_str}"
|
|
|
|
def __repr__(self):
|
|
return f"IntBase('{self.value_str}', {self.base})"
|
|
|
|
|
|
class FourBytes(SympyClassBase):
|
|
"""
|
|
Representación universal de patrones de 4 elementos
|
|
con soporte algebraico completo y operaciones aritméticas
|
|
"""
|
|
|
|
def __init__(self, dotted_string):
|
|
self.original = str(dotted_string)
|
|
self.elements = self.original.split('.')
|
|
|
|
if len(self.elements) != 4:
|
|
raise ValueError(f"FourBytes requiere exactamente 4 elementos: {dotted_string}")
|
|
|
|
self.has_symbols = any(not elem.isdigit() for elem in self.elements)
|
|
|
|
if self.has_symbols:
|
|
# Modo algebraico: crear expresión simbólica
|
|
self._symbolic_elements = []
|
|
for elem in self.elements:
|
|
if elem.isdigit():
|
|
self._symbolic_elements.append(int(elem))
|
|
else:
|
|
self._symbolic_elements.append(sympy.Symbol(elem))
|
|
|
|
# CORREGIDO: Crear expresión escalar en lugar de Matrix para compatibilidad con IP4
|
|
# Convertir a representación de entero de 32 bits simbólico
|
|
self._symbolic_value = (self._symbolic_elements[0] * 2**24 +
|
|
self._symbolic_elements[1] * 2**16 +
|
|
self._symbolic_elements[2] * 2**8 +
|
|
self._symbolic_elements[3])
|
|
super().__init__(self._symbolic_value, dotted_string)
|
|
else:
|
|
# Modo numérico: validar rangos y convertir
|
|
self._numeric_elements = [int(elem) for elem in self.elements]
|
|
# Crear valor como entero de 32 bits (para IPs)
|
|
self._numeric_value = (self._numeric_elements[0] << 24 |
|
|
self._numeric_elements[1] << 16 |
|
|
self._numeric_elements[2] << 8 |
|
|
self._numeric_elements[3])
|
|
super().__init__(self._numeric_value, dotted_string)
|
|
|
|
def is_valid_ip_range(self):
|
|
"""Verifica si todos los elementos numéricos están en rango IP"""
|
|
if self.has_symbols:
|
|
return None # No se puede validar con símbolos
|
|
return all(0 <= x <= 255 for x in self._numeric_elements)
|
|
|
|
def substitute(self, **kwargs):
|
|
"""Sustituye símbolos por valores"""
|
|
if not self.has_symbols:
|
|
return self
|
|
|
|
new_elements = []
|
|
for elem in self.elements:
|
|
if elem in kwargs:
|
|
new_elements.append(str(kwargs[elem]))
|
|
else:
|
|
new_elements.append(elem)
|
|
|
|
return FourBytes('.'.join(new_elements))
|
|
|
|
def __getitem__(self, index):
|
|
"""Acceso a elementos individuales"""
|
|
if self.has_symbols:
|
|
return self._symbolic_elements[index]
|
|
else:
|
|
return self._numeric_elements[index]
|
|
|
|
def to_ip_int(self):
|
|
"""Convierte a entero de 32 bits (para IPs)"""
|
|
if self.has_symbols:
|
|
# CORREGIDO: Ya tenemos la expresión escalar directamente
|
|
return self._symbolic_value
|
|
else:
|
|
return self._numeric_value
|
|
|
|
@staticmethod
|
|
def _int_to_fourbytes(value):
|
|
"""Convierte entero de 32 bits a formato x.y.z.w"""
|
|
w = value & 0xFF
|
|
z = (value >> 8) & 0xFF
|
|
y = (value >> 16) & 0xFF
|
|
x = (value >> 24) & 0xFF
|
|
return f"{x}.{y}.{z}.{w}"
|
|
|
|
# ========== OPERADORES ARITMÉTICOS ==========
|
|
|
|
def __add__(self, other):
|
|
"""Suma: convierte a int, opera, reconvierte a FourBytes"""
|
|
if self.has_symbols:
|
|
return super().__add__(other) # Delegar a SymPy
|
|
|
|
if isinstance(other, FourBytes):
|
|
if other.has_symbols:
|
|
return super().__add__(other)
|
|
result_int = self._numeric_value + other._numeric_value
|
|
elif isinstance(other, int):
|
|
result_int = self._numeric_value + other
|
|
else:
|
|
return super().__add__(other)
|
|
|
|
# Mantener en rango de 32 bits
|
|
result_int = result_int & 0xFFFFFFFF
|
|
result_str = self._int_to_fourbytes(result_int)
|
|
return FourBytes(result_str)
|
|
|
|
def __sub__(self, other):
|
|
"""Resta: convierte a int, opera, reconvierte a FourBytes"""
|
|
if self.has_symbols:
|
|
return super().__sub__(other)
|
|
|
|
if isinstance(other, FourBytes):
|
|
if other.has_symbols:
|
|
return super().__sub__(other)
|
|
result_int = self._numeric_value - other._numeric_value
|
|
elif isinstance(other, int):
|
|
result_int = self._numeric_value - other
|
|
else:
|
|
return super().__sub__(other)
|
|
|
|
# Mantener en rango de 32 bits (underflow se convierte en valor alto)
|
|
result_int = result_int & 0xFFFFFFFF
|
|
result_str = self._int_to_fourbytes(result_int)
|
|
return FourBytes(result_str)
|
|
|
|
# ========== CONVERSIONES DE BASE ==========
|
|
|
|
def ToBase(self, base):
|
|
"""
|
|
Convierte cada elemento a la base especificada
|
|
Retorna expresión con cada elemento convertido
|
|
"""
|
|
if self.has_symbols:
|
|
# Para elementos simbólicos, retornar expresión algebraica
|
|
converted_elements = []
|
|
for elem in self.elements:
|
|
if elem.isdigit():
|
|
int_val = int(elem)
|
|
converted = IntBase._convert_to_base_string(int_val, base)
|
|
converted_elements.append(f"{base}#{converted}")
|
|
else:
|
|
# Símbolo: expresar como conversión algebraica
|
|
converted_elements.append(f"{base}#{elem}")
|
|
return '.'.join(converted_elements)
|
|
else:
|
|
# Para elementos numéricos, conversión directa
|
|
converted_elements = []
|
|
for elem_val in self._numeric_elements:
|
|
converted = IntBase._convert_to_base_string(elem_val, base)
|
|
converted_elements.append(f"{base}#{converted}")
|
|
return '.'.join(converted_elements)
|
|
|
|
def ToCIDR(self, prefix_length):
|
|
"""Convierte a notación CIDR"""
|
|
return f"{self.original}/{prefix_length}"
|
|
|
|
def ToHex(self):
|
|
"""Convierte cada elemento a hexadecimal"""
|
|
return self.ToBase(16)
|
|
|
|
def ToBinary(self):
|
|
"""Convierte cada elemento a binario"""
|
|
return self.ToBase(2)
|
|
|
|
def ToOctal(self):
|
|
"""Convierte cada elemento a octal"""
|
|
return self.ToBase(8)
|
|
|
|
def __str__(self):
|
|
"""Representación string"""
|
|
return self.original
|
|
|
|
def __repr__(self):
|
|
return f"FourBytes('{self.original}')"
|
|
|
|
|
|
# ========== TOKENIZADOR ALGEBRAICO ==========
|
|
|
|
def preprocess_tokens(expression):
|
|
"""
|
|
Tokenizador que convierte patrones específicos en objetos tipados
|
|
con precedencia correcta: IntBase (más específico) antes que FourBytes
|
|
"""
|
|
# 1. MAYOR PRECEDENCIA: IntBase (patrón más específico)
|
|
# Pattern: base#valor
|
|
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', r'IntBase("\2", \1)', expression)
|
|
|
|
# 2. MENOR PRECEDENCIA: FourBytes (patrón más general)
|
|
# Pattern: x.x.x.x pero evitar casos como obj.method
|
|
# Solo tokenizar si hay exactamente 4 elementos separados por puntos
|
|
# y no es parte de una cadena de métodos
|
|
pattern = r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b'
|
|
|
|
def replace_fourbytes(match):
|
|
candidate = match.group(1)
|
|
# Verificar que realmente tiene 4 elementos
|
|
parts = candidate.split('.')
|
|
if len(parts) == 4:
|
|
return f'FourBytes("{candidate}")'
|
|
return candidate
|
|
|
|
expression = re.sub(pattern, replace_fourbytes, expression)
|
|
|
|
return expression |