// This file is distributed under the BSD License. // See "license.txt" for details. // Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) // Copyright 2009-2018, Jason Turner (jason@emptycrate.com) // http://www.chaiscript.com // This is an open source non-commercial project. Dear PVS-Studio, please check it. // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #ifndef CHAISCRIPT_PROXY_FUNCTIONS_HPP_ #define CHAISCRIPT_PROXY_FUNCTIONS_HPP_ #include #include #include #include #include #include #include #include #include "../chaiscript_defines.hpp" #include "boxed_cast.hpp" #include "boxed_value.hpp" #include "dynamic_object.hpp" #include "function_params.hpp" #include "proxy_functions_detail.hpp" #include "type_info.hpp" namespace chaiscript { class Type_Conversions; namespace exception { class bad_boxed_cast; struct arity_error; } // namespace exception } // namespace chaiscript namespace chaiscript { class Boxed_Number; struct AST_Node; using AST_NodePtr = std::unique_ptr; namespace dispatch { template std::function functor(std::shared_ptr func, const Type_Conversions_State *t_conversions); class Param_Types { public: Param_Types() : m_has_types(false) { } explicit Param_Types(std::vector> t_types) : m_types(std::move(t_types)) , m_has_types(false) { update_has_types(); } void push_front(std::string t_name, Type_Info t_ti) { m_types.emplace(m_types.begin(), std::move(t_name), t_ti); update_has_types(); } bool operator==(const Param_Types &t_rhs) const noexcept { return m_types == t_rhs.m_types; } std::vector convert(Function_Params t_params, const Type_Conversions_State &t_conversions) const { auto vals = t_params.to_vector(); const auto dynamic_object_type_info = user_type(); for (size_t i = 0; i < vals.size(); ++i) { const auto &name = m_types[i].first; if (!name.empty()) { const auto &bv = vals[i]; if (!bv.get_type_info().bare_equal(dynamic_object_type_info)) { const auto &ti = m_types[i].second; if (!ti.is_undef()) { if (!bv.get_type_info().bare_equal(ti)) { if (t_conversions->converts(ti, bv.get_type_info())) { try { // We will not catch any bad_boxed_dynamic_cast that is thrown, let the user get it // either way, we are not responsible if it doesn't work vals[i] = t_conversions->boxed_type_conversion(m_types[i].second, t_conversions.saves(), vals[i]); } catch (...) { try { // try going the other way vals[i] = t_conversions->boxed_type_down_conversion(m_types[i].second, t_conversions.saves(), vals[i]); } catch (const chaiscript::detail::exception::bad_any_cast &) { throw exception::bad_boxed_cast(bv.get_type_info(), *m_types[i].second.bare_type_info()); } } } } } } } } return vals; } // first result: is a match // second result: needs conversions std::pair match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept { const auto dynamic_object_type_info = user_type(); bool needs_conversion = false; if (!m_has_types) { return std::make_pair(true, needs_conversion); } if (vals.size() != m_types.size()) { return std::make_pair(false, needs_conversion); } for (size_t i = 0; i < vals.size(); ++i) { const auto &name = m_types[i].first; if (!name.empty()) { const auto &bv = vals[i]; if (bv.get_type_info().bare_equal(dynamic_object_type_info)) { try { const Dynamic_Object &d = boxed_cast(bv, &t_conversions); if (!(name == "Dynamic_Object" || d.get_type_name() == name)) { return std::make_pair(false, false); } } catch (const std::bad_cast &) { return std::make_pair(false, false); } } else { const auto &ti = m_types[i].second; if (!ti.is_undef()) { if (!bv.get_type_info().bare_equal(ti)) { if (!t_conversions->converts(ti, bv.get_type_info())) { return std::make_pair(false, false); } else { needs_conversion = true; } } } else { return std::make_pair(false, false); } } } } return std::make_pair(true, needs_conversion); } const std::vector> &types() const noexcept { return m_types; } private: void update_has_types() { for (const auto &type : m_types) { if (!type.first.empty()) { m_has_types = true; return; } } m_has_types = false; } std::vector> m_types; bool m_has_types; }; /** * Pure virtual base class for all Proxy_Function implementations * Proxy_Functions are a type erasure of type safe C++ * function calls. At runtime parameter types are expected to be * tested against passed in types. * Dispatch_Engine only knows how to work with Proxy_Function, no other * function classes. */ class Proxy_Function_Base { public: virtual ~Proxy_Function_Base() = default; Boxed_Value operator()(const Function_Params ¶ms, const chaiscript::Type_Conversions_State &t_conversions) const { if (m_arity < 0 || size_t(m_arity) == params.size()) { return do_call(params, t_conversions); } else { throw exception::arity_error(static_cast(params.size()), m_arity); } } /// Returns a vector containing all of the types of the parameters the function returns/takes /// if the function is variadic or takes no arguments (arity of 0 or -1), the returned /// value contains exactly 1 Type_Info object: the return type /// \returns the types of all parameters. const std::vector &get_param_types() const noexcept { return m_types; } virtual bool operator==(const Proxy_Function_Base &) const noexcept = 0; virtual bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const = 0; virtual bool is_attribute_function() const noexcept { return false; } bool has_arithmetic_param() const noexcept { return m_has_arithmetic_param; } virtual std::vector> get_contained_functions() const { return std::vector>(); } //! Return true if the function is a possible match //! to the passed in values bool filter(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept { assert(m_arity == -1 || (m_arity > 0 && static_cast(vals.size()) == m_arity)); if (m_arity < 0) { return true; } else if (m_arity > 1) { return compare_type_to_param(m_types[1], vals[0], t_conversions) && compare_type_to_param(m_types[2], vals[1], t_conversions); } else { return compare_type_to_param(m_types[1], vals[0], t_conversions); } } /// \returns the number of arguments the function takes or -1 if it is variadic int get_arity() const noexcept { return m_arity; } static bool compare_type_to_param(const Type_Info &ti, const Boxed_Value &bv, const Type_Conversions_State &t_conversions) noexcept { const auto boxed_value_ti = user_type(); const auto boxed_number_ti = user_type(); const auto function_ti = user_type>(); if (ti.is_undef() || ti.bare_equal(boxed_value_ti) || (!bv.get_type_info().is_undef() && ((ti.bare_equal(boxed_number_ti) && bv.get_type_info().is_arithmetic()) || ti.bare_equal(bv.get_type_info()) || bv.get_type_info().bare_equal(function_ti) || t_conversions->converts(ti, bv.get_type_info())))) { return true; } else { return false; } } virtual bool compare_first_type(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept { /// TODO is m_types guaranteed to be at least 2?? return compare_type_to_param(m_types[1], bv, t_conversions); } protected: virtual Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const = 0; Proxy_Function_Base(std::vector t_types, int t_arity) : m_types(std::move(t_types)) , m_arity(t_arity) , m_has_arithmetic_param(false) { for (size_t i = 1; i < m_types.size(); ++i) { if (m_types[i].is_arithmetic()) { m_has_arithmetic_param = true; return; } } } static bool compare_types(const std::vector &tis, const Function_Params &bvs, const Type_Conversions_State &t_conversions) noexcept { if (tis.size() - 1 != bvs.size()) { return false; } else { const size_t size = bvs.size(); for (size_t i = 0; i < size; ++i) { if (!compare_type_to_param(tis[i + 1], bvs[i], t_conversions)) { return false; } } } return true; } std::vector m_types; int m_arity; bool m_has_arithmetic_param; }; } // namespace dispatch /// \brief Common typedef used for passing of any registered function in ChaiScript using Proxy_Function = std::shared_ptr; /// \brief Const version of Proxy_Function. Points to a const Proxy_Function. This is how most registered functions /// are handled internally. using Const_Proxy_Function = std::shared_ptr; namespace exception { /// \brief Exception thrown if a function's guard fails class guard_error : public std::runtime_error { public: guard_error() noexcept : std::runtime_error("Guard evaluation failed") { } guard_error(const guard_error &) = default; ~guard_error() noexcept override = default; }; } // namespace exception namespace dispatch { /// A Proxy_Function implementation that is not type safe, the called function /// is expecting a vector that it works with how it chooses. class Dynamic_Proxy_Function : public Proxy_Function_Base { public: Dynamic_Proxy_Function(const int t_arity, std::shared_ptr t_parsenode, Param_Types t_param_types = Param_Types(), Proxy_Function t_guard = Proxy_Function()) : Proxy_Function_Base(build_param_type_list(t_param_types), t_arity) , m_param_types(std::move(t_param_types)) , m_guard(std::move(t_guard)) , m_parsenode(std::move(t_parsenode)) { // assert(t_parsenode); } bool operator==(const Proxy_Function_Base &rhs) const noexcept override { const Dynamic_Proxy_Function *prhs = dynamic_cast(&rhs); return this == &rhs || ((prhs != nullptr) && this->m_arity == prhs->m_arity && !this->m_guard && !prhs->m_guard && this->m_param_types == prhs->m_param_types); } bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const override { return call_match_internal(vals, t_conversions).first; } bool has_guard() const noexcept { return bool(m_guard); } Proxy_Function get_guard() const noexcept { return m_guard; } bool has_parse_tree() const noexcept { return static_cast(m_parsenode); } const AST_Node &get_parse_tree() const { if (m_parsenode) { return *m_parsenode; } else { throw std::runtime_error("Dynamic_Proxy_Function does not have parse_tree"); } } protected: bool test_guard(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const { if (m_guard) { try { return boxed_cast((*m_guard)(params, t_conversions)); } catch (const exception::arity_error &) { return false; } catch (const exception::bad_boxed_cast &) { return false; } } else { return true; } } // first result: is a match // second result: needs conversions std::pair call_match_internal(const Function_Params &vals, const Type_Conversions_State &t_conversions) const { const auto comparison_result = [&]() { if (m_arity < 0) { return std::make_pair(true, false); } else if (vals.size() == size_t(m_arity)) { return m_param_types.match(vals, t_conversions); } else { return std::make_pair(false, false); } }(); return std::make_pair(comparison_result.first && test_guard(vals, t_conversions), comparison_result.second); } private: static std::vector build_param_type_list(const Param_Types &t_types) { // For the return type std::vector types{chaiscript::detail::Get_Type_Info::get()}; for (const auto &t : t_types.types()) { if (t.second.is_undef()) { types.push_back(chaiscript::detail::Get_Type_Info::get()); } else { types.push_back(t.second); } } return types; } protected: Param_Types m_param_types; private: Proxy_Function m_guard; std::shared_ptr m_parsenode; }; template class Dynamic_Proxy_Function_Impl final : public Dynamic_Proxy_Function { public: Dynamic_Proxy_Function_Impl(Callable t_f, int t_arity = -1, std::shared_ptr t_parsenode = AST_NodePtr(), Param_Types t_param_types = Param_Types(), Proxy_Function t_guard = Proxy_Function()) : Dynamic_Proxy_Function(t_arity, std::move(t_parsenode), std::move(t_param_types), std::move(t_guard)) , m_f(std::move(t_f)) { } protected: Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { const auto [is_a_match, needs_conversions] = call_match_internal(params, t_conversions); if (is_a_match) { if (needs_conversions) { return m_f(Function_Params{m_param_types.convert(params, t_conversions)}); } else { return m_f(params); } } else { throw exception::guard_error(); } } private: Callable m_f; }; template Proxy_Function make_dynamic_proxy_function(Callable &&c, Arg &&...a) { return chaiscript::make_shared>(std::forward(c), std::forward(a)...); } /// An object used by Bound_Function to represent "_" parameters /// of a binding. This allows for unbound parameters during bind. struct Placeholder_Object { }; /// An implementation of Proxy_Function that takes a Proxy_Function /// and substitutes bound parameters into the parameter list /// at runtime, when call() is executed. /// it is used for bind(function, param1, _, param2) style calls class Bound_Function final : public Proxy_Function_Base { public: Bound_Function(const Const_Proxy_Function &t_f, const std::vector &t_args) : Proxy_Function_Base(build_param_type_info(t_f, t_args), (t_f->get_arity() < 0 ? -1 : static_cast(build_param_type_info(t_f, t_args).size()) - 1)) , m_f(t_f) , m_args(t_args) { assert(m_f->get_arity() < 0 || m_f->get_arity() == static_cast(m_args.size())); } bool operator==(const Proxy_Function_Base &t_f) const noexcept override { return &t_f == this; } bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const override { return m_f->call_match(Function_Params(build_param_list(vals)), t_conversions); } std::vector get_contained_functions() const override { return std::vector{m_f}; } std::vector build_param_list(const Function_Params ¶ms) const { auto parg = params.begin(); auto barg = m_args.begin(); std::vector args; while (!(parg == params.end() && barg == m_args.end())) { while (barg != m_args.end() && !(barg->get_type_info() == chaiscript::detail::Get_Type_Info::get())) { args.push_back(*barg); ++barg; } if (parg != params.end()) { args.push_back(*parg); ++parg; } if (barg != m_args.end() && barg->get_type_info() == chaiscript::detail::Get_Type_Info::get()) { ++barg; } } return args; } protected: static std::vector build_param_type_info(const Const_Proxy_Function &t_f, const std::vector &t_args) { assert(t_f->get_arity() < 0 || t_f->get_arity() == static_cast(t_args.size())); if (t_f->get_arity() < 0) { return std::vector(); } const auto types = t_f->get_param_types(); assert(types.size() == t_args.size() + 1); // this analysis warning is invalid in MSVC12 and doesn't exist in MSVC14 std::vector retval{types[0]}; for (size_t i = 0; i < types.size() - 1; ++i) { if (t_args[i].get_type_info() == chaiscript::detail::Get_Type_Info::get()) { retval.push_back(types[i + 1]); } } return retval; } Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { return (*m_f)(Function_Params{build_param_list(params)}, t_conversions); } private: Const_Proxy_Function m_f; std::vector m_args; }; class Proxy_Function_Impl_Base : public Proxy_Function_Base { public: explicit Proxy_Function_Impl_Base(const std::vector &t_types) : Proxy_Function_Base(t_types, static_cast(t_types.size()) - 1) { } bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { return static_cast(vals.size()) == get_arity() && (compare_types(m_types, vals, t_conversions) && compare_types_with_cast(vals, t_conversions)); } virtual bool compare_types_with_cast(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept = 0; }; /// For any callable object template class Proxy_Function_Callable_Impl final : public Proxy_Function_Impl_Base { public: explicit Proxy_Function_Callable_Impl(Callable f) : Proxy_Function_Impl_Base(detail::build_param_type_list(static_cast(nullptr))) , m_f(std::move(f)) { } bool compare_types_with_cast(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { return detail::compare_types_cast(static_cast(nullptr), vals, t_conversions); } bool operator==(const Proxy_Function_Base &t_func) const noexcept override { return dynamic_cast *>(&t_func) != nullptr; } protected: Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { return detail::call_func(static_cast(nullptr), m_f, params, t_conversions); } private: Callable m_f; }; class Assignable_Proxy_Function : public Proxy_Function_Impl_Base { public: explicit Assignable_Proxy_Function(const std::vector &t_types) : Proxy_Function_Impl_Base(t_types) { } virtual void assign(const std::shared_ptr &t_rhs) = 0; }; template class Assignable_Proxy_Function_Impl final : public Assignable_Proxy_Function { public: Assignable_Proxy_Function_Impl(std::reference_wrapper> t_f, std::shared_ptr> t_ptr) : Assignable_Proxy_Function(detail::build_param_type_list(static_cast(nullptr))) , m_f(std::move(t_f)) , m_shared_ptr_holder(std::move(t_ptr)) { assert(!m_shared_ptr_holder || m_shared_ptr_holder.get() == &m_f.get()); } bool compare_types_with_cast(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { return detail::compare_types_cast(static_cast(nullptr), vals, t_conversions); } bool operator==(const Proxy_Function_Base &t_func) const noexcept override { return dynamic_cast *>(&t_func) != nullptr; } std::function internal_function() const { return m_f.get(); } void assign(const std::shared_ptr &t_rhs) override { m_f.get() = dispatch::functor(t_rhs, nullptr); } protected: Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { return detail::call_func(static_cast(nullptr), m_f.get(), params, t_conversions); } private: std::reference_wrapper> m_f; std::shared_ptr> m_shared_ptr_holder; }; /// Attribute getter Proxy_Function implementation template class Attribute_Access final : public Proxy_Function_Base { public: explicit Attribute_Access(T Class::*t_attr) : Proxy_Function_Base(param_types(), 1) , m_attr(t_attr) { } bool is_attribute_function() const noexcept override { return true; } bool operator==(const Proxy_Function_Base &t_func) const noexcept override { const Attribute_Access *aa = dynamic_cast *>(&t_func); if (aa) { return m_attr == aa->m_attr; } else { return false; } } bool call_match(const Function_Params &vals, const Type_Conversions_State &) const noexcept override { if (vals.size() != 1) { return false; } const auto class_type_info = user_type(); return vals[0].get_type_info().bare_equal(class_type_info); } protected: Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { const Boxed_Value &bv = params[0]; if (bv.is_const()) { const Class *o = boxed_cast(bv, &t_conversions); return do_call_impl(o); } else { Class *o = boxed_cast(bv, &t_conversions); return do_call_impl(o); } } private: template auto do_call_impl(Class *o) const { if constexpr (std::is_pointer::value) { return detail::Handle_Return::handle(o->*m_attr); } else { return detail::Handle_Return::type>::handle(o->*m_attr); } } template auto do_call_impl(const Class *o) const { if constexpr (std::is_pointer::value) { return detail::Handle_Return::handle(o->*m_attr); } else { return detail::Handle_Return::type>::type>::handle(o->*m_attr); } } static std::vector param_types() { return {user_type(), user_type()}; } std::vector m_param_types{user_type(), user_type()}; T Class::*m_attr; }; } // namespace dispatch namespace exception { /// \brief Exception thrown in the case that a method dispatch fails /// because no matching function was found /// /// May be thrown due to an arity_error, a guard_error or a bad_boxed_cast /// exception class dispatch_error : public std::runtime_error { public: dispatch_error(const Function_Params &t_parameters, std::vector t_functions) : std::runtime_error("Error with function dispatch") , parameters(t_parameters.to_vector()) , functions(std::move(t_functions)) { } dispatch_error(const Function_Params &t_parameters, std::vector t_functions, const std::string &t_desc) : std::runtime_error(t_desc) , parameters(t_parameters.to_vector()) , functions(std::move(t_functions)) { } dispatch_error(const dispatch_error &) = default; ~dispatch_error() noexcept override = default; std::vector parameters; std::vector functions; }; } // namespace exception namespace dispatch { namespace detail { template bool types_match_except_for_arithmetic(const FuncType &t_func, const chaiscript::Function_Params &plist, const Type_Conversions_State &t_conversions) noexcept { const std::vector &types = t_func->get_param_types(); if (t_func->get_arity() == -1) { return false; } assert(plist.size() == types.size() - 1); return std::mismatch(plist.begin(), plist.end(), types.begin() + 1, [&](const Boxed_Value &bv, const Type_Info &ti) { return Proxy_Function_Base::compare_type_to_param(ti, bv, t_conversions) || (bv.get_type_info().is_arithmetic() && ti.is_arithmetic()); }) == std::make_pair(plist.end(), types.end()); } template Boxed_Value dispatch_with_conversions(InItr begin, const InItr &end, const chaiscript::Function_Params &plist, const Type_Conversions_State &t_conversions, const Funcs &t_funcs) { InItr matching_func(end); while (begin != end) { if (types_match_except_for_arithmetic(begin->second, plist, t_conversions)) { if (matching_func == end) { matching_func = begin; } else { // handle const members vs non-const member, which is not really ambiguous const auto &mat_fun_param_types = matching_func->second->get_param_types(); const auto &next_fun_param_types = begin->second->get_param_types(); if (plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { matching_func = begin; // keep the new one, the const/non-const matchup is correct } else if (!plist[0].is_const() && !mat_fun_param_types[1].is_const() && next_fun_param_types[1].is_const()) { // keep the old one, it has a better const/non-const matchup } else { // ambiguous function call throw exception::dispatch_error(plist, std::vector(t_funcs.begin(), t_funcs.end())); } } } ++begin; } if (matching_func == end) { // no appropriate function to attempt arithmetic type conversion on throw exception::dispatch_error(plist, std::vector(t_funcs.begin(), t_funcs.end())); } std::vector newplist; newplist.reserve(plist.size()); const std::vector &tis = matching_func->second->get_param_types(); std::transform(tis.begin() + 1, tis.end(), plist.begin(), std::back_inserter(newplist), [](const Type_Info &ti, const Boxed_Value ¶m) -> Boxed_Value { if (ti.is_arithmetic() && param.get_type_info().is_arithmetic() && param.get_type_info() != ti) { return Boxed_Number(param).get_as(ti).bv; } else { return param; } }); try { return (*(matching_func->second))(chaiscript::Function_Params{newplist}, t_conversions); } catch (const exception::bad_boxed_cast &) { // parameter failed to cast } catch (const exception::arity_error &) { // invalid num params } catch (const exception::guard_error &) { // guard failed to allow the function to execute } throw exception::dispatch_error(plist, std::vector(t_funcs.begin(), t_funcs.end())); } } // namespace detail /// Take a vector of functions and a vector of parameters. Attempt to execute /// each function against the set of parameters, in order, until a matching /// function is found or throw dispatch_error if no matching function is found template Boxed_Value dispatch(const Funcs &funcs, const Function_Params &plist, const Type_Conversions_State &t_conversions) { std::vector> ordered_funcs; ordered_funcs.reserve(funcs.size()); for (const auto &func : funcs) { const auto arity = func->get_arity(); if (arity == -1) { ordered_funcs.emplace_back(plist.size(), func.get()); } else if (arity == static_cast(plist.size())) { size_t numdiffs = 0; for (size_t i = 0; i < plist.size(); ++i) { if (!func->get_param_types()[i + 1].bare_equal(plist[i].get_type_info())) { ++numdiffs; } } ordered_funcs.emplace_back(numdiffs, func.get()); } } for (size_t i = 0; i <= plist.size(); ++i) { for (const auto &func : ordered_funcs) { try { if (func.first == i && (i == 0 || func.second->filter(plist, t_conversions))) { return (*(func.second))(plist, t_conversions); } } catch (const exception::bad_boxed_cast &) { // parameter failed to cast, try again } catch (const exception::arity_error &) { // invalid num params, try again } catch (const exception::guard_error &) { // guard failed to allow the function to execute, // try again } } } return detail::dispatch_with_conversions(ordered_funcs.cbegin(), ordered_funcs.cend(), plist, t_conversions, funcs); } } // namespace dispatch } // namespace chaiscript #endif