Skip to content
Open
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
19 changes: 13 additions & 6 deletions include/chaiscript/language/chaiscript_eval.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,13 +324,17 @@ namespace chaiscript {
false,
*t_ss);
} catch (const exception::bad_boxed_cast &) {
// Value is not directly callable. Fall back to calling by name through the
// dispatch engine, which handles the case where a global (e.g., class namespace)
// shadows a function (e.g., constructor) with the same name.
try {
using ConstFunctionTypeRef = const Const_Proxy_Function &;
Const_Proxy_Function f = t_ss->boxed_cast<ConstFunctionTypeRef>(fn);
// handle the case where there is only 1 function to try to call and dispatch fails on it
throw exception::eval_error("Error calling function '" + this->children[0]->text + "'", params, make_vector(f), false, *t_ss);
} catch (const exception::bad_boxed_cast &) {
throw exception::eval_error("'" + this->children[0]->pretty_print() + "' does not evaluate to a function.");
return t_ss->call_function(this->children[0]->text, m_loc, Function_Params{params}, t_ss.conversions());
} catch (const exception::dispatch_error &e) {
throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'",
e.parameters,
e.functions,
false,
*t_ss);
}
} catch (const exception::arity_error &e) {
throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'");
Expand All @@ -342,6 +346,9 @@ namespace chaiscript {
}

Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { return do_eval_internal<true>(t_ss); }

private:
mutable std::atomic_uint_fast32_t m_loc = {0};
};

template<typename T>
Expand Down
20 changes: 20 additions & 0 deletions include/chaiscript/utility/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <utility>
#include <vector>

#include "../dispatchkit/dynamic_object.hpp"
#include "../dispatchkit/operators.hpp"
#include "../dispatchkit/register_function.hpp"
#include "../language/chaiscript_common.hpp"
Expand Down Expand Up @@ -59,6 +60,25 @@ namespace chaiscript::utility {
}
}

/// Overload of add_class that also registers static functions accessible via ClassName.func() syntax.
/// Static functions are exposed as attributes of a namespace object with the class name.
template<typename Class, typename ModuleType>
void add_class(ModuleType &t_module,
const std::string &t_class_name,
const std::vector<chaiscript::Proxy_Function> &t_constructors,
const std::vector<std::pair<chaiscript::Proxy_Function, std::string>> &t_funcs,
const std::vector<std::pair<chaiscript::Proxy_Function, std::string>> &t_static_funcs) {
add_class<Class>(t_module, t_class_name, t_constructors, t_funcs);

if (!t_static_funcs.empty()) {
dispatch::Dynamic_Object ns(t_class_name);
for (const auto &sf : t_static_funcs) {
ns.get_attr(sf.second) = chaiscript::Boxed_Value(sf.first);
}
t_module.add_global_const(chaiscript::const_var(ns), t_class_name);
}
}

template<typename Enum, typename ModuleType>
typename std::enable_if<std::is_enum<Enum>::value, void>::type
add_class(ModuleType &t_module,
Expand Down
45 changes: 45 additions & 0 deletions unittests/compiled_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,51 @@ TEST_CASE("Utility_Test utility class wrapper") {
chai.eval("t = Utility_Test();");
}

class Utility_Test_Static {
public:
Utility_Test_Static(int t_x, int t_y) : m_x(t_x), m_y(t_y) {}
Utility_Test_Static() : m_x(0), m_y(0) {}

int area() const { return m_x * m_y; }

static int static_area(int t_x, int t_y) { return t_x * t_y; }
static std::string name() { return "Utility_Test_Static"; }

int m_x;
int m_y;
};

TEST_CASE("Utility_Test add_class with static functions") {
auto m = std::make_shared<chaiscript::Module>();

using namespace chaiscript;

chaiscript::utility::add_class<Utility_Test_Static>(
*m,
"UTS",
{constructor<Utility_Test_Static()>(), constructor<Utility_Test_Static(int, int)>(),
constructor<Utility_Test_Static(const Utility_Test_Static &)>()},
{{fun(&Utility_Test_Static::area), "area"},
{fun(&Utility_Test_Static::m_x), "x"},
{fun(&Utility_Test_Static::m_y), "y"},
{fun(static_cast<Utility_Test_Static &(Utility_Test_Static::*)(const Utility_Test_Static &)>(&Utility_Test_Static::operator=)), "="}},
{{fun(&Utility_Test_Static::static_area), "area"},
{fun(&Utility_Test_Static::name), "name"}});

chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(m);

// Instance methods still work
CHECK(chai.eval<int>("auto t = UTS(3, 4); t.area()") == 12);

// Static functions accessible via namespace dot syntax
CHECK(chai.eval<int>("UTS.area(5, 6)") == 30);
CHECK(chai.eval<std::string>("UTS.name()") == "Utility_Test_Static");

// Constructor still works alongside the namespace
CHECK(chai.eval<int>("auto t2 = UTS(7, 8); t2.x") == 7);
}

enum Utility_Test_Numbers {
ONE,
TWO,
Expand Down
Loading