Add support for object parameters to Wren calls

This commit is contained in:
King_DuckZ 2024-05-25 01:47:51 +02:00
parent b00bd59027
commit 05298b6111
9 changed files with 282 additions and 37 deletions

175
examples/call_cpp/main.cpp Normal file
View file

@ -0,0 +1,175 @@
/* 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/>.
*/
#include "wrenpp/vm_fun.hpp"
#include "wrenpp/def_configuration.hpp"
#include "wrenpp/callback_manager.hpp"
#include "wrenpp/class_manager.hpp"
#include <chrono>
#include <thread>
#include <iostream>
namespace {
constexpr char g_module_a[] =
"foreign class ClassA {" R"script(
construct new(msg) {
System.print("Wren ClassA constructed")
}
foreign say_hi()
}
)script";
constexpr char g_module_b[] = ""
R"script(
import "module_a" for ClassA
foreign class ClassB {
construct new(nickname) {
System.print("Wren ClassB constructed")
}
do_action(obj_a, times) {
System.print("Wren ClassB is doing its action %(times) times...")
for (z in 1..times) {
obj_a.say_hi()
}
greeting_message("John", "Doe", "\\(O.O)/")
}
foreign greeting_message(first_name, family_name, emoji)
}
)script";
//This is three examples in one:
//1. instantiate ClassA in Wren and pass it to do_action() which is a
// non-foreign method in wren
//2. instantiate ClassA in C++, have C++ invoke the non-foreign method
// do_action() on the object obj_b from the previous execution and pass the
// C++ instance of ClassA to it
//3. instantiate ClassB in C++, have C++ invoke the non-foreign method
// do_action() on it and pass the same ClassA instance from the previous
// step to it
constexpr char g_script[] = ""
R"script(
import "module_a" for ClassA
import "module_b" for ClassB
var obj_b = ClassB.new("TheWren")
obj_b.do_action(ClassA.new("instanciated from script"), 2)
obj_b.greeting_message("Jane", "Doe", "ʕ·͡ᴥ·ʔ")
)script";
class MyWrenConfig : public wren::DefConfiguration {
public:
char* load_module_fn (wren::VM* vm, std::string_view module_name) {
if (module_name == "module_a")
return copied(g_module_a, sizeof(g_module_a));
else if (module_name == "module_b")
return copied(g_module_b, sizeof(g_module_b));
return nullptr;
}
void load_module_complete_fn (wren::VM* vm, std::string_view module_name, char* buff) {
static_cast<void>(vm);
static_cast<void>(module_name);
delete[] buff;
}
private:
char* copied (const char* source, std::size_t len) {
char* const buff = new char[len];
std::copy(source, source + len / sizeof(source[0]), buff);
return buff;
}
};
struct ClassA {
explicit ClassA (std::string msg) : m_message(msg) {
std::cout << "C++ ClassA constructed\n";
}
ClassA (const ClassA&) = delete;
ClassA (ClassA&&) = delete;
void say_hi() {
std::cout << "C++ ClassA says \"" << m_message << "\"!\n";
}
std::string m_message;
};
class ClassB {
public:
explicit ClassB (std::string nickname) : m_nickname(nickname) {
std::cout << "C++ ClassB constructed\n";
}
ClassB (const ClassB&) = delete;
ClassB (ClassB&&) = delete;
void greeting_message (
std::string_view first_name,
const char* family_name,
const std::string& emoji
) {
std::cout << "C++ ClassB says \"hello " << first_name << " '" <<
m_nickname << "' " << family_name << "\" " << emoji << '\n';
}
private:
std::string m_nickname;
};
} //unnamed namespace
int main() {
using wren::make_function_bindable;
using wren::make_foreign_class;
using wren::make_wren_object;
//We need a custom one to deal with custom module loading for
//module_a and module_b
MyWrenConfig config;
wren::VM vm(&config, nullptr);
vm.callback_manager()
.add_callback(false, "module_a", "ClassA", "say_hi()", make_function_bindable<&ClassA::say_hi>)
.add_callback(false, "module_b", "ClassB", "greeting_message(_,_,_)", make_function_bindable<&ClassB::greeting_message>)
;
vm.class_manager()
.add_class_maker("module_b", "ClassB", make_foreign_class<ClassB, std::string>)
.add_class_maker("module_a", "ClassA", make_foreign_class<ClassA, std::string>)
;
//Example 1: invoke obj_b.do_action() from Wren passing an obj_a that's
//also instantiated from within the Wren script
vm.interpret("main", g_script);
//Example 2: invoke obj_b.do_action() from C++ passing a new obj_a which
//is instantiated from C++
wren::ForeignObject<ClassA> obj_a = make_wren_object<ClassA>(vm, wren::MN<"module_a", "ClassA">, "instantiated from c++");
wren::call<void>(vm, wren::MN<"main", "obj_b">, "do_action", obj_a, 3);
//Example 3: invoke obj_b.do_action() from C++, where obj_b is also
//instantiated from C++, and pass the C++ obj_a instance to it
auto obj_b = make_wren_object<ClassB>(vm, wren::MN<"module_b", "ClassB">, "TheCpp");
wren::call<void>(vm, obj_b, "do_action", obj_a, 4);
std::cout << "Quitting in 1 sec" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
return 0;
}

