diff --git a/CMakeLists.txt b/CMakeLists.txt index 4265b93..4f11d00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,6 +212,10 @@ if(BUILD_TESTING) target_link_libraries(integer_literal_test ${LIBS}) add_test(NAME Integer_Literal_Test COMMAND integer_literal_test) + add_executable(arithmetic_conversions_test unittests/arithmetic_conversions_test.cpp) + target_link_libraries(arithmetic_conversions_test ${LIBS}) + add_test(NAME Arithmetic_Conversions_Test COMMAND arithmetic_conversions_test) + if (MULTITHREAD_SUPPORT_ENABLED) add_executable(multithreaded_test unittests/multithreaded_test.cpp) target_link_libraries(multithreaded_test ${LIBS}) diff --git a/include/chaiscript/dispatchkit/boxed_number.hpp b/include/chaiscript/dispatchkit/boxed_number.hpp index d9aa0f2..33f515f 100644 --- a/include/chaiscript/dispatchkit/boxed_number.hpp +++ b/include/chaiscript/dispatchkit/boxed_number.hpp @@ -332,6 +332,46 @@ namespace chaiscript validate_boxed_number(bv); } + Boxed_Number get_as(const Type_Info &inp_) const + { + if (inp_.bare_equal_type_info(typeid(int))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(double))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(float))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(long double))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(char))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(unsigned int))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(long))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(unsigned long))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::int8_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::int16_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::int32_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::int64_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::uint8_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::uint16_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::uint32_t))) { + return Boxed_Number(get_as()); + } else if (inp_.bare_equal_type_info(typeid(boost::uint64_t))) { + return Boxed_Number(get_as()); + } else { + throw boost::bad_any_cast(); + } + + } + template Target get_as() const { const Type_Info &inp_ = bv.get_type_info(); diff --git a/include/chaiscript/dispatchkit/dispatchkit.hpp b/include/chaiscript/dispatchkit/dispatchkit.hpp index a5c68f2..b6036c3 100644 --- a/include/chaiscript/dispatchkit/dispatchkit.hpp +++ b/include/chaiscript/dispatchkit/dispatchkit.hpp @@ -1109,6 +1109,14 @@ namespace chaiscript vec.push_back(t_f); std::stable_sort(vec.begin(), vec.end(), &function_less_than); func_objs[t_name] = Proxy_Function(new Dispatch_Function(vec)); + } else if (t_f->has_arithmetic_param()) { + // if the function is the only function but it also contains + // arithmetic operators, we must wrap it in a dispatch function + // to allow for automatic arithmetic type conversions + std::vector vec; + vec.push_back(t_f); + funcs.insert(std::make_pair(t_name, vec)); + func_objs[t_name] = Proxy_Function(new Dispatch_Function(vec)); } else { std::vector vec; vec.push_back(t_f); diff --git a/include/chaiscript/dispatchkit/proxy_functions.hpp b/include/chaiscript/dispatchkit/proxy_functions.hpp index 4008b90..b8504b1 100644 --- a/include/chaiscript/dispatchkit/proxy_functions.hpp +++ b/include/chaiscript/dispatchkit/proxy_functions.hpp @@ -85,6 +85,11 @@ namespace chaiscript virtual bool operator==(const Proxy_Function_Base &) const = 0; virtual bool call_match(const std::vector &vals) const = 0; + bool has_arithmetic_param() const + { + return m_has_arithmetic_param; + } + virtual std::vector > get_contained_functions() const { return std::vector >(); @@ -117,23 +122,8 @@ namespace chaiscript virtual std::string annotation() const = 0; - protected: - virtual Boxed_Value do_call(const std::vector ¶ms) const = 0; - - Proxy_Function_Base(const std::vector &t_types) - : m_types(t_types) + static bool compare_type_to_param(const Type_Info &ti, const Boxed_Value &bv) { - } - - virtual bool compare_first_type(const Boxed_Value &bv) const - { - const std::vector &types = get_param_types(); - - if (types.size() < 2) - { - return true; - } - const Type_Info &ti = types[1]; if (ti.is_undef() || ti.bare_equal(user_type()) || (!bv.get_type_info().is_undef() @@ -150,6 +140,36 @@ namespace chaiscript return false; } } + protected: + virtual Boxed_Value do_call(const std::vector ¶ms) const = 0; + + Proxy_Function_Base(const std::vector &t_types) + : m_types(t_types), m_has_arithmetic_param(false) + { + for (int i = 1; i < m_types.size(); ++i) + { + if (m_types[i].is_arithmetic()) + { + m_has_arithmetic_param = true; + return; + } + } + + } + + virtual bool compare_first_type(const Boxed_Value &bv) const + { + const std::vector &types = get_param_types(); + + if (types.size() < 2) + { + return true; + } + + const Type_Info &ti = types[1]; + return compare_type_to_param(ti, bv); + + } bool compare_types(const std::vector &tis, const std::vector &bvs) const { @@ -170,6 +190,8 @@ namespace chaiscript } std::vector m_types; + bool m_has_arithmetic_param; + }; } @@ -605,6 +627,92 @@ namespace chaiscript namespace dispatch { + namespace detail + { + template + bool types_match_except_for_arithmetic(const FuncType &t_func, const std::vector &plist) + { + if (t_func->get_arity() != plist.size()) + { + return false; + } + + const std::vector &types = t_func->get_param_types(); + + assert(plist.size() == types.size() - 1); + + for (int i = 0; i < plist.size(); ++i) + { + if (Proxy_Function_Base::compare_type_to_param(types[i+1], plist[i]) + || (types[i+1].is_arithmetic() && plist[i].get_type_info().is_arithmetic())) + { + // types continue to match + } else { + return false; + } + } + + // all types match + return true; + } + + template + Boxed_Value dispatch_with_conversions(InItr begin, const InItr &end, const std::vector &plist) + { + InItr orig(begin); + + InItr matching_func(end); + + while (begin != end) + { + if (types_match_except_for_arithmetic(*begin, plist)) + { + if (matching_func == end) + { + matching_func = begin; + } else { + // More than one function matches, not attempting + throw exception::dispatch_error(plist, std::vector(orig, end)); + } + } + + ++begin; + } + + if (matching_func == end) + { + // no appropriate function to attempt arithmetic type conversion on + throw exception::dispatch_error(plist, std::vector(orig, end)); + } + + + std::vector newplist; + const std::vector &tis = (*matching_func)->get_param_types(); + + for (int i = 0; i < plist.size(); ++i) + { + if (tis[i+1].is_arithmetic() + && plist[i].get_type_info().is_arithmetic()) { + newplist.push_back(Boxed_Number(plist[i]).get_as(tis[i+1]).bv); + } else { + newplist.push_back(plist[i]); + } + } + + try { + return (*(*matching_func))(newplist); + } 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(orig, end)); + + } + } /** * Take a vector of functions and a vector of parameters. Attempt to execute @@ -634,7 +742,7 @@ namespace chaiscript ++begin; } - throw exception::dispatch_error(plist, std::vector(orig, end)); + return detail::dispatch_with_conversions(orig, end, plist); } /** diff --git a/include/chaiscript/dispatchkit/type_info.hpp b/include/chaiscript/dispatchkit/type_info.hpp index 88641c6..d9c7901 100644 --- a/include/chaiscript/dispatchkit/type_info.hpp +++ b/include/chaiscript/dispatchkit/type_info.hpp @@ -99,6 +99,12 @@ namespace chaiscript || (ti.m_bare_type_info && m_bare_type_info && *ti.m_bare_type_info == *m_bare_type_info); } + bool bare_equal_type_info(const std::type_info &ti) const + { + return m_bare_type_info != 0 + && (*m_bare_type_info) == ti; + } + bool is_const() const { return m_is_const; } bool is_reference() const { return m_is_reference; } bool is_void() const { return m_is_void; } diff --git a/unittests/arithmetic_conversions_test.cpp b/unittests/arithmetic_conversions_test.cpp new file mode 100644 index 0000000..255a02a --- /dev/null +++ b/unittests/arithmetic_conversions_test.cpp @@ -0,0 +1,58 @@ +// Tests to make sure that type conversions happen only when they should + +#include + +void f1(int) +{ +} + +void f4(std::string) +{ +} + +void f2(int) +{ +} + +void f3(double) +{ +} + + +int main() +{ + chaiscript::ChaiScript chai; + + chai.add(chaiscript::fun(&f1), "f1"); + chai.add(chaiscript::fun(&f2), "f2"); + chai.add(chaiscript::fun(&f3), "f2"); + chai.add(chaiscript::fun(&f1), "f3"); + chai.add(chaiscript::fun(&f4), "f3"); + + // no overloads + chai.eval("f1(0)"); + chai.eval("f1(0l)"); + chai.eval("f1(0ul)"); + chai.eval("f1(0ll)"); + chai.eval("f1(0ull)"); + chai.eval("f1(0.0)"); + chai.eval("f1(0.0f)"); + chai.eval("f1(0.0l)"); + + // expected overloads + chai.eval("f2(1)"); + chai.eval("f2(1.0)"); + + // 1 non-arithmetic overload + chai.eval("f2(1.0)"); + + // this is the one call we expect to fail + try { + chai.eval("f2(1.0l)"); + } catch (const std::exception &) { + return EXIT_SUCCESS; + } + + // if the last one did not throw, we failed + return EXIT_FAILURE; +}