text: # coding=utf-8
  # Copyright 2018-2020 EVA
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  from abc import ABC, abstractmethod
  from enum import IntEnum, unique, auto
  
  from eva.utils import generic_utils
  
  
  @unique
  class ExpressionType(IntEnum):
      INVALID = auto()
      CONSTANT_VALUE = auto()
      TUPLE_VALUE = auto()
      # Compare operators
      COMPARE_EQUAL = auto()
      COMPARE_GREATER = auto()
      COMPARE_LESSER = auto()
      COMPARE_GEQ = auto()
      COMPARE_LEQ = auto()
      COMPARE_NEQ = auto()
      COMPARE_CONTAINS = auto()
      COMPARE_IS_CONTAINED = auto()
      # Logical operators
      LOGICAL_AND = auto()
      LOGICAL_OR = auto()
      LOGICAL_NOT = auto()
      # Arithmetic operators
      ARITHMETIC_ADD = auto()
      ARITHMETIC_SUBTRACT = auto()
      ARITHMETIC_MULTIPLY = auto()
      ARITHMETIC_DIVIDE = auto()
  
      FUNCTION_EXPRESSION = auto()
  
      AGGREGATION_COUNT = auto()
      AGGREGATION_SUM = auto()
      AGGREGATION_MIN = auto()
      AGGREGATION_MAX = auto()
      AGGREGATION_AVG = auto()
  
      CASE = auto()
      # add other types
  
  
  @unique
  class ExpressionReturnType(IntEnum):
      INVALID = auto()
      BOOLEAN = auto()
      INTEGER = auto()
      VARCHAR = auto()
      FLOAT = auto()
      # add others
  
  
  class AbstractExpression(ABC):
  
      def __init__(self, exp_type: ExpressionType, **kwargs):
          allowed_kwargs = {
              'rtype',
              'children'
          }
          generic_utils.validate_kwargs(kwargs, allowed_kwargs)
          self._etype = exp_type
          self._rtype = kwargs.get('rtype', ExpressionReturnType.INVALID)
          self._children = kwargs.get('children', [])
          self._predicates = []
  
      def get_child(self, index: int):
          if index < 0 or index >= len(self._children):
              return None
          else:
              return self._children[index]
  
      @property
      def children(self):
          return self._children
  
      def append_child(self, child):
          self._children.append(child)
  
      def get_children_count(self) -> int:
          return len(self._children)
  
      @property
      def etype(self) -> ExpressionType:
          return self._etype
  
      @etype.setter
      def etype(self, expr_type: ExpressionType):
          self._etype = expr_type
  
      @property
      # def predicates(self) -> List[Predicate]:
      def predicates(self):
          return self._predicates
  
      def clear_predicates(self):
          self._predicates.clear()
  
      def get_predicate_count(self) -> int:
          return len(self._predicates)
  
      @property
      def return_type(self) -> ExpressionReturnType:
          return self._return_type
  
      @return_type.setter
      def return_type(self, return_type: ExpressionReturnType):
          self._return_type = return_type
  
      # todo define a generic return type for this function
      # not sure if we should keep tuple1, tuple2 explicitly
      # since not many sub-classes are using both tuples
      # how about if we maintain *args
      # refactor if need be
      @abstractmethod
      def evaluate(self, *args, **kwargs):
          NotImplementedError('Must be implemented in subclasses.')
  
      def __eq__(self, other):
          is_subtree_equal = True
          if not isinstance(other, AbstractExpression):
              return False
          if self.get_children_count() != other.get_children_count():
              return False
          for child1, child2 in zip(self.children, other.children):
              is_subtree_equal = is_subtree_equal and (child1 == child2)
          return is_subtree_equal
  