eina_js: initial binding for Eina_Value

There is value_cast, which can be used to convert back and forth between
efl::eina::value and v8::Local<v8::Value>. value_cast will throw
std::bad_cast if conversion fails.

There is also `register_*_value` functions to setup the global object
that act like a bridge between the C++ code and the JavaScript code.

There are several tests also.

The JavaScript code will use a non-idiomatic API which needs to manually
clean the resources. Ideally, we'd allow construction in the familiar
`new eina_value(42)` way, but the `this` JavaScript reference is exposed
to C++ code through V8 as `v8::Local<v8::Value>`, which cannot be used
to register finalizers. Finalizers support is only supported in
`v8::Persistent`.

Future work include:

- Fix failing tests.
  - Need to investigate why the code below fails:

    ```
    efl::eina::get<std::string>(efl::eina::value(std::string("Matroška")));
    efl::eina::get<efl::eina::stringshare>(efl::eina::value(std::string("Matroška")));
    ```
- The bridge between JavaScript and C++ code (`register_*_value`
  functions) isn't handling all errors as it should. Implement and add
  tests.
- Move most of the code out of the headers. Currently I'm stumbling upon
  a linking problem.
This commit is contained in:
Vinícius dos Santos Oliveira 2014-10-03 04:18:43 -03:00
parent df7028f3f5
commit 391ebe306f
3 changed files with 512 additions and 2 deletions

View File

@ -56,12 +56,17 @@ bindings/eina_cxx/eina_value.hh
if EFL_ENABLE_TESTS
check_PROGRAMS += tests/eina_js/eina_js_suite
TESTS += tests/eina_js/eina_js_suite
check_PROGRAMS += tests/eina_js/eina_js_suite \
tests/eina_js/eina_js_value
TESTS += tests/eina_js/eina_js_suite \
tests/eina_js/eina_js_value
tests_eina_js_eina_js_suite_SOURCES = \
tests/eina_js/eina_js_suite.cc
tests_eina_js_eina_js_value_SOURCES = \
tests/eina_js/eina_js_value.cc
tests_eina_js_eina_js_suite_CXXFLAGS = -I$(top_builddir)/src/lib/efl \
-DTESTS_WD=\"`pwd`\" \
-DTESTS_SRC_DIR=\"$(top_srcdir)/src/tests/eina_js\" \
@ -76,6 +81,19 @@ tests_eina_js_eina_js_suite_LDADD = \
@CHECK_LIBS@ @USE_EO_LIBS@ @USE_EINA_LIBS@ @USE_EOLIAN_LIBS@ @USE_EOLIAN_JS_LIBS@ @USE_EINA_JS_LIBS@
tests_eina_js_eina_js_suite_DEPENDENCIES = @USE_EINA_INTERNAL_LIBS@ @USE_EINA_JS_INTERNAL_LIBS@ @USE_EO_INTERNAL_LIBS@
tests_eina_js_eina_js_value_CXXFLAGS = -I$(top_builddir)/src/lib/efl \
-DTESTS_WD=\"`pwd`\" \
-DTESTS_SRC_DIR=\"$(top_srcdir)/src/tests/eina_js\" \
-DPACKAGE_BUILD_DIR=\"$(abs_top_builddir)/src/tests/eina_js\" \
-DTESTS_BUILD_DIR=\"$(top_builddir)/src/tests/eina_js\" \
@CHECK_CFLAGS@ \
@EINA_CXX_CFLAGS@ \
@EO_CXX_CFLAGS@ \
@EO_CFLAGS@ \
@EINA_JS_CFLAGS@
tests_eina_js_eina_js_value_LDADD = @CHECK_LIBS@ @USE_EINA_JS_LIBS@ @USE_EINA_LIBS@ @USE_EO_LIBS@
tests_eina_js_eina_js_value_DEPENDENCIES = @USE_EINA_INTERNAL_LIBS@ @USE_EINA_JS_INTERNAL_LIBS@ @USE_EO_INTERNAL_LIBS@
endif
endif

View File

