Last active
February 21, 2023 14:38
-
-
Save robertgzr/4134f3bbe229eabd599335b41427e8e0 to your computer and use it in GitHub Desktop.
jsc.mini
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
__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
hitting another wall here with:
we need to somehow pass
Function::wrapper
throughJSObjectMakeFunctionWithCallback