CSValue.h

// CSValue.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2012

#include "Ceda/cxUtils/xstring.h"
#include "Ceda/cxUtils/MathExt.h"
#include "Ceda/cxUtils/CedaAssert.h"
#include "Ceda/cxUtils/IException.h"
@import "Ceda/cxObject/Object.h"
@import "Ceda/cxObject/ConstructDestructCopyAssignReflectedVariable.h"
@import "cxCedaScript.h"

/*
A convention for coercions
--------------------------

We want to establish a convention for coercions amongst C++ value types.  It is proposed that
the following function be defined for lots of combinations of T1,T2

    bool Coerce(T1& lhs, const T2& rhs, bool allowExplicitConversions = false);

E.g.

    inline bool Coerce(bool& lhs, bool rhs, bool allowExplicitConversions = false)
    {
        lhs = rhs;
        return true;
    }

    inline bool Coerce(bool& lhs, int64 rhs, bool allowExplicitConversions = false)
    {
        if (allowExplicitConversions)
        {
            lhs = (rhs != 0);
            return true;
        }
        else
        {
            return false;
        }
    }

Alternatively we define two functions

    bool Assign(T& lhs, T rhs);
    bool Coerce(T& lhs, T rhs);
    
E.g.
    inline bool Assign(bool& lhs, int64 rhs) { return false; }
    inline bool Coerce(bool& lhs, int64 rhs) { lhs = (rhs != 0); return true; }

Showing error messages when a coercion fails
--------------------------------------------

There is no need for Coerce() to be concerned with throwing exceptions.  Instead we can write
a function like this

    template<typename T1, typename T2>
    void CoerceOrThrow(T1& lhs, const T2& rhs, ConstStringZ dstType, bool allowExplicitConversions = false)
    {
        if (!Coerce(lhs,rhs,allowExplicitConversions))
        {
            throw CSValueException(cxMakeString2("No implicit conversion from value " << rhs << " to " << dstType));
        }
    } 
*/

