diff --git a/include/Ark/VM/State.hpp b/include/Ark/VM/State.hpp index 8f2635c3..c15a62cd 100644 --- a/include/Ark/VM/State.hpp +++ b/include/Ark/VM/State.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -82,7 +83,7 @@ namespace Ark * @param name the name of the function in ArkScript * @param function the code of the function */ - void loadFunction(const std::string& name, Value::ProcType function) noexcept; + void loadFunction(const std::string& name, Procedure::CallbackType&& function) noexcept; /** * @brief Set the script arguments in sys:args diff --git a/include/Ark/VM/Value.hpp b/include/Ark/VM/Value.hpp index b28def71..8feccdec 100644 --- a/include/Ark/VM/Value.hpp +++ b/include/Ark/VM/Value.hpp @@ -19,6 +19,7 @@ #include #include +#include #include namespace Ark @@ -68,14 +69,13 @@ namespace Ark class ARK_API Value { public: - using ProcType = Value (*)(std::vector&, VM*); using Iterator = std::vector::iterator; using Value_t = std::variant< double, // 8 bytes std::string, // 32 bytes internal::PageAddr_t, // 2 bytes - ProcType, // 8 bytes + Procedure, // 32 bytes internal::Closure, // 24 bytes UserType, // 24 bytes std::vector, // 24 bytes @@ -113,8 +113,9 @@ namespace Ark explicit Value(int value) noexcept; explicit Value(double value) noexcept; explicit Value(const std::string& value) noexcept; + explicit Value(const char* value) noexcept; explicit Value(internal::PageAddr_t value) noexcept; - explicit Value(ProcType value) noexcept; + explicit Value(Procedure&& value) noexcept; explicit Value(std::vector&& value) noexcept; explicit Value(internal::Closure&& value) noexcept; explicit Value(UserType&& value) noexcept; @@ -170,7 +171,7 @@ namespace Ark [[nodiscard]] constexpr uint8_t typeNum() const noexcept { return static_cast(m_type); } [[nodiscard]] internal::PageAddr_t pageAddr() const { return std::get(m_value); } - [[nodiscard]] const ProcType& proc() const { return std::get(m_value); } + [[nodiscard]] const Procedure& proc() const { return std::get(m_value); } [[nodiscard]] const internal::Closure& closure() const { return std::get(m_value); } [[nodiscard]] internal::Closure& refClosure() { return std::get(m_value); } }; diff --git a/include/Ark/VM/Value/Procedure.hpp b/include/Ark/VM/Value/Procedure.hpp new file mode 100644 index 00000000..cfd3e00c --- /dev/null +++ b/include/Ark/VM/Value/Procedure.hpp @@ -0,0 +1,63 @@ +/** + * @file Procedure.hpp + * @author Justin Andreas Lacoste (me@justin.cx) + * @brief Wrapper object for user-defined functions + * @date 2025-06-09 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef ARK_VM_PROCEDURE_HPP +#define ARK_VM_PROCEDURE_HPP + +#include +#include + +namespace Ark +{ + class Value; + class VM; + + /** + * @brief Storage class to hold custom functions + */ + class ARK_API Procedure + { + public: + using PointerType = Value (*)(std::vector&, VM*); + using CallbackType = std::function&, VM*)>; + + /// + /// Due to clang (sometimes) rejecting forward declared types + /// in templates (`Value` causes issues), we have to implement + /// the constructor for the actual `CallbackType` using SFINAE + /// and a templated constructor, such that when clang + /// encounters the constructor, it knows the actual + /// declaration of `Value`. + /// + /** + * @brief Create a new procedure. + */ + template + Procedure(T&& cb) : + m_procedure(cb) + { + } + + /** + * @brief Create a new procedure from a stateless C function pointer. + */ + Procedure(PointerType c_ptr); + + Value operator()(std::vector&, VM*) const; + + bool operator<(const Procedure& other) const noexcept; + bool operator==(const Procedure& other) const noexcept; + + private: + CallbackType m_procedure; + }; +} + +#endif diff --git a/src/arkreactor/VM/State.cpp b/src/arkreactor/VM/State.cpp index 59cc53d2..29ca6d72 100644 --- a/src/arkreactor/VM/State.cpp +++ b/src/arkreactor/VM/State.cpp @@ -117,9 +117,9 @@ namespace Ark return feed(welder.bytecode()); } - void State::loadFunction(const std::string& name, const Value::ProcType function) noexcept + void State::loadFunction(const std::string& name, Procedure::CallbackType&& function) noexcept { - m_binded[name] = Value(function); + m_binded[name] = Value(std::move(function)); } void State::setArgs(const std::vector& args) noexcept diff --git a/src/arkreactor/VM/Value.cpp b/src/arkreactor/VM/Value.cpp index 7cbf4ae4..7901f1c5 100644 --- a/src/arkreactor/VM/Value.cpp +++ b/src/arkreactor/VM/Value.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -15,7 +16,7 @@ namespace Ark if (type == ValueType::List) m_value = std::vector(); else if (type == ValueType::String) - m_value = ""; + m_value = std::string(); } Value::Value(const int value) noexcept : @@ -30,12 +31,16 @@ namespace Ark m_type(ValueType::String), m_value(value) {} + Value::Value(const char* value) noexcept : + m_type(ValueType::String), m_value(std::string(value)) + {} + Value::Value(internal::PageAddr_t value) noexcept : m_type(ValueType::PageAddr), m_value(value) {} - Value::Value(Value::ProcType value) noexcept : - m_type(ValueType::CProc), m_value(value) + Value::Value(Procedure&& value) noexcept : + m_type(ValueType::CProc), m_value(std::move(value)) {} Value::Value(std::vector&& value) noexcept : diff --git a/src/arkreactor/VM/Value/Procedure.cpp b/src/arkreactor/VM/Value/Procedure.cpp new file mode 100644 index 00000000..b29de1eb --- /dev/null +++ b/src/arkreactor/VM/Value/Procedure.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include + +namespace Ark +{ + Value Procedure::operator()(std::vector& args, VM* vm) const + { + return m_procedure(args, vm); + } + + Procedure::Procedure(PointerType c_pointer) + { + m_procedure = c_pointer; + } + + bool Procedure::operator<(const Procedure&) const noexcept + { + return false; + } + + bool Procedure::operator==(const Procedure&) const noexcept + { + return false; + } +}; diff --git a/src/arkscript/main.cpp b/src/arkscript/main.cpp index 8865f168..84dcdce0 100644 --- a/src/arkscript/main.cpp +++ b/src/arkscript/main.cpp @@ -226,7 +226,7 @@ int main(int argc, char** argv) sizeof(Ark::Value), sizeof(Ark::Value::Value_t), sizeof(Ark::ValueType), - sizeof(Ark::Value::ProcType), + sizeof(Ark::Procedure), sizeof(Ark::internal::Closure), sizeof(Ark::UserType), // vm diff --git a/tests/unittests/Suites/EmbeddingSuite.cpp b/tests/unittests/Suites/EmbeddingSuite.cpp index 52d4fdda..72c60b5d 100644 --- a/tests/unittests/Suites/EmbeddingSuite.cpp +++ b/tests/unittests/Suites/EmbeddingSuite.cpp @@ -1,10 +1,12 @@ #include #include +#include #include #include using namespace boost; +using namespace Ark::literals; Ark::Value my_function(std::vector& args, Ark::VM* vm [[maybe_unused]]) { @@ -142,6 +144,63 @@ ut::suite<"Embedding"> embedding_suite = [] { }; }; + "[load cpp function with captured data]"_test = [] { + Ark::State state; + + int capture = 42; + state.loadFunction("my_function", [=](std::vector& args, [[maybe_unused]] Ark::VM* /*vm*/) { + int solution = 0; + for (const Ark::Value& value : args) + { + solution += value.number(); + } + return Ark::Value(capture + solution); + }); + + should("compile the string without any error") = [&] { + expect(mut(state).doString("(let bar (my_function 1 2 3 1))")); + }; + + Ark::VM vm(state); + should("return exit code 0") = [&] { + expect(mut(vm).run() == 0_i); + }; + + should("compute egg to 49") = [&] { + auto egg = mut(vm)["bar"]; + expect(egg.valueType() == Ark::ValueType::Number); + expect(egg.number() == 49_i); + }; + }; + + "[load cpp function with captured reference]"_test = [] { + Ark::State state; + + std::string name = ""; + state.loadFunction("my_function", [&name](std::vector& args, [[maybe_unused]] Ark::VM* /*vm*/) { + for (const Ark::Value& value : args) + { + name.append(value.string()); + } + return Ark::Value(); + }); + + should("compile the string without any error") = [&] { + expect(mut(state).doString(R"( + (my_function "Iron" " " "Man") + )")); + }; + + Ark::VM vm(state); + should("return exit code 0") = [&] { + expect(mut(vm).run() == 0_i); + }; + + should("have mutated the capture variable") = [&] { + expect(name == "Iron Man"); + }; + }; + "[load cpp function and call it from arkscript]"_test = [] { Ark::State state; state.loadFunction("my_function", my_function); diff --git a/tests/unittests/Suites/TypeCheckerSuite.cpp b/tests/unittests/Suites/TypeCheckerSuite.cpp index 3d663dc8..ebb9f512 100644 --- a/tests/unittests/Suites/TypeCheckerSuite.cpp +++ b/tests/unittests/Suites/TypeCheckerSuite.cpp @@ -122,9 +122,9 @@ Input parse_input(const std::string& path) break; case Ark::ValueType::CProc: - given_args.emplace_back([](std::vector&, Ark::VM*) -> Ark::Value { + given_args.emplace_back(Ark::Procedure([](std::vector&, Ark::VM*) -> Ark::Value { return Ark::Value(Ark::ValueType::Nil); - }); + })); break; case Ark::ValueType::Nil: