Browse Source

add QuickJS engine

pull/12/head
Jerry Sievert 5 years ago
parent
commit
139a1b8d41
3 changed files with 338 additions and 4 deletions
  1. +13
    -2
      Makefile
  2. +323
    -0
      src/QuickJSEngine.cpp
  3. +2
    -2
      src/ScriptEngine.hpp

+ 13
- 2
Makefile View File

@@ -2,9 +2,9 @@ RACK_DIR ?= ../..


FLAGS += FLAGS +=
CFLAGS += CFLAGS +=
CXXFLAGS +=
CXXFLAGS += -g


LDFLAGS +=
LDFLAGS += -g


SOURCES += $(wildcard src/*.cpp) SOURCES += $(wildcard src/*.cpp)


@@ -24,6 +24,17 @@ $(duktape):
cd dep && $(SHA256) duktape-2.4.0.tar.xz 86a89307d1633b5cedb2c6e56dc86e92679fc34b05be551722d8cc69ab0771fc cd dep && $(SHA256) duktape-2.4.0.tar.xz 86a89307d1633b5cedb2c6e56dc86e92679fc34b05be551722d8cc69ab0771fc
cd dep && $(UNTAR) duktape-2.4.0.tar.xz cd dep && $(UNTAR) duktape-2.4.0.tar.xz


# QuickJS
quickjs := dep/QuickJS/libquickjs.a
DEPS += $(quickjs)
OBJECTS += $(quickjs)
FLAGS += -Idep/QuickJS
LDFLAGS += -Ldep/QuickJS -lquickjs

$(quickjs):
cd dep && git clone "https://github.com/JerrySievert/QuickJS"
cd dep/QuickJS && $(MAKE)

# # LuaJIT # # LuaJIT


# luajit := dep/lib/luajit.a # luajit := dep/lib/luajit.a


+ 323
- 0
src/QuickJSEngine.cpp View File

@@ -0,0 +1,323 @@
#include "ScriptEngine.hpp"
#include <quickjs.h>

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) {
setMessage("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)) {
setMessage(ErrorToString(ctx));
JS_FreeValue(ctx, val);
JS_FreeValue(ctx, global_obj);

return -1;
}

// 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, &dividerValue, divider) == 0) {
setFrameDivider(dividerValue);
}

// bufferSize
JSValue buffer = JS_GetPropertyStr(ctx, config, "bufferSize");
int32_t bufferValue;
if (JS_ToInt32(ctx, &bufferValue, buffer) == 0) {
block->bufferSize = rack::clamp(bufferValue, 1, MAX_BUFFER_SIZE);
}

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) {
rack::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) {
rack::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) * block->bufferSize, 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) {
rack::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) {
rack::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
std::string updateTypes = "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);
rack::WARN("QuickJS: %s", errorString.c_str());
setMessage(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);
rack::WARN("QuickJS: %s", errorString.c_str());
setMessage(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
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);
rack::WARN("QuickJS: %s", errorString.c_str());
setMessage(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]);
rack::INFO("VCV Prototype: %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]);
rack::DEBUG("VCV Prototype: %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]);
rack::INFO("VCV Prototype: %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]);
rack::WARN("VCV Prototype: %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)->setMessage(s);
}
return JS_UNDEFINED;
}
};


ScriptEngine* createQuickJSEngine() {
return new QuickJSEngine();
}

+ 2
- 2
src/ScriptEngine.hpp View File

@@ -50,12 +50,12 @@ struct ScriptEngine {
// List of ScriptEngines // List of ScriptEngines


// Add your createMyEngine() function here. // Add your createMyEngine() function here.
ScriptEngine* createDuktapeEngine();
ScriptEngine* createQuickJSEngine();


inline ScriptEngine* createScriptEngine(std::string ext) { inline ScriptEngine* createScriptEngine(std::string ext) {
ext = rack::string::lowercase(ext); ext = rack::string::lowercase(ext);
if (ext == "js") if (ext == "js")
return createDuktapeEngine();
return createQuickJSEngine();
// Add your file extension check here. // Add your file extension check here.
return NULL; return NULL;
} }

Loading…
Cancel
Save