namespace ceda
{

template <typename T1, typename T2>
inline bool Assign(T1& lhs, const T2& rhs) { return false; }

template <typename T>
inline bool Assign(T& lhs, const T& rhs) { lhs = rhs; return true; }

@scope
{
    // Returns true if the values of type T1 are a subset of the values of type T2
    @def bool IsNumericSubType(T1,T2) =
    {
        @let bool v = false
        @for ( (t1,t2) in 
            [
                (int8,int8),
                
                (int16,int8),
                (int16,int16),
                
                (int32,int8),
                (int32,int16),
                (int32,int32),
                
                (int64,int8),
                (int64,int16),
                (int64,int32),
                (int64,int64),

                (uint8,uint8),
                
                (uint16,uint8),
                (uint16,uint16),
                
                (uint32,uint8),
                (uint32,uint16),
                (uint32,uint32),
                
                (uint64,uint8),
                (uint64,uint16),
                (uint64,uint32),
                (uint64,uint64),

                (float32,int8),
                (float32,int16),
                (float32,uint8),
                (float32,uint16),
                (float32,float32),

                (float64,int8),
                (float64,int16),
                (float64,int32),
                (float64,uint8),
                (float64,uint16),
                (float64,uint32),
                (float64,float32),
                (float64,float64)
            ] )
        {
            @if (@str(t1)==@str(T2) && @str(t2)==@str(T1)) @let v=true
        }
        v
    }

    @for (T1 in [int8,int16,int32,int64,uint8,uint16,uint32,uint64,float32,float64])
    {
        @for (T2 in [int8,int16,int32,int64,uint8,uint16,uint32,uint64,float32,float64])
        {
            inline bool Assign(T1& lhs, T2 rhs)
            {
                @if (IsNumericSubType(T2,T1))
                {
                    lhs = rhs;
                    return true;
                }
                @else
                {
                    lhs = (T1) rhs;
                    return IsNumericallyEqual(lhs,rhs);
                }
            }

        }
    }
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// ReflectedValue

$struct+ ReflectedValue
{
    ReflectedValue();
    ~ReflectedValue();
    ReflectedValue(const ReflectedValue& rhs);
    ReflectedValue& operator=(const ReflectedValue& rhs);
    void swap(ReflectedValue& rhs);
    
    bool operator==(const ReflectedValue& rhs) const;
    bool operator!=(const ReflectedValue& rhs) const { return !operator==(rhs); }

    bool operator<(const ReflectedValue& rhs) const;
    bool operator<=(const ReflectedValue& rhs) const { return !(rhs < *this); }
    bool operator>(const ReflectedValue& rhs) const { return rhs < *this; }
    bool operator>=(const ReflectedValue& rhs) const { return !(*this < rhs); }

    template <typename T>
    bool Coerce(T& v, bool allowExplicitConversions = false) const
    {
        return ceda::Coerce(v,m_rbc,m_data.data(),allowExplicitConversions);
    }

    ReflectionByteCode m_rbc;
    xvector<octet_t> m_data;
};

template <typename T>
bool Assign(T& lhs, const ReflectedValue& rhs)
{
    return rhs.Coerce(lhs);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// CSValueException

struct @api CSValueException : public IException
{
    CSValueException(ConstStringZ description) :
        m_description(description)
    {
    }
	virtual void Write(xostream& os) const { os << m_description; }
    
    xstring m_description;    
};


///////////////////////////////////////////////////////////////////////////////////////////////////
// CSValue

enum ECSValue
{
    CSV_VOID,
    CSV_BOOL,
    CSV_INT64,
    CSV_FLOAT64,
    CSV_STRING8,
    CSV_REFLECTED_VALUE
};

$variant+ CSValue
{
    void;
    bool;
    int64;
    float64;
    string8;
    ReflectedValue;
};

@api bool Assign(bool& v, const CSValue& csv);

@api bool Assign(int8& v, const CSValue& csv);
@api bool Assign(int16& v, const CSValue& csv);
@api bool Assign(int32& v, const CSValue& csv);
@api bool Assign(int64& v, const CSValue& csv);

@api bool Assign(uint8& v, const CSValue& csv);
@api bool Assign(uint16& v, const CSValue& csv);
@api bool Assign(uint32& v, const CSValue& csv);
@api bool Assign(uint64& v, const CSValue& csv);

@api bool Assign(float32& v, const CSValue& csv);
@api bool Assign(float64& v, const CSValue& csv);

@api bool Assign(string8& v, const CSValue& csv);

// Throws exceptions if assignment not possible
// What?  How can it not be possible? And why don't we just execute lhs=rhs?
@api void Assign(CSValue& lhs, const CSValue& rhs);

@api bool Coerce(bool& v, const CSValue& csv, bool allowExplicitConversions = false);

@api bool Coerce(int8& v, const CSValue& csv, bool allowExplicitConversions = false);
@api bool Coerce(int16& v, const CSValue& csv, bool allowExplicitConversions = false);
@api bool Coerce(int32& v, const CSValue& csv, bool allowExplicitConversions = false);
@api bool Coerce(int64& v, const CSValue& csv, bool allowExplicitConversions = false);

@api bool Coerce(uint8& v, const CSValue& csv, bool allowExplicitConversions = false);
@api bool Coerce(uint16& v, const CSValue& csv, bool allowExplicitConversions = false);
@api bool Coerce(uint32& v, const CSValue& csv, bool allowExplicitConversions = false);
@api bool Coerce(uint64& v, const CSValue& csv, bool allowExplicitConversions = false);

@api bool Coerce(float32& v, const CSValue& csv, bool allowExplicitConversions = false);
@api bool Coerce(float64& v, const CSValue& csv, bool allowExplicitConversions = false);

@api bool Coerce(string8& v, const CSValue& csv, bool allowExplicitConversions = false);

template <typename T>
inline bool Coercible(const CSValue& csv, bool allowExplicitConversions = false)
{
    T val;
    return Coerce(val,csv,allowExplicitConversions);
}

inline const char* _GetTypeName(const bool*) { return "bool"; }
inline const char* _GetTypeName(const int8*) { return "int8"; }
inline const char* _GetTypeName(const int16*) { return "int16"; }
inline const char* _GetTypeName(const int32*) { return "int32"; }
inline const char* _GetTypeName(const int64*) { return "int64"; }
inline const char* _GetTypeName(const uint8*) { return "uint8"; }
inline const char* _GetTypeName(const uint16*) { return "uint16"; }
inline const char* _GetTypeName(const uint32*) { return "uint32"; }
inline const char* _GetTypeName(const uint64*) { return "uint64"; }
inline const char* _GetTypeName(const float32*) { return "float32"; }
inline const char* _GetTypeName(const float64*) { return "float64"; }
inline const char* _GetTypeName(const string8*) { return "string8"; }

template <typename T>
inline const char* GetTypeName() { return _GetTypeName( (const T*) 0 ); }

template <typename T>
[[noreturn]] void ThrowNoImplicitConversionFromValue(T v, ConstStringZ dstType)
{
    throw CSValueException(cxMakeString2("No implicit conversion from value " << v << " to " << dstType));
}

// As<T>(CSValue) is intended to be called by the cedascript parser.  It throws an appropriate 
// exception if a conversion is not possible.
template <typename T>
T As(const CSValue& csv, bool allowExplicitConversions = false)
{ 
    T v; 
    if (!Coerce(v,csv,allowExplicitConversions))
    {
        ThrowNoImplicitConversionFromValue(csv, GetTypeName<T>());
    }
    return v;
}

// Assign x = x(y) where y represents a comma separated list of arguments
@api void ApplyFunction(CSValue& x,const xvector<CSValue>& y);

// Assign x = x.y
@api void ApplyDot(CSValue& x, const xstring& y);

// Assign x = x[i]
@api void ApplySubscript(CSValue& x, const CSValue& i);

// Assign x = len(x)
@api void ApplyLen(CSValue& x);

// Unary operations
@api void ApplyUnaryPlus(CSValue& x);           // x = +x
@api void ApplyUnaryMinus(CSValue& x);          // x = -x
@api void ApplyUnaryNot(CSValue& x);            // x = !x
@api void ApplyUnaryBitComplement(CSValue& x);  // x = ~x

// Binary arithmetic operations
@api const CSValue operator+(const CSValue& x, const CSValue& y);
@api const CSValue operator-(const CSValue& x, const CSValue& y);
@api const CSValue operator*(const CSValue& x, const CSValue& y);
@api const CSValue operator/(const CSValue& x, const CSValue& y);
@api const CSValue operator%(const CSValue& x, const CSValue& y);

// Binary shift operations
@api const CSValue operator<<(const CSValue& x, const CSValue& y);
@api const CSValue operator>>(const CSValue& x, const CSValue& y);

// Binary logical operations
@api const CSValue operator||(const CSValue& x, const CSValue& y);
@api const CSValue operator&&(const CSValue& x, const CSValue& y);

// Binary bitwise operations
@api const CSValue operator|(const CSValue& x, const CSValue& y);
@api const CSValue operator&(const CSValue& x, const CSValue& y);
@api const CSValue operator^(const CSValue& x, const CSValue& y);

/*
// Unary operations
@api bool operator!(const CSValue& x);
@api CSValue operator+(const CSValue& x);
@api CSValue operator-(const CSValue& x);
@api CSValue operator~(const CSValue& x);
*/

// Comparision assuming value semantics (i.e. equivalence of value only)
@api bool IsEqual(const CSValue& x, const CSValue& y);
@api bool IsLess(const CSValue& x, const CSValue& y);

@api CSValue Power(const CSValue& x, const CSValue& y);

} // namespace ceda