// This file is distributed under the BSD License. // See "license.txt" for details. // Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) // Copyright 2009-2014, Jason Turner (jason@emptycrate.com) // http://www.chaiscript.com #ifndef CHAISCRIPT_DYNAMIC_CAST_CONVERSION_HPP_ #define CHAISCRIPT_DYNAMIC_CAST_CONVERSION_HPP_ #include #include #include #include #include #include #include "../chaiscript_threading.hpp" #include "bad_boxed_cast.hpp" #include "boxed_cast_helper.hpp" #include "boxed_value.hpp" #include "type_info.hpp" namespace chaiscript { namespace exception { class bad_boxed_dynamic_cast : public bad_boxed_cast { public: bad_boxed_dynamic_cast(const Type_Info &t_from, const std::type_info &t_to, const std::string &t_what) CHAISCRIPT_NOEXCEPT : bad_boxed_cast(t_from, t_to, t_what) { } bad_boxed_dynamic_cast(const Type_Info &t_from, const std::type_info &t_to) CHAISCRIPT_NOEXCEPT : bad_boxed_cast(t_from, t_to) { } bad_boxed_dynamic_cast(const std::string &w) CHAISCRIPT_NOEXCEPT : bad_boxed_cast(w) { } virtual ~bad_boxed_dynamic_cast() CHAISCRIPT_NOEXCEPT {} }; } namespace exception { class bad_boxed_type_cast : public bad_boxed_cast { public: bad_boxed_type_cast(const Type_Info &t_from, const std::type_info &t_to, const std::string &t_what) CHAISCRIPT_NOEXCEPT : bad_boxed_cast(t_from, t_to, t_what) { } bad_boxed_type_cast(const Type_Info &t_from, const std::type_info &t_to) CHAISCRIPT_NOEXCEPT : bad_boxed_cast(t_from, t_to) { } bad_boxed_type_cast(const std::string &w) CHAISCRIPT_NOEXCEPT : bad_boxed_cast(w) { } virtual ~bad_boxed_type_cast() CHAISCRIPT_NOEXCEPT {} }; } namespace detail { class Type_Conversion_Base { public: virtual Boxed_Value convert(const Boxed_Value &from) const = 0; virtual Boxed_Value convert_down(const Boxed_Value &to) const = 0; const Type_Info &to() const { return m_to; } const Type_Info &from() const { return m_from; } protected: Type_Conversion_Base(const Type_Info &t_to, const Type_Info &t_from) : m_to(t_to), m_from(t_from) { } virtual ~Type_Conversion_Base() {} private: Type_Info m_to; Type_Info m_from; }; template class Dynamic_Caster { public: static Boxed_Value cast(const Boxed_Value &t_from) { if (t_from.get_type_info().bare_equal(user_type())) { if (t_from.is_pointer()) { // Dynamic cast out the contained boxed value, which we know is the type we want if (t_from.is_const()) { std::shared_ptr data = std::dynamic_pointer_cast(detail::Cast_Helper >::cast(t_from, nullptr)); if (!data) { throw std::bad_cast(); } return Boxed_Value(data); } else { std::shared_ptr data = std::dynamic_pointer_cast(detail::Cast_Helper >::cast(t_from, nullptr)); if (!data) { throw std::bad_cast(); } return Boxed_Value(data); } } else { // Pull the reference out of the contained boxed value, which we know is the type we want if (t_from.is_const()) { const From &d = detail::Cast_Helper::cast(t_from, nullptr); const To &data = dynamic_cast(d); return Boxed_Value(std::cref(data)); } else { From &d = detail::Cast_Helper::cast(t_from, nullptr); To &data = dynamic_cast(d); return Boxed_Value(std::ref(data)); } } } else { throw chaiscript::exception::bad_boxed_dynamic_cast(t_from.get_type_info(), typeid(To), "Unknown dynamic_cast_conversion"); } } }; template class Dynamic_Conversion_Impl : public Type_Conversion_Base { public: Dynamic_Conversion_Impl() : Type_Conversion_Base(user_type(), user_type()) { } virtual Boxed_Value convert_down(const Boxed_Value &t_base) const CHAISCRIPT_OVERRIDE { return Dynamic_Caster::cast(t_base); } virtual Boxed_Value convert(const Boxed_Value &t_derived) const CHAISCRIPT_OVERRIDE { return Dynamic_Caster::cast(t_derived); } }; } class Type_Conversions { public: Type_Conversions() { } Type_Conversions(const Type_Conversions &t_other) : m_conversions(t_other.get_conversions()) { } void add_conversion(const std::shared_ptr &conversion) { chaiscript::detail::threading::unique_lock l(m_mutex); m_conversions.insert(conversion); } template bool converts() const { return converts(user_type(), user_type()); } bool converts(const Type_Info &to, const Type_Info &from) const { return has_conversion(to, from) || has_conversion(from, to); } template Boxed_Value boxed_type_conversion(const Boxed_Value &from) const { try { return get_conversion(user_type(), from.get_type_info())->convert(from); } catch (const std::out_of_range &) { throw exception::bad_boxed_dynamic_cast(from.get_type_info(), typeid(To), "No known conversion"); } catch (const std::bad_cast &) { throw exception::bad_boxed_dynamic_cast(from.get_type_info(), typeid(To), "Unable to perform dynamic_cast operation"); } } template Boxed_Value boxed_type_down_conversion(const Boxed_Value &to) const { try { return get_conversion(to.get_type_info(), user_type())->convert_down(to); } catch (const std::out_of_range &) { throw exception::bad_boxed_dynamic_cast(to.get_type_info(), typeid(From), "No known conversion"); } catch (const std::bad_cast &) { throw exception::bad_boxed_dynamic_cast(to.get_type_info(), typeid(From), "Unable to perform dynamic_cast operation"); } } bool has_conversion(const Type_Info &to, const Type_Info &from) const { chaiscript::detail::threading::shared_lock l(m_mutex); return find(to, from) != m_conversions.end(); } std::shared_ptr get_conversion(const Type_Info &to, const Type_Info &from) const { chaiscript::detail::threading::shared_lock l(m_mutex); auto itr = find(to, from); if (itr != m_conversions.end()) { return *itr; } else { throw std::out_of_range("No such conversion exists from " + from.bare_name() + " to " + to.bare_name()); } } private: std::set >::const_iterator find( const Type_Info &to, const Type_Info &from) const { return std::find_if(m_conversions.begin(), m_conversions.end(), [&to, &from](const std::shared_ptr &conversion) { return conversion->to().bare_equal(to) && conversion->from().bare_equal(from); } ); } std::set> get_conversions() const { chaiscript::detail::threading::shared_lock l(m_mutex); return m_conversions; } mutable chaiscript::detail::threading::shared_mutex m_mutex; std::set> m_conversions; }; typedef std::shared_ptr Type_Conversion; /// \brief Used to register a to / parent class relationship with ChaiScript. Necessary if you /// want automatic conversions up your inheritance hierarchy. /// /// Create a new to class registration for applying to a module or to the ChaiScript engine /// Currently, due to limitations in module loading on Windows, and for the sake of portability, /// if you have a type that is introduced in a loadable module and is used by multiple modules /// (through a tertiary dll that is shared between the modules, static linking the new type /// into both loadable modules would not be portable), you need to register the to type /// relationship in all modules that use the newly added type in a polymorphic way. /// /// Example: /// \code /// class Base /// {}; /// class Derived : public Base /// {}; /// /// chaiscript::ChaiScript chai; /// chai.add(chaiscript::to_class()); /// \endcode /// template Type_Conversion base_class() { //Can only be used with related polymorphic types //may be expanded some day to support conversions other than child -> parent static_assert(std::is_base_of::value, "Classes are not related by inheritance"); static_assert(std::is_polymorphic::value, "Base class must be polymorphic"); static_assert(std::is_polymorphic::value, "Derived class must be polymorphic"); return std::shared_ptr(new detail::Dynamic_Conversion_Impl()); } } #endif