//! This module defines traits describing general properties of abstract domains
//! as well as several abstract domain types implementing these traits.

use crate::intermediate_representation::*;

mod bitvector;
pub use bitvector::*;

mod identifier;
pub use identifier::*;

mod pointer;
pub use pointer::*;

mod data;
pub use data::*;

mod mem_region;
pub use mem_region::*;

/// The main trait describing an abstract domain.
///
/// Each abstract domain is partially ordered and has a maximal element (which can be generated by `top()`).
/// Abstract domains of the same type can be merged.
pub trait AbstractDomain: Sized + Eq + Clone {
    fn merge(&self, other: &Self) -> Self;

    /// Returns whether the element represents the top element or not.
    fn is_top(&self) -> bool;
}

/// A trait for types representing values with a fixed size (in bytes).
///
/// For abstract domains, the bytesize is a parameter of the domain itself,
/// i.e. you cannot merge values of different bytesizes,
/// since they lie in different posets (one for each bytesize).
pub trait SizedDomain {
    /// Return the size of the represented value in bytes.
    fn bytesize(&self) -> ByteSize;

    /// Return a new top element with the given bytesize.
    /// The function is expected to panic if the type in question does not also implement the `HasTop` trait.
    fn new_top(bytesize: ByteSize) -> Self;
}

/// An abstract domain implementing this trait has a global maximum, i.e. a *Top* element.
pub trait HasTop {
    /// Return an instance of the *Top* element.
    ///
    /// Since an abstract domain type may represent a whole family of abstract domains,
    /// this function takes an instance of the domain as a parameter,
    /// so it can return the *Top* element of the same family member that the provided instance belongs to.
    fn top(&self) -> Self;
}

/// A trait for abstract domains that can represent values loaded into CPU register.
///
/// The domain implements all general operations used to manipulate register values.
/// The domain is parametrized by its bytesize (which represents the size of the register).
/// It has a *Top* element, which is only characterized by its bytesize.
pub trait RegisterDomain: AbstractDomain + SizedDomain + HasTop {
    /// Compute the (abstract) result of a binary operation
    fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self;

    /// Compute the (abstract) result of a unary operation
    fn un_op(&self, op: UnOpType) -> Self;

    /// Extract a sub-bitvector
    fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self;

    /// Perform a typecast to extend a bitvector or to cast between integer and floating point types.
    fn cast(&self, kind: CastOpType, width: ByteSize) -> Self;

    /// Return the bytesize of the result of the given binary operation.
    /// Has a generic implementation that should not be overwritten!
    fn bin_op_bytesize(&self, op: BinOpType, rhs: &Self) -> ByteSize {
        use BinOpType::*;
        match op {
            Piece => self.bytesize() + rhs.bytesize(),
            IntAdd | IntSub | IntMult | IntDiv | IntSDiv | IntRem | IntSRem | IntLeft
            | IntRight | IntSRight | IntAnd | IntOr | IntXOr | FloatAdd | FloatSub | FloatMult
            | FloatDiv => self.bytesize(),
            IntEqual | IntNotEqual | IntLess | IntLessEqual | IntSLess | IntSLessEqual
            | IntCarry | IntSCarry | IntSBorrow | BoolAnd | BoolOr | BoolXOr | FloatEqual
            | FloatNotEqual | FloatLess | FloatLessEqual => ByteSize::new(1),
        }
    }
}
