#include "ScriptEngine.hpp" #include static JSClassID QuickJSEngineClass; static std::string ErrorToString(JSContext *ctx) { JSValue exception_val, val; const char *stack; const char *str; bool is_error; std::string ret; size_t s1, s2; exception_val = JS_GetException(ctx); is_error = JS_IsError(ctx, exception_val); str = JS_ToCStringLen(ctx, &s1, exception_val); if (!str) { return std::string("error thrown but no error message"); } if (!is_error) { ret = std::string("Throw:\n") + std::string(str); } else { val = JS_GetPropertyStr(ctx, exception_val, "stack"); if (!JS_IsUndefined(val)) { stack = JS_ToCStringLen(ctx, &s2, val); ret = std::string(str) + std::string("\n") + std::string(stack); JS_FreeCString(ctx, stack); } JS_FreeValue(ctx, val); } JS_FreeCString(ctx, str); JS_FreeValue(ctx, exception_val); return ret; } struct QuickJSEngine : ScriptEngine { JSRuntime *rt = NULL; JSContext *ctx = NULL; QuickJSEngine() { rt = JS_NewRuntime(); } ~QuickJSEngine() { if (ctx) { JS_FreeContext(ctx); } if (rt) { JS_FreeRuntime(rt); } } std::string getEngineName() override { return "JavaScript"; } int run(const std::string& path, const std::string& script) override { assert(!ctx); // Create quickjs context ctx = JS_NewContext(rt); if (!ctx) { display("Could not create QuickJS context"); return -1; } // Initialize globals // user pointer JSValue global_obj = JS_GetGlobalObject(ctx); JSValue handle = JS_NewObjectClass(ctx, QuickJSEngineClass); JS_SetOpaque(handle, this); JS_SetPropertyStr(ctx, global_obj, "p", handle); // console JSValue console = JS_NewObject(ctx); JS_SetPropertyStr(ctx, console, "log", JS_NewCFunction(ctx, native_console_log, "log", 1)); JS_SetPropertyStr(ctx, console, "info", JS_NewCFunction(ctx, native_console_info, "info", 1)); JS_SetPropertyStr(ctx, console, "debug", JS_NewCFunction(ctx, native_console_debug, "debug", 1)); JS_SetPropertyStr(ctx, console, "warn", JS_NewCFunction(ctx, native_console_warn, "warn", 1)); JS_SetPropertyStr(ctx, global_obj, "console", console); // display JS_SetPropertyStr(ctx, global_obj, "display", JS_NewCFunction(ctx, native_display, "display", 1)); // config: Set defaults JSValue config = JS_NewObject(ctx); // frameDivider JSValue fd =JS_NewInt32(ctx, 32); JS_SetPropertyStr(ctx, config, "frameDivider", fd); JS_FreeValue(ctx, fd); // bufferSize JSValue bs = JS_NewInt32(ctx, 1); JS_SetPropertyStr(ctx, config, "bufferSize", bs); JS_SetPropertyStr(ctx, global_obj, "config", config); JS_FreeValue(ctx, bs); // Compile string JSValue val = JS_Eval(ctx, script.c_str(), script.size(), path.c_str(), 0); if (JS_IsException(val)) { display(ErrorToString(ctx)); JS_FreeValue(ctx, val); JS_FreeValue(ctx, global_obj); return -1; } ProcessBlock* block = getProcessBlock(); // config: Read values config = JS_GetPropertyStr(ctx, global_obj, "config"); { // frameDivider JSValue divider = JS_GetPropertyStr(ctx, config, "frameDivider"); int32_t dividerValue; if (JS_ToInt32(ctx, ÷rValue, divider) == 0) { setFrameDivider(dividerValue); } // bufferSize JSValue buffer = JS_GetPropertyStr(ctx, config, "bufferSize"); int32_t bufferValue; if (JS_ToInt32(ctx, &bufferValue, buffer) == 0) { setBufferSize(bufferValue); } JS_FreeValue(ctx, config); } // block JSValue blockIdx = JS_NewObject(ctx); { // inputs JSValue arr = JS_NewArray(ctx); for (int i = 0; i < NUM_ROWS; i++) { JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) block->inputs[i], sizeof(float) * block->bufferSize, NULL, NULL, true); if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { WARN("Unable to set property %d of inputs array", i); } } JS_SetPropertyStr(ctx, blockIdx, "inputs", arr); // outputs arr = JS_NewArray(ctx); for (int i = 0; i < NUM_ROWS; i++) { JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) block->outputs[i], sizeof(float) * block->bufferSize, NULL, NULL, true); if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { WARN("Unable to set property %d of outputs array", i); } } JS_SetPropertyStr(ctx, blockIdx, "outputs", arr); // knobs JSValue knobsIdx = JS_NewArrayBuffer(ctx, (uint8_t *) &block->knobs, sizeof(float) * NUM_ROWS, NULL, NULL, true); JS_SetPropertyStr(ctx, blockIdx, "knobs", knobsIdx); // switches JSValue switchesIdx = JS_NewArrayBuffer(ctx, (uint8_t *) &block->switches, sizeof(bool) * NUM_ROWS, NULL, NULL, true); JS_SetPropertyStr(ctx, blockIdx, "switches", switchesIdx); // lights arr = JS_NewArray(ctx); for (int i = 0; i < NUM_ROWS; i++) { JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) &block->lights[i], sizeof(float) * 3, NULL, NULL, true); if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { WARN("Unable to set property %d of lights array", i); } } JS_SetPropertyStr(ctx, blockIdx, "lights", arr); // switchlights arr = JS_NewArray(ctx); for (int i = 0; i < NUM_ROWS; i++) { JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) &block->switchLights[i], sizeof(float) * 3, NULL, NULL, true); if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) { WARN("Unable to set property %d of switchLights array", i); } } JS_SetPropertyStr(ctx, blockIdx, "switchLights", arr); } JS_SetPropertyStr(ctx, global_obj, "block", blockIdx); // this is a horrible hack to get QuickJS to allocate the correct types static const std::string updateTypes = R"( for (var i = 0; i < 6; i++) { block.inputs[i] = new Float32Array(block.inputs[i]); block.outputs[i] = new Float32Array(block.outputs[i]); block.lights[i] = new Float32Array(block.lights[i]); block.switchLights[i] = new Float32Array(block.switchLights[i]); } block.knobs = new Float32Array(block.knobs); block.switches = new Uint8Array(block.switches); )"; JSValue hack = JS_Eval(ctx, updateTypes.c_str(), updateTypes.size(), "QuickJS Hack", 0); if (JS_IsException(hack)) { std::string errorString = ErrorToString(ctx); WARN("QuickJS: %s", errorString.c_str()); display(errorString.c_str()); } JS_FreeValue(ctx, hack); JS_FreeValue(ctx, val); JS_FreeValue(ctx, global_obj); if (JS_IsException(val)) { std::string errorString = ErrorToString(ctx); WARN("QuickJS: %s", errorString.c_str()); display(errorString.c_str()); JS_FreeValue(ctx, val); JS_FreeValue(ctx, blockIdx); JS_FreeValue(ctx, global_obj); return -1; } return 0; } int process() override { // global object JSValue global_obj = JS_GetGlobalObject(ctx); // block ProcessBlock* block = getProcessBlock(); JSValue blockIdx = JS_GetPropertyStr(ctx, global_obj, "block"); { // sampleRate JSValue sampleRate = JS_NewFloat64(ctx, (double) block->sampleRate); JS_SetPropertyStr(ctx, blockIdx, "sampleRate", sampleRate); // sampleTime JSValue sampleTime = JS_NewFloat64(ctx, (double) block->sampleTime); JS_SetPropertyStr(ctx, blockIdx, "sampleTime", sampleTime); // bufferSize JSValue bufferSize = JS_NewInt32(ctx, (double) block->bufferSize); JS_SetPropertyStr(ctx, blockIdx, "bufferSize", bufferSize); } JSValue process = JS_GetPropertyStr(ctx, global_obj, "process"); JSValue val = JS_Call(ctx, process, JS_UNDEFINED, 1, &blockIdx); if (JS_IsException(val)) { std::string errorString = ErrorToString(ctx); WARN("QuickJS: %s", errorString.c_str()); display(errorString.c_str()); JS_FreeValue(ctx, val); JS_FreeValue(ctx, process); JS_FreeValue(ctx, blockIdx); JS_FreeValue(ctx, global_obj); return -1; } JS_FreeValue(ctx, val); JS_FreeValue(ctx, process); JS_FreeValue(ctx, global_obj); return 0; } static QuickJSEngine* getQuickJSEngine(JSContext* ctx) { JSValue global_obj = JS_GetGlobalObject(ctx); JSValue p = JS_GetPropertyStr(ctx, global_obj, "p"); QuickJSEngine* engine = (QuickJSEngine*) JS_GetOpaque(p, QuickJSEngineClass); JS_FreeValue(ctx, p); JS_FreeValue(ctx, global_obj); return engine; } static JSValue native_console_log(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc) { const char *s = JS_ToCString(ctx, argv[0]); INFO("QuickJS: %s", s); } return JS_UNDEFINED; } static JSValue native_console_debug(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc) { const char *s = JS_ToCString(ctx, argv[0]); DEBUG("QuickJS: %s", s); } return JS_UNDEFINED; } static JSValue native_console_info(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc) { const char *s = JS_ToCString(ctx, argv[0]); INFO("QuickJS: %s", s); } return JS_UNDEFINED; } static JSValue native_console_warn(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc) { const char *s = JS_ToCString(ctx, argv[0]); WARN("QuickJS: %s", s); } return JS_UNDEFINED; } static JSValue native_display(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc) { const char *s = JS_ToCString(ctx, argv[0]); getQuickJSEngine(ctx)->display(s); } return JS_UNDEFINED; } }; __attribute__((constructor(1000))) static void constructor() { addScriptEngine("js"); }