View file

@ -0,0 +1,5 @@
executable('call_cpp',
'main.cpp',
dependencies: wrenpp_dep,
install: false,
)

View file

@ -1,4 +1,4 @@
/* Copyright 2020-2022, Michele Santullo
/* Copyright 2020-2024, Michele Santullo
* This file is part of wrenpp.
*
* Wrenpp is free software: you can redistribute it and/or modify
@ -63,8 +63,6 @@ void user_get_env (wren::VM& vm, wren::ModuleAndName) {
} //unnamed namespace
int main() {
//typedef wren::ModuleAndName MN;
wren::DefConfiguration config;
wren::VM vm(&config, nullptr);
vm.callback_manager().add_callback(true, "main", "User", "get_env(_)", [](){return &user_get_env;});

View file

@ -2,3 +2,4 @@ subdir('dieroll')
subdir('greet')
subdir('calendar')
subdir('math_vector')
subdir('call_cpp')

View file

@ -0,0 +1,63 @@
/* 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 "wrenpp/handle.hpp"
#include <utility>
namespace wren {
template <typename T>
class ForeignObject {
public:
ForeignObject() = default;
ForeignObject (std::nullptr_t) : ForeignObject() {}
ForeignObject (T* obj, Handle&& handle);
~ForeignObject() noexcept = default;
void set_to_slot (int num);
T& object() { return *m_object; }
const T& object() const { return *m_object; }
Handle& handle() { return m_handle; }
const Handle& handle() const { return m_handle; }
bool is_valid() const { return nullptr != m_object; }
operator T&() { return object(); }
operator const T&() const { return object(); }
operator Handle&() { return handle(); }
operator const Handle&() const { return handle(); }
bool operator!= (std::nullptr_t) const { return nullptr != m_object; }
bool operator== (std::nullptr_t) const { return nullptr == m_object; }
private:
Handle m_handle;
T* m_object{nullptr};
};
template <typename T>
inline ForeignObject<T>::ForeignObject (T* obj, Handle&& handle) :
m_handle(std::move(handle)),
m_object(obj)
{ }
template <typename T>
inline void ForeignObject<T>::set_to_slot (int num) {
m_handle.set_to_slot(num);
}
} //namespace wren

View file

@ -1,4 +1,4 @@
/* Copyright 2020-2022, Michele Santullo
/* Copyright 2020-2024, Michele Santullo
* This file is part of wrenpp.
*
* Wrenpp is free software: you can redistribute it and/or modify
@ -18,6 +18,7 @@
#pragma once
#include "../vm.hpp"
#include "../handle.hpp"
#include "module_and_name.hpp"
#include <string_view>
#include <vector>
@ -87,6 +88,8 @@ namespace wren {
void set (VM& vm, int slot_num, const char* beg, const char* end);
void set (VM& vm, int slot_num, int value);
void set (VM& vm, int slot_num, std::size_t value);
void set (VM& vm, int slot_num, const Handle& handle);
void set (VM& vm, int slot_num, const ModuleAndName& name);
std::string_view slot_string_view (VM& vm, int slot_num);
template <typename T> detail::GetTypeToRetType_t<T> get (VM& vm, int slot_num);
template <typename T> T* foreign (VM& vm, int slot_num);
@ -138,6 +141,14 @@ namespace wren {
vm.set_slot_double(slot_num, static_cast<double>(value));
}
inline void set (VM& vm, int slot_num, const Handle& handle) {
vm.set_slot_handle(handle, slot_num);
}
inline void set (VM& vm, int slot_num, const ModuleAndName& name) {
vm.variable_or_throw(name.module_name_char(), name.class_name_char(), slot_num);
}
template <typename T>
inline T* foreign (VM& vm, int slot_num) {
T* obj = static_cast<T*>(vm.slot_foreign(slot_num));

View file

@ -1,4 +1,4 @@
/* Copyright 2020-2022, Michele Santullo
/* Copyright 2020-2024, Michele Santullo
* This file is part of wrenpp.
*
* Wrenpp is free software: you can redistribute it and/or modify
@ -42,6 +42,8 @@ namespace wren {
operator WrenHandle*() const { return m_handle; }
operator bool() const { return nullptr != m_handle; }
void set_to_slot (int num);
private:
WrenHandle* m_handle;
VM* m_vm;

View file

@ -1,4 +1,4 @@
/* Copyright 2020-2022, Michele Santullo
/* Copyright 2020-2024, Michele Santullo
* This file is part of wrenpp.
*
* Wrenpp is free software: you can redistribute it and/or modify
@ -26,6 +26,7 @@
#include "detail/construct_foreign_class.hpp"
#include "detail/setters_getters.hpp"
#include "detail/wren_class_name_from_type.hpp"
#include "detail/foreign_object.hpp"
#include <string>
#include <string_view>
#include <type_traits>
@ -46,11 +47,11 @@ namespace wren {
R call (VM& vm, ModuleAndName object, const char (&method)[N], const Args&... args);
template <typename T, typename... Args>
T* make_wren_object(VM& vm, ModuleAndName mn, Args&&... args);
ForeignObject<T> make_wren_object(VM& vm, ModuleAndName mn, Args&&... args);
#if defined(WRENPP_WITH_NAME_GUESSING)
template <typename T, dhandy::bt::string Mod, typename... Args>
T* make_wren_object(VM& vm, Args&&... args);
ForeignObject<T> make_wren_object(VM& vm, Args&&... args);
#endif
void interpret (VM& vm, const std::string& module_name, const std::string& script);
@ -61,28 +62,13 @@ namespace wren {
void abort_fiber_with_error (VM& vm, int slot, std::string_view message);
namespace detail {
template <typename T>
inline void set_single_for_call (VM& vm, int slot, const T& value) {
set(vm, slot, value);
}
template <>
inline void set_single_for_call (VM& vm, int slot_num, const Handle& handle) {
vm.set_slot_handle(handle, slot_num);
}
template <>
inline void set_single_for_call (VM& vm, int slot_num, const ModuleAndName& name) {
variable(vm, name, slot_num);
}
//Sets arguments so wren is ready to perform a function call. It doesn't
//touch at slot 0, so eventual objects for method calls must be set
//manually
template <typename... Args, int... Indices>
inline void set_for_call (std::integer_sequence<int, Indices...>, VM& vm, const Args&... args) {
vm.ensure_slots(sizeof...(Args));
(set_single_for_call(vm, Indices + 1, args), ...);
(set(vm, Indices + 1, args), ...);
}
template <auto V>
@ -145,7 +131,7 @@ namespace wren {
}
else {
ModuleAndName wren_name = wren_class_name_from_type<R>(vm);
R* const new_object = make_wren_object<R>(vm, wren_name, std::move(ret));
ForeignObject<R> new_object = make_wren_object<R>(vm, wren_name, std::move(ret));
static_cast<void>(new_object);
}
}
@ -197,15 +183,13 @@ namespace wren {
inline R call (VM& vm, const Handle& object, const char (&method)[N], const Args&... args) {
using dhandy::bt::string;
const constexpr char dummy_name_buff[N] = {0};
const constexpr auto params = string<N, char>(dummy_name_buff) +
string("(") +
const constexpr auto params = string("(") +
((static_cast<void>(args), string(",_")) + ... + string("")).template substr<1>() +
string(")");
;
char cat_buff[params.size() + 1];
char cat_buff[params.size() + 1 + N - 1];
std::copy(method, method + N - 1, cat_buff);
std::copy(params.data() + N - 1, params.data() + params.size() + 1, cat_buff + N - 1);
std::copy(params.data(), params.data() + params.size() + 1, cat_buff + N - 1);
return call<R, Args...>(vm, object, vm.make_call_handle(cat_buff), args...);
}
@ -218,12 +202,12 @@ namespace wren {
}
template <typename T, typename... Args>
inline T* make_wren_object(VM& vm, ModuleAndName mn, Args&&... args) {
variable(vm, mn, 0);
inline ForeignObject<T> make_wren_object(VM& vm, ModuleAndName mn, Args&&... args) {
variable_ensure_slot(vm, mn, 0);
void* const mem = vm.set_slot_new_foreign(0, 0, sizeof(T));
try {
T* const obj = new(mem) T{std::forward<Args>(args)...};
return obj;
return {obj, vm.slot_handle(0)};
}
catch (const std::runtime_error& err) {
abort_fiber_with_error(vm, 1, err.what());
@ -233,7 +217,7 @@ namespace wren {
#if defined(WRENPP_WITH_NAME_GUESSING)
template <typename T, dhandy::bt::string Mod, typename... Args>
inline T* make_wren_object(VM& vm, Args&&... args) {
inline ForeignObject<T> make_wren_object(VM& vm, Args&&... args) {
return make_wren_object<T>(
vm,
MN<Mod, g_guessed_class_name<T>>,

View file

@ -1,4 +1,4 @@
/* Copyright 2020-2022, Michele Santullo
/* Copyright 2020-2024, Michele Santullo
* This file is part of wrenpp.
*
* Wrenpp is free software: you can redistribute it and/or modify
@ -18,6 +18,7 @@
#include "wrenpp/handle.hpp"
#include "wrenpp/vm.hpp"
#include <cassert>
#include <ciso646>
namespace wren {
Handle::~Handle() noexcept {
@ -25,7 +26,12 @@ namespace wren {
}
void Handle::release() noexcept {
assert(m_vm);
m_vm->release_handle(*this);
assert(m_vm or not m_handle);
if (m_vm)
m_vm->release_handle(*this);
}
void Handle::set_to_slot (int slot) {
m_vm->set_slot_handle(*this, slot);
}
} //namespace wren