pybind11 for pkpy 2.0 (#299)

* update pkbind.

* some fix.

* some fix.

* update job name.

* fix CI.

* remove iostream.
This commit is contained in:
ykiko 2024-08-23 09:55:27 +08:00 committed by GitHub
parent 372325d663
commit 7e99584ddc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 4194 additions and 1139 deletions

76
.github/workflows/pybind11.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: PKBIND Build and Test
on:
push:
paths-ignore:
- "docs/**"
- "web/**"
- "**.md"
pull_request:
paths-ignore:
- "docs/**"
- "web/**"
- "**.md"
jobs:
build_linux:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up GCC
run: |
sudo apt-get update
sudo apt-get install -y gcc g++
- name: Set up CMake
uses: jwlawson/actions-setup-cmake@v1.10
- name: Test
run: |
cd include/pybind11/tests
cmake -B build
cmake --build build --config Release --parallel
./build/PKBIND_TEST
build_win:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Set up CMake
uses: jwlawson/actions-setup-cmake@v1.10
- name: Test
run: |
cd include\pybind11\tests
cmake -B build
cmake --build build --config Release --parallel
build\Release\PKBIND_TEST.exe
build_mac:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Clang
run: |
brew install llvm
echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
- name: Set up CMake
uses: jwlawson/actions-setup-cmake@v1.10
- name: Test
run: |
cd include/pybind11/tests
cmake -B build -DENABLE_TEST=ON
cmake --build build --config Release --parallel
./build/PKBIND_TEST

3
include/pybind11/embed.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
#include "pybind11.h"

View File

@ -0,0 +1,164 @@
#pragma once
#include "builtins.h"
namespace pkbind {
template <typename policy>
class accessor : public interface<accessor<policy>> {
using key_type = typename policy::key_type;
friend interface<handle>;
friend interface<accessor<policy>>;
friend tuple;
friend list;
friend dict;
accessor(handle obj, key_type key) : m_obj(obj), m_value(), m_key(key) {}
public:
auto ptr() const {
if(m_value.empty()) {
m_value = borrow(policy::get(m_obj, m_key));
}
return m_value.ptr();
}
template <typename Value>
accessor& operator= (Value&& value) && {
policy::set(m_obj, m_key, pkbind::cast(std::forward<Value>(value)));
return *this;
}
template <typename Value>
accessor& operator= (Value&& value) & {
m_value = std::forward<Value>(value);
return *this;
}
operator handle () const { return ptr(); }
private:
handle m_obj;
mutable object m_value;
key_type m_key;
};
namespace policy {
struct attr {
using key_type = name;
static handle get(handle obj, name key) {
raise_call<py_getattr>(obj.ptr(), key.index());
return py_retval();
}
static void set(handle obj, name key, handle value) { raise_call<py_setattr>(obj.ptr(), key.index(), value.ptr()); }
};
template <typename Key>
struct item {
using key_type = Key;
static handle get(handle obj, int key) { return get(obj, int_(key)); }
static handle get(handle obj, name key) { return get(obj, str(key)); }
static handle get(handle obj, handle key) {
raise_call<py_getitem>(obj.ptr(), key.ptr());
return py_retval();
}
static void set(handle obj, int key, handle value) { set(obj, int_(key), value); }
static void set(handle obj, name key, handle value) { set(obj, str(key), value); }
static void set(handle obj, handle key, handle value) { raise_call<py_setitem>(obj.ptr(), key.ptr(), value.ptr()); }
};
struct tuple {
using key_type = int;
static handle get(handle obj, int key) { return py_tuple_getitem(obj.ptr(), key); }
static void set(handle obj, int key, handle value) { py_tuple_setitem(obj.ptr(), key, value.ptr()); }
};
struct list {
using key_type = int;
static handle get(handle obj, int key) { return py_list_getitem(obj.ptr(), key); }
static void set(handle obj, int key, handle value) { py_list_setitem(obj.ptr(), key, value.ptr()); }
};
template <typename Key>
struct dict {
using key_type = Key;
static handle get(handle obj, int key) { return get(obj, int_(key)); }
static handle get(handle obj, name key) { return get(obj, str(key)); }
static handle get(handle obj, handle key) {
raise_call<py_dict_getitem>(obj.ptr(), key.ptr());
return py_retval();
}
static void set(handle obj, int key, handle value) { set(obj, int_(key), value); }
static void set(handle obj, name key, handle value) { set(obj, str(key), value); }
static void set(handle obj, handle key, handle value) {
raise_call<py_dict_setitem>(obj.ptr(), key.ptr(), value.ptr());
}
};
} // namespace policy
// implement other methods of interface
template <typename Derived>
inline attr_accessor interface<Derived>::attr(name key) const {
return {ptr(), key};
}
template <typename Derived>
inline item_accessor<int> interface<Derived>::operator[] (int key) const {
return {ptr(), key};
}
template <typename Derived>
inline item_accessor<name> interface<Derived>::operator[] (name key) const {
return {ptr(), key};
}
template <typename Derived>
inline item_accessor<handle> interface<Derived>::operator[] (handle key) const {
return {ptr(), key};
}
template <typename... Args>
object str::format(Args&&... args) {
return attr("format")(std::forward<Args>(args)...);
}
inline tuple_accessor tuple::operator[] (int index) const { return {m_ptr, index}; }
inline list_accessor list::operator[] (int index) const { return {m_ptr, index}; };
inline dict_accessor<int> dict::operator[] (int key) const { return {m_ptr, key}; }
inline dict_accessor<name> dict::operator[] (name key) const { return {m_ptr, key}; }
inline dict_accessor<handle> dict::operator[] (handle key) const { return {m_ptr, key}; }
inline dict::iterator::iterator(handle h) : items(h.attr("items")()), iter(items.begin()) {}
inline std::pair<object, object> dict::iterator::operator* () const {
tuple pair = *iter;
return {borrow(pair[0]), borrow(pair[1])};
}
} // namespace pkbind

View File

@ -2,107 +2,192 @@
#include "types.h" #include "types.h"
namespace pybind11 { namespace pkbind {
inline void exec(const char* code, handle global = {}, handle local = {}) {
vm->py_exec(code, global.ptr(), local.ptr()); template <typename... Args>
inline void print(Args&&... args) {
handle print = py_getbuiltin(py_name("print"));
print(std::forward<Args>(args)...);
} }
// wrapper for builtin functions in Python inline object eval(std::string_view code, handle globals = none(), handle locals = none()) {
inline bool hasattr(const handle& obj, const handle& name) { if(globals.is_none() && locals.is_none()) {
auto& key = _builtin_cast<pkpy::Str>(name); std::string src{code};
return vm->getattr(obj.ptr(), key, false) != nullptr; raise_call<py_eval>(src.c_str(), nullptr);
return object::from_ret();
} else {
handle eval = py_getbuiltin(py_name("eval"));
return eval(str(code), globals.is_none() ? dict() : globals, locals.is_none() ? dict() : locals);
}
} }
inline bool hasattr(const handle& obj, const char* name) { return vm->getattr(obj.ptr(), name, false) != nullptr; } inline object exec(std::string_view code, handle globals = none(), handle locals = none()) {
if(globals.is_none() && locals.is_none()) {
inline void delattr(const handle& obj, const handle& name) { std::string src{code};
auto& key = _builtin_cast<pkpy::Str>(name); raise_call<py_exec>(src.c_str(), "exec", EXEC_MODE, nullptr);
vm->delattr(obj.ptr(), key); return object::from_ret();
} else {
handle exec = py_getbuiltin(py_name("exec"));
return exec(str(code), globals, locals);
}
} }
inline void delattr(const handle& obj, const char* name) { vm->delattr(obj.ptr(), name); } inline object locals() {
handle locals = py_getbuiltin(py_name("locals"));
inline object getattr(const handle& obj, const handle& name) { return locals();
auto& key = _builtin_cast<pkpy::Str>(name);
return reinterpret_borrow<object>(vm->getattr(obj.ptr(), key));
} }
inline object getattr(const handle& obj, const char* name) { inline object globals() {
return reinterpret_borrow<object>(vm->getattr(obj.ptr(), name)); handle globals = py_getbuiltin(py_name("globals"));
return globals();
} }
inline object getattr(const handle& obj, const handle& name, const handle& default_) { inline bool hasattr(handle obj, name name) {
if(!hasattr(obj, name)) { return reinterpret_borrow<object>(default_); } auto pc = py_peek(0);
return getattr(obj, name); auto result = py_getattr(obj.ptr(), name.index());
if(result) {
return true;
} else {
py_clearexc(pc);
return false;
}
} }
inline object getattr(const handle& obj, const char* name, const handle& default_) { inline object getattr(handle obj, name name) {
if(!hasattr(obj, name)) { return reinterpret_borrow<object>(default_); } raise_call<py_getattr>(obj.ptr(), name.index());
return getattr(obj, name); return object::from_ret();
} }
inline void setattr(const handle& obj, const handle& name, const handle& value) { inline void setattr(handle obj, name name, handle value) {
auto& key = _builtin_cast<pkpy::Str>(name); raise_call<py_setattr>(obj.ptr(), name.index(), value.ptr());
vm->setattr(obj.ptr(), key, value.ptr());
} }
inline void setattr(const handle& obj, const char* name, const handle& value) { inline void delattr(handle obj, name name) { raise_call<py_delattr>(obj.ptr(), name.index()); }
vm->setattr(obj.ptr(), name, value.ptr());
inline py_i64 hash(handle obj) {
py_i64 result = 0;
raise_call<py_hash>(obj.ptr(), &result);
return result;
}
inline bool isinstance(handle obj, type type) { return py_isinstance(obj.ptr(), type.index()); }
inline bool python_error::match(type type) const { return isinstance(m_exception.ptr(), type); }
template <typename T>
constexpr inline bool is_pyobject_v = std::is_base_of_v<object, std::decay_t<T>> || std::is_same_v<type, T>;
template <typename T>
inline type type::of() {
if constexpr(is_pyobject_v<T>) {
if constexpr(is_check_v<T>) {
return type(tp_object);
} else {
return type(T::type_or_check());
}
} else {
auto it = m_type_map->find(typeid(T));
if(it != m_type_map->end()) {
return type(it->second);
} else {
// if not found, raise error
std::string msg = "can not c++ instance cast to object, type: {";
msg += type_name<T>();
msg += "} is not registered.";
throw std::runtime_error(msg);
}
}
} }
template <typename T> template <typename T>
inline bool isinstance(const handle& obj) { inline bool type::isinstance(handle obj) {
pkpy::Type cls = _builtin_cast<pkpy::Type>(type::handle_of<T>().ptr()); if constexpr(is_pyobject_v<T>) {
return vm->isinstance(obj.ptr(), cls); // for every python object wrapper type, there must be a `type_or_check` method.
// for some types, it returns the underlying type in pkpy, e.g., `int_` -> `tp_int`.
// for other types that may not have a corresponding type in pkpy, it returns a check function.
// e.g., `iterable` -> `[](handle h){ return hasattr(h, "iter"); }`.
auto type_or_check = T::type_or_check();
if constexpr(is_check_v<T>) {
return type_or_check(obj);
} else {
return pkbind::isinstance(obj, type(type_or_check));
}
} else {
return pkbind::isinstance(obj, of<T>());
}
}
inline bool issubclass(type derived, type base) { return py_issubclass(derived.index(), base.index()); }
template <typename T>
inline bool isinstance(handle obj) {
return type::isinstance<T>(obj);
} }
template <> template <>
inline bool isinstance<handle>(const handle&) = delete; inline bool isinstance<handle>(handle) = delete;
template <>
inline bool isinstance<iterable>(const handle& obj) {
return hasattr(obj, "__iter__");
}
template <>
inline bool isinstance<iterator>(const handle& obj) {
return hasattr(obj, "__iter__") && hasattr(obj, "__next__");
}
inline bool isinstance(const handle& obj, const handle& type) {
return vm->isinstance(obj.ptr(), _builtin_cast<pkpy::Type>(type));
}
inline int64_t hash(const handle& obj) { return vm->py_hash(obj.ptr()); }
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE = void>
struct type_caster; struct type_caster;
template <typename T> template <typename T>
handle object cast(T&& value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = {}) {
_cast(T&& value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = handle()) { // decay_t can resolve c-array type, but remove_cv_ref_t can't.
using U = std::remove_pointer_t<std::remove_cv_t<std::remove_reference_t<T>>>; using underlying_type = std::decay_t<T>;
return type_caster<U>::cast(std::forward<T>(value), policy, parent);
}
template <typename T> if constexpr(std::is_convertible_v<underlying_type, handle> && !is_pyobject_v<underlying_type>) {
object return object(std::forward<T>(value), object::realloc_t{});
cast(T&& value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = handle()) { } else if constexpr(is_unique_pointer_v<underlying_type>) {
return reinterpret_borrow<object>(_cast(std::forward<T>(value), policy, parent)); using pointer = typename underlying_type::pointer;
} return type_caster<pointer>::cast(value.release(), return_value_policy::take_ownership, parent);
template <typename T>
T cast(handle obj, bool convert = false) {
using Caster = type_caster<std::remove_pointer_t<std::remove_cv_t<std::remove_reference_t<T>>>>;
Caster caster;
if(caster.load(obj, convert)) {
if constexpr(std::is_rvalue_reference_v<T>) {
return std::move(caster.value);
} else { } else {
return caster.value; static_assert(!is_multiple_pointer_v<underlying_type>, "multiple pointer is not supported.");
static_assert(!std::is_void_v<std::remove_pointer_t<underlying_type>>,
"void* is not supported, consider using py::capsule.");
// resolve for automatic policy.
if(policy == return_value_policy::automatic) {
policy = std::is_pointer_v<underlying_type> ? return_value_policy::take_ownership
: std::is_lvalue_reference_v<T&&> ? return_value_policy::copy
: return_value_policy::move;
} else if(policy == return_value_policy::automatic_reference) {
policy = std::is_pointer_v<underlying_type> ? return_value_policy::reference
: std::is_lvalue_reference_v<T&&> ? return_value_policy::copy
: return_value_policy::move;
}
return type_caster<underlying_type>::cast(std::forward<T>(value), policy, parent);
} }
} }
throw std::runtime_error("Unable to cast Python instance to C++ type");
template <typename T>
T cast(handle obj, bool convert = true) {
using caster_t = type_caster<T>;
constexpr auto is_dangling_v = (std::is_reference_v<T> || is_pointer_v<T>) && caster_t::is_temporary_v;
static_assert(!is_dangling_v, "dangling reference or pointer is not allowed.");
assert(obj.ptr() != nullptr);
caster_t caster;
if(caster.load(obj, convert)) {
if constexpr(std::is_reference_v<T>) {
return caster.value();
} else {
return std::move(caster.value());
} }
} // namespace pybind11 } else {
std::string msg = "cast python instance to c++ failed, obj type is: {";
msg += type::of(obj).name();
msg += "}, target type is: {";
msg += type_name<T>();
msg += "}.";
throw cast_error(msg);
}
}
template <typename Derived>
template <typename T>
T interface<Derived>::cast() const {
return pkbind::cast<T>(handle(this->ptr()), true);
}
} // namespace pkbind

View File

@ -1,166 +1,190 @@
#pragma once #pragma once
#include "instance.h" #include "instance.h"
#include "builtins.h"
#include "type_traits.h"
namespace pybind11 { namespace pkbind {
using pkpy::is_floating_point_v;
using pkpy::is_integral_v;
template <typename T> template <typename T>
constexpr inline bool is_string_v = std::is_same_v<T, char*> || std::is_same_v<T, const char*> || constexpr inline bool is_string_v =
std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>; std::is_same_v<T, const char*> || std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>;
template <typename T> /// The `type_caster` class is responsible for converting between Python objects and C++ objects.
constexpr bool is_pyobject_v = std::is_base_of_v<handle, T>; ///
/// The static method `type_caster<T>::cast(...)` is used to convert a C++ object to a Python object.
/// If the conversion fails, an exception is thrown.
///
/// The method `type_caster<T>::load(...)` is used to try to convert a Python object to a C++ object.
/// If the conversion is successful, it returns true, and you can then call `type_caster<T>::value()`
/// to access the resulting C++ object. If the conversion fails, it returns false.
///
/// NOTE: The type T could be a reference type or a pointer type. What is the lifetime of the reference or pointer?
/// It depends on the referenced type. For some types, such as bool, int, float, etc., the loaded value is stored
/// in the type_caster itself, so the lifetime of the reference is no longer than the lifetime of the type_caster
/// object. For other user-registered types, the lifetime of the reference is the same as the corresponding Python
/// object. A static variable `is_temporary_v` is used to indicate whether the loaded value is temporary or not.
template <typename T, typename> template <typename T, typename SFINAE>
struct type_caster; struct type_caster {
T* data;
static_assert(!std::is_pointer_v<T>, "type caster for pointer type must be specialized.");
static_assert(!std::is_reference_v<T>, "type caster for reference type must be specialized.");
template <typename U>
static object cast(U&& value, return_value_policy policy, handle parent) {
// TODO: support implicit cast
return instance::create(type::of<T>(), std::forward<U>(value), parent, policy);
}
bool load(handle src, bool convert) {
if(isinstance<T>(src)) {
auto& i = *static_cast<instance*>(py_touserdata(src.ptr()));
data = &i.as<T>();
return true;
}
return false;
}
T& value() { return *data; }
constexpr inline static bool is_temporary_v = false;
};
template <> template <>
struct type_caster<bool> { struct type_caster<bool> {
bool value; bool data;
bool load(const handle& src, bool) { static object cast(bool src, return_value_policy, handle) { return bool_(src); }
if(isinstance<pybind11::bool_>(src)) {
value = pkpy::_py_cast<bool>(vm, src.ptr()); bool load(handle src, bool) {
if(isinstance<pkbind::bool_>(src)) {
data = py_tobool(src.ptr());
return true; return true;
} }
return false; return false;
} }
static handle cast(bool src, return_value_policy, handle) { return src ? vm->True : vm->False; } bool& value() { return data; }
constexpr inline static bool is_temporary_v = true;
}; };
template <typename T> template <typename T>
struct type_caster<T, std::enable_if_t<is_integral_v<T>>> { struct type_caster<T, std::enable_if_t<is_integer_v<T>>> {
T value; T data;
bool load(const handle& src, bool convert) { static object cast(T src, return_value_policy, handle) { return int_(src); }
if(isinstance<pybind11::int_>(src)) {
value = pkpy::_py_cast<T>(vm, src.ptr()); bool load(handle src, bool) {
if(isinstance<int_>(src)) {
data = static_cast<T>(py_toint(src.ptr()));
return true; return true;
} }
return false; return false;
} }
static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); } T& value() { return data; }
constexpr inline static bool is_temporary_v = true;
}; };
template <typename T> template <typename T>
struct type_caster<T, std::enable_if_t<is_floating_point_v<T>>> { struct type_caster<T, std::enable_if_t<is_floating_point_v<T>>> {
T value; T data;
bool load(const handle& src, bool convert) { static object cast(T src, return_value_policy, handle) { return float_(src); }
if(isinstance<pybind11::float_>(src)) {
value = pkpy::_py_cast<T>(vm, src.ptr()); bool load(handle src, bool convert) {
if(isinstance<pkbind::float_>(src)) {
data = py_tofloat(src.ptr());
return true; return true;
} }
if(convert && isinstance<pybind11::int_>(src)) { if(convert && isinstance<pkbind::int_>(src)) {
value = pkpy::_py_cast<int64_t>(vm, src.ptr()); data = py_toint(src.ptr());
return true; return true;
} }
return false; return false;
} }
static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); } T& value() { return data; }
constexpr inline static bool is_temporary_v = true;
}; };
template <typename T> template <typename T>
struct type_caster<T, std::enable_if_t<is_string_v<T>>> { struct type_caster<T, std::enable_if_t<is_string_v<T>>> {
T value; T data;
bool load(const handle& src, bool) { template <typename U>
if(isinstance<pybind11::str>(src)) { static object cast(U&& src, return_value_policy, handle) {
// FIXME: support other kinds of string return str(std::forward<U>(src));
value = pkpy::_py_cast<std::string>(vm, src.ptr()); }
bool load(handle src, bool) {
if(isinstance<pkbind::str>(src)) {
data = py_tostr(src.ptr());
return true; return true;
} }
return false; return false;
} }
static handle cast(const std::string& src, return_value_policy, handle) { return pkpy::py_var(vm, src); } T& value() { return data; }
constexpr inline static bool is_temporary_v = true;
}; };
template <typename T> template <typename T>
struct type_caster<T, std::enable_if_t<is_pyobject_v<T>>> { struct type_caster<T, std::enable_if_t<is_pyobject_v<T>>> {
T value; T data;
bool load(const handle& src, bool) { template <typename U>
static object cast(U&& src, return_value_policy, handle) {
return object(std::forward<U>(src));
}
bool load(handle src, bool) {
if(isinstance<T>(src)) { if(isinstance<T>(src)) {
value = reinterpret_borrow<T>(src); data = T(src.ptr(), object::realloc_t{});
return true; return true;
} }
return false; return false;
} }
template <typename U> T& value() { return data; }
static handle cast(U&& src, return_value_policy, handle) {
return std::forward<U>(src);
}
};
template <typename T, typename> constexpr inline static bool is_temporary_v = true;
struct type_caster {
value_wrapper<T> value;
using underlying_type = std::remove_pointer_t<decltype(value.pointer)>;
bool load(handle src, bool convert) {
if(isinstance<underlying_type>(src)) {
auto& i = _builtin_cast<instance>(src);
value.pointer = &i.cast<underlying_type>();
return true;
}
return false;
}
template <typename U>
static handle cast(U&& value, return_value_policy policy, const handle& parent = handle()) {
// TODO: support implicit cast
const auto& info = typeid(underlying_type);
bool existed = vm->_cxx_typeid_map.find(info) != vm->_cxx_typeid_map.end();
if(existed) {
auto type = vm->_cxx_typeid_map[info];
return instance::create(std::forward<U>(value), type, policy, parent.ptr());
}
vm->TypeError("type not registered");
}
}; };
template <typename T> template <typename T>
struct type_caster<T, std::enable_if_t<std::is_pointer_v<T> || std::is_reference_v<T>>> { struct type_caster<T, std::enable_if_t<is_pointer_v<T> || std::is_reference_v<T>>> {
using underlying = std::conditional_t<std::is_pointer_v<T>, std::remove_pointer_t<T>, std::remove_reference_t<T>>; using underlying =
std::remove_cv_t<std::conditional_t<is_pointer_v<T>, std::remove_pointer_t<T>, std::remove_reference_t<T>>>;
struct wrapper {
type_caster<underlying> caster; type_caster<underlying> caster;
operator T () {
if constexpr(std::is_pointer_v<T>) {
return caster.value.pointer;
} else {
return caster.value;
}
}
};
wrapper value;
bool load(const handle& src, bool convert) { return value.caster.load(src, convert); }
template <typename U> template <typename U>
static handle cast(U&& value, return_value_policy policy, const handle& parent) { static object cast(U&& value, return_value_policy policy, handle parent) {
return type_caster<underlying>::cast(std::forward<U>(value), policy, parent); return type_caster<underlying>::cast(std::forward<U>(value), policy, parent);
} }
};
} // namespace pybind11
bool load(handle src, bool convert) { return caster.load(src, convert); }
T value() {
if constexpr(std::is_pointer_v<T>) {
return &caster.value();
} else {
return caster.value();
}
}
constexpr inline static bool is_temporary_v = type_caster<underlying>::is_temporary_v;
};
} // namespace pkbind