@ -0,0 +1,228 @@
#ifndef EINA_JS_VALUE_HH
#define EINA_JS_VALUE_HH
#include <v8.h>
#include <Eina.hh>
#include <type_traits>
namespace efl { namespace js {
namespace detail {
template<class T, class = void>
struct is_representable_as_v8_integer: std::false_type {};
template<class T>
struct is_representable_as_v8_integer
<T,
typename std::enable_if<std::is_integral<T>::value
/* v8::Integer only stores 32-bit signed and unsigned
numbers. */
&& (sizeof(T) <= sizeof(int32_t))>::type>
: std::true_type {};
template<class T>
typename std::enable_if<is_representable_as_v8_integer<T>::value
&& std::is_signed<T>::value,
v8::Local<v8::Value>>::type
to_v8_number(const T &v, v8::Isolate *isolate)
{
return v8::Integer::New(isolate, v);
}
template<class T>
typename std::enable_if<is_representable_as_v8_integer<T>::value
&& std::is_unsigned<T>::value,
v8::Local<v8::Value>>::type
to_v8_number(const T &v, v8::Isolate *isolate)
{
return v8::Integer::NewFromUnsigned(isolate, v);
}
template<class T>
typename std::enable_if<(std::is_integral<T>::value
&& !is_representable_as_v8_integer<T>::value)
|| std::is_floating_point<T>::value,
v8::Local<v8::Value>>::type
to_v8_number(const T &v, v8::Isolate *isolate)
{
return v8::Number::New(isolate, v);
}
template<class T>
typename std::enable_if<std::is_same<T, ::efl::eina::stringshare>::value
|| std::is_same<T, std::string>::value,
v8::Local<v8::Value>>::type
to_v8_string(const T &v, v8::Isolate *isolate)
{
return v8::String::NewFromUtf8(isolate, v.c_str(),
v8::String::kNormalString, v.size());
}
} // namespace detail
template<class T>
typename std::enable_if<std::is_same<T, v8::Local<v8::Value>>::value, T>::type
value_cast(const ::efl::eina::value &v, v8::Isolate *isolate)
{
using detail::to_v8_number;
using detail::to_v8_string;
using ::efl::eina::get;
const auto &t = v.type_info();
if (t == EINA_VALUE_TYPE_UINT64) {
return to_v8_number(get<uint64_t>(v), isolate);
} else if (t == EINA_VALUE_TYPE_UCHAR) {
return to_v8_number(get<unsigned char>(v), isolate);
} else if (t == EINA_VALUE_TYPE_USHORT) {
return to_v8_number(get<unsigned short>(v), isolate);
} else if (t == EINA_VALUE_TYPE_UINT) {
return to_v8_number(get<unsigned int>(v), isolate);
} else if (t == EINA_VALUE_TYPE_ULONG) {
return to_v8_number(get<unsigned long>(v), isolate);
} else if (t == EINA_VALUE_TYPE_CHAR) {
return to_v8_number(get<char>(v), isolate);
} else if (t == EINA_VALUE_TYPE_SHORT) {
return to_v8_number(get<short>(v), isolate);
} else if (t == EINA_VALUE_TYPE_INT) {
return to_v8_number(get<int>(v), isolate);
} else if (t == EINA_VALUE_TYPE_LONG) {
return to_v8_number(get<long>(v), isolate);
} else if (t == EINA_VALUE_TYPE_FLOAT) {
return to_v8_number(get<float>(v), isolate);
} else if (t == EINA_VALUE_TYPE_DOUBLE) {
return to_v8_number(get<double>(v), isolate);
} else if (t == EINA_VALUE_TYPE_STRINGSHARE) {
return to_v8_string(get<::efl::eina::stringshare>(v), isolate);
} else if (t == EINA_VALUE_TYPE_STRING) {
return to_v8_string(get<std::string>(v), isolate);
}
throw std::bad_cast{};
}
template<class T>
typename std::enable_if<std::is_same<T, ::efl::eina::value>::value, T>::type
value_cast(const v8::Local<v8::Value> &v)
{
using ::efl::eina::value;
if (v->IsBoolean()) {
return value(int{v->BooleanValue()});
} else if (v->IsInt32()) {
return value(v->Int32Value());
} else if (v->IsUint32()) {
return value(v->Uint32Value());
} else if (v->IsNumber()) {
return value(v->NumberValue());
} else if (v->IsString()) {
v8::String::Utf8Value data(v);
return value(std::string(*data, data.length()));
}
throw std::bad_cast{};
}
/*
# JS binding
- There is the `value()` constructor, which accepts a primitive value as input
argument and might throw.
- The returned object has a `get()` method, which can be used to get the
wrapped value as a JavaScript value.
- The returned object has a `set()` method, which can be used to change the
wrapped value.
*/
inline
void register_make_value(v8::Isolate *isolate,
v8::Handle<v8::ObjectTemplate> global,
v8::Local<v8::String> name)
{
//v8::Local<v8::FunctionTemplate>
using v8::Local;
using v8::Value;
using v8::String;
using v8::Object;
using v8::FunctionTemplate;
using v8::FunctionCallbackInfo;
typedef ::efl::eina::value value_type;
typedef value_type *ptr_type;
auto ctor = [](const FunctionCallbackInfo<Value> &info) {
auto set = [](const FunctionCallbackInfo<Value> &info) {
if (info.Length() != 1)
return;
void *ptr = info.Holder()->GetAlignedPointerFromInternalField(0);
try {
*static_cast<ptr_type>(ptr) = value_cast<value_type>(info[0]);
} catch(const std::bad_cast &e) {
} catch(const ::efl::eina::system_error &e) {
}
};
auto get = [](const FunctionCallbackInfo<Value> &info) {
void *ptr = info.Holder()->GetAlignedPointerFromInternalField(0);
auto &value = *static_cast<ptr_type>(ptr);
info.GetReturnValue().Set(value_cast<Local<Value>>
(value, info.GetIsolate()));
};
if (info.Length() != 1)
return;
auto obj_tpl = v8::ObjectTemplate::New(info.GetIsolate());
obj_tpl->SetInternalFieldCount(1);
auto ret = obj_tpl->NewInstance();
info.GetReturnValue().Set(ret);
ret->Set(String::NewFromUtf8(info.GetIsolate(), "set"),
FunctionTemplate::New(info.GetIsolate(), set)->GetFunction());
ret->Set(String::NewFromUtf8(info.GetIsolate(), "get"),
FunctionTemplate::New(info.GetIsolate(), get)->GetFunction());
try {
std::unique_ptr<value_type>
ptr(new value_type(value_cast<value_type>(info[0])));
ret->SetAlignedPointerInInternalField(0, ptr.get());
ptr.release();
} catch(const std::bad_cast &e) {
} catch(const ::efl::eina::system_error &e) {
}
};
global->Set(name, FunctionTemplate::New(isolate, ctor));
}
inline
void register_destroy_value(v8::Isolate *isolate,
v8::Handle<v8::ObjectTemplate> global,
v8::Local<v8::String> name)
{
//v8::Local<v8::FunctionTemplate>
using v8::Handle;
using v8::Local;
using v8::Value;
using v8::FunctionTemplate;
using v8::FunctionCallbackInfo;
typedef ::efl::eina::value value_type;
typedef value_type *ptr_type;
auto dtor = [](const FunctionCallbackInfo<Value> &info) {
if (info.Length() != 1)
return;
auto o = info[0]->ToObject();
delete static_cast<ptr_type>(o->GetAlignedPointerFromInternalField(0));
o->SetAlignedPointerInInternalField(0, nullptr);
assert(o->GetAlignedPointerFromInternalField(0) == nullptr);
};
global->Set(name, FunctionTemplate::New(isolate, dtor));
}
} } // namespace efl::js
#endif /* EINA_JS_VALUE_HH */

