#pragma once #include "cast.h" namespace pkbind { namespace impl { template struct constructor {}; template > struct factory; template struct factory> { Fn fn; auto make() { using Self = callable_return_t; return [fn = std::move(fn)](Self* self, Args... args) { new (self) Self(fn(args...)); }; } }; } // namespace impl template impl::constructor init() { return {}; } template impl::factory init(Fn&& fn) { return {std::forward(fn)}; } struct arg_with_default { const char* name; object value; }; struct arg { const char* name; arg(const char* name) : name(name) {} template arg_with_default operator= (T&& value) { return arg_with_default{name, cast(std::forward(value))}; } }; struct kwargs_proxy { handle value; }; struct args_proxy { handle value; kwargs_proxy operator* () { return kwargs_proxy{value}; } }; template args_proxy interface::operator* () const { return args_proxy{handle(this->ptr())}; } template template object interface::operator() (Args&&... args) const { py_push(ptr()); py_pushnil(); int argc = 0; int kwargsc = 0; auto foreach = [&](auto&& argument) { using type = std::decay_t; if constexpr(std::is_constructible_v) { argc += 1; py_push(handle(argument).ptr()); } else if constexpr(std::is_same_v) { kwargsc += 1; arg_with_default& default_ = argument; py_pushname(name(default_.name).index()); py_push(default_.value.ptr()); } else if constexpr(std::is_same_v) { tuple args = argument.value.template cast(); for(auto arg: args) { argc += 1; py_push(arg.ptr()); } } else if constexpr(std::is_same_v) { dict kwargs = argument.value.template cast(); kwargs.apply([&](handle key, handle value) { kwargsc += 1; name name = key.cast(); py_pushname(name.index()); py_push(value.ptr()); }); } else { argc += 1; py_push(pkbind::cast(std::forward(argument), policy).ptr()); } }; (foreach(std::forward(args)), ...); raise_call(argc, kwargsc); return object::from_ret(); } class function : public object { PKBIND_TYPE_IMPL(object, function, tp_function); }; namespace impl { template , typename IndexSequence = std::make_index_sequence>> struct template_parser; class function_record { template friend struct template_parser; using destructor_t = void (*)(function_record*); using wrapper_t = bool (*)(function_record&, std::vector&, std::vector>&, bool convert, handle parent); struct arguments_t { std::vector names; std::vector defaults; }; public: template function_record(Fn&& f, const Extras&... extras) { using Callable = std::decay_t; if constexpr(std::is_trivially_copyable_v && sizeof(Callable) <= sizeof(buffer)) { // if the callable object is trivially copyable and the size is less than 16 bytes, // store it in the buffer new (buffer) auto(std::forward(f)); destructor = [](function_record* self) { reinterpret_cast(self->buffer)->~Callable(); }; } else { // otherwise, store it in the heap data = new auto(std::forward(f)); destructor = [](function_record* self) { delete static_cast(self->data); }; } using Parser = template_parser>; Parser::initialize(*this, extras...); wrapper = Parser::call; } function_record(const function_record&) = delete; function_record& operator= (const function_record&) = delete; function_record(function_record&& other) noexcept { std::memcpy(this, &other, sizeof(function_record)); std::memset(&other, 0, sizeof(function_record)); } function_record& operator= (function_record&&) = delete; ~function_record() { if(destructor) { destructor(this); } if(arguments) { delete arguments; } if(next) { delete next; } if(signature) { delete[] signature; } } void append(function_record* record) { function_record* p = this; while(p->next) { p = p->next; } p->next = record; } template T& as() { if constexpr(std::is_trivially_copyable_v && sizeof(T) <= sizeof(buffer)) { return *reinterpret_cast(buffer); } else { return *static_cast(data); } } static function_record& from(handle h) { auto slot = py_getslot(h.ptr(), 0); return *static_cast(py_touserdata(slot)); } void operator() (int argc, handle stack) { function_record* p = this; bool has_self = argc == 3; std::vector args; handle self = py_offset(stack.ptr(), 0); if(has_self) { args.push_back(self); } auto tuple = py_offset(stack.ptr(), 0 + has_self); for(int i = 0; i < py_tuple_len(tuple); ++i) { args.push_back(py_tuple_getitem(tuple, i)); } auto dict = steal(py_offset(stack.ptr(), 1 + has_self)); std::vector> kwargs; dict.apply([&](handle key, handle value) { kwargs.emplace_back(key, value); }); // foreach function record and call the function with not convert while(p != nullptr) { auto result = p->wrapper(*p, args, kwargs, false, self); if(result) { return; } p = p->next; } p = this; // foreach function record and call the function with convert while(p != nullptr) { auto result = p->wrapper(*p, args, kwargs, true, self); if(result) { return; } p = p->next; } std::string msg = "no matching function found, function signature:\n"; p = this; while(p != nullptr) { msg += " "; msg += p->signature; msg += "\n"; p = p->next; } throw std::runtime_error(msg); } private: union { void* data; char buffer[16]; }; wrapper_t wrapper = nullptr; function_record* next = nullptr; arguments_t* arguments = nullptr; destructor_t destructor = nullptr; const char* signature = nullptr; return_value_policy policy = return_value_policy::automatic; }; template void invoke(Fn&& fn, std::index_sequence, std::tuple...>& casters, return_value_policy policy, handle parent) { using underlying_type = std::decay_t; using return_type = callable_return_t; constexpr bool is_void = std::is_void_v; constexpr bool is_member_function_pointer = std::is_member_function_pointer_v; if constexpr(is_member_function_pointer) { // helper function to unpack the arguments to call the member pointer auto unpack = [&](class_type_t& self, auto&... args) { return (self.*fn)(args...); }; if constexpr(!is_void) { py_assign(py_retval(), pkbind::cast(unpack(std::get(casters).value()...), policy, parent).ptr()); } else { unpack(std::get(casters).value()...); py_newnone(py_retval()); } } else { if constexpr(!is_void) { py_assign(py_retval(), pkbind::cast(fn(std::get(casters).value()...), policy, parent).ptr()); } else { fn(std::get(casters).value()...); py_newnone(py_retval()); } } } template struct template_parser, std::tuple, std::index_sequence> { using types = type_list; /// count of the Callable parameters. constexpr inline static auto argc = types::size; // count the number of py::args and py::kwargs constexpr inline static auto args_count = types::template count; constexpr inline static auto kwargs_count = types::template count; static_assert(args_count <= 1, "py::args can occur at most once"); static_assert(kwargs_count <= 1, "py::kwargs can occur at most once"); /// find the position of py::args and py::kwargs constexpr inline static auto args_pos = types::template find; constexpr inline static auto kwargs_pos = types::template find; // FIXME: temporarily, args and kwargs must be at the end of the arguments list /// if have py::kwargs, it must be at the end of the arguments list. static_assert(kwargs_count == 0 || kwargs_pos == argc - 1, "py::kwargs must be the last parameter"); /// if have py::args, it must be before py::kwargs or at the end of the arguments list. static_assert(args_count == 0 || args_pos == kwargs_pos - 1 || args_pos == argc - 1, "py::args must be before py::kwargs or at the end of the parameter list"); using extras = type_list; // count the number of py::doc and py::return_value_policy constexpr inline static auto doc_count = extras::template count; constexpr inline static auto policy_count = extras::template count; static_assert(doc_count <= 1, "doc can occur at most once"); static_assert(policy_count <= 1, "return_value_policy can occur at most once"); constexpr inline static auto policy_pos = extras::template find; constexpr inline static auto last_arg_without_default_pos = types::template find_last; constexpr inline static auto first_arg_with_default_pos = types::template find; static_assert(last_arg_without_default_pos < first_arg_with_default_pos || first_arg_with_default_pos == -1, "parameter with default value must be after parameter without default value"); /// count of named parameters(explicit with name). constexpr inline static auto named_only_argc = extras::template count; constexpr inline static auto named_default_argc = extras::template count; constexpr inline static auto named_argc = named_only_argc + named_default_argc; /// count of normal parameters(which are not py::args or py::kwargs). constexpr inline static auto normal_argc = argc - (args_pos != -1) - (kwargs_pos != -1); /// all parameters must either have no names or all must have names. static_assert(named_argc == 0 || named_argc == normal_argc, "all parameters must either have no names or all must have names."); static void initialize(function_record& record, const Extras&... extras) { auto extras_tuple = std::make_tuple(extras...); constexpr static bool has_named_args = (named_argc > 0); if constexpr(policy_pos != -1) { record.policy = std::get(extras_tuple); } // TODO: set others // set default arguments if constexpr(has_named_args) { record.arguments = new function_record::arguments_t(); auto add_arguments = [&](const auto& default_) { using type = remove_cvref_t; if constexpr(std::is_same_v) { auto& arguments = *record.arguments; arguments.names.emplace_back(default_.name); arguments.defaults.emplace_back(); } else if constexpr(std::is_same_v) { auto& arguments = *record.arguments; arguments.names.emplace_back(default_.name); arguments.defaults.emplace_back(std::move(default_.value)); } }; (add_arguments(extras), ...); } // set signature { std::string sig = "("; std::size_t index = 0; auto append = [&](auto _t) { using T = remove_cvref_t; if constexpr(std::is_same_v) { sig += "*args"; } else if constexpr(std::is_same_v) { sig += "**kwargs"; } else if constexpr(has_named_args) { sig += record.arguments->names[index].c_str(); sig += ": "; sig += type_info::of().name; if(!record.arguments->defaults[index].empty()) { sig += " = "; sig += record.arguments->defaults[index] .attr("__repr__")() .cast(); } } else { sig += "_: "; sig += type_info::of().name; } if(index + 1 < argc) { sig += ", "; } index++; }; (append(type_identity{}), ...); sig += ")"; char* buffer = new char[sig.size() + 1]; std::memcpy(buffer, sig.data(), sig.size()); buffer[sig.size()] = '\0'; record.signature = buffer; } } /// try to call a C++ function(store in function_record) with the arguments which are from /// Python. if success, return true, otherwise return false. static bool call(function_record& record, std::vector& args, std::vector>& kwargs, bool convert, handle parent) { // first, we try to load arguments into the stack. // use argc + 1 to avoid compile error when argc is 0. handle stack[argc + 1] = {}; // if have default arguments, load them if constexpr(named_default_argc > 0) { auto& defaults = record.arguments->defaults; for(std::size_t i = named_only_argc; i < named_argc; ++i) { stack[i] = defaults[i]; } } // load arguments from call arguments if(args.size() > normal_argc) { if constexpr(args_pos == -1) { return false; } } for(std::size_t i = 0; i < std::min(normal_argc, (int)args.size()); ++i) { stack[i] = args[i]; } object repack_args; // pack the args if constexpr(args_pos != -1) { const auto n = static_cast(args.size() > normal_argc ? args.size() - normal_argc : 0); auto pack = tuple(n); for(int i = 0; i < n; ++i) { pack[i] = args[normal_argc + i]; } repack_args = std::move(pack); stack[args_pos] = repack_args; } // pack the kwargs int index = 0; if constexpr(named_argc != 0) { int arg_index = 0; while(arg_index < named_argc && index < kwargs.size()) { const auto name = kwargs[index].first; const auto value = kwargs[index].second; if(name.cast() == record.arguments->names[arg_index]) { stack[arg_index] = value; index += 1; } arg_index += 1; } } object repacked_kwargs; if constexpr(kwargs_pos != -1) { auto pack = dict(); while(index < kwargs.size()) { pack[kwargs[index].first] = kwargs[index].second; index += 1; } repacked_kwargs = std::move(pack); stack[kwargs_pos] = repacked_kwargs; } // check if all the arguments are valid for(std::size_t i = 0; i < argc; ++i) { if(!stack[i]) { return false; } } // ok, all the arguments are valid, call the function std::tuple...> casters; if(((std::get(casters).load(stack[Is], convert)) && ...)) { invoke(record.as(), std::index_sequence{}, casters, record.policy, parent); return true; } return false; } }; } // namespace impl class cpp_function : public function { inline static py_Type m_type = 0; PKBIND_TYPE_IMPL(function, cpp_function, tp_function); static void register_() { m_type = py_newtype("function_record", tp_object, nullptr, [](void* data) { static_cast(data)->~function_record(); }); } static bool is_function_record(handle h) { if(isinstance(h)) { auto slot = py_getslot(h.ptr(), 0); if(slot) { return py_typeof(slot) == m_type; } } return false; } template cpp_function(bool is_method, const char* name, Fn&& fn, const Extras&... extras) : function(alloc_t{}) { // bind the function std::string sig = name; sig += is_method ? "(self, *args, **kwargs)" : "(*args, **kwargs)"; auto call = [](int argc, py_Ref stack) { handle func = py_inspect_currentfunction(); auto data = py_touserdata(py_getslot(func.ptr(), 0)); auto& record = *static_cast(data); try { record(argc, stack); return true; } catch(std::domain_error& e) { py_exception(tp_ValueError, e.what()); } catch(std::invalid_argument& e) { py_exception(tp_ValueError, e.what()); } catch(std::length_error& e) { py_exception(tp_ValueError, e.what()); } catch(std::out_of_range& e) { py_exception(tp_IndexError, e.what()); } catch(std::range_error& e) { py_exception(tp_ValueError, e.what()); } catch(stop_iteration&) { StopIteration(); } catch(index_error& e) { py_exception(tp_IndexError, e.what()); } catch(key_error& e) { py_exception(tp_KeyError, e.what()); } catch(value_error& e) { py_exception(tp_ValueError, e.what()); } catch(type_error& e) { py_exception(tp_TypeError, e.what()); } catch(import_error& e) { py_exception(tp_ImportError, e.what()); } catch(attribute_error& e) { py_exception(tp_AttributeError, e.what()); } catch(std::exception& e) { py_exception(tp_RuntimeError, e.what()); } return false; }; py_newfunction(m_ptr, sig.c_str(), call, nullptr, 1); assert(m_type != 0 && "function record type not registered"); auto slot = py_getslot(m_ptr, 0); void* data = py_newobject(slot, m_type, 0, sizeof(impl::function_record)); new (data) impl::function_record(std::forward(fn), extras...); } template cpp_function(Fn&& fn, const Extras&... extras) : cpp_function("lambda", std::forward(fn), extras...) {} }; class property : public object { PKBIND_TYPE_IMPL(object, property, tp_property); property(handle getter, handle setter = none()) : object() { auto start = py_peek(0); py_push(getter.ptr()); py_push(setter.ptr()); raise_call(type::of().index(), 2, start); *this = object::from_ret(); } }; class staticmethod : public object { PKBIND_TYPE_IMPL(object, staticmethod, tp_staticmethod); staticmethod(handle method) : object() { auto start = py_peek(0); py_push(method.ptr()); raise_call(type::of().index(), 1, start); *this = object::from_ret(); } }; namespace impl { template void bind_function(handle obj, const char* name_, Fn&& fn, const Extras&... extras) { constexpr bool has_named_args = ((std::is_same_v || std::is_same_v) || ...); auto name = py_name(name_); auto func = py_getdict(obj.ptr(), name); if(func && cpp_function::is_function_record(func)) { auto slot = py_getslot(func, 0); auto& record = *static_cast(py_touserdata(slot)); if constexpr(has_named_args && is_method) { record.append(new function_record(std::forward(fn), arg("self"), extras...)); } else { record.append(new function_record(std::forward(fn), extras...)); } } else { if constexpr(is_static) { py_setdict( obj.ptr(), name, staticmethod(cpp_function(is_method, name_, std::forward(fn), extras...).ptr()) .ptr()); } else { if constexpr(has_named_args && is_method) { py_setdict( obj.ptr(), name, cpp_function(is_method, name_, std::forward(fn), arg("self"), extras...) .ptr()); } else { py_setdict(obj.ptr(), name, cpp_function(is_method, name_, std::forward(fn), extras...).ptr()); } } } } template void bind_property(handle obj, const char* name, Getter&& getter_, Setter&& setter_, const Extras&... extras) { if constexpr(std::is_same_v, std::nullptr_t>) { cpp_function getter(true, name, std::forward(getter_), return_value_policy::reference_internal, extras...); property prop(getter.ptr()); setattr(obj, name, prop); } else { cpp_function getter(true, name, std::forward(getter_), return_value_policy::reference_internal, extras...); cpp_function setter(true, name, std::forward(setter_), return_value_policy::reference_internal, extras...); property prop(getter.ptr(), setter.ptr()); setattr(obj, name, prop); } } } // namespace impl inline dict::dict(std::initializer_list args) : dict() { for(auto& arg: args) { this->operator[] (arg.name) = arg.value; } } } // namespace pkbind