Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/Ark/VM/State.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <Ark/Constants.hpp>

#include <Ark/VM/Value.hpp>
#include <Ark/VM/Value/Procedure.hpp>
#include <Ark/Compiler/Common.hpp>
#include <Ark/Exceptions.hpp>
#include <Ark/Compiler/IntermediateRepresentation/InstLoc.hpp>
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions include/Ark/VM/Value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <Ark/VM/Value/Closure.hpp>
#include <Ark/VM/Value/UserType.hpp>
#include <Ark/VM/Value/Procedure.hpp>
#include <Ark/Platform.hpp>

namespace Ark
Expand Down Expand Up @@ -68,14 +69,13 @@ namespace Ark
class ARK_API Value
{
public:
using ProcType = Value (*)(std::vector<Value>&, VM*);
using Iterator = std::vector<Value>::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<Value>, // 24 bytes
Expand Down Expand Up @@ -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>&& value) noexcept;
explicit Value(internal::Closure&& value) noexcept;
explicit Value(UserType&& value) noexcept;
Expand Down Expand Up @@ -170,7 +171,7 @@ namespace Ark
[[nodiscard]] constexpr uint8_t typeNum() const noexcept { return static_cast<uint8_t>(m_type); }

[[nodiscard]] internal::PageAddr_t pageAddr() const { return std::get<internal::PageAddr_t>(m_value); }
[[nodiscard]] const ProcType& proc() const { return std::get<ProcType>(m_value); }
[[nodiscard]] const Procedure& proc() const { return std::get<Procedure>(m_value); }
[[nodiscard]] const internal::Closure& closure() const { return std::get<internal::Closure>(m_value); }
[[nodiscard]] internal::Closure& refClosure() { return std::get<internal::Closure>(m_value); }
};
Expand Down
63 changes: 63 additions & 0 deletions include/Ark/VM/Value/Procedure.hpp
Original file line number Diff line number Diff line change
@@ -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 <functional>
#include <vector>

namespace Ark
{
class Value;
class VM;

/**
* @brief Storage class to hold custom functions
*/
class ARK_API Procedure
{
public:
using PointerType = Value (*)(std::vector<Value>&, VM*);
using CallbackType = std::function<Value(std::vector<Value>&, 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 <typename T>
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<Value>&, VM*) const;

bool operator<(const Procedure& other) const noexcept;
bool operator==(const Procedure& other) const noexcept;

private:
CallbackType m_procedure;
};
}

#endif
4 changes: 2 additions & 2 deletions src/arkreactor/VM/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>& args) noexcept
Expand Down
11 changes: 8 additions & 3 deletions src/arkreactor/VM/Value.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <Ark/VM/Value.hpp>
#include <Ark/VM/Value/Procedure.hpp>

#include <fmt/format.h>
#include <fmt/ostream.h>
Expand All @@ -15,7 +16,7 @@ namespace Ark
if (type == ValueType::List)
m_value = std::vector<Value>();
else if (type == ValueType::String)
m_value = "";
m_value = std::string();
}

Value::Value(const int value) noexcept :
Expand All @@ -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>&& value) noexcept :
Expand Down
27 changes: 27 additions & 0 deletions src/arkreactor/VM/Value/Procedure.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <Ark/VM/Value.hpp>
#include <Ark/VM/Value/Procedure.hpp>

#include <utility>

namespace Ark
{
Value Procedure::operator()(std::vector<Value>& 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;
}
};
2 changes: 1 addition & 1 deletion src/arkscript/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions tests/unittests/Suites/EmbeddingSuite.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include <boost/ut.hpp>

#include <Ark/Ark.hpp>
#include <Ark/Literals.hpp>
#include <vector>
#include <iostream>

using namespace boost;
using namespace Ark::literals;

Ark::Value my_function(std::vector<Ark::Value>& args, Ark::VM* vm [[maybe_unused]])
{
Expand Down Expand Up @@ -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<Ark::Value>& 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<Ark::Value>& 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);
Expand Down
4 changes: 2 additions & 2 deletions tests/unittests/Suites/TypeCheckerSuite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ Input parse_input(const std::string& path)
break;

case Ark::ValueType::CProc:
given_args.emplace_back([](std::vector<Ark::Value>&, Ark::VM*) -> Ark::Value {
given_args.emplace_back(Ark::Procedure([](std::vector<Ark::Value>&, Ark::VM*) -> Ark::Value {
return Ark::Value(Ark::ValueType::Nil);
});
}));
break;

case Ark::ValueType::Nil:
Expand Down
Loading