Skip to content

Instantly share code, notes, and snippets.

@robertgzr
Last active February 21, 2023 14:38
Show Gist options
  • Save robertgzr/4134f3bbe229eabd599335b41427e8e0 to your computer and use it in GitHub Desktop.
Save robertgzr/4134f3bbe229eabd599335b41427e8e0 to your computer and use it in GitHub Desktop.
jsc.mini
#include <JavaScriptCore/JavaScript.h>
#include <exception> // std::exception
#include <functional> // std::function, std::bind_front, std::invoke
#include <iostream> // std::cout
#include <type_traits> // std::enable_if, std::is_void
// Function wraps function to bind providing JS/C++ interop and type
// conversion
template <typename Return, typename... Arguments> class Function {
public:
Function(std::function<Return(Arguments...)> fn) : function_(fn){};
auto wrapper(JSContextRef ctx, JSObjectRef /*func*/, JSObjectRef /*self*/,
size_t argc, const JSValueRef argv[], JSValueRef *
/*exception*/) -> JSValueRef {
auto values = std::vector<JSValueRef>(argv, argv + argc);
auto arguments = make_arguments(ctx, values);
// we need to delegate the actual function call and return value conversion
// code because of possible void return value
return wrapper_impl(std::is_void<Return>(), ctx, arguments);
}
private:
std::function<Return(Arguments...)> function_;
// wrapper delegates
auto wrapper_impl(std::true_type, JSContextRef ctx,
std::tuple<Arguments...> arguments) -> JSValueRef {
std::apply(function_, arguments);
return JSValueMakeUndefined(ctx);
}
auto wrapper_impl(std::false_type, JSContextRef ctx,
std::tuple<Arguments...> arguments) -> JSValueRef {
return to_js(ctx, std::apply(function_, arguments));
}
// helpers to convert the JS arguments to C++ tuple of argument values
static auto make_arguments(JSContextRef ctx, std::vector<JSValueRef> &values)
-> std::tuple<Arguments...> {
if (sizeof...(Arguments) == values.size())
throw std::runtime_error("unexpected arguments");
return make_arguments_impl<Arguments...>(
ctx, values, std::make_index_sequence<sizeof...(Arguments)>{});
}
template <typename... Ts, size_t... Idxs>
static auto make_arguments_impl(JSContextRef ctx,
std::vector<JSValueRef> &values,
std::index_sequence<Idxs...>)
-> std::tuple<Ts...> {
return {from_js(ctx, values[Idxs])...};
}
// type conversion helpers, JS <> C++ type conversion happens here
template <typename T>
static auto from_js(JSContextRef ctx, JSValueRef value) -> T;
static auto from_js(JSContextRef ctx, JSValueRef value) -> std::string {
if (!JSValueIsString(ctx, value))
throw std::runtime_error("unexpected value");
auto str_ = JSValueToStringCopy(ctx, value, nullptr);
char *tmp_ = new char[JSStringGetMaximumUTF8CStringSize(str_) + 1];
JSStringGetUTF8CString(str_, tmp_,
JSStringGetMaximumUTF8CStringSize(str_) + 1);
return std::string(tmp_);
}
template <typename T>
static auto to_js(JSContextRef ctx, T value) -> JSValueRef {
throw std::runtime_error("unimplemented");
};
};
// Main handles the setup/teardown of global context, bind capabilities and
// executing JS, it should be analogous to starship::main
class Main {
public:
Main() {
if (!(ctx_ = JSGlobalContextCreate(nullptr))) {
throw std::runtime_error("failed to allocate context");
}
}
~Main() {
JSGarbageCollect(ctx_);
JSGlobalContextRelease(ctx_);
}
template <typename Return, typename... Arguments>
auto bind(const std::vector<std::string> &path,
std::function<Return(Arguments...)> function) -> void {
// TODO: handle path
auto name = JSStringCreateWithUTF8CString(path.back().c_str());
auto fn = Function<Return, Arguments...>(function);
auto cb = std::bind_front(&decltype(fn)::wrapper, fn);
auto obj = JSObjectMakeFunctionWithCallback(ctx_, name, cb);
JSObjectSetProperty(ctx_, JSContextGetGlobalObject(ctx_), name, obj,
kJSPropertyAttributeNone, nullptr);
JSStringRelease(name);
}
auto execute(const std::string &script) -> void {
auto str = JSStringCreateWithUTF8CString(script.c_str());
if (!JSCheckScriptSyntax(ctx_, str, NULL, 0, NULL))
throw std::runtime_error("syntax error");
JSEvaluateScript(ctx_, str, NULL, NULL, 1, NULL);
JSStringRelease(str);
}
private:
JSGlobalContextRef ctx_ = {0};
};
// actual main
auto main() -> int {
std::cerr << "hello world" << std::endl;
try {
auto m = Main();
m.bind({"log"}, std::function([](std::string message) -> void {
std::cout << message << std::endl;
}));
m.execute("log('hello from JS');");
} catch (std::exception ex) {
std::cerr << ex.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
jscmini: CXX := clang++
jscmini: CFLAGS += -stdlib=libc++ -std=c++20
jscmini: CFLAGS += -fexperimental-library
jscmini: CFLAGS += $(shell pkg-config --cflags --libs javascriptcoregtk-5.0)
jscmini: CFLAGS += -fuse-ld=lld -Wl,--as-needed
jscmini:
$(CXX) -Wall $(CFLAGS) -o $@ main.cc
.PHONY: run
run: jscmini
@./$<
.PHONY: clean
clean: RM ?= rm
clean:
$(RM) -fr jscmini
.PHONY: all
all: clean jscmini run
.DEFAULT_GOAL: all
@robertgzr
Copy link
Author

hitting another wall here with:

clang++ -Wall -stdlib=libc++ -std=c++20 -fexperimental-library -I/usr/include/webkitgtk-5.0 -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -I/usr/lib64/libffi/include -ljavascriptcoregtk-5.0 -lgobject-2.0 -lglib-2.0  -fuse-ld=lld -Wl,--as-needed -o jscmini main.cc
main.cc:101:16: error: no matching function for call to 'JSObjectMakeFunctionWithCallback'
    auto obj = JSObjectMakeFunctionWithCallback(ctx_, name, cb);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cc:125:7: note: in instantiation of function template specialization 'Main::bind<void, std::string>' requested here
    m.bind({"log"}, std::function([](std::string message) -> void {
      ^
/usr/include/webkitgtk-5.0/JavaScriptCore/JSObjectRef.h:422:23: note: candidate function not viable: no known conversion from '__bind_front_t<decay_t<const OpaqueJSValue *(Function<void, std::string>::*)(const OpaqueJSContext *, OpaqueJSValue *, OpaqueJSValue *, unsigned long, const OpaqueJSValue *const *, const OpaqueJSValue **)>, decay_t<Function<void, std::string> &>>' (aka '__bind_front_t<const OpaqueJSValue *(Function<void, std::string>::*)(const OpaqueJSContext *, OpaqueJSValue *, OpaqueJSValue *, unsigned long, const OpaqueJSValue *const *, const OpaqueJSValue **), Function<void, std::string>>') to 'JSObjectCallAsFunctionCallback' (aka 'const OpaqueJSValue *(*)(const OpaqueJSContext *, OpaqueJSValue *, OpaqueJSValue *, unsigned long, const OpaqueJSValue *const *, const OpaqueJSValue **)') for 3rd argument
JS_EXPORT JSObjectRef JSObjectMakeFunctionWithCallback(JSContextRef ctx, JSStringRef name, JSObjectCallAsFunctionCallback callAsFunction);
                      ^
1 error generated.
make: *** [Makefile:8: jscmini] Error 1

we need to somehow pass Function::wrapper through JSObjectMakeFunctionWithCallback

@robertgzr
Copy link
Author

__bind_front_t<const OpaqueJSValue *(Function<void, std::string>::*)(const OpaqueJSContext *, OpaqueJSValue *, OpaqueJSValue *, unsigned long, const OpaqueJSValue *const *, const OpaqueJSValue **), Function<void, std::string>>')
               const OpaqueJSValue *(                             *)(const OpaqueJSContext *, OpaqueJSValue *, OpaqueJSValue *, unsigned long, const OpaqueJSValue *const *, const OpaqueJSValue **)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment