Skip to content

Commit

Permalink
feat(value): add to_function
Browse files Browse the repository at this point in the history
Signed-off-by: Tony Gorez <gorez.tony@gmail.com>
  • Loading branch information
tony-go committed Feb 22, 2024
1 parent ff33ef2 commit d017bc4
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/engine/include/includejs/engine_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class INCLUDEJS_ENGINE_EXPORT Value {
auto to_number() const -> double;
auto to_string() const -> std::string;
auto to_boolean() const -> bool;
auto to_function() const -> Function;
auto at(const std::string &property) const -> std::optional<Value>;
auto at(const unsigned int &position) const -> std::optional<Value>;
auto set(const std::string &property, Value value) -> void;
Expand Down
34 changes: 34 additions & 0 deletions src/engine/javascript_core/engine_value.cc
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,31 @@ auto Value::to_boolean() const -> bool {
return JSValueToBoolean(this->internal->context, this->internal->value);
}

auto Value::to_function() const -> Function {
assert(is_function());

return [context = this->internal->context,
value = this->internal->value](std::vector<Value> args) {
JSValueRef exception = nullptr;
JSObjectRef func = JSValueToObject(context, value, &exception);
assert(!exception);
assert(func != nullptr);

std::vector<JSValueRef> js_args;
js_args.reserve(args.size());
for (Value &arg : args) {
js_args.push_back(static_cast<JSValueRef>(arg.native()));
}

// Now we can call the function, get the result and return through
// the Value class.
JSValueRef result = JSObjectCallAsFunction(
context, func, nullptr, js_args.size(), js_args.data(), nullptr);
assert(result != nullptr);
return Value{context, result};
};
}

auto Value::at(const std::string &property) const -> std::optional<Value> {
assert(is_object());
JSValueRef exception = nullptr;
Expand All @@ -179,6 +204,15 @@ auto Value::at(const std::string &property) const -> std::optional<Value> {
property_string, &exception);
assert(!exception);
JSStringRelease(property_string);

// The function could not be called outside of this object context
// as the C++ function store is tied to the object.
// This is why we don't support function properties.
if (JSObjectIsFunction(
this->internal->context,
JSValueToObject(this->internal->context, result, &exception))) {
throw std::runtime_error{"Function properties are not supported"};
}
return std::make_optional<Value>(this->internal->context, result);
}

Expand Down
27 changes: 15 additions & 12 deletions test/engine/engine_value_function_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
TEST(IncludeJS_Engine, is_function) {
sourcemeta::includejs::Engine engine;

auto obj = engine.context().make_object();
obj.set(
"foo",
[](std::vector<sourcemeta::includejs::Value> arguments)
-> sourcemeta::includejs::Value { return std::move(arguments[0]); });
obj.set("bar", engine.context().from(42));

EXPECT_TRUE(obj.at("foo").has_value());
EXPECT_TRUE(obj.at("foo").value().is_function());

EXPECT_TRUE(obj.at("bar").has_value());
EXPECT_FALSE(obj.at("bar").value().is_function());
auto result = engine.evaluate("(function a() {})", "index.js");

EXPECT_TRUE(result.is_function());
}

TEST(IncludeJS_Engine, to_function) {
sourcemeta::includejs::Engine engine;

auto result = engine.evaluate("(function a() { return 42; })", "index.js");

EXPECT_TRUE(result.is_function());

auto value = result.to_function()({});
EXPECT_TRUE(value.is_number());
EXPECT_EQ(value.to_number(), 42);
}
11 changes: 11 additions & 0 deletions test/engine/engine_value_object_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,14 @@ TEST(IncludeJS_Engine, set_object_function) {
EXPECT_TRUE(result2.is_string());
EXPECT_EQ(result2.to_string(), "bar");
}

TEST(IncludeJS_Engine, at_throw_for_functions) {
sourcemeta::includejs::Engine engine;
sourcemeta::includejs::Context &context = engine.context();

auto obj = context.make_object();
obj.set("woo",
[](std::vector<sourcemeta::includejs::Value> args)
-> sourcemeta::includejs::Value { return std::move(args[0]); });
EXPECT_THROW(obj.at("woo"), std::runtime_error);
}

0 comments on commit d017bc4

Please sign in to comment.