from __future__ import annotations
from cake._abc import BasicExpression, BasicNode
from cake.basic import OtherType
from cake import (
Comparity,
ComparitySymbol
)
from typing import Any, Union
from .add import (
Operation,
Add,
)
from .divide import Divide, FloorDiv, Modulo
from .multiply import Multiply, Power
from .binaries import LeftShift, RightShift, And, Xor, Or
OtherType = Union[OtherType, Operation]
'''Meths implemented
__iter__, __next__,
__repr__, __str__,
__add__, __radd__, __iadd__
__sub__, __rsub__, __isub__
__mul__, __rmul__, __imul__,
__truediv__, __rtruediv__, __itruediv__
__floordiv__, __rfloordiv__, __ifloordiv__
__mod__, __rmod__, __imod__
__pow__, __rpow__, __ipow__,
__lshift__, __rlshift__, __ilshift__
__rshift__, __rrshift__, __irshift__
__and__, __rand__, __iand__
__xor__, __rxor__, __ixor__
__or__, __ror__, __ior__
__neg__, __pos__
__eq__, __ne__
__gt__, __ge__, __lt__, __le__
'''
[docs]class Expression(BasicExpression):
''' Represents an expression within the cake library,
and expression is not equal to a value,
inorder to compute equations use an :class:`Equations` object.
Which are simply 2 expressions with a comparity operator between them.
.. hint::
Iterating through expressions simply returns the node,
not the operand between them.
.. warning::
Expressions when being compared using symbols such as ``>`` will not return a boolean value,
instead :class:`Comparity` is returned.
.. code-block:: py
# Making your own expressions
>>> expr = Expression(Add('x', 5))
>>> expr
x + 5
Parameters
----------
starting_op: :class:`cake.Operation`
Operation the expression begins with.
.. code-block:: py
>>> op = Add('x', 5)
>>> Expression(op)
x + 5
'''
def __init__(self, starting_op: Operation) -> None:
self.exp = starting_op
def _try_get_child_value(self, child: Any, true_value: bool, **kwds) -> Any:
if hasattr(child, 'solve'):
return child.solve(**kwds)
elif hasattr(child, 'true_value') and true_value:
return child.true_value(**kwds)
elif hasattr(child, 'evaluate'):
return child.evaluate(**kwds)
elif isinstance(child, Operation):
return self._identify_helper(child, raise_not_impl=True)(child, true_value=true_value, **kwds)
return child
''' Operations '''
def _add(self, node: Add, *, true_value: bool = False, **kwds) -> Any:
r = 0
for child in node.nodes:
try:
r += self._try_get_child_value(child, true_value, **kwds)
except Exception:
r += child
return r
def _multiply(self, node: Multiply, *, true_value: bool = False, **kwds) -> Any:
r = self._try_get_child_value(node.nodes[0], **kwds)
for child in node.nodes[1:]:
try:
r *= self._try_get_child_value(child, true_value, **kwds)
except Exception:
r *= child
return r
def _power(self, node: Power, *, true_value: bool = False, **kwds) -> Any:
base, power = node.nodes ## Validates is true power op
base = self._try_get_child_value(base, true_value, **kwds)
power = self._try_get_child_value(power, true_value, **kwds)
return base ** power
def _truediv(self, node: Divide, *, true_value: bool = False, **kwds) -> Any:
numerator, denominator = node.nodes ## Validates node is a division
numerator = self._try_get_child_value(numerator, true_value, **kwds)
denominator = self._try_get_child_value(denominator, true_value, **kwds)
return numerator / denominator
def _floordiv(self, node: Divide, *, true_value: bool = False, **kwds) -> Any:
numerator, denominator = node.nodes
numerator = self._try_get_child_value(numerator, true_value, **kwds)
denominator = self._try_get_child_value(denominator, true_value, **kwds)
return numerator // denominator
def _modulo(self, node: Divide, *, true_value: bool = False, **kwds) -> Any:
numerator, denominator = node.nodes
numerator = self._try_get_child_value(numerator, true_value, **kwds)
denominator = self._try_get_child_value(denominator, true_value, **kwds)
return numerator % denominator
def _leftshift(self, node: LeftShift, *, true_value: bool = False, **kwds) -> Any:
left, right = node.nodes
left = self._try_get_child_value(left, true_value, **kwds)
right = self._try_get_child_value(right, true_value, **kwds)
return left << right
def _rightshift(self, node: RightShift, *, true_value: bool = False, **kwds) -> Any:
left, right = node.nodes
left = self._try_get_child_value(left, true_value, **kwds)
right = self._try_get_child_value(right, true_value, **kwds)
return left >> right
def _and(self, node: And, *, true_value: bool = False, **kwds) -> Any:
left, right = node.nodes
left = self._try_get_child_value(left, true_value, **kwds)
right = self._try_get_child_value(right, true_value, **kwds)
return left & right
def _xor(self, node: Xor, *, true_value: bool = False, **kwds) -> Any:
left, right = node.nodes
left = self._try_get_child_value(left, true_value, **kwds)
right = self._try_get_child_value(right, true_value, **kwds)
return left ^ right
def _or(self, node: Or, *, true_value: bool = False, **kwds) -> Any:
left, right = node.nodes
left = self._try_get_child_value(left, true_value, **kwds)
right = self._try_get_child_value(right, true_value, **kwds)
return left | right
''' End operations '''
def _identify_helper(self, node: Any = None, *, raise_not_impl: bool = False) -> Any:
node = node or self.exp
if isinstance(node, Add):
return self._add
elif isinstance(node, Multiply):
return self._multiply
elif isinstance(node, Power):
return self._power
elif isinstance(node, Divide):
return self._truediv
elif isinstance(node, FloorDiv):
return self._floordiv
elif isinstance(node, Modulo):
return self._modulo
elif isinstance(node, LeftShift):
return self._leftshift
elif isinstance(node, RightShift):
return self._rightshift
elif isinstance(node, And):
return self._and
elif isinstance(node, Xor):
return self._xor
elif isinstance(node, Or):
return self._or
elif isinstance(node, Operation):
if hasattr(node, 'run'):
return node.run
if raise_not_impl:
raise NotImplemented
return None
[docs] def solve(self, /, true_value: bool = False, **values) -> Any:
''' Produces a solution to the expression using provided values.
Any variables in the expression must be passed as a **kwarg**!
Parameters
----------
true_value: :class:`bool`
Whether to use ``true_value`` when evaluating :class:`Sqrt`
.. code-block:: py
>>> expr = Expression(Add('x', 5))
>>> expr
x + 5
>>> expr.solve(x=3)
Integral(8)
'''
# Since expression is a tree, we use a recursive type approach.
# Gather nodes for base expression, whilst traversing through these nodes solve and repeat
## So with Add(..., Power(3, x), ...)
## We add onto 0, if x is provided, compute and add if possible else expression
## if x is not provided we create an add expression for Add(..., Power(3, x))
r = self._identify_helper(raise_not_impl=True)(self.exp, true_value=true_value, **values)
if isinstance(r, Operation):
return Expression(r)
return r
def __repr__(self) -> str:
return f'Expression({str(self.exp)})'
def __str__(self) -> str:
return str(self.exp)
''' Numerical Methods '''
def __add__(self, other: OtherType) -> Expression:
return Expression(Add(self.exp, other))
__radd__ = __add__
__iadd__ = __add__
def __sub__(self, other: OtherType) -> Expression:
return Expression(Add(self.exp, -other))
def __rsub__(self, other: OtherType) -> Expression:
return Expression(Add(other, -self.exp))
__isub__ = __sub__
def __mul__(self, other: OtherType) -> Expression:
if (x := other == 1) and not isinstance(x, Comparity):
return Expression(self.exp) # Reduces messy expressions
if isinstance(self.exp, Divide):
if isinstance(other, Divide):
return Expression(Divide(self.exp.nodes[0] * other.nodes[0], self.exp.nodes[1] * other.nodes[0]))
elif isinstance(other, Expression) and isinstance(other.exp, Divide):
return Expression(Divide(self.exp.nodes[0] * other.exp.nodes[0], self.exp.nodes[1] * other.exp.nodes[0]))
return Expression(Divide(self.exp.nodes[0] * other, self.exp.nodes[1]))
if isinstance(self.exp, Add):
nodes = []
for node in self.exp.nodes:
nodes.append(node * other)
return Expression(Add(*nodes))
return Expression(Multiply(self.exp, other))
__rmul__ = __mul__
__imul__ = __mul__
__call__ = __mul__
def __truediv__(self, other: OtherType) -> Expression:
return Expression(Divide(self.exp, other))
def __rtruediv__(self, other: OtherType) -> Expression:
return Expression(Divide(other, self.exp))
__itruediv__ = __truediv__
def __floordiv__(self, other: OtherType) -> Expression:
return Expression(FloorDiv(self.exp, other))
def __rfloordiv__(self, other: OtherType) -> Expression:
return Expression(FloorDiv(other, self.exp))
__ifloordiv__ = __floordiv__
def __mod__(self, other: OtherType) -> Expression:
return Expression(Modulo(self.exp, other))
def __rmod__(self, other: OtherType) -> Expression:
return Expression(Modulo(other, self.exp))
__imod__ = __mod__
def __pow__(self, other: OtherType) -> Expression:
if other == 1:
return Expression(self.exp) ## Reduces messy expressions
return Expression(Power(self.exp, other))
def __rpow__(self, other: OtherType) -> Expression:
return Expression(Power(other, self.exp))
__ipow__ = __pow__
def __lshift__(self, other: OtherType) -> Expression:
return Expression(LeftShift(self.exp, other))
def __rlshift__(self, other: OtherType) -> Expression:
return Expression(LeftShift(other, self.exp))
__ilshift__ = __lshift__
def __rshift__(self, other: OtherType) -> Expression:
return Expression(RightShift(self.exp, other))
def __rrshift__(self, other: OtherType) -> Expression:
return Expression(RightShift(other, self.exp))
__irshift__ = __rshift__
def __and__(self, other: OtherType) -> Expression:
return Expression(And(self.exp, other))
def __rand__(self, other: OtherType) -> Expression:
return Expression(And(other, self.exp))
__iand__ = __and__
def __xor__(self, other: OtherType) -> Expression:
return Expression(Xor(self.exp, other))
def __rxor__(self, other: OtherType) -> Expression:
return Expression(Xor(other, self.exp))
__ixor__ = __xor__
def __or__(self, other: OtherType) -> Expression:
return Expression(Or(self.exp, other))
def __ror__(self, other: OtherType) -> Expression:
return Expression(Or(other, self.exp))
__ior__ = __or__
''' END NUMERIC METHODS '''
''' UNARY OPS '''
def __neg__(self) -> Expression:
return self * -1
def __pos__(self) -> Expression:
return Expression(self.exp)
''' END UNARY OPS '''
''' OTHER METHODS '''
def __eq__(self, other: OtherType) -> Comparity:
return Comparity(self, other, ComparitySymbol.EQUAL_TO)
def __ne__(self, other: OtherType) -> Comparity:
return Comparity(self, other, ComparitySymbol.NOT_EQUAL_TO)
def __lt__(self, other: OtherType) -> Comparity:
return Comparity(self, other, ComparitySymbol.LESS_THAN)
def __le__(self, other: OtherType) -> Comparity:
return Comparity(self, other, ComparitySymbol.LESS_OR_EQUAL_TO)
def __gt__(self, other: OtherType) -> Comparity:
return Comparity(self, other, ComparitySymbol.GREATER_THAN)
def __ge__(self, other: OtherType) -> Comparity:
return Comparity(self, other, ComparitySymbol.GREATER_OR_EQUAL_TO)
def __next__(self) -> BasicNode:
raise NotImplemented