View File

@ -0,0 +1,264 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <cassert>
#include <eina_js_value.hh>
#include <Eo.hh>
void print(const v8::FunctionCallbackInfo<v8::Value> &args)
{
bool first = true;
for (int i = 0; i < args.Length(); i++) {
v8::HandleScope handle_scope(args.GetIsolate());
if (first) {
first = false;
} else {
printf(" ");
}
v8::String::Utf8Value str(args[i]);
std::cout << std::string(*str, str.length());
}
printf("\n");
fflush(stdout);
}
static const char script[] =
"function assert(test, message) { if (test !== true) throw message; };"
"var my_value = make_value(1);"
"var wrapped = my_value.get();"
"assert(typeof(wrapped) === 'number', '#1');"
"assert(wrapped === 1, '#2');"
"my_value.set(2);"
"assert(wrapped === 1, '#3');"
"wrapped = my_value.get();"
"assert(typeof(wrapped) === 'number', '#4');"
"assert(wrapped === 2, '#5');"
"my_value.set(true);"
"assert(wrapped === 2, '#6');"
"wrapped = my_value.get();"
// boolean is represented as integer in the efl::eina::value layer
"assert(typeof(wrapped) === 'number', '#7');"
"assert(wrapped === 1, '#8');"
"destroy_value(my_value);"
;
int main(int argc, char *argv[])
{
efl::eina::eina_init eina_init;
efl::eo::eo_init eo_init;
v8::V8::InitializeICU();
v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
v8::Handle<v8::Context> context = [&isolate]() {
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
efl::js::register_make_value(isolate, global,
v8::String::NewFromUtf8(isolate,
"make_value"));
efl::js::register_destroy_value(isolate, global,
v8::String::NewFromUtf8(isolate,
"destroy_value"));
global->Set(v8::String::NewFromUtf8(isolate, "print"),
v8::FunctionTemplate::New(isolate, print));
return v8::Context::New(isolate, NULL, global);
}();
if (context.IsEmpty()) {
fprintf(stderr, "Error creating context\n");
return 1;
}
context->Enter();
{
// Enter the execution environment before evaluating any code.
v8::Context::Scope context_scope(context);
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<uint64_t>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<uint64_t>::max()),
isolate)->NumberValue()
== double(UINT64_MAX));
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned char>::max()),
isolate)->IsUint32());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned char>::max()),
isolate)->Uint32Value() == UINT8_MAX);
if (sizeof(short) > sizeof(int32_t)) {
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned short>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned short>::max()),
isolate)->NumberValue()
== double(std::numeric_limits<unsigned short>::max()));
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<short>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<short>::max()),
isolate)->NumberValue()
== double(std::numeric_limits<short>::max()));
} else {
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned short>::max()),
isolate)->IsUint32());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned short>::max()),
isolate)->Uint32Value()
== std::numeric_limits<unsigned short>::max());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<short>::max()),
isolate)->IsInt32());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<short>::max()),
isolate)->Int32Value()
== std::numeric_limits<short>::max());
}
if (sizeof(int) > sizeof(int32_t)) {
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned int>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned int>::max()),
isolate)->NumberValue()
== double(std::numeric_limits<unsigned int>::max()));
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<int>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<int>::max()),
isolate)->NumberValue()
== double(std::numeric_limits<int>::max()));
} else {
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned int>::max()),
isolate)->IsUint32());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned int>::max()),
isolate)->Uint32Value()
== std::numeric_limits<unsigned int>::max());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<int>::max()),
isolate)->IsInt32());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<int>::max()),
isolate)->Int32Value()
== std::numeric_limits<int>::max());
}
if (sizeof(long) > sizeof(int32_t)) {
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned long>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned long>::max()),
isolate)->NumberValue()
== double(std::numeric_limits<unsigned long>::max()));
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<long>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<long>::max()),
isolate)->NumberValue()
== double(std::numeric_limits<long>::max()));
} else {
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned long>::max()),
isolate)->IsUint32());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<unsigned long>::max()),
isolate)->Uint32Value()
== std::numeric_limits<unsigned long>::max());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<long>::max()),
isolate)->IsInt32());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<long>::max()),
isolate)->Int32Value()
== std::numeric_limits<long>::max());
}
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<float>::max()),
isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::numeric_limits<float>::max()),
isolate)->NumberValue()
== double(std::numeric_limits<float>::max()));
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(42.42), isolate)->IsNumber());
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(42.42), isolate)->NumberValue()
== 42.42);
assert(efl::eina::get<int>
(efl::js::value_cast<efl::eina::value>
(v8::Boolean::New(isolate, true))) == 1);
assert(efl::eina::get<int32_t>
(efl::js::value_cast<efl::eina::value>
(v8::Integer::New(isolate, INT32_MAX))) == INT32_MAX);
assert(efl::eina::get<uint32_t>
(efl::js::value_cast<efl::eina::value>
(v8::Integer::NewFromUnsigned(isolate, UINT32_MAX)))
== UINT32_MAX);
assert(efl::eina::get<double>
(efl::js::value_cast<efl::eina::value>
(v8::Number::New(isolate,
std::numeric_limits<double>::max())))
== std::numeric_limits<double>::max());
{
const char utf8_data[] = "Matroška";
assert(efl::js::value_cast<efl::eina::value>
(v8::String::NewFromUtf8(isolate, utf8_data))
== efl::eina::value(std::string(utf8_data)));
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(std::string(utf8_data)), isolate)
->StrictEquals(v8::String::NewFromUtf8(isolate, utf8_data)));
assert(efl::js::value_cast<v8::Local<v8::Value>>
(efl::eina::value(efl::eina::stringshare(utf8_data)), isolate)
->StrictEquals(v8::String::NewFromUtf8(isolate, utf8_data)));
}
{
v8::HandleScope handle_scope(isolate);
v8::TryCatch try_catch;
auto source = v8::String::NewFromUtf8(isolate, script);
v8::Handle<v8::Script> script = v8::Script::Compile(std::move(source));
assert(!script.IsEmpty());
/*v8::Handle<v8::Value> result = */script->Run();
if (try_catch.HasCaught()) {
v8::String::Utf8Value message(try_catch.Message()->Get());
std::cerr << std::string(*message, message.length()) << std::endl;
std::abort();
}
}
}
context->Exit();
}