Skip to content

Commit

Permalink
feat(value): add array (#38)
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 authored Feb 22, 2024
1 parent ba9f9e2 commit 6e72884
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/engine/include/includejs/engine_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class INCLUDEJS_ENGINE_EXPORT Context {
auto make_error(const std::string &message) const -> Value;
auto make_object() const -> Value;
auto make_promise() const -> Promise;
auto make_array() const -> Value;
auto from(const std::string &value) const -> Value;
auto from(const char *) const -> Value;
auto from(int value) const -> Value;
Expand Down
4 changes: 4 additions & 0 deletions src/engine/include/includejs/engine_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@ class INCLUDEJS_ENGINE_EXPORT Value {
auto is_boolean() const -> bool;
auto is_undefined() const -> bool;
auto is_null() const -> bool;
auto is_array() const -> bool;
auto to_number() const -> double;
auto to_string() const -> std::string;
auto to_boolean() const -> bool;
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;
auto set(const std::string &property, Function function) -> void;
auto push(Value value) -> void;
auto to_map() const -> std::map<std::string, Value>;
auto to_vector() const -> std::vector<Value>;

// For internal use only
#ifndef DOXYGEN
Expand Down
5 changes: 5 additions & 0 deletions src/engine/javascript_core/engine_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ auto Context::make_object() const -> Value {
return {this->internal->context, res};
}

auto Context::make_array() const -> Value {
return {this->internal->context,
JSObjectMakeArray(this->internal->context, 0, nullptr, nullptr)};
}

auto Context::make_promise() const -> Promise {
return {static_cast<const void *>(this->internal->context)};
}
Expand Down
138 changes: 106 additions & 32 deletions src/engine/javascript_core/engine_value.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,59 @@ struct Value::Internal {
JSValueRef value;
};

static auto js_string_to_std_string(JSStringRef value) -> std::string {
// JavaScriptCore doesn't have a function to fetch the UTF-8 byte size
// of a given string. It can only give us the amount of Unicode code-points
// in the string, which may be more than the bytes required to store it.
// As a consequence, we can't do much than allocating the maximum possible
// buffer to hold the string.
const size_t max_size = JSStringGetMaximumUTF8CStringSize(value);
std::vector<char> buffer(max_size);
// Converts a JavaScript string into a null-terminated UTF-8 string,
// and copies the result into an external byte buffer.
JSStringGetUTF8CString(value, buffer.data(), max_size);
return {buffer.data()};
}

// Converting a value into a string first requires copying
// the value reference into a string reference.
static auto js_value_to_std_string(JSContextRef context, JSValueRef value)
-> std::string {
JSValueRef exception = nullptr;
JSStringRef copy = JSValueToStringCopy(context, value, &exception);
assert(!exception);

try {
std::string result{js_string_to_std_string(copy)};
JSStringRelease(copy);
return result;
} catch (const std::exception &) {
JSStringRelease(copy);
throw;
}
}

static auto get_current_object(JSContextRef context, JSValueRef value)
-> JSObjectRef {
JSValueRef exception = nullptr;
JSObjectRef object = JSValueToObject(context, value, &exception);
assert(!exception);
assert(object == value);
return object;
}

static auto get_object_length(JSContextRef context, JSObjectRef object)
-> std::size_t {
JSValueRef exception = nullptr;
JSStringRef length_string = JSStringCreateWithUTF8CString("length");
const double length = JSValueToNumber(
context, JSObjectGetProperty(context, object, length_string, &exception),
&exception);
assert(!exception);
JSStringRelease(length_string);
return static_cast<std::size_t>(length);
}

Value::Value(const void *context, const void *value)
: internal{std::make_unique<Value::Internal>()} {
this->internal->context = static_cast<JSContextRef>(context);
Expand All @@ -42,6 +95,10 @@ auto Value::is_undefined() const -> bool {
return JSValueIsUndefined(this->internal->context, this->internal->value);
}

auto Value::is_array() const -> bool {
return JSValueIsArray(this->internal->context, this->internal->value);
}

auto Value::is_error() const -> bool {
JSObjectRef global = JSContextGetGlobalObject(this->internal->context);
JSStringRef class_name = JSStringCreateWithUTF8CString("Error");
Expand Down Expand Up @@ -83,38 +140,6 @@ auto Value::to_number() const -> double {
return result;
}

static auto js_string_to_std_string(JSStringRef value) -> std::string {
// JavaScriptCore doesn't have a function to fetch the UTF-8 byte size
// of a given string. It can only give us the amount of Unicode code-points
// in the string, which may be more than the bytes required to store it.
// As a consequence, we can't do much than allocating the maximum possible
// buffer to hold the string.
const size_t max_size = JSStringGetMaximumUTF8CStringSize(value);
std::vector<char> buffer(max_size);
// Converts a JavaScript string into a null-terminated UTF-8 string,
// and copies the result into an external byte buffer.
JSStringGetUTF8CString(value, buffer.data(), max_size);
return {buffer.data()};
}

// Converting a value into a string first requires copying
// the value reference into a string reference.
static auto js_value_to_std_string(JSContextRef context, JSValueRef value)
-> std::string {
JSValueRef exception = nullptr;
JSStringRef copy = JSValueToStringCopy(context, value, &exception);
assert(!exception);

try {
std::string result{js_string_to_std_string(copy)};
JSStringRelease(copy);
return result;
} catch (const std::exception &) {
JSStringRelease(copy);
throw;
}
}

auto Value::to_string() const -> std::string {
assert(is_string());
return js_value_to_std_string(this->internal->context, this->internal->value);
Expand Down Expand Up @@ -256,6 +281,55 @@ auto Value::to_map() const -> std::map<std::string, Value> {
return map;
}

auto Value::to_vector() const -> std::vector<Value> {
assert(is_array());
JSObjectRef object =
get_current_object(this->internal->context, this->internal->value);
std::size_t length = get_object_length(this->internal->context, object);

JSValueRef exception = nullptr;
std::vector<Value> vector;
for (unsigned int index = 0; index < static_cast<std::size_t>(length);
index++) {
JSValueRef value = JSObjectGetPropertyAtIndex(this->internal->context,
object, index, &exception);
assert(!exception);
vector.emplace_back(static_cast<JSContextRef>(this->internal->context),
value);
}
return vector;
}

auto Value::at(const unsigned int &position) const -> std::optional<Value> {
assert(is_array());
JSObjectRef object =
get_current_object(this->internal->context, this->internal->value);
std::size_t length = get_object_length(this->internal->context, object);

if (position >= static_cast<std::size_t>(length)) {
return std::nullopt;
}

JSValueRef exception = nullptr;
JSValueRef result = JSObjectGetPropertyAtIndex(this->internal->context,
object, position, &exception);
assert(!exception);
return std::make_optional<Value>(this->internal->context, result);
}

auto Value::push(Value value) -> void {
assert(is_array());
JSObjectRef object =
get_current_object(this->internal->context, this->internal->value);
std::size_t length = get_object_length(this->internal->context, object);

JSValueRef exception = nullptr;
JSObjectSetPropertyAtIndex(
this->internal->context, object, static_cast<unsigned int>(length),
static_cast<JSValueRef>(value.native()), &exception);
assert(!exception);
}

auto Value::native() const -> const void * {
return static_cast<const void *>(this->internal->value);
}
Expand Down
1 change: 1 addition & 0 deletions test/engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ add_executable(includejs_engine_unit
engine_evaluate_test.cc
engine_stacktraces_test.cc
engine_value_object_test.cc
engine_value_array_test.cc
engine_value_undefined_test.cc
engine_value_null_test.cc)

Expand Down
44 changes: 44 additions & 0 deletions test/engine/engine_value_array_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <gtest/gtest.h>

#include <includejs/engine.h>

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

auto arr = engine.context().make_array();

EXPECT_TRUE(arr.is_array());
}

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

auto arr = engine.context().make_array();
arr.push(engine.context().from(42));

auto vec = arr.to_vector();
EXPECT_EQ(vec.size(), 1);
EXPECT_EQ(vec.at(0).to_number(), 42);
}

TEST(IncludeJS_Engine, evaluate_array) {
sourcemeta::includejs::Engine engine;
sourcemeta::includejs::Value result{engine.evaluate("([])", "index.js")};
EXPECT_TRUE(result.is_array());
}

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

auto arr = engine.context().make_array();
arr.push(engine.context().from(42));
arr.push(engine.context().from("baz"));

sourcemeta::includejs::Value result = arr.at(0).value();
EXPECT_TRUE(result.is_number());
EXPECT_EQ(result.to_number(), 42);

sourcemeta::includejs::Value result2 = arr.at(1).value();
EXPECT_TRUE(result2.is_string());
EXPECT_EQ(result2.to_string(), "baz");
}

0 comments on commit 6e72884

Please sign in to comment.