View File

@ -1,75 +1,93 @@
#pragma once #pragma once
#include "cpp_function.h" #include "module.h"
#include "type_traits.h"
namespace pybind11 { namespace pkbind {
class module : public object { struct dynamic_attr {};
public: template <typename T, typename Base = void>
using object::object;
static module import(const char* name) {
if(name == std::string_view{"__main__"}) {
return module{vm->_main, true};
} else {
return module{vm->py_import(name, false), true};
}
}
};
// TODO:
// 1. inheritance
// 2. virtual function
// 3. factory function
template <typename T, typename... Others>
class class_ : public type { class class_ : public type {
protected:
handle m_scope;
public: public:
using type::type; using type::type;
using underlying_type = T;
template <typename... Args> template <typename... Args>
class_(const handle& scope, const char* name, Args&&... args) : class_(handle scope, const char* name, const Args&... args) :
type(vm->new_type_object(scope.ptr(), name, vm->tp_object, false, pkpy::PyTypeInfo::Vt::get<instance>()), type(py_newtype(name,
true) { std::is_same_v<Base, void> ? tp_object : type::of<Base>().index(),
pkpy::PyVar mod = scope.ptr(); scope.ptr(),
mod->attr().set(name, m_ptr); [](void* data) {
vm->_cxx_typeid_map[typeid(T)] = _builtin_cast<pkpy::Type>(m_ptr); static_cast<instance*>(data)->~instance();
vm->bind_func(m_ptr, "__new__", -1, [](pkpy::VM* vm, pkpy::ArgsView args) { })),
auto cls = _builtin_cast<pkpy::Type>(args[0]); m_scope(scope) {
return instance::create<T>(cls); m_type_map->try_emplace(typeid(T), this->index());
});
auto& info = type_info::of<T>();
info.name = name;
py_newfunction(
py_tpgetmagic(this->index(), py_MagicNames::__new__),
"__new__(type, *args, **kwargs)",
[](int, py_Ref stack) {
auto cls = py_offset(stack, 0);
[[maybe_unused]] auto args = py_offset(stack, 1);
[[maybe_unused]] auto kwargs = py_offset(stack, 2);
auto info = &type_info::of<T>();
int slot = ((std::is_same_v<dynamic_attr, Args> || ...) ? -1 : 0);
void* data = py_newobject(retv, steal<type>(cls).index(), slot, sizeof(instance));
new (data) instance{instance::Flag::Own, operator new (info->size), info};
return true;
},
nullptr,
0);
} }
/// bind constructor /// bind constructor
template <typename... Args, typename... Extra> template <typename... Args, typename... Extra>
class_& def(init<Args...>, const Extra&... extra) { class_& def(impl::constructor<Args...>, const Extra&... extra) {
if constexpr(!std::is_constructible_v<T, Args...>) { if constexpr(!std::is_constructible_v<T, Args...>) {
static_assert(std::is_constructible_v<T, Args...>, "Invalid constructor arguments"); static_assert(std::is_constructible_v<T, Args...>, "Invalid constructor arguments");
} else { } else {
bind_function( impl::bind_function<true, false>(
*this, *this,
"__init__", "__init__",
[](T* self, Args... args) { [](T* self, Args... args) {
new (self) T(args...); new (self) T(args...);
}, },
pkpy::BindType::DEFAULT,
extra...); extra...);
return *this; return *this;
} }
} }
template <typename Fn, typename... Extra>
class_& def(impl::factory<Fn> factory, const Extra&... extra) {
using ret = callable_return_t<Fn>;
if constexpr(!std::is_same_v<T, ret>) {
static_assert(std::is_same_v<T, ret>, "Factory function must return the class type");
} else {
impl::bind_function<true, false>(*this, "__init__", factory.make(), extra...);
return *this;
}
}
/// bind member function /// bind member function
template <typename Fn, typename... Extra> template <typename Fn, typename... Extra>
class_& def(const char* name, Fn&& f, const Extra&... extra) { class_& def(const char* name, Fn&& f, const Extra&... extra) {
using first = std::tuple_element_t<0, callable_args_t<remove_cvref_t<Fn>>>; using first = remove_cvref_t<std::tuple_element_t<0, callable_args_t<remove_cvref_t<Fn>>>>;
constexpr bool is_first_base_of_v = std::is_reference_v<first> && std::is_base_of_v<T, remove_cvref_t<first>>; constexpr bool is_first_base_of_v = std::is_base_of_v<first, T> || std::is_same_v<first, T>;
if constexpr(!is_first_base_of_v) { if constexpr(!is_first_base_of_v) {
static_assert(is_first_base_of_v, static_assert(
is_first_base_of_v,
"If you want to bind member function, the first argument must be the base class"); "If you want to bind member function, the first argument must be the base class");
} else { } else {
bind_function(*this, name, std::forward<Fn>(f), pkpy::BindType::DEFAULT, extra...); impl::bind_function<true, false>(*this, name, std::forward<Fn>(f), extra...);
} }
return *this; return *this;
@ -82,21 +100,29 @@ public:
return *this; return *this;
} }
// TODO: factory function
/// bind static function /// bind static function
template <typename Fn, typename... Extra> template <typename Fn, typename... Extra>
class_& def_static(const char* name, Fn&& f, const Extra&... extra) { class_& def_static(const char* name, Fn&& f, const Extra&... extra) {
bind_function(*this, name, std::forward<Fn>(f), pkpy::BindType::STATICMETHOD, extra...); impl::bind_function<false, true>(*this, name, std::forward<Fn>(f), extra...);
return *this; return *this;
} }
template <typename MP, typename... Extras> template <typename MP, typename... Extras>
class_& def_readwrite(const char* name, MP mp, const Extras&... extras) { class_& def_readwrite(const char* name, MP mp, const Extras&... extras) {
if constexpr(!std::is_member_object_pointer_v<MP>) { if constexpr(!std::is_member_object_pointer_v<MP>) {
static_assert(std::is_member_object_pointer_v<MP>, "def_readwrite only supports pointer to data member"); static_assert(std::is_member_object_pointer_v<MP>,
"def_readwrite only supports pointer to data member");
} else { } else {
bind_property(*this, name, mp, mp, extras...); impl::bind_property(
*this,
name,
[mp](class_type_t<MP>& self) -> auto& {
return self.*mp;
},
[mp](class_type_t<MP>& self, const member_type_t<MP>& value) {
self.*mp = value;
},
extras...);
} }
return *this; return *this;
} }
@ -104,22 +130,34 @@ public:
template <typename MP, typename... Extras> template <typename MP, typename... Extras>
class_& def_readonly(const char* name, MP mp, const Extras&... extras) { class_& def_readonly(const char* name, MP mp, const Extras&... extras) {
if constexpr(!std::is_member_object_pointer_v<MP>) { if constexpr(!std::is_member_object_pointer_v<MP>) {
static_assert(std::is_member_object_pointer_v<MP>, "def_readonly only supports pointer to data member"); static_assert(std::is_member_object_pointer_v<MP>,
"def_readonly only supports pointer to data member");
} else { } else {
bind_property(*this, name, mp, nullptr, extras...); impl::bind_property(
*this,
name,
[mp](class_type_t<MP>& self) -> auto& {
return self.*mp;
},
nullptr,
extras...);
} }
return *this; return *this;
} }
template <typename Getter, typename Setter, typename... Extras> template <typename Getter, typename Setter, typename... Extras>
class_& def_property(const char* name, Getter&& g, Setter&& s, const Extras&... extras) { class_& def_property(const char* name, Getter&& g, Setter&& s, const Extras&... extras) {
bind_property(*this, name, std::forward<Getter>(g), std::forward<Setter>(s), extras...); impl::bind_property(*this,
name,
std::forward<Getter>(g),
std::forward<Setter>(s),
extras...);
return *this; return *this;
} }
template <typename Getter, typename... Extras> template <typename Getter, typename... Extras>
class_& def_property_readonly(const char* name, Getter&& mp, const Extras&... extras) { class_& def_property_readonly(const char* name, Getter&& mp, const Extras&... extras) {
bind_property(*this, name, std::forward<Getter>(mp), nullptr, extras...); impl::bind_property(*this, name, std::forward<Getter>(mp), nullptr, extras...);
return *this; return *this;
} }
@ -150,28 +188,41 @@ public:
template <typename T, typename... Others> template <typename T, typename... Others>
class enum_ : public class_<T, Others...> { class enum_ : public class_<T, Others...> {
std::map<const char*, pkpy::PyVar> m_values; std::vector<std::pair<const char*, object>> m_values;
public: public:
using class_<T, Others...>::class_; using Base = class_<T, Others...>;
template <typename... Args> template <typename... Args>
enum_(const handle& scope, const char* name, Args&&... args) : enum_(const handle& scope, const char* name, Args&&... args) :
class_<T, Others...>(scope, name, std::forward<Args>(args)...) {} class_<T, Others...>(scope, name, std::forward<Args>(args)...) {
Base::def(init([](int value) {
return static_cast<T>(value);
}));
Base::def("__eq__", [](T& self, T& other) {
return self == other;
});
Base::def_property_readonly("value", [](T& self) {
return int_(static_cast<std::underlying_type_t<T>>(self));
});
}
enum_& value(const char* name, T value) { enum_& value(const char* name, T value) {
handle var = type_caster<T>::cast(value, return_value_policy::copy); auto var = pkbind::cast(value, return_value_policy::copy);
this->m_ptr->attr().set(name, var.ptr()); setattr(*this, name, var);
m_values[name] = var.ptr(); m_values.emplace_back(name, std::move(var));
return *this; return *this;
} }
enum_& export_values() { enum_& export_values() {
pkpy::PyVar mod = this->m_ptr->attr("__module__");
for(auto& [name, value]: m_values) { for(auto& [name, value]: m_values) {
mod->attr().set(name, value); setattr(Base::m_scope, name, value);
} }
return *this; return *this;
} }
}; };
} // namespace pybind11
} // namespace pkbind

View File

@ -1,358 +0,0 @@
#pragma once
#include "cast.h"
#include <bitset>
namespace pybind11 {
template <std::size_t Nurse, std::size_t... Patients>
struct keep_alive {};
template <typename T>
struct call_guard {
static_assert(std::is_default_constructible_v<T>, "call_guard must be default constructible");
};
// append the overload to the beginning of the overload list
struct prepend {};
template <typename... Args>
struct init {};
// TODO: support more customized tags
// struct kw_only {};
//
// struct pos_only {};
//
// struct default_arg {};
//
// struct arg {
// const char* name;
// const char* description;
// };
//
// struct default_arg {
// const char* name;
// const char* description;
// const char* value;
// };
template <typename Fn,
typename Extra,
typename Args = callable_args_t<std::decay_t<Fn>>,
typename IndexSequence = std::make_index_sequence<std::tuple_size_v<Args>>>
struct generator;
class function_record {
union {
void* data;
char buffer[16];
};
// TODO: optimize the function_record size to reduce memory usage
const char* name;
function_record* next;
void (*destructor)(function_record*);
return_value_policy policy = return_value_policy::automatic;
handle (*wrapper)(function_record&, pkpy::ArgsView, bool convert, handle parent);
template <typename Fn, typename Extra, typename Args, typename IndexSequence>
friend struct generator;
public:
template <typename Fn, typename... Extras>
function_record(Fn&& f, const char* name, const Extras&... extras) : name(name), next(nullptr) {
if constexpr(sizeof(f) <= sizeof(buffer)) {
new (buffer) auto(std::forward<Fn>(f));
destructor = [](function_record* self) {
reinterpret_cast<Fn*>(self->buffer)->~Fn();
};
} else {
data = new auto(std::forward<Fn>(f));
destructor = [](function_record* self) {
delete static_cast<Fn*>(self->data);
};
}
using Generator = generator<std::decay_t<Fn>, std::tuple<Extras...>>;
Generator::initialize(*this, extras...);
wrapper = Generator::generate();
}
~function_record() { destructor(this); }
template <typename Fn>
auto& cast() {
if constexpr(sizeof(Fn) <= sizeof(buffer)) {
return *reinterpret_cast<Fn*>(buffer);
} else {
return *static_cast<Fn*>(data);
}
}
void append(function_record* record) {
function_record* p = this;
while(p->next != nullptr) {
p = p->next;
}
p->next = record;
}
handle operator() (pkpy::ArgsView view) {
function_record* p = this;
// foreach function record and call the function with not convert
while(p != nullptr) {
handle result = p->wrapper(*this, view, false, {});
if(result) { return result; }
p = p->next;
}
p = this;
// foreach function record and call the function with convert
while(p != nullptr) {
handle result = p->wrapper(*this, view, true, {});
if(result) { return result; }
p = p->next;
}
vm->TypeError("no matching function found");
}
};
template <typename Fn, std::size_t... Is, typename... Args>
handle invoke(Fn&& fn,
std::index_sequence<Is...>,
std::tuple<type_caster<Args>...>& casters,
return_value_policy policy,
handle parent) {
using underlying_type = std::decay_t<Fn>;
using ret = callable_return_t<underlying_type>;
// if the return type is void, return None
if constexpr(std::is_void_v<ret>) {
// resolve the member function pointer
if constexpr(std::is_member_function_pointer_v<underlying_type>) {
[&](class_type_t<underlying_type>& self, auto&... args) {
(self.*fn)(args...);
}(std::get<Is>(casters).value...);
} else {
fn(std::get<Is>(casters).value...);
}
return vm->None;
} else {
// resolve the member function pointer
if constexpr(std::is_member_function_pointer_v<remove_cvref_t<Fn>>) {
return type_caster<ret>::cast(
[&](class_type_t<underlying_type>& self, auto&... args) {
return (self.*fn)(args...);
}(std::get<Is>(casters).value...),
policy,
parent);
} else {
return type_caster<ret>::cast(fn(std::get<Is>(casters).value...), policy, parent);
}
}
}
template <typename Fn, typename... Args, std::size_t... Is, typename... Extras>
struct generator<Fn, std::tuple<Extras...>, std::tuple<Args...>, std::index_sequence<Is...>> {
static void initialize(function_record& record, const Extras&... extras) {}
static auto generate() {
return +[](function_record& self, pkpy::ArgsView view, bool convert, handle parent) {
// FIXME:
// Temporarily, args and kwargs must be at the end of the arguments list
// Named arguments are not supported yet
constexpr bool has_args = types_count_v<args, remove_cvref_t<Args>...> != 0;
constexpr bool has_kwargs = types_count_v<kwargs, remove_cvref_t<Args>...> != 0;
constexpr std::size_t count = sizeof...(Args) - has_args - has_kwargs;
handle stack[sizeof...(Args)] = {};
// initialize the stack
if(!has_args && (view.size() != count)) { return handle(); }
if(has_args && (view.size() < count)) { return handle(); }
for(std::size_t i = 0; i < count; ++i) {
stack[i] = view[i];
}
// pack the args and kwargs
if constexpr(has_args) {
const auto n = view.size() - count;
pkpy::PyVar var = vm->new_object<pkpy::Tuple>(vm->tp_tuple, n);
auto& tuple = var.obj_get<pkpy::Tuple>();
for(std::size_t i = 0; i < n; ++i) {
tuple[i] = view[count + i];
}
stack[count] = var;
}
if constexpr(has_kwargs) {
const auto n = vm->s_data._sp - view.end();
pkpy::PyVar var = vm->new_object<pkpy::Dict>(vm->tp_dict);
auto& dict = var.obj_get<pkpy::Dict>();
for(std::size_t i = 0; i < n; i += 2) {
pkpy::i64 index = pkpy::_py_cast<pkpy::i64>(vm, view[count + i]);
pkpy::PyVar str = vm->new_object<pkpy::Str>(vm->tp_str, pkpy::StrName(index).sv());
dict.set(vm, str, view[count + i + 1]);
}
stack[count + 1] = var;
}
// check if all the arguments are not valid
for(std::size_t i = 0; i < sizeof...(Args); ++i) {
if(!stack[i]) { return handle(); }
}
// ok, all the arguments are valid, call the function
std::tuple<type_caster<Args>...> casters;
// check type compatibility
if(((std::get<Is>(casters).load(stack[Is], convert)) && ...)) {
return invoke(self.cast<Fn>(), std::index_sequence<Is...>{}, casters, self.policy, parent);
}
return handle();
};
}
};
constexpr inline static auto _wrapper = +[](pkpy::VM*, pkpy::ArgsView view) {
auto& record = pkpy::lambda_get_userdata<function_record>(view.begin());
return record(view).ptr();
};
class cpp_function : public function {
public:
template <typename Fn, typename... Extras>
cpp_function(Fn&& f, const Extras&... extras) {
pkpy::any userdata = function_record(std::forward<Fn>(f), "anonymous", extras...);
m_ptr = vm->bind_func(nullptr, "", -1, _wrapper, std::move(userdata));
inc_ref();
}
};
template <typename Fn, typename... Extras>
handle bind_function(const handle& obj, const char* name, Fn&& fn, pkpy::BindType type, const Extras&... extras) {
// do not use cpp_function directly to avoid unnecessary reference count change
pkpy::PyVar var = obj.ptr();
pkpy::PyVar callable = var->attr().try_get(name);
// if the function is not bound yet, bind it
if(!callable) {
pkpy::any userdata = function_record(std::forward<Fn>(fn), name, extras...);
callable = vm->bind_func(var, name, -1, _wrapper, std::move(userdata));
} else {
auto& userdata = callable.obj_get<pkpy::NativeFunc>()._userdata;
function_record* record = new function_record(std::forward<Fn>(fn), name, extras...);
constexpr bool is_prepend = (types_count_v<prepend, Extras...> != 0);
if constexpr(is_prepend) {
// if prepend is specified, append the new record to the beginning of the list
function_record* last = (function_record*)userdata.data;
userdata.data = record;
record->append(last);
} else {
// otherwise, append the new record to the end of the list
function_record* last = (function_record*)userdata.data;
last->append(record);
}
}
return callable;
}
template <typename Getter_, typename Setter_, typename... Extras>
handle
bind_property(const handle& obj, const char* name, Getter_&& getter_, Setter_&& setter_, const Extras&... extras) {
pkpy::PyVar var = obj.ptr();
pkpy::PyVar getter = vm->None;
pkpy::PyVar setter = vm->None;
using Getter = std::decay_t<Getter_>;
using Setter = std::decay_t<Setter_>;
getter = vm->new_object<pkpy::NativeFunc>(
vm->tp_native_func,
[](pkpy::VM* vm, pkpy::ArgsView view) -> pkpy::PyVar {
auto& getter = pkpy::lambda_get_userdata<Getter>(view.begin());
if constexpr(std::is_member_pointer_v<Getter>) {
using Self = class_type_t<Getter>;
auto& self = _builtin_cast<instance>(view[0]).cast<Self>();
if constexpr(std::is_member_object_pointer_v<Getter>) {
return type_caster<member_type_t<Getter>>::cast(self.*getter,
return_value_policy::reference_internal,
view[0])
.ptr();
} else {
return type_caster<callable_return_t<Getter>>::cast((self.*getter)(),
return_value_policy::reference_internal,
view[0])
.ptr();
}
} else {
using Self = std::tuple_element_t<0, callable_args_t<Getter>>;
auto& self = _builtin_cast<instance>(view[0]).cast<Self>();
return type_caster<callable_return_t<Getter>>::cast(getter(self),
return_value_policy::reference_internal,
view[0])
.ptr();
}
},
1,
std::forward<Getter_>(getter_));
if constexpr(!std::is_same_v<Setter, std::nullptr_t>) {
setter = vm->new_object<pkpy::NativeFunc>(
vm->tp_native_func,
[](pkpy::VM* vm, pkpy::ArgsView view) -> pkpy::PyVar {
auto& setter = pkpy::lambda_get_userdata<Setter>(view.begin());
if constexpr(std::is_member_pointer_v<Setter>) {
using Self = class_type_t<Setter>;
auto& self = _builtin_cast<instance>(view[0]).cast<Self>();
if constexpr(std::is_member_object_pointer_v<Setter>) {
type_caster<member_type_t<Setter>> caster;
if(caster.load(view[1], true)) {
self.*setter = caster.value;
return vm->None;
}
} else {
type_caster<std::tuple_element_t<1, callable_args_t<Setter>>> caster;
if(caster.load(view[1], true)) {
(self.*setter)(caster.value);
return vm->None;
}
}
} else {
using Self = std::tuple_element_t<0, callable_args_t<Setter>>;
auto& self = _builtin_cast<instance>(view[0]).cast<Self>();
type_caster<std::tuple_element_t<1, callable_args_t<Setter>>> caster;
if(caster.load(view[1], true)) {
setter(self, caster.value);
return vm->None;
}
}
vm->TypeError("invalid argument");
},
2,
std::forward<Setter_>(setter_));
}
pkpy::PyVar property = vm->new_object<pkpy::Property>(vm->tp_property, getter, setter);
var->attr().set(name, property);
return property;
}
} // namespace pybind11

View File

@ -0,0 +1,115 @@
#pragma once
#include "object.h"
namespace pkbind {
/// represent a all exception raised by python.
class python_error : public std::exception {
public:
python_error(char* what, object exception) : m_what(what), m_exception(std::move(exception)) {}
const char* what() const noexcept override { return m_what; }
// get the python exception object
object& exception() { return m_exception; }
~python_error() { std::free(m_what); }
bool match(py_Type type) const { return py_isinstance(m_exception.ptr(), type); }
bool match(class type) const;
private:
char* m_what;
object m_exception;
};
using error_already_set = python_error;
template <auto Fn, typename... Args>
inline auto raise_call(Args&&... args) {
auto pc = py_peek(0);
auto result = Fn(std::forward<Args>(args)...);
using type = decltype(result);
if constexpr(std::is_same_v<type, bool>) {
if(result != false) {
return result;
}
} else if constexpr(std::is_same_v<type, int>) {
if(result != -1) {
return result;
}
} else {
static_assert(dependent_false<type>, "invalid return type");
}
bool o = py_matchexc(tp_Exception);
object e = object::from_ret();
auto what = py_formatexc();
py_clearexc(pc);
throw python_error(what, std::move(e));
}
class stop_iteration {};
class cast_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class index_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class key_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class value_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class type_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class import_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
class attribute_error : public std::runtime_error {
using std::runtime_error::runtime_error;
};
inline object::operator bool () const { return raise_call<py_bool>(m_ptr); }
#define PKBIND_BINARY_OPERATOR(name, lop, rop) \
inline object operator name (handle lhs, handle rhs) { \
raise_call<py_binaryop>(lhs.ptr(), rhs.ptr(), lop, rop); \
return object(retv, object::realloc_t{}); \
}
PKBIND_BINARY_OPERATOR(==, __eq__, __eq__)
PKBIND_BINARY_OPERATOR(!=, __ne__, __ne__)
PKBIND_BINARY_OPERATOR(<, __lt__, __gt__)
PKBIND_BINARY_OPERATOR(<=, __le__, __ge__)
PKBIND_BINARY_OPERATOR(>, __gt__, __lt__)
PKBIND_BINARY_OPERATOR(>=, __ge__, __le__)
PKBIND_BINARY_OPERATOR(+, __add__, __radd__)
PKBIND_BINARY_OPERATOR(-, __sub__, __rsub__)
PKBIND_BINARY_OPERATOR(*, __mul__, __rmul__)
PKBIND_BINARY_OPERATOR(/, __truediv__, __rtruediv__)
PKBIND_BINARY_OPERATOR(%, __mod__, __rmod__)
// FIXME: support __rand__ ...
PKBIND_BINARY_OPERATOR(&, __and__, 0)
PKBIND_BINARY_OPERATOR(|, __or__, 0)
PKBIND_BINARY_OPERATOR(^, __xor__, 0)
PKBIND_BINARY_OPERATOR(<<, __lshift__, 0)
PKBIND_BINARY_OPERATOR(>>, __rshift__, 0)
#undef PKBIND_BINARY_OPERATOR
} // namespace pkbind

View File

@ -0,0 +1,686 @@
#pragma once
#include "cast.h"
namespace pkbind {
namespace impl {
template <typename... Args>
struct constructor {};
template <typename Fn, typename Args = callable_args_t<Fn>>
struct factory;
template <typename Fn, typename... Args>
struct factory<Fn, std::tuple<Args...>> {
Fn fn;
auto make() {
using Self = callable_return_t<Fn>;
return [fn = std::move(fn)](Self* self, Args... args) {
new (self) Self(fn(args...));
};
}
};
} // namespace impl
template <typename... Args>
impl::constructor<Args...> init() {
return {};
}
template <typename Fn>
impl::factory<Fn> init(Fn&& fn) {
return {std::forward<Fn>(fn)};
}
struct arg_with_default {
const char* name;
object value;
};
struct arg {
const char* name;
arg(const char* name) : name(name) {}
template <typename T>
arg_with_default operator= (T&& value) {
return arg_with_default{name, cast(std::forward<T>(value))};
}
};
struct kwargs_proxy {
handle value;
};
struct args_proxy {
handle value;
kwargs_proxy operator* () { return kwargs_proxy{value}; }
};
template <typename Derived>
args_proxy interface<Derived>::operator* () const {
return args_proxy{handle(this->ptr())};
}
template <typename Derived>
template <return_value_policy policy, typename... Args>
object interface<Derived>::operator() (Args&&... args) const {
py_push(ptr());
py_pushnil();
int argc = 0;
int kwargsc = 0;
auto foreach = [&](auto&& argument) {
using type = std::decay_t<decltype(argument)>;
if constexpr(std::is_constructible_v<handle, type>) {
argc += 1;
py_push(handle(argument).ptr());
} else if constexpr(std::is_same_v<type, arg_with_default>) {
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<type, args_proxy>) {
tuple args = argument.value.template cast<tuple>();
for(auto arg: args) {
argc += 1;
py_push(arg.ptr());
}
} else if constexpr(std::is_same_v<type, kwargs_proxy>) {
dict kwargs = argument.value.template cast<dict>();
kwargs.apply([&](handle key, handle value) {
kwargsc += 1;
name name = key.cast<std::string_view>();
py_pushname(name.index());
py_push(value.ptr());
});
} else {
argc += 1;
py_push(pkbind::cast(std::forward<decltype(argument)>(argument), policy).ptr());
}
};
(foreach(std::forward<Args>(args)), ...);
raise_call<py_vectorcall>(argc, kwargsc);
return object::from_ret();
}
class function : public object {
PKBIND_TYPE_IMPL(object, function, tp_function);
};
namespace impl {
template <typename Callable,
typename Extra,
typename Args = callable_args_t<Callable>,
typename IndexSequence = std::make_index_sequence<std::tuple_size_v<Args>>>
struct template_parser;
class function_record {
template <typename C, typename E, typename A, typename I>
friend struct template_parser;
using destructor_t = void (*)(function_record*);
using wrapper_t = bool (*)(function_record&,
std::vector<handle>&,
std::vector<std::pair<handle, handle>>&,
bool convert,
handle parent);
struct arguments_t {
std::vector<std::string> names;
std::vector<object> defaults;
};
public:
template <typename Fn, typename... Extras>
function_record(Fn&& f, const Extras&... extras) {
using Callable = std::decay_t<Fn>;
if constexpr(std::is_trivially_copyable_v<Callable> && 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<Fn>(f));
destructor = [](function_record* self) {
reinterpret_cast<Callable*>(self->buffer)->~Callable();
};
} else {
// otherwise, store it in the heap
data = new auto(std::forward<Fn>(f));
destructor = [](function_record* self) {
delete static_cast<Callable*>(self->data);
};
}
using Parser = template_parser<Callable, std::tuple<Extras...>>;
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 <typename T>
T& as() {
if constexpr(std::is_trivially_copyable_v<T> && sizeof(T) <= sizeof(buffer)) {
return *reinterpret_cast<T*>(buffer);
} else {
return *static_cast<T*>(data);
}
}
static function_record& from(handle h) {
auto slot = py_getslot(h.ptr(), 0);
return *static_cast<function_record*>(py_touserdata(slot));
}
void operator() (int argc, handle stack) {
function_record* p = this;
bool has_self = argc == 3;
std::vector<handle> 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<pkbind::dict>(py_offset(stack.ptr(), 1 + has_self));
std::vector<std::pair<handle, handle>> 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 <typename Fn, std::size_t... Is, typename... Args>
void invoke(Fn&& fn,
std::index_sequence<Is...>,
std::tuple<type_caster<Args>...>& casters,
return_value_policy policy,
handle parent) {
using underlying_type = std::decay_t<Fn>;
using return_type = callable_return_t<underlying_type>;
constexpr bool is_void = std::is_void_v<return_type>;
constexpr bool is_member_function_pointer = std::is_member_function_pointer_v<underlying_type>;
if constexpr(is_member_function_pointer) {
// helper function to unpack the arguments to call the member pointer
auto unpack = [&](class_type_t<underlying_type>& self, auto&... args) {
return (self.*fn)(args...);
};
if constexpr(!is_void) {
py_assign(py_retval(),
pkbind::cast(unpack(std::get<Is>(casters).value()...), policy, parent).ptr());
} else {
unpack(std::get<Is>(casters).value()...);
py_newnone(py_retval());
}
} else {
if constexpr(!is_void) {
py_assign(py_retval(),
pkbind::cast(fn(std::get<Is>(casters).value()...), policy, parent).ptr());
} else {
fn(std::get<Is>(casters).value()...);
py_newnone(py_retval());
}
}
}
template <typename Callable, typename... Extras, typename... Args, std::size_t... Is>
struct template_parser<Callable,
std::tuple<Extras...>,
std::tuple<Args...>,
std::index_sequence<Is...>> {
using types = type_list<Args...>;
/// 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<pkbind::args>;
constexpr inline static auto kwargs_count = types::template count<pkbind::kwargs>;
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<pkbind::args>;
constexpr inline static auto kwargs_pos = types::template find<pkbind::kwargs>;
// 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<Extras...>;
// count the number of py::doc and py::return_value_policy
constexpr inline static auto doc_count = extras::template count<const char*>;
constexpr inline static auto policy_count = extras::template count<pkbind::return_value_policy>;
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<pkbind::return_value_policy>;
constexpr inline static auto last_arg_without_default_pos =
types::template find_last<pkbind::arg>;
constexpr inline static auto first_arg_with_default_pos =
types::template find<pkbind::arg_with_default>;
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<pkbind::arg>;
constexpr inline static auto named_default_argc =
extras::template count<pkbind::arg_with_default>;
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<policy_pos>(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<decltype(default_)>;
if constexpr(std::is_same_v<arg, type>) {
auto& arguments = *record.arguments;
arguments.names.emplace_back(default_.name);
arguments.defaults.emplace_back();
} else if constexpr(std::is_same_v<arg_with_default, type>) {
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<typename decltype(_t)::type>;
if constexpr(std::is_same_v<T, args>) {
sig += "*args";
} else if constexpr(std::is_same_v<T, kwargs>) {
sig += "**kwargs";
} else if constexpr(has_named_args) {
sig += record.arguments->names[index].c_str();
sig += ": ";
sig += type_info::of<T>().name;
if(!record.arguments->defaults[index].empty()) {
sig += " = ";
sig += record.arguments->defaults[index]
.attr("__repr__")()
.cast<std::string_view>();
}
} else {
sig += "_: ";
sig += type_info::of<T>().name;
}
if(index + 1 < argc) { sig += ", "; }
index++;
};
(append(type_identity<Args>{}), ...);
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<handle>& args,
std::vector<std::pair<handle, handle>>& 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<int>(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<std::string_view>() == 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<type_caster<Args>...> casters;
if(((std::get<Is>(casters).load(stack[Is], convert)) && ...)) {
invoke(record.as<Callable>(),
std::index_sequence<Is...>{},
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<impl::function_record*>(data)->~function_record();
});
}
static bool is_function_record(handle h) {
if(isinstance<function>(h)) {
auto slot = py_getslot(h.ptr(), 0);
if(slot) { return py_typeof(slot) == m_type; }
}
return false;
}
template <typename Fn, typename... Extras>
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<impl::function_record*>(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>(fn), extras...);
}
template <typename Fn, typename... Extras>
cpp_function(Fn&& fn, const Extras&... extras) :
cpp_function("lambda", std::forward<Fn>(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<py_tpcall>(type::of<property>().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<py_tpcall>(type::of<staticmethod>().index(), 1, start);
*this = object::from_ret();
}
};
namespace impl {
template <bool is_method, bool is_static, typename Fn, typename... Extras>
void bind_function(handle obj, const char* name_, Fn&& fn, const Extras&... extras) {
constexpr bool has_named_args =
((std::is_same_v<Extras, arg> || std::is_same_v<Extras, arg_with_default>) || ...);
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<function_record*>(py_touserdata(slot));
if constexpr(has_named_args && is_method) {
record.append(new function_record(std::forward<Fn>(fn), arg("self"), extras...));
} else {
record.append(new function_record(std::forward<Fn>(fn), extras...));
}
} else {
if constexpr(is_static) {
py_setdict(
obj.ptr(),
name,
staticmethod(cpp_function(is_method, name_, std::forward<Fn>(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>(fn), arg("self"), extras...)
.ptr());
} else {
py_setdict(obj.ptr(),
name,
cpp_function(is_method, name_, std::forward<Fn>(fn), extras...).ptr());
}
}
}
}
template <typename Getter, typename Setter, typename... Extras>
void bind_property(handle obj,
const char* name,
Getter&& getter_,
Setter&& setter_,
const Extras&... extras) {
if constexpr(std::is_same_v<std::decay_t<Setter>, std::nullptr_t>) {
cpp_function getter(true,
name,
std::forward<Getter>(getter_),
return_value_policy::reference_internal,
extras...);
property prop(getter.ptr());
setattr(obj, name, prop);
} else {
cpp_function getter(true,
name,
std::forward<Getter>(getter_),
return_value_policy::reference_internal,
extras...);
cpp_function setter(true,
name,
std::forward<Setter>(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<arg_with_default> args) : dict() {
for(auto& arg: args) {
this->operator[] (arg.name) = arg.value;
}
}
} // namespace pkbind

View File

@ -1,15 +1,14 @@
#pragma once #pragma once
#include "kernel.h" #include "accessor.h"
namespace pkbind {
namespace pybind11 {
struct type_info { struct type_info {
const char* name; std::string_view name;
std::size_t size; std::size_t size;
std::size_t alignment; std::size_t alignment;
void (*destructor)(void*); void (*destructor)(void*);
void (*copy)(void*, const void*);
void (*move)(void*, void*);
const std::type_info* type; const std::type_info* type;
template <typename T> template <typename T>
@ -17,18 +16,11 @@ struct type_info {
static_assert(!std::is_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>, static_assert(!std::is_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>,
"T must not be a reference type or const type."); "T must not be a reference type or const type.");
static type_info info = { static type_info info = {
typeid(T).name(), type_name<T>(),
sizeof(T), sizeof(T),
alignof(T), alignof(T),
[](void* ptr) { [](void* ptr) {
((T*)ptr)->~T(); delete static_cast<T*>(ptr);
operator delete (ptr);
},
[](void* dst, const void* src) {
new (dst) T(*(const T*)src);
},
[](void* dst, void* src) {
new (dst) T(std::move(*(T*)src));
}, },
&typeid(T), &typeid(T),
}; };
@ -37,11 +29,8 @@ struct type_info {
}; };
// all registered C++ class will be ensured as instance type. // all registered C++ class will be ensured as instance type.
class instance { struct instance {
public:
// use to record the type information of C++ class. // use to record the type information of C++ class.
private:
enum Flag { enum Flag {
None = 0, None = 0,
Own = 1 << 0, // if the instance is owned by C++ side. Own = 1 << 0, // if the instance is owned by C++ side.
@ -50,96 +39,78 @@ private:
Flag flag; Flag flag;
void* data; void* data;
const type_info* type; const type_info* info;
pkpy::PyVar parent; object parent;
// pkpy::PyVar
public: public:
instance() noexcept : flag(Flag::None), data(nullptr), type(nullptr), parent(nullptr) {} template <typename Value>
static object create(type type, Value&& value_, handle parent_, return_value_policy policy) {
using underlying_type = remove_cvref_t<Value>;
instance(const instance&) = delete; auto& value = [&]() -> auto& {
// note that, pybind11 will ignore the const qualifier.
instance(instance&& other) noexcept : flag(other.flag), data(other.data), type(other.type), parent(other.parent) { // in fact, try to modify a const value will result in undefined behavior.
other.flag = Flag::None;
other.data = nullptr;
other.type = nullptr;
other.parent = nullptr;
}
template <typename T>
static pkpy::PyVar create(pkpy::Type type) {
instance instance;
instance.type = &type_info::of<T>();
instance.data = operator new (sizeof(T));
instance.flag = Flag::Own;
return vm->new_object<pybind11::instance>(type, std::move(instance));
}
template <typename T>
static pkpy::PyVar create(T&& value,
pkpy::Type type,
return_value_policy policy = return_value_policy::automatic_reference,
pkpy::PyVar parent = nullptr) noexcept {
using underlying_type = std::remove_cv_t<std::remove_reference_t<T>>;
// resolve for automatic policy.
if(policy == return_value_policy::automatic) {
policy = std::is_pointer_v<underlying_type> ? return_value_policy::take_ownership
: std::is_lvalue_reference_v<T&&> ? return_value_policy::copy
: return_value_policy::move;
} else if(policy == return_value_policy::automatic_reference) {
policy = std::is_pointer_v<underlying_type> ? return_value_policy::reference
: std::is_lvalue_reference_v<T&&> ? return_value_policy::copy
: return_value_policy::move;
}
auto& _value = [&]() -> auto& {
/**
* note that, pybind11 will ignore the const qualifier.
* in fact, try to modify a const value will result in undefined behavior.
*/
if constexpr(std::is_pointer_v<underlying_type>) { if constexpr(std::is_pointer_v<underlying_type>) {
return *reinterpret_cast<underlying_type*>(value); return *reinterpret_cast<underlying_type*>(value_);
} else { } else {
return const_cast<underlying_type&>(value); return const_cast<underlying_type&>(value_);
} }
}(); }();
instance instance; using primary = std::remove_pointer_t<underlying_type>;
instance.type = &type_info::of<underlying_type>();
auto info = &type_info::of<primary>();
void* data = nullptr;
Flag flag = Flag::None;
object parent;
if(policy == return_value_policy::take_ownership) { if(policy == return_value_policy::take_ownership) {
instance.data = &_value; data = &value;
instance.flag = Flag::Own; flag = Flag::Own;
} else if(policy == return_value_policy::copy) { } else if(policy == return_value_policy::copy) {
instance.data = ::new auto(_value); if constexpr(std::is_copy_constructible_v<primary>) {
instance.flag = Flag::Own; data = new auto(value);
flag = Flag::Own;
} else {
std::string msg = "cannot use copy policy on non-copyable type: ";
msg += type_name<primary>();
throw std::runtime_error(msg);
}
} else if(policy == return_value_policy::move) { } else if(policy == return_value_policy::move) {
instance.data = ::new auto(std::move(_value)); if constexpr(std::is_move_constructible_v<primary>) {
instance.flag = Flag::Own; data = new auto(std::move(value));
flag = Flag::Own;
} else {
std::string msg = "cannot use move policy on non-moveable type: ";
msg += type_name<primary>();
throw std::runtime_error(msg);
}
} else if(policy == return_value_policy::reference) { } else if(policy == return_value_policy::reference) {
instance.data = &_value; data = &value;
instance.flag = Flag::None; flag = Flag::None;
} else if(policy == return_value_policy::reference_internal) { } else if(policy == return_value_policy::reference_internal) {
instance.data = &_value; data = &value;
instance.flag = Flag::Ref; flag = Flag::Ref;
instance.parent = parent; parent = borrow(parent_);
} }
return vm->new_object<pybind11::instance>(type, std::move(instance)); object result(object::alloc_t{});
void* temp = py_newobject(result.ptr(), type.index(), 1, sizeof(instance));
new (temp) instance{flag, data, info, std::move(parent)};
return result;
} }
~instance() { ~instance() {
if(flag & Flag::Own) { type->destructor(data); } if(flag & Flag::Own) {
info->destructor(data);
} }
void _gc_mark(pkpy::VM* vm) const noexcept {
if(parent && (flag & Flag::Ref)) { PK_OBJ_MARK(parent); }
} }
template <typename T> template <typename T>
T& cast() noexcept { T& as() noexcept {
return *static_cast<T*>(data); return *static_cast<T*>(data);
} }
}; };
} // namespace pybind11
} // namespace pkbind

View File

@ -1,99 +1,150 @@
#pragma once #pragma once
#include <pocketpy.h> #include <array>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <typeindex>
#include <stdexcept>
#include <unordered_map>
namespace pybind11 { #include "pocketpy.h"
inline pkpy::VM* vm = nullptr; #include "type_traits.h"
inline std::map<pkpy::PyVar, int*>* _ref_counts_map = nullptr;
inline void initialize(bool enable_os = true) { namespace pkbind {
vm = new pkpy::VM(enable_os);
_ref_counts_map = new std::map<pkpy::PyVar, int*>();
// use to keep alive PyObject, when the object is hold by C++ side. class handle;
vm->heap._gc_marker_ex = [](pkpy::VM* vm) {
for(auto iter = _ref_counts_map->begin(); iter != _ref_counts_map->end();) { /// hold the object temporarily
auto ref_count = iter->second; template <int N>
if(*ref_count != 0) { struct reg_t {
// if ref count is not zero, then mark it. py_Ref value;
PK_OBJ_MARK(iter->first);
++iter; void operator= (py_Ref ref) & { py_setreg(N, ref); }
operator py_Ref () & {
assert(value && "register is not initialized");
return value;
}
void operator= (handle value) &;
operator handle () &;
// pkpy provide user 8 registers.
// 8th register is used for object pool, so N is limited to [0, 7).
static_assert(N >= 0 && N <= 6, "N must be in [0, 7)");
};
struct retv_t {
py_Ref value;
void operator= (py_Ref ref) & { py_assign(value, ref); }
operator py_Ref () & {
assert(value && "return value is not initialized");
return value;
}
void operator= (handle value) &;
operator handle () &;
};
/// hold the object long time.
struct object_pool {
inline static int cache = -1;
inline static py_Ref pool = nullptr;
inline static std::vector<int>* indices_ = nullptr;
struct object_ref {
py_Ref data;
int index;
};
static void initialize(int size) noexcept {
// use 8th register.
pool = py_getreg(7);
py_newtuple(pool, size);
indices_ = new std::vector<int>(size, 0);
}
static void finalize() noexcept {
delete indices_;
indices_ = nullptr;
}
/// alloc an object from pool, note that the object is uninitialized.
static object_ref alloc() {
auto& indices = *indices_;
if(cache != -1) {
auto index = cache;
cache = -1;
indices[index] = 1;
return {py_tuple_getitem(pool, index), index};
}
for(int i = 0; i < indices.size(); ++i) {
if(indices[i] == 0) {
indices[i] = 1;
return {py_tuple_getitem(pool, i), i};
}
}
throw std::runtime_error("object pool is full");
}
/// alloc an object from pool, the object is initialized with ref.
static object_ref realloc(py_Ref ref) {
auto result = alloc();
py_assign(result.data, ref);
return result;
}
static void inc_ref(object_ref ref) {
if(!indices_) { return; }
if(ref.data == py_tuple_getitem(pool, ref.index)) {
auto& indices = *indices_;
indices[ref.index] += 1;
} else { } else {
// if ref count is zero, then delete it. throw std::runtime_error("object_ref is invalid");
iter = _ref_counts_map->erase(iter); }
delete ref_count; }
static void dec_ref(object_ref ref) {
if(!indices_) { return; }
if(ref.data == py_tuple_getitem(pool, ref.index)) {
auto& indices = *indices_;
indices[ref.index] -= 1;
assert(indices[ref.index] >= 0 && "ref count is negative");
if(indices[ref.index] == 0) { cache = ref.index; }
} else {
throw std::runtime_error("object_ref is invalid");
} }
} }
}; };
struct action {
using function = void (*)();
inline static std::vector<function> starts;
static void initialize() noexcept {
for(auto func: starts) {
func();
}
} }
inline void finalize() { // register a function to be called at the start of the vm.
delete _ref_counts_map; static void register_start(function func) { starts.push_back(func); }
delete vm;
}
enum class return_value_policy : uint8_t {
/**
* This is the default return value policy, which falls back to the policy
* return_value_policy::take_ownership when the return value is a pointer.
* Otherwise, it uses return_value::move or return_value::copy for rvalue
* and lvalue references, respectively. See below for a description of what
* all of these different policies do.
*/
automatic = 0,
/**
* As above, but use policy return_value_policy::reference when the return
* value is a pointer. This is the default conversion policy for function
* arguments when calling Python functions manually from C++ code (i.e. via
* handle::operator()). You probably won't need to use this.
*/
automatic_reference,
/**
* Reference an existing object (i.e. do not create a new copy) and take
* ownership. Python will call the destructor and delete operator when the
* object's reference count reaches zero. Undefined behavior ensues when
* the C++ side does the same..
*/
take_ownership,
/**
* Create a new copy of the returned object, which will be owned by
* Python. This policy is comparably safe because the lifetimes of the two
* instances are decoupled.
*/
copy,
/**
* Use std::move to move the return value contents into a new instance
* that will be owned by Python. This policy is comparably safe because the
* lifetimes of the two instances (move source and destination) are
* decoupled.
*/
move,
/**
* Reference an existing object, but do not take ownership. The C++ side
* is responsible for managing the object's lifetime and deallocating it
* when it is no longer used. Warning: undefined behavior will ensue when
* the C++ side deletes an object that is still referenced and used by
* Python.
*/
reference,
/**
* This policy only applies to methods and properties. It references the
* object without taking ownership similar to the above
* return_value_policy::reference policy. In contrast to that policy, the
* function or property's implicit this argument (called the parent) is
* considered to be the the owner of the return value (the child).
* pybind11 then couples the lifetime of the parent to the child via a
* reference relationship that ensures that the parent cannot be garbage
* collected while Python is still using the child. More advanced
* variations of this scheme are also possible using combinations of
* return_value_policy::reference and the keep_alive call policy
*/
reference_internal
}; };
} // namespace pybind11
template <int N>
inline reg_t<N> reg;
inline retv_t retv;
inline std::unordered_map<std::type_index, py_Type>* m_type_map = nullptr;
} // namespace pkbind

View File

@ -0,0 +1,47 @@
#pragma once
#include "function.h"
namespace pkbind {
class module : public object {
PKBIND_TYPE_IMPL(object, module, tp_module)
static module __main__() { return module(py_getmodule("__main__"), object::ref_t{}); }
static module import(const char* name) {
raise_call<py_import>(name);
return module(py_retval(), object::realloc_t{});
}
module def_submodule(const char* name, const char* doc = nullptr) {
// auto package = (attr("__package__").cast<std::string>() += ".") += attr("__name__").cast<std::string_view>();
auto fname = (attr("__name__").cast<std::string>() += ".") += name;
auto m = py_newmodule(fname.c_str());
setattr(*this, name, m);
return module(m, object::ref_t{});
}
template <typename Fn, typename... Extras>
module& def(const char* name, Fn&& fn, const Extras... extras) {
impl::bind_function<false, false>(*this, name, std::forward<Fn>(fn), extras...);
return *this;
}
};
using module_ = module;
#define PYBIND11_EMBEDDED_MODULE(name, variable) \
static void _pkbind_register_##name(::pkbind::module& variable); \
namespace pkbind::impl { \
auto _module_##name = [] { \
::pkbind::action::register_start([] { \
auto m = ::pkbind::module(py_newmodule(#name), ::pkbind::object::ref_t{}); \
_pkbind_register_##name(m); \
}); \
return 1; \
}(); \
} \
static void _pkbind_register_##name(::pkbind::module& variable)
} // namespace pkbind

View File

@ -2,251 +2,304 @@
#include "kernel.h" #include "kernel.h"
namespace pybind11 { namespace pkbind {
class handle; class handle;
class object; class object;
class attr_accessor;
class item_accessor;
class iterator; class iterator;
class str; class str;
class bytes;
class iterable;
class tuple;
class dict;
class list;
class set;
class function;
class module;
class type;
class bool_;
class int_;
class float_;
class str;
class bytes;
template <typename T> struct arg;
T& _builtin_cast(const handle& obj); struct arg_with_default;
struct args_proxy;
template <typename T> enum class return_value_policy : uint8_t {
T reinterpret_borrow(const handle& h); /**
* This is the default return value policy, which falls back to the policy
* return_value_policy::take_ownership when the return value is a pointer.
* Otherwise, it uses return_value::move or return_value::copy for rvalue
* and lvalue references, respectively. See below for a description of what
* all of these different policies do.
*/
automatic = 0,
template <typename T> /**
T reinterpret_steal(const handle& h); * As above, but use policy return_value_policy::reference when the return
* value is a pointer. This is the default conversion policy for function
* arguments when calling Python functions manually from C++ code (i.e. via
* handle::operator()). You probably won't need to use this.
*/
automatic_reference,
class handle { /**
protected: * Reference an existing object (i.e. do not create a new copy) and take
pkpy::PyVar m_ptr = nullptr; * ownership. Python will call the destructor and delete operator when the
mutable int* ref_count = nullptr; * object's reference count reaches zero. Undefined behavior ensues when
* the C++ side does the same..
*/
take_ownership,
/**
* Create a new copy of the returned object, which will be owned by
* Python. This policy is comparably safe because the lifetimes of the two
* instances are decoupled.
*/
copy,
/**
* Use std::move to move the return value contents into a new instance
* that will be owned by Python. This policy is comparably safe because the
* lifetimes of the two instances (move source and destination) are
* decoupled.
*/
move,
/**
* Reference an existing object, but do not take ownership. The C++ side
* is responsible for managing the object's lifetime and deallocating it
* when it is no longer used. Warning: undefined behavior will ensue when
* the C++ side deletes an object that is still referenced and used by
* Python.
*/
reference,
/**
* This policy only applies to methods and properties. It references the
* object without taking ownership similar to the above
* return_value_policy::reference policy. In contrast to that policy, the
* function or property's implicit this argument (called the parent) is
* considered to be the the owner of the return value (the child).
* pybind11 then couples the lifetime of the parent to the child via a
* reference relationship that ensures that the parent cannot be garbage
* collected while Python is still using the child. More advanced
* variations of this scheme are also possible using combinations of
* return_value_policy::reference and the keep_alive call policy
*/
reference_internal
};
template <typename policy>
class accessor;
namespace policy {
struct attr;
template <typename Key>
struct item;
struct tuple;
struct list;
template <typename Key>
struct dict;
} // namespace policy
using attr_accessor = accessor<policy::attr>;
template <typename Key>
using item_accessor = accessor<policy::item<Key>>;
using tuple_accessor = accessor<policy::tuple>;
using list_accessor = accessor<policy::list>;
template <typename Key>
using dict_accessor = accessor<policy::dict<Key>>;
/// call a pkpy function which may raise a python exception.
template <auto Fn, typename... Args>
auto raise_call(Args&&... args);
/// an effective representation of a python small string.
class name {
public: public:
handle() = default; name() = default;
handle(const handle& h) = default;
handle& operator= (const handle& other) = default;
handle(pkpy::PyVar ptr) : m_ptr(ptr) {} name(const name&) = default;
pkpy::PyVar ptr() const { return m_ptr; } name& operator= (const name&) = default;
int reference_count() const { return ref_count == nullptr ? 0 : *ref_count; } explicit name(py_Name data) : data(data) {}
const handle& inc_ref() const { name(const char* str) : data(py_name(str)) {}
assert(m_ptr != nullptr);
if(ref_count == nullptr) { name(const char* data, int size) : data(py_namev({data, size})) {}
auto iter = _ref_counts_map->find(m_ptr);
if(iter == _ref_counts_map->end()) { name(std::string_view str) : name(str.data(), static_cast<int>(str.size())) {}
ref_count = ::new int(1);
_ref_counts_map->insert({m_ptr, ref_count}); name(handle h);
} else {
ref_count = iter->second; py_Name index() const { return data; }
*ref_count += 1;
} const char* c_str() const { return py_name2str(data); }
} else {
*ref_count += 1; operator std::string_view () const {
} auto temp = py_name2sv(data);
return *this; return std::string_view(temp.data, temp.size);
} }
const handle& dec_ref() const { private:
assert(m_ptr != nullptr); py_Name data;
assert(ref_count != nullptr); };
*ref_count -= 1;
try {
if(*ref_count == 0) {
_ref_counts_map->erase(m_ptr);
::delete ref_count;
ref_count = nullptr;
}
} catch(std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; }
return *this;
}
template <typename Derived>
class interface {
public: public:
template <typename T> /// equal to `self is None` in python.
T cast() const; bool is_none() const { return py_isnone(ptr()); }
explicit operator bool () const { return m_ptr.operator bool (); } /// equal to `self is other` in python.
bool is(const interface& other) const { return py_isidentical(ptr(), other.ptr()); }
bool is(const handle& other) const { return m_ptr == other.m_ptr; } void assign(const interface& other) { py_assign(ptr(), other.ptr()); }
bool is_none() const { return m_ptr == vm->None; }
bool in(const handle& other) const {
return pkpy::py_cast<bool>(vm, vm->call(vm->py_op("contains"), other.m_ptr, m_ptr));
}
bool contains(const handle& other) const {
return pkpy::py_cast<bool>(vm, vm->call(vm->py_op("contains"), m_ptr, other.m_ptr));
}
iterator begin() const; iterator begin() const;
iterator end() const; iterator end() const;
str doc() const; attr_accessor attr(name key) const;
attr_accessor attr(const char* name) const;
attr_accessor attr(const handle& name) const;
attr_accessor attr(object&& name) const;
item_accessor operator[] (int64_t key) const;
item_accessor operator[] (const char* key) const;
item_accessor operator[] (const handle& key) const;
item_accessor operator[] (object&& key) const;
object operator- () const; object operator- () const;
object operator~() const; object operator~() const;
args_proxy operator* () const;
item_accessor<int> operator[] (int index) const;
item_accessor<name> operator[] (name key) const;
item_accessor<handle> operator[] (handle key) const;
template <return_value_policy policy = return_value_policy::automatic, typename... Args> template <return_value_policy policy = return_value_policy::automatic, typename... Args>
object operator() (Args&&... args) const; object operator() (Args&&... args) const;
private: auto doc() const { return attr("__doc__"); }
friend object operator+ (const handle& lhs, const handle& rhs);
friend object operator- (const handle& lhs, const handle& rhs);
friend object operator* (const handle& lhs, const handle& rhs);
friend object operator% (const handle& lhs, const handle& rhs);
friend object operator/ (const handle& lhs, const handle& rhs);
friend object operator| (const handle& lhs, const handle& rhs);
friend object operator& (const handle& lhs, const handle& rhs);
friend object operator^ (const handle& lhs, const handle& rhs);
friend object operator<< (const handle& lhs, const handle& rhs);
friend object operator>> (const handle& lhs, const handle& rhs);
friend object operator+= (const handle& lhs, const handle& rhs);
friend object operator-= (const handle& lhs, const handle& rhs);
friend object operator*= (const handle& lhs, const handle& rhs);
friend object operator/= (const handle& lhs, const handle& rhs);
friend object operator%= (const handle& lhs, const handle& rhs);
friend object operator|= (const handle& lhs, const handle& rhs);
friend object operator&= (const handle& lhs, const handle& rhs);
friend object operator^= (const handle& lhs, const handle& rhs);
friend object operator<<= (const handle& lhs, const handle& rhs);
friend object operator>>= (const handle& lhs, const handle& rhs);
friend object operator== (const handle& lhs, const handle& rhs);
friend object operator!= (const handle& lhs, const handle& rhs);
friend object operator< (const handle& lhs, const handle& rhs);
friend object operator> (const handle& lhs, const handle& rhs);
friend object operator<= (const handle& lhs, const handle& rhs);
friend object operator>= (const handle& lhs, const handle& rhs);
template <typename T> template <typename T>
friend T& _builtin_cast(const handle& obj) { T cast() const;
// FIXME: 2.0 does not use Py_<T> anymore
static_assert(!std::is_reference_v<T>, "T must not be a reference type."); private:
return obj.ptr().obj_get<T>(); py_Ref ptr() const { return static_cast<const Derived*>(this)->ptr(); }
}
}; };
static_assert(std::is_trivially_copyable_v<handle>); /// a simple wrapper to py_Ref.
/// Note that it does not manage the lifetime of the object.
class handle : public interface<handle> {
public:
handle() = default;
handle(const handle&) = default;
handle& operator= (const handle&) = default;
handle(py_Ref ptr) : m_ptr(ptr) {}
auto ptr() const { return m_ptr; }
explicit operator bool () const { return m_ptr != nullptr; }
protected:
py_Ref m_ptr = nullptr;
};
class object : public handle { class object : public handle {
public: public:
object(const object& other) : handle(other) { inc_ref(); } object() = default;
object(object&& other) noexcept : handle(other) { object(const object& other) : handle(other), m_index(other.m_index) {
if(other.in_pool()) { object_pool::inc_ref(other); }
}
object(object&& other) : handle(other), m_index(other.m_index) {
other.m_ptr = nullptr; other.m_ptr = nullptr;
other.ref_count = nullptr; other.m_index = -1;
} }
object& operator= (const object& other) { object& operator= (const object& other) {
if(this != &other) { if(this != &other) {
dec_ref(); if(in_pool()) { object_pool::dec_ref(*this); }
if(other.in_pool()) { object_pool::inc_ref(other); }
m_ptr = other.m_ptr; m_ptr = other.m_ptr;
ref_count = other.ref_count; m_index = other.m_index;
inc_ref();
} }
return *this; return *this;
} }
object& operator= (object&& other) noexcept { object& operator= (object&& other) {
if(this != &other) { if(this != &other) {
dec_ref(); if(in_pool()) { object_pool::dec_ref(*this); }
m_ptr = other.m_ptr; m_ptr = other.m_ptr;
ref_count = other.ref_count; m_index = other.m_index;
other.m_ptr = nullptr; other.m_ptr = nullptr;
other.ref_count = nullptr; other.m_index = -1;
} }
return *this; return *this;
} }
~object() { ~object() {
if(m_ptr != nullptr) { dec_ref(); } if(in_pool()) { object_pool::dec_ref(*this); }
} }
bool is_singleton() const { return m_ptr && m_index == -1; }
bool empty() const { return m_ptr == nullptr && m_index == -1; }
/// return whether the object is in the object pool.
bool in_pool() const { return m_ptr && m_index != -1; }
public:
static auto type_or_check() { return tp_object; }
struct alloc_t {};
struct realloc_t {};
struct ref_t {};
object(alloc_t) {
auto ref = object_pool::alloc();
m_ptr = ref.data;
m_index = ref.index;
}
object(handle h, realloc_t) {
auto ref = object_pool::realloc(h.ptr());
m_ptr = ref.data;
m_index = ref.index;
}
object(handle h, ref_t) : handle(h) {}
static object from_ret() { return object(retv, realloc_t{}); }
operator object_pool::object_ref () const { return {m_ptr, m_index}; }
explicit operator bool () const;
protected: protected:
object(const handle& h, bool borrow) : handle(h) { int m_index = -1;
if(borrow) { inc_ref(); }
}
template <typename T>
friend T reinterpret_borrow(const handle& h) {
return {h, true};
}
template <typename T>
friend T reinterpret_steal(const handle& h) {
return {h, false};
}
}; };
inline void setattr(const handle& obj, const handle& name, const handle& value); template <typename T = object>
inline void setitem(const handle& obj, const handle& key, const handle& value); T steal(handle h) {
return T(h, object::ref_t{});
#define PYBIND11_BINARY_OPERATOR(OP, NAME) \
inline object operator OP (const handle& lhs, const handle& rhs) { \
return reinterpret_borrow<object>(vm->call(vm->py_op(NAME), lhs.m_ptr, rhs.m_ptr)); \
} }
PYBIND11_BINARY_OPERATOR(+, "add"); template <typename T = object>
PYBIND11_BINARY_OPERATOR(-, "sub"); T borrow(handle h) {
PYBIND11_BINARY_OPERATOR(*, "mul"); return T(h, object::realloc_t{});
PYBIND11_BINARY_OPERATOR(/, "truediv"); }
PYBIND11_BINARY_OPERATOR(%, "mod");
PYBIND11_BINARY_OPERATOR(|, "or_");
PYBIND11_BINARY_OPERATOR(&, "and_");
PYBIND11_BINARY_OPERATOR(^, "xor");
PYBIND11_BINARY_OPERATOR(<<, "lshift");
PYBIND11_BINARY_OPERATOR(>>, "rshift");
PYBIND11_BINARY_OPERATOR(+=, "iadd"); template <int N>
PYBIND11_BINARY_OPERATOR(-=, "isub"); void reg_t<N>::operator= (handle h) & {
PYBIND11_BINARY_OPERATOR(*=, "imul"); py_setreg(N, h.ptr());
PYBIND11_BINARY_OPERATOR(/=, "itruediv"); }
PYBIND11_BINARY_OPERATOR(%=, "imod");
PYBIND11_BINARY_OPERATOR(|=, "ior");
PYBIND11_BINARY_OPERATOR(&=, "iand");
PYBIND11_BINARY_OPERATOR(^=, "ixor");
PYBIND11_BINARY_OPERATOR(<<=, "ilshift");
PYBIND11_BINARY_OPERATOR(>>=, "irshift");
PYBIND11_BINARY_OPERATOR(==, "eq"); template <int N>
PYBIND11_BINARY_OPERATOR(!=, "ne"); reg_t<N>::operator handle () & {
PYBIND11_BINARY_OPERATOR(<, "lt"); assert(value && "register is not initialized");
PYBIND11_BINARY_OPERATOR(>, "gt"); return value;
PYBIND11_BINARY_OPERATOR(<=, "le"); }
PYBIND11_BINARY_OPERATOR(>=, "ge");
#undef PYBIND11_BINARY_OPERATOR inline void retv_t::operator= (handle h) & { py_assign(value, h.ptr()); }
} // namespace pybind11 inline retv_t::operator handle () & {
assert(value && "return value is not initialized");
return value;
}
static_assert(std::is_trivially_copyable_v<name>);
static_assert(std::is_trivially_copyable_v<handle>);
} // namespace pkbind

View File

@ -1,12 +1,86 @@
#pragma once #pragma once
#include <tuple> #include <tuple>
#include <memory>
#include <string_view>
#include <type_traits> #include <type_traits>
namespace pybind11 { namespace pkbind {
template <typename T>
struct type_identity {
using type = T;
};
template <typename T> template <typename T>
constexpr bool dependent_false = false; constexpr bool dependent_false = false;
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
/// check if T is one of Ts...
template <typename T, typename... Ts>
constexpr inline bool is_one_of = (std::is_same_v<T, Ts> || ...);
using std::is_member_function_pointer_v;
using std::is_member_object_pointer_v;
/// check if T is a function pointer type
template <typename T>
constexpr inline bool is_function_pointer_v = std::is_function_v<std::remove_pointer_t<T>>;
/// check if T is a functor type(has a unique operator())
template <typename T, typename U = void>
constexpr bool is_functor_v = false;
template <typename T>
constexpr inline bool is_functor_v<T, std::void_t<decltype(&T::operator())>> = true;
template <typename T>
constexpr inline bool is_integer_v = std::is_integral_v<T> && !is_one_of<T, bool, char, wchar_t>;
template <typename T>
constexpr inline bool is_floating_point_v = std::is_floating_point_v<T>;
template <typename T>
constexpr inline bool is_unique_pointer_v = false;
template <typename T>
constexpr inline bool is_unique_pointer_v<std::unique_ptr<T>> = true;
template <typename T>
constexpr bool is_pointer_v = std::is_pointer_v<T> && !std::is_same_v<T, const char*>;
template <typename T>
constexpr inline bool is_multiple_pointer_v = std::is_pointer_v<T> && is_multiple_pointer_v<std::remove_pointer_t<T>>;
template <typename T>
constexpr auto type_name() {
#if __GNUC__ || __clang__
std::string_view name = __PRETTY_FUNCTION__;
// format is "auto type_name() [T = int]"
std::size_t start = name.find("= ") + 2;
std::size_t end = name.rfind(']');
return name.substr(start, end - start);
#elif _MSC_VER
std::string_view name = __FUNCSIG__;
// format is possible one of three following:
// - "auto __cdecl type_name<int>(void)"
// - "auto __cdecl type_name<struct X>(void)"
// - "auto __cdecl type_name<class X>(void)"
std::size_t start = name.find('<') + 1;
std::size_t end = name.rfind(">(");
name = name.substr(start, end - start);
auto space = name.find(' ');
return space == std::string_view::npos ? name : name.substr(space + 1);
#else
static_assert(false, "current compiler is not supported");
#endif
}
static_assert(type_name<int>() == "int" && type_name<double>() == "double",
"type_name() test failed, please report this issue");
template <typename T, typename Tuple> template <typename T, typename Tuple>
struct tuple_push_front; struct tuple_push_front;
@ -24,9 +98,9 @@ struct function_traits {
static_assert(dependent_false<Fn>, "unsupported function type"); static_assert(dependent_false<Fn>, "unsupported function type");
}; };
#define PYBIND11_FUNCTION_TRAITS_SPECIALIZE(qualifiers) \ #define PYBIND11_FUNCTION_TRAITS_SPECIALIZE(...) \
template <typename R, typename... Args> \ template <typename R, typename... Args> \
struct function_traits<R(Args...) qualifiers> { \ struct function_traits<R(Args...) __VA_ARGS__> { \
using return_type = R; \ using return_type = R; \
using args_type = std::tuple<Args...>; \ using args_type = std::tuple<Args...>; \
constexpr static std::size_t args_count = sizeof...(Args); \ constexpr static std::size_t args_count = sizeof...(Args); \
@ -70,17 +144,6 @@ using class_type_t = typename member_traits<T>::class_type;
// some traits for distinguishing between function pointers, member function pointers and // some traits for distinguishing between function pointers, member function pointers and
// functors // functors
using std::is_member_function_pointer_v;
using std::is_member_object_pointer_v;
template <typename T>
constexpr inline bool is_function_pointer_v = std::is_function_v<std::remove_pointer_t<T>>;
template <typename T, typename U = void>
constexpr bool is_functor_v = false;
template <typename T>
constexpr inline bool is_functor_v<T, std::void_t<decltype(&T::operator())>> = true;
template <typename T, typename SFINAE = void> template <typename T, typename SFINAE = void>
struct callable_traits; struct callable_traits;
@ -93,8 +156,8 @@ struct callable_traits<T, std::enable_if_t<is_member_function_pointer_v<T>>> {
template <typename T> template <typename T>
struct callable_traits<T, std::enable_if_t<is_function_pointer_v<T>>> { struct callable_traits<T, std::enable_if_t<is_function_pointer_v<T>>> {
using args_type = function_args_t<std::remove_pointer<T>>; using args_type = function_args_t<std::remove_pointer_t<T>>;
using return_type = function_return_t<std::remove_pointer<T>>; using return_type = function_return_t<std::remove_pointer_t<T>>;
}; };
template <typename T> template <typename T>
@ -112,46 +175,64 @@ using callable_return_t = typename callable_traits<Callable>::return_type;
template <typename Callable> template <typename Callable>
constexpr std::size_t callable_args_count_v = std::tuple_size_v<callable_args_t<Callable>>; constexpr std::size_t callable_args_count_v = std::tuple_size_v<callable_args_t<Callable>>;
template <typename... Ts>
struct type_list {
constexpr inline static int size = sizeof...(Ts);
template <typename T> template <typename T>
struct type_identity { constexpr inline static int count = [] {
using type = T; int count = 0;
((count += std::is_same_v<T, Ts>), ...);
return count;
}();
/// find first index of T in type_list.
template <typename T>
constexpr inline static int find = [] {
bool arr[size + 1] = {std::is_same_v<T, Ts>...};
for(int i = 0; i < size; ++i) {
if(arr[i]) return i;
}
return -1;
}();
template <typename T>
constexpr inline static int find_last = [] {
bool arr[size + 1] = {std::is_same_v<T, Ts>...};
for(int i = size - 1; i >= 0; --i) {
if(arr[i]) return i;
}
return -1;
}();
}; };
template <typename T> template <typename... Args>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>; struct overload_cast_t {
template <typename Return>
constexpr auto operator() (Return (*pf)(Args...)) const noexcept -> decltype(pf) {
return pf;
}
template <typename T, typename... Ts> template <typename Return, typename Class>
constexpr inline std::size_t types_count_v = (std::is_same_v<T, Ts> + ...); constexpr auto operator() (Return (Class::* pmf)(Args...), std::false_type = {}) const noexcept -> decltype(pmf) {
return pmf;
}
template <typename T> template <typename Return, typename Class>
constexpr inline std::size_t types_count_v<T> = 0; constexpr auto operator() (Return (Class::* pmf)(Args...) const, std::true_type) const noexcept -> decltype(pmf) {
return pmf;
template <typename T> }
struct value_wrapper {
T* pointer;
operator T& () { return *pointer; }
}; };
template <typename T> /// Syntax sugar for resolving overloaded function pointers:
struct value_wrapper<T*> { /// - regular: static_cast<Return (Class::*)(Arg0, Arg1, Arg2)>(&Class::func)
T* pointer; /// - sweet: overload_cast<Arg0, Arg1, Arg2>(&Class::func)
template <typename... Args>
constexpr inline overload_cast_t<Args...> overload_cast;
operator T* () { return pointer; } /// Const member function selector for overload_cast
}; /// - regular: static_cast<Return (Class::*)(Arg) const>(&Class::func)
/// - sweet: overload_cast<Arg>(&Class::func, const_)
constexpr inline auto const_ = std::true_type{};
template <typename T> } // namespace pkbind
struct value_wrapper<T&> {
T* pointer;
operator T& () { return *pointer; }
};
template <typename T>
struct value_wrapper<T&&> {
T* pointer;
operator T&& () { return std::move(*pointer); }
};
} // namespace pybind11

View File

@ -1,208 +1,362 @@
#pragma once #pragma once
#include "object.h" #include "error.h"
namespace pkbind {
#define PKBIND_TYPE_IMPL(parent, child, expr) \
\
private: \
friend class type; \
static auto type_or_check() { return expr; } \
\
public: \
using parent::parent; \
using parent::operator=; \
child(const object& o) : parent(type::isinstance<child>(o) ? o : type::of<child>()(o)) {} \
child(object&& o) : \
parent(type::isinstance<child>(o) ? std::move(o) : type::of<child>()(std::move(o))) {}
namespace pybind11 {
class type : public object { class type : public object {
public: protected:
using object::object;
template <typename T> template <typename T>
static handle handle_of(); constexpr inline static bool is_check_v =
}; std::is_invocable_r_v<bool, decltype(T::type_or_check()), handle>;
class iterable : public object { static auto type_or_check() { return tp_type; }
public:
using object::object;
iterable() = delete;
};
class iterator : public object {
public:
using object::object;
iterator() = delete;
};
class list : public object {
public:
using object::object;
list() : object(vm->new_object<pkpy::List>(pkpy::VM::tp_list), true) {}
};
class tuple : public object {
public:
using object::object;
tuple(int n) : object(vm->new_object<pkpy::Tuple>(pkpy::VM::tp_tuple, n), true) {}
//& operator[](int i){ return _args[i]; }
// PyVar operator[](int i) const { return _args[i]; }
};
class set : public object {
public:
using object::object;
// set() : object(vm->new_object<pkpy::Se>(pkpy::VM::tp_set), true) {}
};
class dict : public object {
public:
using object::object;
dict() : object(vm->new_object<pkpy::Dict>(pkpy::VM::tp_dict), true) {}
};
class str : public object {
public: public:
using object ::object; using object ::object;
str(const char* c, int len) : using object ::operator=;
object(vm->new_object<pkpy::Str>(pkpy::VM::tp_str, c, len), true) {
// note: type is global instance, so we use ref_t.
explicit type(py_Type type) : object(py_tpobject(type), ref_t{}) {}
py_Type index() const { return py_totype(ptr()); }
const char* name() const { return py_tpname(index()); }
static type of(handle h) { return type(py_typeof(h.ptr())); }
template <typename T>
static type of();
template <typename T>
static bool isinstance(handle obj);
}; };
str(const char* c = "") : str(c, strlen(c)) {} class none : public object {
PKBIND_TYPE_IMPL(object, none, tp_NoneType);
str(const std::string& s) : str(s.data(), s.size()) {} // note: none is global instance, so we use ref_t.
none() : object(py_None, ref_t{}) {}
str(std::string_view sv) : str(sv.data(), sv.size()) {}
explicit str(const bytes& b);
explicit str(handle h);
operator std::string () const;
template <typename... Args>
str format(Args&&... args) const;
};
class int_ : public object {
public:
using object::object;
int_(int64_t value) : object(pkpy::py_var(vm, value), true) {}
};
class float_ : public object {
public:
using object::object;
float_(double value) : object(pkpy::py_var(vm, value), true) {}
}; };
class bool_ : public object { class bool_ : public object {
public: PKBIND_TYPE_IMPL(object, bool_, tp_bool);
using object::object;
bool_(bool value) : object(pkpy::py_var(vm, value), true) {} // same as none, bool is a singleton.
bool_(bool value) : object(value ? py_True : py_False, ref_t{}) {}
explicit operator bool () { return py_tobool(ptr()); }
}; };
class function : public object { class int_ : public object {
public: PKBIND_TYPE_IMPL(object, int_, tp_int);
using object::object;
int_(py_i64 value) : object(alloc_t{}) { py_newint(m_ptr, value); }
explicit operator py_i64 () { return py_toint(ptr()); }
}; };
class attr_accessor : public object { class float_ : public object {
PKBIND_TYPE_IMPL(object, float_, tp_float);
float_(py_f64 value) : object(alloc_t{}) { py_newfloat(m_ptr, value); }
explicit operator py_f64 () { return py_tofloat(ptr()); }
};
bool hasattr(handle obj, name name);
class iterable : public object {
PKBIND_TYPE_IMPL(object, iterable, [](handle h) {
return hasattr(h, "__iter__");
});
};
class iterator : public object {
template <typename Dervied>
friend class interface;
iterator() : object(), m_value() {}
iterator(handle h) : object(h, realloc_t{}), m_value() { operator++ (); }
public:
PKBIND_TYPE_IMPL(object, iterator, [](handle h) {
return hasattr(h, "__iter__") && hasattr(h, "__next__");
});
iterator& operator++ () {
int result = raise_call<py_next>(m_ptr);
if(result == 1) {
m_value = object(retv, realloc_t{});
} else if(result == 0) {
m_value = object();
}
return *this;
}
object operator* () const { return m_value; }
friend bool operator== (const iterator& lhs, const iterator& rhs) {
return lhs.m_value.ptr() == rhs.m_value.ptr();
}
friend bool operator!= (const iterator& lhs, const iterator& rhs) { return !(lhs == rhs); }
static iterator sentinel() { return iterator(); }
private: private:
object key; object m_value;
};
template <typename Dervied>
iterator interface<Dervied>::begin() const {
raise_call<py_iter>(ptr());
return iterator(retv);
}
template <typename Dervied>
iterator interface<Dervied>::end() const {
return iterator::sentinel();
}
class str : public object {
PKBIND_TYPE_IMPL(object, str, tp_str);
str(const char* data, int size) : object(alloc_t{}) { py_newstrn(m_ptr, data, size); }
str(const char* data) : str(data, static_cast<int>(strlen(data))) {}
str(std::string_view s) : str(s.data(), static_cast<int>(s.size())) {}
template <typename... Args>
object format(Args&&... args);
};
class tuple : public object {
PKBIND_TYPE_IMPL(object, tuple, tp_tuple);
tuple(int size) : object(alloc_t{}) { py_newtuple(m_ptr, size); }
tuple(std::initializer_list<handle> args) : tuple(static_cast<int>(args.size())) {
int index = 0;
for(auto& arg: args) {
py_tuple_setitem(m_ptr, index++, arg.ptr());
}
}
tuple_accessor operator[] (int index) const;
int size() const { return py_tuple_len(m_ptr); }
bool empty() const { return size() == 0; }
class iterator {
friend tuple;
iterator(py_Ref ptr, int index) : ptr(ptr), index(index) {}
public: public:
template <typename T> bool operator== (const iterator& other) const { return index == other.index; }
attr_accessor(const object& obj, T&& key) : object(obj), key(std::forward<T>(key)){};
template <typename T> bool operator!= (const iterator& other) const { return index != other.index; }
attr_accessor& operator= (T&& value) & {
static_assert(std::is_base_of_v<object, std::decay_t<T>>, "T must be derived from object"); iterator& operator++ () {
m_ptr = std::forward<T>(value); index++;
return *this; return *this;
} }
template <typename T> object operator* () const { return borrow(py_tuple_getitem(ptr, index)); }
attr_accessor& operator= (T&& value) && {
static_assert(std::is_base_of_v<object, std::decay_t<T>>, "T must be derived from object"); private:
setattr(*this, key, std::forward<T>(value)); py_Ref ptr;
int index;
};
iterator begin() const { return iterator(m_ptr, 0); }
iterator end() const { return iterator(m_ptr, size()); }
};
class list : public object {
PKBIND_TYPE_IMPL(object, list, tp_list);
list() : object(alloc_t{}) { py_newlist(m_ptr); }
list(int size) : object(alloc_t{}) { py_newlistn(m_ptr, size); }
list(std::initializer_list<handle> args) : list(static_cast<int>(args.size())) {
int index = 0;
for(auto& arg: args) {
py_list_setitem(m_ptr, index++, arg.ptr());
}
}
list_accessor operator[] (int index) const;
int size() const { return py_list_len(m_ptr); }
bool empty() const { return size() == 0; }
void swap(int i, int j) { py_list_swap(m_ptr, i, j); }
void append(handle item) { py_list_append(m_ptr, item.ptr()); }
void insert(int index, handle item) { py_list_insert(m_ptr, index, item.ptr()); }
void remove(int index) { py_list_delitem(m_ptr, index); }
void clear() { py_list_clear(m_ptr); }
class iterator {
friend list;
iterator(py_Ref ptr, int index) : ptr(ptr), index(index) {}
public:
iterator(const iterator&) = default;
bool operator== (const iterator& other) const { return index == other.index; }
bool operator!= (const iterator& other) const { return index != other.index; }
iterator operator++ () {
index++;
return *this; return *this;
} }
object operator* () const { return borrow(py_list_getitem(ptr, index)); }
private:
py_Ref ptr;
int index;
}; };
inline attr_accessor handle::attr(const char* name) const { iterator begin() const { return iterator(m_ptr, 0); }
return attr_accessor(reinterpret_borrow<object>(*this), str(name));
}
inline attr_accessor handle::attr(const handle& name) const { iterator end() const { return iterator(m_ptr, size()); }
return attr_accessor(reinterpret_borrow<object>(*this), reinterpret_borrow<object>(name));
}
inline attr_accessor handle::attr(object&& name) const {
return attr_accessor(reinterpret_borrow<object>(*this), std::move(name));
}
class item_accessor : public object {
public:
object key;
public:
template <typename T>
item_accessor(const object& obj, T&& key) : object(obj), key(std::forward<T>(key)){};
template <typename T>
item_accessor& operator= (T&& value) & {
static_assert(std::is_base_of_v<object, std::decay_t<T>>, "T must be derived from object");
m_ptr = std::forward<T>(value);
}
template <typename T>
item_accessor& operator= (object&& value) && {
static_assert(std::is_base_of_v<object, std::decay_t<T>>, "T must be derived from object");
setitem(*this, key, std::forward<T>(value));
}
}; };
inline item_accessor handle::operator[] (int64_t key) const { class dict : public object {
return item_accessor(reinterpret_borrow<object>(*this), int_(key)); PKBIND_TYPE_IMPL(object, dict, tp_dict);
dict() : object(alloc_t{}) { py_newdict(m_ptr); }
dict(std::initializer_list<arg_with_default> args);
dict_accessor<int> operator[] (int key) const;
dict_accessor<name> operator[] (name key) const;
dict_accessor<handle> operator[] (handle key) const;
int size() const { return py_dict_len(m_ptr); }
bool empty() const { return size() == 0; }
template <typename Fn>
void apply(Fn&& fn) {
static_assert(std::is_invocable_v<Fn, py_Ref, py_Ref>);
using ret = std::invoke_result_t<Fn, py_Ref, py_Ref>;
auto callback = +[](py_Ref key, py_Ref value, void* data) -> bool {
auto fn = reinterpret_cast<Fn*>(data);
if constexpr(std::is_same_v<ret, void>) {
(*fn)(key, value);
return true;
} else {
return (*fn)(key, value);
}
};
raise_call<py_dict_apply>(m_ptr, callback, &fn);
} }
inline item_accessor handle::operator[] (const char* key) const { class iterator {
return item_accessor(reinterpret_borrow<object>(*this), str(key)); friend dict;
iterator() : iter(pkbind::iterator::sentinel()) {}
iterator(handle ptr);
public:
iterator(const iterator&) = default;
bool operator== (const iterator& other) const { return iter == other.iter; }
bool operator!= (const iterator& other) const { return !(*this == other); }
iterator operator++ () {
++iter;
return *this;
} }
inline item_accessor handle::operator[] (const handle& key) const { std::pair<object, object> operator* () const;
return item_accessor(reinterpret_borrow<object>(*this), reinterpret_borrow<object>(key));
}
inline item_accessor handle::operator[] (object&& key) const { private:
return item_accessor(reinterpret_borrow<object>(*this), std::move(key)); object items;
} pkbind::iterator iter;
};
iterator begin() const { return iterator(m_ptr); }
iterator end() const { return iterator(); }
};
class slice : public object {
PKBIND_TYPE_IMPL(object, slice, tp_slice);
};
class set : public object {};
class args : public tuple { class args : public tuple {
using tuple::tuple; PKBIND_TYPE_IMPL(tuple, args, tp_tuple);
}; };
class kwargs : public dict { class kwargs : public dict {
using dict::dict; PKBIND_TYPE_IMPL(dict, kwargs, tp_dict);
}; };
// TODO:
class capsule : public object {
struct capsule_impl {
void* data;
void (*destructor)(void*);
};
inline static py_Type m_type = 0;
PKBIND_TYPE_IMPL(object, capsule, m_type);
static void register_() {
m_type = py_newtype("capsule", tp_object, nullptr, [](void* data) {
auto impl = static_cast<capsule_impl*>(data);
if(impl->data && impl->destructor) { impl->destructor(impl->data); }
});
}
capsule(void* data, void (*destructor)(void*) = nullptr) : object(alloc_t{}) {
void* impl = py_newobject(m_ptr, m_type, 0, sizeof(capsule_impl));
new (impl) capsule_impl{data, destructor};
}
void* data() { return static_cast<capsule_impl*>(py_touserdata(m_ptr))->data; }
template <typename T> template <typename T>
handle type::handle_of() { T& cast() {
if constexpr(std::is_same_v<T, object>) { return vm->_t(vm->tp_object); } return *static_cast<T*>(data());
#define PYBIND11_TYPE_MAPPER(type, tp) \
else if constexpr(std::is_same_v<T, type>) { return vm->_t(vm->tp); }
PYBIND11_TYPE_MAPPER(type, tp_type)
PYBIND11_TYPE_MAPPER(str, tp_str)
PYBIND11_TYPE_MAPPER(int_, tp_int)
PYBIND11_TYPE_MAPPER(float_, tp_float)
PYBIND11_TYPE_MAPPER(bool_, tp_bool)
PYBIND11_TYPE_MAPPER(list, tp_list)
PYBIND11_TYPE_MAPPER(tuple, tp_tuple)
PYBIND11_TYPE_MAPPER(args, tp_tuple)
PYBIND11_TYPE_MAPPER(dict, tp_dict)
PYBIND11_TYPE_MAPPER(kwargs, tp_dict)
#undef PYBIND11_TYPE_MAPPER
else {
auto result = vm->_cxx_typeid_map.find(typeid(T));
if(result != vm->_cxx_typeid_map.end()) { return vm->_t(result->second); }
vm->TypeError("Type not registered");
}
} }
};
} // namespace pybind11 } // namespace pkbind

View File

@ -0,0 +1,204 @@
#pragma once
#include "pybind11.h"
namespace pkbind::impl {
enum op_id : int {
op_add,
op_sub,
op_mul,
op_div,
op_mod,
op_divmod,
op_pow,
op_lshift,
op_rshift,
op_and,
op_xor,
op_or,
op_neg,
op_pos,
op_abs,
op_invert,
op_int,
op_long,
op_float,
op_str,
op_cmp,
op_gt,
op_ge,
op_lt,
op_le,
op_eq,
op_ne,
op_iadd,
op_isub,
op_imul,
op_idiv,
op_imod,
op_ilshift,
op_irshift,
op_iand,
op_ixor,
op_ior,
op_complex,
op_bool,
op_nonzero,
op_repr,
op_truediv,
op_itruediv,
op_hash
};
enum op_type : int {
op_l, /* base type on left */
op_r, /* base type on right */
op_u /* unary operator */
};
struct self_t {};
const static self_t self = self_t();
/// Type for an unused type slot
struct undefined_t {};
/// Don't warn about an unused variable
inline self_t __self() { return self; }
/// base template of operator implementations
template <op_id, op_type, typename B, typename L, typename R>
struct op_impl {};
/// Operator implementation generator
template <op_id id, op_type ot, typename L, typename R>
struct op_ {
constexpr static bool op_enable_if_hook = true;
template <typename Class, typename... Extra>
void execute(Class& cl, const Extra&... extra) const {
using Base = typename Class::underlying_type;
using L_type = std::conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = std::conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute, extra...);
}
template <typename Class, typename... Extra>
void execute_cast(Class& cl, const Extra&... extra) const {
using Base = typename Class::type;
using L_type = std::conditional_t<std::is_same<L, self_t>::value, Base, L>;
using R_type = std::conditional_t<std::is_same<R, self_t>::value, Base, R>;
using op = op_impl<id, ot, Base, L_type, R_type>;
cl.def(op::name(), &op::execute_cast, extra...);
}
};
#define PKBIND_BINARY_OPERATOR(id, rid, op, expr) \
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const* name() { return "__" #id "__"; } \
static auto execute(const L& l, const R& r) -> decltype(expr) { return (expr); } \
static B execute_cast(const L& l, const R& r) { return B(expr); } \
}; \
\
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_r, B, L, R> { \
static char const* name() { return "__" #rid "__"; } \
static auto execute(const R& r, const L& l) -> decltype(expr) { return (expr); } \
static B execute_cast(const R& r, const L& l) { return B(expr); } \
}; \
\
inline op_<op_##id, op_l, self_t, self_t> op(const self_t&, const self_t&) { \
return op_<op_##id, op_l, self_t, self_t>(); \
} \
\
template <typename T> \
op_<op_##id, op_l, self_t, T> op(const self_t&, const T&) { \
return op_<op_##id, op_l, self_t, T>(); \
} \
\
template <typename T> \
op_<op_##id, op_r, T, self_t> op(const T&, const self_t&) { \
return op_<op_##id, op_r, T, self_t>(); \
}
#define PKBIND_INPLACE_OPERATOR(id, op, expr) \
template <typename B, typename L, typename R> \
struct op_impl<op_##id, op_l, B, L, R> { \
static char const* name() { return "__" #id "__"; } \
static auto execute(L& l, const R& r) -> decltype(expr) { return expr; } \
static B execute_cast(L& l, const R& r) { return B(expr); } \
}; \
\
template <typename T> \
op_<op_##id, op_l, self_t, T> op(const self_t&, const T&) { \
return op_<op_##id, op_l, self_t, T>(); \
}
#define PKBIND_UNARY_OPERATOR(id, op, expr) \
template <typename B, typename L> \
struct op_impl<op_##id, op_u, B, L, undefined_t> { \
static char const* name() { return "__" #id "__"; } \
static auto execute(const L& l) -> decltype(expr) { return expr; } \
static B execute_cast(const L& l) { return B(expr); } \
}; \
\
inline op_<op_##id, op_u, self_t, undefined_t> op(const self_t&) { \
return op_<op_##id, op_u, self_t, undefined_t>(); \
}
PKBIND_BINARY_OPERATOR(sub, rsub, operator-, l - r)
PKBIND_BINARY_OPERATOR(add, radd, operator+, l + r)
PKBIND_BINARY_OPERATOR(mul, rmul, operator*, l* r)
PKBIND_BINARY_OPERATOR(truediv, rtruediv, operator/, l / r)
PKBIND_BINARY_OPERATOR(mod, rmod, operator%, l % r)
PKBIND_BINARY_OPERATOR(lshift, rlshift, operator<<, l << r)
PKBIND_BINARY_OPERATOR(rshift, rrshift, operator>>, l >> r)
PKBIND_BINARY_OPERATOR(and, rand, operator&, l& r)
PKBIND_BINARY_OPERATOR(xor, rxor, operator^, l ^ r)
PKBIND_BINARY_OPERATOR(eq, eq, operator==, l == r)
PKBIND_BINARY_OPERATOR(ne, ne, operator!=, l != r)
PKBIND_BINARY_OPERATOR(or, ror, operator|, l | r)
PKBIND_BINARY_OPERATOR(gt, lt, operator>, l > r)
PKBIND_BINARY_OPERATOR(ge, le, operator>=, l >= r)
PKBIND_BINARY_OPERATOR(lt, gt, operator<, l < r)
PKBIND_BINARY_OPERATOR(le, ge, operator<=, l <= r)
// PKBIND_BINARY_OPERATOR(pow, rpow, pow, std::pow(l, r))
PKBIND_INPLACE_OPERATOR(iadd, operator+=, l += r)
PKBIND_INPLACE_OPERATOR(isub, operator-=, l -= r)
PKBIND_INPLACE_OPERATOR(imul, operator*=, l *= r)
PKBIND_INPLACE_OPERATOR(itruediv, operator/=, l /= r)
PKBIND_INPLACE_OPERATOR(imod, operator%=, l %= r)
PKBIND_INPLACE_OPERATOR(ilshift, operator<<=, l <<= r)
PKBIND_INPLACE_OPERATOR(irshift, operator>>=, l >>= r)
PKBIND_INPLACE_OPERATOR(iand, operator&=, l &= r)
PKBIND_INPLACE_OPERATOR(ixor, operator^=, l ^= r)
PKBIND_INPLACE_OPERATOR(ior, operator|=, l |= r)
PKBIND_UNARY_OPERATOR(neg, operator-, -l)
PKBIND_UNARY_OPERATOR(pos, operator+, +l)
// WARNING: This usage of `abs` should only be done for existing STL overloads.
// Adding overloads directly in to the `std::` namespace is advised against:
// https://en.cppreference.com/w/cpp/language/extending_std
// PKBIND_UNARY_OPERATOR(abs, abs, std::abs(l))
// PKBIND_UNARY_OPERATOR(hash, hash, std::hash<L>()(l))
// PKBIND_UNARY_OPERATOR(invert, operator~, (~l))
// PKBIND_UNARY_OPERATOR(bool, operator!, !!l)
// PKBIND_UNARY_OPERATOR(int, int_, (int)l)
// PKBIND_UNARY_OPERATOR(float, float_, (double)l)
#undef PKBIND_BINARY_OPERATOR
#undef PKBIND_INPLACE_OPERATOR
#undef PKBIND_UNARY_OPERATOR
} // namespace pkbind::impl
namespace pkbind {
using impl::self;
} // namespace pkbind

View File

@ -0,0 +1,3 @@
#pragma once
#include "pybind11.h"

View File

@ -1,3 +1,65 @@
#pragma once #pragma once
#include "internal/class.h" #include "internal/class.h"
namespace pkbind {
namespace literals {
inline arg operator""_a (const char* c, size_t) { return arg(c); }
} // namespace literals
inline bool initialized = false;
/// initialize the vm.
inline void initialize(int object_pool_size = 1024) {
if(!initialized) { py_initialize(); }
// initialize all registers.
reg<0>.value = py_getreg(0);
reg<1>.value = py_getreg(1);
reg<2>.value = py_getreg(2);
reg<3>.value = py_getreg(3);
reg<4>.value = py_getreg(4);
reg<5>.value = py_getreg(5);
reg<6>.value = py_getreg(6);
// initialize ret.
retv.value = py_retval();
// initialize object pool.
object_pool::initialize(object_pool_size);
m_type_map = new std::unordered_map<std::type_index, py_Type>();
// register types.
capsule::register_();
cpp_function::register_();
action::initialize();
initialized = true;
}
/// finalize the vm.
inline void finalize(bool test = false) {
if(!initialized) { return; }
delete m_type_map;
m_type_map = nullptr;
object_pool::finalize();
if(test) {
py_resetvm();
} else {
py_finalize();
}
}
/// a RAII class to initialize and finalize python interpreter
class scoped_interpreter {
public:
scoped_interpreter(int object_pool_size = 1024) { initialize(object_pool_size); }
~scoped_interpreter() { finalize(); }
};
} // namespace pkbind
namespace pybind11 = pkbind;

170
include/pybind11/stl.h Normal file
View File

@ -0,0 +1,170 @@
#include "pybind11.h"
#include <array>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <map>
#include <unordered_map>
namespace pkbind {
template <typename T, std::size_t N>
struct type_caster<std::array<T, N>> {
std::array<T, N> data;
template <typename U>
static handle cast(U&& src, return_value_policy policy, handle parent) {
auto list = pkbind::list();
for(auto&& item: src) {
list.append(pkbind::cast(std::move(item), policy, parent));
}
return list;
}
bool load(handle src, bool convert) {
if(!isinstance<list>(src)) { return false; }
auto list = src.cast<pkbind::list>();
if(list.size() != N) { return false; }
for(int i = 0; i < N; ++i) {
type_caster<T> caster;
if(!caster.load(list[i], convert)) { return false; }
data[i] = std::move(caster.value());
}
return true;
}
std::array<T, N>& value() { return data; }
constexpr inline static bool is_temporary_v = true;
};
template <typename T>
constexpr bool is_py_list_like_v = false;
template <typename T, typename Allocator>
constexpr bool is_py_list_like_v<std::vector<T, Allocator>> = true;
template <typename T, typename Allocator>
constexpr bool is_py_list_like_v<std::list<T, Allocator>> = true;
template <typename T, typename Allocator>
constexpr bool is_py_list_like_v<std::deque<T, Allocator>> = true;
template <>
struct type_caster<std::vector<bool>> {
std::vector<bool> data;
template <typename U>
static object cast(U&& src, return_value_policy policy, handle parent) {
auto list = pkbind::list();
for(auto&& item: src) {
list.append(pkbind::cast(bool(item), policy, parent));
}
return list;
}
bool load(handle src, bool convert) {
if(!isinstance<list>(src)) { return false; }
auto list = src.cast<pkbind::list>();
data.clear();
data.reserve(list.size());
for(auto item: list) {
type_caster<bool> caster;
if(!caster.load(item, convert)) { return false; }
data.push_back(caster.value());
}
return true;
}
std::vector<bool>& value() { return data; }
constexpr inline static bool is_temporary_v = true;
};
template <typename T>
struct type_caster<T, std::enable_if_t<is_py_list_like_v<T>>> {
T data;
template <typename U>
static object cast(U&& src, return_value_policy policy, handle parent) {
auto list = pkbind::list();
for(auto&& item: src) {
list.append(pkbind::cast(std::move(item), policy, parent));
}
return list;
}
bool load(handle src, bool convert) {
if(!isinstance<list>(src)) { return false; }
auto list = src.cast<pkbind::list>();
for(auto item: list) {
type_caster<typename T::value_type> caster;
if(!caster.load(item, convert)) { return false; }
data.push_back(std::move(caster.value()));
}
return true;
}
T& value() { return data; }
constexpr inline static bool is_temporary_v = true;
};
template <typename T>
constexpr bool is_py_map_like_v = false;
template <typename Key, typename T, typename Compare, typename Allocator>
constexpr bool is_py_map_like_v<std::map<Key, T, Compare, Allocator>> = true;
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
constexpr bool is_py_map_like_v<std::unordered_map<Key, T, Hash, KeyEqual, Allocator>> = true;
template <typename T>
struct type_caster<T, std::enable_if_t<is_py_map_like_v<T>>> {
T data;
template <typename U>
static object cast(U&& src, return_value_policy policy, handle parent) {
auto dict = pkbind::dict();
for(auto&& [key, value]: src) {
dict[pkbind::cast(std::move(key), policy, parent)] =
pkbind::cast(std::move(value), policy, parent);
}
return dict;
}
bool load(handle src, bool convert) {
if(!isinstance<dict>(src)) { return false; }
auto dict = src.cast<pkbind::dict>();
for(auto item: dict) {
type_caster<typename T::key_type> key_caster;
if(!key_caster.load(item.first, convert)) { return false; }
type_caster<typename T::mapped_type> value_caster;
if(!value_caster.load(item.second, convert)) { return false; }
data.try_emplace(std::move(key_caster.value()), std::move(value_caster.value()));
}
return true;
}
T& value() { return data; }
constexpr inline static bool is_temporary_v = true;
};
} // namespace pkbind

View File

@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.10)
project(PKBIND_TEST)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../.." "${CMAKE_CURRENT_BINARY_DIR}/pocketpy")
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
DOWNLOAD_EXTRACT_TIMESTAMP true
)
if(WIN32)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()
FetchContent_MakeAvailable(googletest)
file(GLOB CPP_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
add_executable(PKBIND_TEST ${CPP_SOURCES})
target_include_directories(PKBIND_TEST PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..")
target_link_libraries(PKBIND_TEST PRIVATE pocketpy gtest_main)

View File

@ -0,0 +1,121 @@
#include "test.h"
namespace {
int copy_constructor_calls = 0;
int move_constructor_calls = 0;
int destructor_calls = 0;
struct Point {
int x;
int y;
Point(int x, int y) : x(x), y(y) {}
Point(const Point& other) : x(other.x), y(other.y) { ++copy_constructor_calls; }
Point(Point&& other) : x(other.x), y(other.y) { ++move_constructor_calls; }
~Point() { ++destructor_calls; }
bool operator== (const Point& p) const { return x == p.x && y == p.y; }
};
TEST_F(PYBIND11_TEST, exec_and_eval) {
auto m = py::module::__main__();
py::dict locals = {py::arg("x") = 1, py::arg("y") = 2};
py::object obj = py::eval("x + y", py::none{}, locals);
EXPECT_EQ(obj.cast<int>(), 3);
py::exec("x = 1 + 2");
EXPECT_EQ(py::eval("x").cast<int>(), 3);
py::exec("y = 1 + 2", m.attr("__dict__"));
EXPECT_EQ(py::eval("y", m.attr("__dict__")).cast<int>(), 3);
EXPECT_EQ(locals["x"].cast<int>(), 1);
EXPECT_EQ(locals["y"].cast<int>(), 2);
}
TEST_F(PYBIND11_TEST, locals_and_globals) {
py::exec("x = 1");
auto globals = py::globals();
EXPECT_EQ(globals["x"].cast<int>(), 1);
globals["y"] = 2;
EXPECT_EQ(py::eval("y").cast<int>(), 2);
}
TEST_F(PYBIND11_TEST, cast) {
auto m = py::module::__main__();
py::class_<Point>(m, "Point")
.def(py::init<int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y)
.def("__eq__", &Point::operator==);
Point p(1, 2);
// for py::cast's default policy
// if argument is lvalue, policy is copy
py::object o = py::cast(p);
EXPECT_EQ(py::cast<Point&>(o), p);
EXPECT_EQ(copy_constructor_calls, 1);
EXPECT_EQ(move_constructor_calls, 0);
// if argument is rvalue, policy is move
py::object o2 = py::cast(std::move(p));
EXPECT_EQ(py::cast<Point&>(o2), p);
EXPECT_EQ(copy_constructor_calls, 1);
EXPECT_EQ(move_constructor_calls, 1);
// if argument is pointer, policy is reference(no taking ownership)
py::object o3 = py::cast(&p);
EXPECT_EQ(py::cast<Point&>(o3), p);
EXPECT_EQ(copy_constructor_calls, 1);
EXPECT_EQ(move_constructor_calls, 1);
py::finalize(true);
EXPECT_EQ(destructor_calls, 2);
}
TEST_F(PYBIND11_TEST, cpp_call_py) {
auto m = py::module::__main__();
py::exec(R"(
class Test:
def __init__(self, x, y):
self.x = x
self.y = y
def sum1(self, z):
return self.x + self.y + z
def sum2(self, z, *args):
return self.x + self.y + z + sum(args)
def sum3(self, z, n=0):
return self.x + self.y + z + n
def sum4(self, z, *args, **kwargs):
return self.x + self.y + z + sum(args) + kwargs['a'] + kwargs['b']
)");
auto obj = py::eval("Test(1, 2)");
EXPECT_EQ(obj.attr("sum1")(3).cast<int>(), 6);
EXPECT_EQ(obj.attr("sum2")(3, 4, 5).cast<int>(), 15);
using namespace py::literals;
EXPECT_EQ(obj.attr("sum3")(3, "n"_a = 4).cast<int>(), 10);
EXPECT_EQ(obj.attr("sum4")(3, 4, 5, "a"_a = 6, "b"_a = 7).cast<int>(), 28);
}
} // namespace

View File

@ -0,0 +1,225 @@
#include "test.h"
namespace {
struct Point {
int x;
int y;
private:
int z;
public:
inline static int constructor_calls = 0;
inline static int copy_constructor_calls = 0;
inline static int move_constructor_calls = 0;
inline static int destructor_calls = 0;
Point() : x(0), y(0), z(0) { constructor_calls++; }
Point(int x, int y, int z) : x(x), y(y), z(z) { constructor_calls++; }
Point(const Point& p) : x(p.x), y(p.y), z(p.z) {
copy_constructor_calls++;
constructor_calls++;
}
Point(Point&& p) noexcept : x(p.x), y(p.y), z(p.z) {
move_constructor_calls++;
constructor_calls++;
}
Point& operator= (const Point& p) {
x = p.x;
y = p.y;
z = p.z;
return *this;
}
~Point() { destructor_calls++; }
int& get_z() { return z; }
void set_z(int z) { this->z = z; }
std::string stringfy() const {
return "(" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ")";
}
};
struct Line {
Point start;
Point end;
};
TEST_F(PYBIND11_TEST, class) {
py::module_ m = py::module_::import("__main__");
py::class_<Point>(m, "Point")
.def(py::init<>())
.def(py::init<int, int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y)
.def_property("z", &Point::get_z, &Point::set_z)
.def("stringfy", &Point::stringfy);
py::exec(R"(
p = Point()
assert p.stringfy() == '(0, 0, 0)'
p = Point(1, 2, 3)
assert p.x == 1
assert p.y == 2
assert p.z == 3
assert p.stringfy() == '(1, 2, 3)'
p.x = 10
p.y = 20
p.z = 30
assert p.stringfy() == '(10, 20, 30)'
)");
py::class_<Line> line(m, "Line");
line // not bind constructor
.def_readwrite("start", &Line::start)
.def_readwrite("end", &Line::end);
// bind constructor
line.def(py::init<>());
py::exec(R"(
l = Line()
l.start = Point(1, 2, 3)
l.end = Point(4, 5, 6)
p = l.start
assert l.start.stringfy() == '(1, 2, 3)'
assert l.end.stringfy() == '(4, 5, 6)'
)");
py::finalize(true);
EXPECT_EQ(Point::constructor_calls, Point::destructor_calls);
EXPECT_EQ(Point::copy_constructor_calls, 0);
EXPECT_EQ(Point::move_constructor_calls, 0);
}
TEST_F(PYBIND11_TEST, inheritance) {
static int constructor_calls = 0;
struct Point {
int x;
int y;
Point() : x(0), y(0) { constructor_calls++; }
Point(int x, int y) : x(x), y(y) { constructor_calls++; }
};
struct Point3D : Point {
int z;
Point3D() : Point(), z(0) { constructor_calls++; }
Point3D(int x, int y, int z) : Point(x, y), z(z) { constructor_calls++; }
};
py::module_ m = py::module_::import("__main__");
py::class_<Point>(m, "Point")
.def(py::init<>())
.def(py::init<int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y);
py::class_<Point3D, Point>(m, "Point3D")
.def(py::init<>())
.def(py::init<int, int, int>())
.def_readwrite("z", &Point3D::z);
py::exec(R"(
p = Point3D()
assert type(p) == Point3D
assert p.x == 0
assert p.y == 0
assert p.z == 0
p = Point3D(1, 2, 3)
assert p.x == 1
assert p.y == 2
assert p.z == 3
p.x = 10
p.y = 20
p.z = 30
assert p.x == 10
assert p.y == 20
assert p.z == 30
)");
py::finalize(true);
EXPECT_EQ(constructor_calls, 4);
}
TEST_F(PYBIND11_TEST, dynamic_attr) {
py::module_ m = py::module_::import("__main__");
struct Point {
int x;
int y;
Point(int x, int y) : x(x), y(y) {}
};
py::class_<Point>(m, "Point", py::dynamic_attr())
.def(py::init<int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y);
py::object p = py::eval("Point(1, 2)");
EXPECT_EQ(p.attr("x").cast<int>(), 1);
EXPECT_EQ(p.attr("y").cast<int>(), 2);
p.attr("z") = py::int_(3);
EXPECT_EQ(p.attr("z").cast<int>(), 3);
}
TEST_F(PYBIND11_TEST, enum) {
enum class Color { RED, Yellow, GREEN, BLUE };
py::module_ m = py::module_::import("__main__");
py::enum_<Color> color(m, "Color");
color.value("RED", Color::RED)
.value("Yellow", Color::Yellow)
.value("GREEN", Color::GREEN)
.value("BLUE", Color::BLUE);
EXPECT_EVAL_EQ("Color.RED", Color::RED);
EXPECT_EVAL_EQ("Color.Yellow", Color::Yellow);
EXPECT_EVAL_EQ("Color.GREEN", Color::GREEN);
EXPECT_EVAL_EQ("Color.BLUE", Color::BLUE);
EXPECT_EVAL_EQ("Color(0)", Color::RED);
EXPECT_EVAL_EQ("Color(1)", Color::Yellow);
EXPECT_EVAL_EQ("Color(2)", Color::GREEN);
EXPECT_EVAL_EQ("Color(3)", Color::BLUE);
EXPECT_EXEC_EQ("Color(0)", "Color.RED");
EXPECT_EXEC_EQ("Color(1)", "Color.Yellow");
EXPECT_EXEC_EQ("Color(2)", "Color.GREEN");
EXPECT_EXEC_EQ("Color(3)", "Color.BLUE");
EXPECT_EVAL_EQ("Color.RED.value", static_cast<int>(Color::RED));
EXPECT_EVAL_EQ("Color.Yellow.value", static_cast<int>(Color::Yellow));
EXPECT_EVAL_EQ("Color.GREEN.value", static_cast<int>(Color::GREEN));
EXPECT_EVAL_EQ("Color.BLUE.value", static_cast<int>(Color::BLUE));
color.export_values();
EXPECT_EVAL_EQ("RED", Color::RED);
EXPECT_EVAL_EQ("Yellow", Color::Yellow);
EXPECT_EVAL_EQ("GREEN", Color::GREEN);
EXPECT_EVAL_EQ("BLUE", Color::BLUE);
}
} // namespace

View File

@ -0,0 +1,50 @@
#include "test.h"
TEST_F(PYBIND11_TEST, exception_python_to_cpp) {
auto test = [](const char* code, py_Type type) {
try {
py::exec(code);
} catch(py::python_error& e) {
EXPECT_TRUE(e.match(type));
}
};
test("raise ValueError()", tp_ValueError);
test("raise KeyError()", tp_KeyError);
test("raise IndexError()", tp_IndexError);
test("raise StopIteration()", tp_StopIteration);
test("raise Exception()", tp_Exception);
}
TEST_F(PYBIND11_TEST, exception_cpp_to_python) {
auto m = py::module_::__main__();
#define TEST_EXCEPTION(cppe, pye) \
m.def("test_" #cppe, []() { \
throw cppe(#cppe); \
}); \
py::exec("try:\n test_" #cppe "()\nexcept Exception as e:\n assert isinstance(e, " #pye \
")\n assert str(e) == '" #cppe "'")
using namespace std;
TEST_EXCEPTION(runtime_error, RuntimeError);
TEST_EXCEPTION(domain_error, ValueError);
TEST_EXCEPTION(invalid_argument, ValueError);
TEST_EXCEPTION(length_error, ValueError);
TEST_EXCEPTION(out_of_range, IndexError);
TEST_EXCEPTION(range_error, ValueError);
using namespace py;
m.def("test_stop_iteration", []() {
throw py::stop_iteration();
});
py::exec("try:\n test_stop_iteration()\nexcept StopIteration as e:\n pass");
TEST_EXCEPTION(index_error, IndexError);
// FIXME: TEST_EXCEPTION(key_error, KeyError);
TEST_EXCEPTION(value_error, ValueError);
TEST_EXCEPTION(type_error, TypeError);
TEST_EXCEPTION(import_error, ImportError);
TEST_EXCEPTION(attribute_error, AttributeError);
TEST_EXCEPTION(runtime_error, RuntimeError);
}

View File

@ -0,0 +1,402 @@
#include "test.h"
namespace {
TEST_F(PYBIND11_TEST, vectorcall) {
auto m = py::module_::__main__();
py::exec(R"(
def add(a, b):
return a + b
)");
// position only
EXPECT_CAST_EQ(m.attr("add")(1, 2), 3);
// FIXME: pkpy does not support such calling.
// keyword only
// EXPECT_CAST_EQ(m.attr("add")(py::arg("a") = 1, py::arg("b") = 2), 3);
// mix
// EXPECT_CAST_EQ(m.attr("add")(1, py::arg("b") = 2), 3);
py::exec(R"(
def add2(a, *args):
return a + sum(args)
)");
EXPECT_CAST_EQ(m.attr("add2")(1, 2, 3, 4), 10);
EXPECT_EQ(py::type::of<py::tuple>()(py::eval("[1, 2, 3]")), py::eval("(1, 2, 3)"));
EXPECT_EQ(py::type::of<py::list>()(py::eval("(1, 2, 3)")), py::eval("[1, 2, 3]"));
}
TEST_F(PYBIND11_TEST, constructor) {
auto m = py::module_::__main__();
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
bool operator== (const Point& p) const { return x == p.x && y == p.y; }
};
py::class_<Point>(m, "Point")
.def(py::init<int, int>(), py::arg("x") = 1, py::arg("y") = 2)
.def(py::init([](py::tuple tuple) {
return Point(tuple[0].cast<int>(), tuple[1].cast<int>());
}))
.def("__eq__", &Point::operator==);
EXPECT_EVAL_EQ("Point()", Point(1, 2));
EXPECT_EVAL_EQ("Point(3)", Point(3, 2));
EXPECT_EVAL_EQ("Point(3, 4)", Point(3, 4));
EXPECT_EVAL_EQ("Point((3, 4))", Point(3, 4));
}
TEST_F(PYBIND11_TEST, args) {
auto m = py::module_::__main__();
// test for binding function with args
m.def("sum", [](py::args args) {
int sum = 0;
for(auto arg: args) {
sum += arg.cast<int>();
}
return sum;
});
EXPECT_EVAL_EQ("sum(1, 2, 3)", 6);
}
TEST_F(PYBIND11_TEST, kwargs) {
auto m = py::module_::__main__();
// test for binding function with kwargs
m.def("cal", [](py::kwargs kwargs) {
int sum = kwargs["a"].cast<int>() + kwargs["b"].cast<int>() * kwargs["c"].cast<int>();
return sum;
});
EXPECT_EVAL_EQ("cal(a=1, b=2, c=3)", 7);
}
TEST_F(PYBIND11_TEST, defaults) {
auto m = py::module_::__main__();
// test for binding function with defaults
m.def(
"cal",
[](int a, int b = 2, int c = 3) {
return a + b * c;
},
py::arg("a"),
py::arg("b") = 2,
py::arg("c") = 3);
EXPECT_EVAL_EQ("cal(1)", 7); // a = 1, b = 2, c = 3
EXPECT_EVAL_EQ("cal(1, 4)", 13); // a = 1, b = 4, c = 3
EXPECT_EVAL_EQ("cal(1, 4, 5)", 21); // a = 1, b = 4, c = 5
EXPECT_EVAL_EQ("cal(2, c=6)", 14); // a = 2, b = 2, c = 6
EXPECT_EVAL_EQ("cal(2, b=4, c=6)", 26); // a = 2, b = 4, c = 6
}
TEST_F(PYBIND11_TEST, defaults_with_args) {
auto m = py::module_::__main__();
// test for binding function with defaults
m.def(
"cal",
[](int a, int b, int c, py::args args) {
int sum = a + b + c;
for(auto arg: args) {
sum += arg.cast<int>();
}
return sum;
},
py::arg("a"),
py::arg("b") = 2,
py::arg("c") = 3);
EXPECT_EVAL_EQ("cal(1)", 6); // a = 1, b = 2, c = 3
EXPECT_EVAL_EQ("cal(1, 4)", 8); // a = 1, b = 4, c = 3
EXPECT_EVAL_EQ("cal(1, 4, 5)", 10); // a = 1, b = 4, c = 5
EXPECT_EVAL_EQ("cal(1, 4, 5, 6)", 16); // a = 1, b = 4, c = 5, args = (6)
EXPECT_EVAL_EQ("cal(1, 4, 5, 6, 7)", 23); // a = 1, b = 4, c = 5, args = (6, 7)
}
TEST_F(PYBIND11_TEST, default_with_args_and_kwargs) {
auto m = py::module_::__main__();
// test for binding function with defaults
m.def(
"cal",
[](int a, int b, int c, py::args args, py::kwargs kwargs) {
int sum = a + b + c;
for(auto arg: args) {
sum += arg.cast<int>();
}
for(auto item: kwargs) {
sum += item.second.cast<int>();
}
return sum;
},
py::arg("a"),
py::arg("b") = 2,
py::arg("c") = 3);
EXPECT_EVAL_EQ("cal(1)", 6); // a = 1, b = 2, c = 3
EXPECT_EVAL_EQ("cal(1, 4)", 8); // a = 1, b = 4, c = 3
EXPECT_EVAL_EQ("cal(1, 4, 5)", 10); // a = 1, b = 4, c = 5
EXPECT_EVAL_EQ("cal(1, 4, 5, 6)", 16); // a = 1, b = 4, c = 5, args = (6)
EXPECT_EVAL_EQ("cal(1, 4, 5, 6, 7)", 23); // a = 1, b = 4, c = 5, args = (6, 7)
EXPECT_EVAL_EQ("cal(1, 4, 5, d=6, e=7)", 23); // a = 1, b = 4, c = 5, kwargs = {d=6, e=7}
EXPECT_EVAL_EQ("cal(1, d=6, e=7)", 19); // a = 1, b = 2, c = 3, kwargs = {d=6, e=7}
EXPECT_EVAL_EQ("cal(1)", 6); // a = 1, b = 2, c = 3
EXPECT_EVAL_EQ("cal(1, b=4)", 8); // a = 1, b = 4, c = 3
EXPECT_EVAL_EQ("cal(1, c=5)", 8); // a = 1, b = 2, c = 5
EXPECT_EVAL_EQ("cal(1, 4, d=6)", 14); // a = 1, b = 4, c = 3, kwargs = {d=6}
EXPECT_EVAL_EQ("cal(1, c=5, d=6)", 14); // a = 1, b = 2, c = 5, kwargs = {d=6}
EXPECT_EVAL_EQ("cal(1, 4, 5, d=6)", 16); // a = 1, b = 4, c = 5, kwargs = {d=6}
EXPECT_EVAL_EQ("cal(1, 4, 5)", 10); // a = 1, b = 4, c = 5, args = (), kwargs = {}
EXPECT_EVAL_EQ("cal(1, 4, 5, *[], **{})", 10); // a = 1, b = 4, c = 5, args = (), kwargs = {}
EXPECT_EVAL_EQ("cal(1, 4, 5, 6, 7, d=8, e=9)", 40); // a = 1, b = 4, c = 5, args = (6, 7), kwargs = {d=8, e=9}
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
int sum(int x = 1, int y = 2) { return this->x + this->y + x + y; }
};
py::class_<Point>(m, "Point")
.def(py::init<int, int>()) //
.def("sum", &Point::sum, py::arg("x") = 1, py::arg("y") = 2);
EXPECT_EVAL_EQ("Point(1, 2).sum()", 6); // x = 1, y = 2
EXPECT_EVAL_EQ("Point(1, 2).sum(3)", 8); // x = 3, y = 2
EXPECT_EVAL_EQ("Point(1, 2).sum(3, 4)", 10); // x = 3, y = 4
}
TEST_F(PYBIND11_TEST, overload) {
auto m = py::module_::__main__();
// test for binding function with overloads
m.def("cal", [](int a, int b) {
return a + b;
});
m.def("cal", [](int a, int b, int c) {
return a + b + c;
});
EXPECT_EVAL_EQ("cal(1, 2)", 3);
EXPECT_EVAL_EQ("cal(1, 2, 3)", 6);
}
TEST_F(PYBIND11_TEST, return_value_policy) {
static int copy_constructor_calls = 0;
static int move_constructor_calls = 0;
static int destructor_calls = 0;
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
Point(const Point& p) : x(p.x), y(p.y) { copy_constructor_calls++; }
Point(Point&& p) : x(p.x), y(p.y) { move_constructor_calls++; }
~Point() { destructor_calls++; }
static Point& make_point() {
static Point p(1, 2);
return p;
}
static Point& new_point() { return *new Point(1, 2); }
bool operator== (const Point& p) const { return x == p.x && y == p.y; }
};
py::finalize(true);
auto test = [](py::return_value_policy policy, auto bound_fn, auto fn) {
py::initialize();
copy_constructor_calls = 0;
move_constructor_calls = 0;
destructor_calls = 0;
auto m = py::module_::__main__();
py::class_<Point>(m, "Point")
.def(py::init<int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y)
.def("__eq__", &Point::operator==);
m.def("make_point", bound_fn, policy);
EXPECT_EVAL_EQ("make_point()", Point::make_point());
py::finalize(true);
fn();
};
test(py::return_value_policy::reference, &Point::make_point, []() {
EXPECT_EQ(copy_constructor_calls, 0);
EXPECT_EQ(move_constructor_calls, 0);
EXPECT_EQ(destructor_calls, 0);
});
test(py::return_value_policy::copy, &Point::make_point, []() {
EXPECT_EQ(copy_constructor_calls, 1);
EXPECT_EQ(move_constructor_calls, 0);
EXPECT_EQ(destructor_calls, 1);
});
test(py::return_value_policy::move, &Point::make_point, []() {
EXPECT_EQ(copy_constructor_calls, 0);
EXPECT_EQ(move_constructor_calls, 1);
EXPECT_EQ(destructor_calls, 1);
});
test(py::return_value_policy::take_ownership, &Point::new_point, []() {
EXPECT_EQ(copy_constructor_calls, 0);
EXPECT_EQ(move_constructor_calls, 0);
EXPECT_EQ(destructor_calls, 1);
});
}
TEST_F(PYBIND11_TEST, default_return_value_policy) {
static int copy_constructor_calls = 0;
static int move_constructor_calls = 0;
static int destructor_calls = 0;
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
Point(const Point& p) : x(p.x), y(p.y) { copy_constructor_calls++; }
Point(Point&& p) : x(p.x), y(p.y) { move_constructor_calls++; }
~Point() { destructor_calls++; }
bool operator== (const Point& p) const { return x == p.x && y == p.y; }
};
auto m = py::module_::__main__();
py::class_<Point>(m, "Point")
.def(py::init<int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y)
.def("__eq__", &Point::operator==);
// for function return value policy
// if return type is lvalue reference, policy is copy
m.def("make_point2", []() -> Point& {
static Point p(1, 2);
return p;
});
py::exec("p2 = make_point2()");
EXPECT_EQ(copy_constructor_calls, 1);
EXPECT_EQ(move_constructor_calls, 0);
// if return type is rvalue reference, policy is move
m.def("make_point3", []() -> Point&& {
static Point p(1, 2);
return std::move(p);
});
py::exec("p3 = make_point3()");
EXPECT_EQ(copy_constructor_calls, 1);
EXPECT_EQ(move_constructor_calls, 1);
// if return type is pointer, policy is take_ownership
m.def("make_point4", []() -> Point* {
return new Point(1, 2);
});
py::exec("p4 = make_point4()");
EXPECT_EQ(copy_constructor_calls, 1);
EXPECT_EQ(move_constructor_calls, 1);
py::finalize(true);
EXPECT_EQ(destructor_calls, 3);
}
TEST_F(PYBIND11_TEST, lambda) {
auto m = py::module_::__main__();
static int destructor_calls = 0;
struct NotTrivial {
int data;
int operator() (int x, int y) { return x + y + data; }
~NotTrivial() { destructor_calls++; }
};
struct NotSmall {
size_t a;
size_t b;
size_t c;
size_t d;
size_t operator() (size_t x, size_t y) { return x + y + a + b + c + d; }
~NotSmall() { destructor_calls++; }
};
// test for binding lambda
m.def("cal", NotTrivial{3});
m.def("cal2", NotSmall{3, 4, 5, 6});
EXPECT_EVAL_EQ("cal(1, 2)", 6);
EXPECT_EVAL_EQ("cal2(1, 2)", 21);
py::finalize(true);
EXPECT_EQ(destructor_calls, 4);
}
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
TEST_F(PYBIND11_TEST, overload_cast) {
auto m = py::module_::__main__();
m.def("add", py::overload_cast<int, int>(add));
m.def("add", py::overload_cast<int, int, int>(add));
EXPECT_EVAL_EQ("add(1, 2)", 3);
EXPECT_EVAL_EQ("add(1, 2, 3)", 6);
struct X {
X() {}
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
};
py::class_<X>(m, "X")
.def(py::init<>())
.def("add", py::overload_cast<int, int>(&X::add))
.def("add", py::overload_cast<int, int, int>(&X::add));
EXPECT_EVAL_EQ("X().add(1, 2)", 3);
EXPECT_EVAL_EQ("X().add(1, 2, 3)", 6);
}
} // namespace

View File

@ -0,0 +1,31 @@
#include "test.h"
PYBIND11_EMBEDDED_MODULE(example, m) {
m.def("add", [](int a, int b) {
return a + b;
});
auto math = m.def_submodule("math");
math.def("sub", [](int a, int b) {
return a - b;
});
}
namespace {
TEST_F(PYBIND11_TEST, module) {
py::exec("import example");
EXPECT_EVAL_EQ("example.add(1, 2)", 3);
py::exec("from example import math");
EXPECT_EVAL_EQ("math.sub(1, 2)", -1);
py::exec("from example.math import sub");
EXPECT_EVAL_EQ("sub(1, 2)", -1);
auto math = py::module_::import("example.math");
EXPECT_EQ(math.attr("sub")(4, 3).cast<int>(), 1);
}
} // namespace

View File

@ -0,0 +1,98 @@
#include "test.h"
namespace {
const char* source = R"(
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def __mul__(self, other):
return Point(self.x * other.x, self.y * other.y)
def __truediv__(self, other):
return Point(self.x / other.x, self.y / other.y)
def __floordiv__(self, other):
return Point(self.x // other.x, self.y // other.y)
def __mod__(self, other):
return Point(self.x % other.x, self.y % other.y)
def __pow__(self, other):
return Point(self.x ** other.x, self.y ** other.y)
def __lshift__(self, other):
return Point(self.x << other.x, self.y << other.y)
def __rshift__(self, other):
return Point(self.x >> other.x, self.y >> other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __ne__(self, other) -> bool:
return not self.__eq__(other)
def __lt__(self, other) -> bool:
return self.x < other.x and self.y < other.y
def __le__(self, other) -> bool:
return self.x <= other.x and self.y <= other.y
def __gt__(self, other) -> bool:
return self.x > other.x and self.y > other.y
def __ge__(self, other) -> bool:
return self.x >= other.x and self.y >= other.y
def __repr__(self):
return f'Point({self.x}, {self.y})'
)";
TEST_F(PYBIND11_TEST, object) {
py::module_ m = py::module_::import("__main__");
py::exec(source);
py::exec("p = Point(3, 4)");
py::object p = py::eval("p");
// is
EXPECT_FALSE(p.is_none());
EXPECT_TRUE(p.is(p));
// attrs
EXPECT_EQ(p.attr("x").cast<int>(), 3);
EXPECT_EQ(p.attr("y").cast<int>(), 4);
p.attr("x") = py::int_(5);
p.attr("y") = py::int_(6);
EXPECT_EQ(p.attr("x").cast<int>(), 5);
EXPECT_EQ(p.attr("y").cast<int>(), 6);
EXPECT_EXEC_EQ("p", "Point(5, 6)");
// operators
EXPECT_EVAL_EQ("Point(10, 12)", p + p);
EXPECT_EVAL_EQ("Point(0, 0)", p - p);
EXPECT_EVAL_EQ("Point(25, 36)", p * p);
EXPECT_EVAL_EQ("Point(1, 1)", p / p);
// EXPECT_EVAL_EQ("Point(0, 0)", p // p);
EXPECT_EVAL_EQ("Point(0, 0)", p % p);
// iterators
py::object l = py::eval("[1, 2, 3]");
int index = 0;
for(auto item: l) {
EXPECT_EQ(item.cast<int>(), index + 1);
index++;
}
}
} // namespace

View File

@ -0,0 +1,150 @@
#include "test.h"
#include <pybind11/operators.h>
namespace {
struct Int {
int x;
Int(int x) : x(x) {}
#define OPERATOR_IMPL(op) \
template <typename LHS, typename RHS> \
friend int operator op (const LHS& lhs, const RHS& rhs) { \
int l, r; \
if constexpr(std::is_same_v<LHS, Int>) \
l = lhs.x; \
else \
l = lhs; \
if constexpr(std::is_same_v<RHS, Int>) \
r = rhs.x; \
else \
r = rhs; \
return l op r; \
}
OPERATOR_IMPL(+)
OPERATOR_IMPL(-)
OPERATOR_IMPL(*)
OPERATOR_IMPL(/)
OPERATOR_IMPL(%)
OPERATOR_IMPL(&)
OPERATOR_IMPL(|)
OPERATOR_IMPL(^)
OPERATOR_IMPL(<<)
OPERATOR_IMPL(>>)
#undef OPERATOR_IMPL
bool operator== (const Int& other) const { return x == other.x; }
bool operator!= (const Int& other) const { return x != other.x; }
bool operator< (const Int& other) const { return x < other.x; }
bool operator<= (const Int& other) const { return x <= other.x; }
bool operator> (const Int& other) const { return x > other.x; }
bool operator>= (const Int& other) const { return x >= other.x; }
bool operator!() const { return !x; }
};
} // namespace
TEST_F(PYBIND11_TEST, arithmetic_operators) {
py::module_ m = py::module_::import("__main__");
py::class_<Int>(m, "Int")
.def(py::init<int>())
.def(py::self + py::self)
.def(py::self + int())
.def(int() + py::self)
.def(py::self - py::self)
.def(py::self - int())
.def(int() - py::self)
.def(py::self * py::self)
.def(py::self * int())
.def(int() * py::self)
.def(py::self / py::self)
.def(py::self / int())
.def(int() / py::self)
.def(py::self % py::self)
.def(py::self % int())
.def(int() % py::self)
.def(py::self & py::self)
.def(py::self & int())
.def(int() & py::self)
.def(py::self | py::self)
.def(py::self | int())
.def(int() | py::self)
.def(py::self ^ py::self)
.def(py::self ^ int())
.def(int() ^ py::self)
.def(py::self << py::self)
.def(py::self << int())
.def(int() << py::self)
.def(py::self >> py::self)
.def(py::self >> int())
.def(int() >> py::self);
auto a = py::cast(Int(1));
auto ai = py::cast(1);
auto b = py::cast(Int(2));
auto bi = py::cast(2);
EXPECT_CAST_EQ(a + b, 3);
EXPECT_CAST_EQ(a + bi, 3);
EXPECT_CAST_EQ(ai + b, 3);
EXPECT_CAST_EQ(a - b, -1);
EXPECT_CAST_EQ(a - bi, -1);
EXPECT_CAST_EQ(ai - b, -1);
EXPECT_CAST_EQ(a * b, 2);
EXPECT_CAST_EQ(a * bi, 2);
EXPECT_CAST_EQ(ai * b, 2);
EXPECT_CAST_EQ(a / b, 0);
EXPECT_CAST_EQ(a / bi, 0);
// EXPECT_CAST_EQ(ai / b, 0);
EXPECT_CAST_EQ(a % b, 1);
EXPECT_CAST_EQ(a % bi, 1);
// EXPECT_CAST_EQ(ai % b, 1);
EXPECT_CAST_EQ(a & b, 0);
EXPECT_CAST_EQ(a & bi, 0);
// EXPECT_CAST_EQ(ai & b, 0);
EXPECT_CAST_EQ(a | b, 3);
EXPECT_CAST_EQ(a | bi, 3);
// EXPECT_CAST_EQ(ai | b, 3);
EXPECT_CAST_EQ(a ^ b, 3);
EXPECT_CAST_EQ(a ^ bi, 3);
// EXPECT_CAST_EQ(ai ^ b, 3);
EXPECT_CAST_EQ(a << b, 4);
EXPECT_CAST_EQ(a << bi, 4);
// EXPECT_CAST_EQ(ai << b, 4);
EXPECT_CAST_EQ(a >> b, 0);
EXPECT_CAST_EQ(a >> bi, 0);
// EXPECT_CAST_EQ(ai >> b, 0);
}
TEST_F(PYBIND11_TEST, logic_operators) {
py::module_ m = py::module_::import("__main__");
py::class_<Int>(m, "Int")
.def(py::init<int>())
.def_readwrite("x", &Int::x)
.def(py::self == py::self)
.def(py::self != py::self)
.def(py::self < py::self)
.def(py::self <= py::self)
.def(py::self > py::self)
.def(py::self >= py::self);
auto a = py::cast(Int(1));
auto b = py::cast(Int(2));
EXPECT_FALSE(a == b);
EXPECT_TRUE(a != b);
EXPECT_TRUE(a < b);
EXPECT_TRUE(a <= b);
EXPECT_FALSE(a > b);
EXPECT_FALSE(a >= b);
}

View File

@ -0,0 +1,125 @@
#include "test.h"
#include <pybind11/stl.h>
namespace {
int constructor_calls = 0;
int destructor_calls = 0;
struct Point {
int x;
int y;
Point() : x(0), y(0) { constructor_calls++; }
Point(int x, int y) : x(x), y(y) { constructor_calls++; }
Point(const Point& p) : x(p.x), y(p.y) { constructor_calls++; }
Point(Point&& p) noexcept : x(p.x), y(p.y) { constructor_calls++; }
Point& operator= (const Point& p) {
x = p.x;
y = p.y;
return *this;
}
~Point() { destructor_calls++; }
bool operator== (const Point& p) const { return x == p.x && y == p.y; }
};
} // namespace
TEST_F(PYBIND11_TEST, vector_bool) {
std::vector<bool> v = {true, false, true};
py::object obj = py::cast(v);
EXPECT_EVAL_EQ("[True, False, True]", obj);
std::vector<bool> v2 = obj.cast<std::vector<bool>>();
EXPECT_EQ(v, v2);
}
TEST_F(PYBIND11_TEST, list_like) {
py::class_<Point>(py::module_::__main__(), "Point")
.def(py::init<int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y)
.def("__eq__", &Point::operator==);
// array
{
std::array<Point, 2> a = {Point(1, 2), Point(3, 4)};
py::object obj = py::eval("[Point(1, 2), Point(3, 4)]");
EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj);
std::array<Point, 2> a2 = obj.cast<std::array<Point, 2>>();
EXPECT_EQ(a, a2);
}
// vector
{
std::vector<Point> v = {Point(1, 2), Point(3, 4)};
py::object obj = py::cast(v);
EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj);
std::vector<Point> v2 = obj.cast<std::vector<Point>>();
EXPECT_EQ(v, v2);
}
// list
{
std::list<Point> l = {Point(1, 2), Point(3, 4)};
py::object obj = py::cast(l);
EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj);
std::list<Point> l2 = obj.cast<std::list<Point>>();
EXPECT_EQ(l, l2);
}
// deque
{
std::deque<Point> d = {Point(1, 2), Point(3, 4)};
py::object obj = py::cast(d);
EXPECT_EVAL_EQ("[Point(1, 2), Point(3, 4)]", obj);
std::deque<Point> d2 = obj.cast<std::deque<Point>>();
EXPECT_EQ(d, d2);
}
}
TEST_F(PYBIND11_TEST, dict_like) {
py::class_<Point>(py::module_::__main__(), "Point")
.def(py::init<int, int>())
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y)
.def("__eq__", &Point::operator==);
// map
{
std::map<std::string, Point> m = {
{"a", Point(1, 2)},
{"b", Point(3, 4)}
};
py::object obj = py::cast(m);
EXPECT_EVAL_EQ("{'a': Point(1, 2), 'b': Point(3, 4)}", obj);
std::map<std::string, Point> m2 = obj.cast<std::map<std::string, Point>>();
EXPECT_EQ(m, m2);
}
// unordered_map
{
std::unordered_map<std::string, Point> m = {
{"a", Point(1, 2)},
{"b", Point(3, 4)}
};
py::object obj = py::cast(m);
EXPECT_EVAL_EQ("{'a': Point(1, 2), 'b': Point(3, 4)}", obj);
std::unordered_map<std::string, Point> m2 = obj.cast<std::unordered_map<std::string, Point>>();
EXPECT_EQ(m, m2);
}
}

View File

@ -0,0 +1,16 @@
#include <gtest/gtest.h>
#include <pybind11/pkbind.h>
namespace py = pkbind;
class PYBIND11_TEST : public ::testing::Test {
protected:
void SetUp() override { py::initialize(); }
void TearDown() override { py::finalize(true); }
};
#define EXPECT_CAST_EQ(expr, expected) EXPECT_EQ(py::cast(expr), py::cast(expected))
#define EXPECT_EVAL_EQ(expr, expected) EXPECT_EQ(py::eval(expr).cast<decltype(expected)>(), expected)
#define EXPECT_EXEC_EQ(expr, expected) EXPECT_EQ(py::eval(expr), py::eval(expected))

View File

@ -0,0 +1,169 @@
#include "test.h"
TEST_F(PYBIND11_TEST, int) {
py::object obj = py::int_(123);
py::object obj2 = py::eval("123");
EXPECT_EQ(obj, obj2);
EXPECT_EQ(obj.cast<int>(), 123);
EXPECT_EQ(obj.cast<long>(), 123);
EXPECT_EQ(obj.cast<long long>(), 123);
}
TEST_F(PYBIND11_TEST, float) {
py::object obj = py::float_(123.0);
py::object obj2 = py::eval("123.0");
EXPECT_EQ(obj, obj2);
EXPECT_EQ(obj.cast<float>(), 123.0);
EXPECT_EQ(obj.cast<double>(), 123.0);
}
TEST_F(PYBIND11_TEST, str) {
py::object obj = py::str("123");
py::object obj2 = py::eval("'123'");
EXPECT_EQ(obj, obj2);
EXPECT_STREQ(obj.cast<const char*>(), "123");
EXPECT_EQ(obj.cast<std::string>(), "123");
EXPECT_EQ(obj.cast<std::string_view>(), "123");
auto s = py::str("Hello, {}");
EXPECT_EQ(s.format("world").cast<std::string>(), "Hello, world");
}
TEST_F(PYBIND11_TEST, tuple) {
py::tuple tuple = py::tuple{
py::int_(1),
py::str("123"),
py::int_(3),
};
EXPECT_EQ(tuple, py::eval("(1, '123', 3)"));
EXPECT_EQ(tuple.size(), 3);
EXPECT_FALSE(tuple.empty());
tuple[0] = py::int_(3);
tuple[2] = py::int_(1);
EXPECT_EQ(tuple, py::eval("(3, '123', 1)"));
// iterators.
int index = 0;
for(auto item: tuple) {
if(index == 0) {
EXPECT_EQ(item, py::int_(3));
} else if(index == 1) {
EXPECT_EQ(item, py::str("123"));
} else if(index == 2) {
EXPECT_EQ(item, py::int_(1));
}
index++;
}
}
TEST_F(PYBIND11_TEST, list) {
// constructors
py::list list = py::list();
EXPECT_EQ(list, py::eval("[]"));
EXPECT_EQ(list.size(), 0);
EXPECT_TRUE(list.empty());
list = py::list{
py::int_(1),
py::int_(2),
py::int_(3),
};
EXPECT_EQ(list, py::eval("[1, 2, 3]"));
EXPECT_EQ(list.size(), 3);
EXPECT_FALSE(list.empty());
// accessor
list[0] = py::int_(3);
list[2] = py::int_(1);
EXPECT_EQ(list, py::eval("[3, 2, 1]"));
// iterators
int index = 0;
for(auto item: list) {
if(index == 0) {
EXPECT_EQ(item, py::int_(3));
} else if(index == 1) {
EXPECT_EQ(item, py::int_(2));
} else if(index == 2) {
EXPECT_EQ(item, py::int_(1));
}
index++;
}
// others
list.append(py::int_(4));
EXPECT_EQ(list, py::eval("[3, 2, 1, 4]"));
list.insert(0, py::int_(7));
EXPECT_EQ(list, py::eval("[7, 3, 2, 1, 4]"));
}
TEST_F(PYBIND11_TEST, dict) {
// constructors
py::dict dict = py::dict();
EXPECT_EQ(dict, py::eval("{}"));
EXPECT_EQ(dict.size(), 0);
EXPECT_TRUE(dict.empty());
// accessor
dict["a"] = py::int_(1);
dict["b"] = py::int_(2);
dict["c"] = py::int_(3);
EXPECT_EQ(dict, py::eval("{'a': 1, 'b': 2, 'c': 3}"));
EXPECT_EQ(dict,
py::dict({
{"a", py::int_(1)},
{"b", py::int_(2)},
{"c", py::int_(3)},
}));
// iterators
int index = 0;
for(auto item: dict) {
if(index == 0) {
EXPECT_EQ(item.first.cast<std::string>(), "a");
EXPECT_EQ(item.second, py::int_(1));
} else if(index == 1) {
EXPECT_EQ(item.first.cast<std::string>(), "b");
EXPECT_EQ(item.second, py::int_(2));
} else if(index == 2) {
EXPECT_EQ(item.first.cast<std::string>(), "c");
EXPECT_EQ(item.second, py::int_(3));
}
index++;
}
}
TEST_F(PYBIND11_TEST, capsule) {
static int times = 0;
struct NotTrivial {
~NotTrivial() { times++; }
};
py::handle x = py::capsule(new NotTrivial(), [](void* ptr) {
delete static_cast<NotTrivial*>(ptr);
});
auto m = py::module_::__main__();
m.def("foo", [](int x) {
return py::capsule(new int(x));
});
m.def("bar", [](py::capsule x, int y) {
EXPECT_EQ(x.cast<int>(), y);
delete (int*)x.data();
});
py::exec("bar(foo(123), 123)");
py::finalize(true);
EXPECT_EQ(times, 1);
}