wrenpp/include/wrenpp/detail/construct_foreign_class.hpp

169 lines
5.6 KiB
C++

/* Copyright 2020-2024, Michele Santullo
* This file is part of wrenpp.
*
* Wrenpp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Wrenpp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with wrenpp. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../vm.hpp"
#include "setters_getters.hpp"
#if defined(WRENPP_WITH_NAME_GUESSING)
# include "guess_class_name.hpp"
#endif
#include <utility>
#include <tuple>
#include <cassert>
#include <cstdint>
namespace wren {
namespace detail {
template <typename T, typename... Args, int... Indices>
inline void construct_single (
std::integer_sequence<int, Indices...>,
VM& vm,
void* memory
) {
new(memory) T{get<Args>(vm, Indices + 1)...};
}
struct ConstructResult {
std::uint16_t parameter_count;
bool success;
};
template <typename T, typename... Args>
struct ForeignClassHelper;
template <typename T, template <typename...> class Pack, typename... PackArgs, typename... Args>
struct ForeignClassHelper<T, Pack<PackArgs...>, Args...> {
static ConstructResult construct (VM& vm, int cardinality, void* memory) {
using std::make_integer_sequence;
//search by number of parameters.
//once we find a tuple-like parameter that contains the right
//number of types we invoke construct_single() with those types.
constexpr int pack_size = static_cast<int>(sizeof...(PackArgs));
if (pack_size == cardinality) {
construct_single<T, PackArgs...>(make_integer_sequence<int, pack_size>{}, vm, memory);
return ConstructResult{pack_size, true};
}
else {
return ForeignClassHelper<T, Args...>::construct(vm, cardinality, memory);
}
}
};
template <typename T, typename LoneArg, typename... Args>
struct ForeignClassHelper<T, LoneArg, Args...> {
static ConstructResult construct (VM& vm, int cardinality, void* memory) {
using std::make_integer_sequence;
if (1 == cardinality) {
construct_single<T, LoneArg>(make_integer_sequence<int, 1>{}, vm, memory);
return ConstructResult{1, true};
}
else {
return ForeignClassHelper<T, Args...>::construct(vm, cardinality, memory);
}
}
};
template <typename T>
struct ForeignParamHelper {
static constexpr std::size_t argument_count = 1;
};
template <template <typename...> class Pack, typename... PackArgs>
struct ForeignParamHelper<Pack<PackArgs...>> {
static constexpr std::size_t argument_count = sizeof...(PackArgs);
};
template <typename T>
struct ForeignClassHelper<T> {
static ConstructResult construct (VM& vm, int cardinality, void* memory) {
using std::make_integer_sequence;
if (cardinality == 0) {
construct_single<T>(make_integer_sequence<int, 0>{}, vm, memory);
return ConstructResult{0, true};
}
else {
return ConstructResult{static_cast<std::uint16_t>(cardinality), false};
}
}
};
template <std::size_t Search, std::size_t... Values>
struct AssertIfDuplicateValues {
static_assert(sizeof...(Values) == 0, "Code bug?");
};
template <std::size_t Search, std::size_t Value, std::size_t... Values>
struct AssertIfDuplicateValues<Search, Value, Values...> : AssertIfDuplicateValues<Search, Values...>, AssertIfDuplicateValues<Value, Values...> {
static_assert(Search != Value, "Constructor overloads with the same argument counts are not currently allowed");
static constexpr std::size_t value = 1;
};
template <typename... Args>
constexpr void static_assert_all_packs_are_unique() {
//If more than one arg (ctor overload) is given, check they are unique
if constexpr (sizeof...(Args) > 1) {
constexpr auto dummy = AssertIfDuplicateValues<ForeignParamHelper<Args>::argument_count...>::value;
static_assert(dummy == 1); //always true
}
}
//This is named from the perspective of the user - they say
//MakeForeignClass<X> and it will "produce" object of type X. In
//reality it produces a functor that does that, and it's that functor
//that gets registered into wren, so from the library perspective it
//should be called MakeForeignClassMaker
template <typename T, typename... Args>
class MakeForeignClass {
public:
MakeForeignClass() = default;
foreign_class_t operator()() const {
detail::static_assert_all_packs_are_unique<Args...>();
foreign_class_t ret;
ret.allocate = [](VM& vm, ModuleAndName mn) {
void* const mem = vm.set_slot_new_foreign(0, 0, sizeof(T));
const auto result = detail::ForeignClassHelper<T, Args...>::construct(vm, vm.slot_count() - 1, mem);
if (not result.success) {
abort_fiber_with_error(vm, 1,
std::string{"No registered c++ constructor "} +
#if defined(WRENPP_WITH_NAME_GUESSING)
"for class " + guess_class_name<T>() + " " +
#endif
"takes " + std::to_string(result.parameter_count) +
" parameter" + (result.parameter_count == 1 ? "" : "s")
);
}
};
ret.finalize = [](void* mem) {
const auto cale = static_cast<T*>(mem);
cale->~T();
};
return ret;
}
};
} //namespace detail
template <typename T, typename... Args>
constexpr detail::MakeForeignClass<T, Args...> make_foreign_class;
} //namespace wren