You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

334 lines
10KB

  1. #include "ScriptEngine.hpp"
  2. #include <quickjs/quickjs.h>
  3. static JSClassID QuickJSEngineClass;
  4. static std::string ErrorToString(JSContext *ctx) {
  5. JSValue exception_val, val;
  6. const char *stack;
  7. const char *str;
  8. bool is_error;
  9. std::string ret;
  10. size_t s1, s2;
  11. exception_val = JS_GetException(ctx);
  12. is_error = JS_IsError(ctx, exception_val);
  13. str = JS_ToCStringLen(ctx, &s1, exception_val);
  14. if (!str) {
  15. return std::string("error thrown but no error message");
  16. }
  17. if (!is_error) {
  18. ret = std::string("Throw:\n") + std::string(str);
  19. } else {
  20. val = JS_GetPropertyStr(ctx, exception_val, "stack");
  21. if (!JS_IsUndefined(val)) {
  22. stack = JS_ToCStringLen(ctx, &s2, val);
  23. ret = std::string(str) + std::string("\n") + std::string(stack);
  24. JS_FreeCString(ctx, stack);
  25. }
  26. JS_FreeValue(ctx, val);
  27. }
  28. JS_FreeCString(ctx, str);
  29. JS_FreeValue(ctx, exception_val);
  30. return ret;
  31. }
  32. struct QuickJSEngine : ScriptEngine {
  33. JSRuntime *rt = NULL;
  34. JSContext *ctx = NULL;
  35. QuickJSEngine() {
  36. rt = JS_NewRuntime();
  37. }
  38. ~QuickJSEngine() {
  39. if (ctx) {
  40. JS_FreeContext(ctx);
  41. }
  42. if (rt) {
  43. JS_FreeRuntime(rt);
  44. }
  45. }
  46. std::string getEngineName() override {
  47. return "JavaScript";
  48. }
  49. int run(const std::string& path, const std::string& script) override {
  50. assert(!ctx);
  51. // Create quickjs context
  52. ctx = JS_NewContext(rt);
  53. if (!ctx) {
  54. display("Could not create QuickJS context");
  55. return -1;
  56. }
  57. // Initialize globals
  58. // user pointer
  59. JSValue global_obj = JS_GetGlobalObject(ctx);
  60. JSValue handle = JS_NewObjectClass(ctx, QuickJSEngineClass);
  61. JS_SetOpaque(handle, this);
  62. JS_SetPropertyStr(ctx, global_obj, "p", handle);
  63. // console
  64. JSValue console = JS_NewObject(ctx);
  65. JS_SetPropertyStr(ctx, console, "log",
  66. JS_NewCFunction(ctx, native_console_log, "log", 1));
  67. JS_SetPropertyStr(ctx, console, "info",
  68. JS_NewCFunction(ctx, native_console_info, "info", 1));
  69. JS_SetPropertyStr(ctx, console, "debug",
  70. JS_NewCFunction(ctx, native_console_debug, "debug", 1));
  71. JS_SetPropertyStr(ctx, console, "warn",
  72. JS_NewCFunction(ctx, native_console_warn, "warn", 1));
  73. JS_SetPropertyStr(ctx, global_obj, "console", console);
  74. // display
  75. JS_SetPropertyStr(ctx, global_obj, "display",
  76. JS_NewCFunction(ctx, native_display, "display", 1));
  77. // config: Set defaults
  78. JSValue config = JS_NewObject(ctx);
  79. // frameDivider
  80. JSValue fd =JS_NewInt32(ctx, 32);
  81. JS_SetPropertyStr(ctx, config, "frameDivider", fd);
  82. JS_FreeValue(ctx, fd);
  83. // bufferSize
  84. JSValue bs = JS_NewInt32(ctx, 1);
  85. JS_SetPropertyStr(ctx, config, "bufferSize", bs);
  86. JS_SetPropertyStr(ctx, global_obj, "config", config);
  87. JS_FreeValue(ctx, bs);
  88. // Compile string
  89. JSValue val = JS_Eval(ctx, script.c_str(), script.size(), path.c_str(), 0);
  90. if (JS_IsException(val)) {
  91. display(ErrorToString(ctx));
  92. JS_FreeValue(ctx, val);
  93. JS_FreeValue(ctx, global_obj);
  94. return -1;
  95. }
  96. ProcessBlock* block = getProcessBlock();
  97. // config: Read values
  98. config = JS_GetPropertyStr(ctx, global_obj, "config");
  99. {
  100. // frameDivider
  101. JSValue divider = JS_GetPropertyStr(ctx, config, "frameDivider");
  102. int32_t dividerValue;
  103. if (JS_ToInt32(ctx, &dividerValue, divider) == 0) {
  104. setFrameDivider(dividerValue);
  105. }
  106. // bufferSize
  107. JSValue buffer = JS_GetPropertyStr(ctx, config, "bufferSize");
  108. int32_t bufferValue;
  109. if (JS_ToInt32(ctx, &bufferValue, buffer) == 0) {
  110. setBufferSize(bufferValue);
  111. }
  112. JS_FreeValue(ctx, config);
  113. }
  114. // block
  115. JSValue blockIdx = JS_NewObject(ctx);
  116. {
  117. // inputs
  118. JSValue arr = JS_NewArray(ctx);
  119. for (int i = 0; i < NUM_ROWS; i++) {
  120. JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) block->inputs[i], sizeof(float) * block->bufferSize, NULL, NULL, true);
  121. if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) {
  122. WARN("Unable to set property %d of inputs array", i);
  123. }
  124. }
  125. JS_SetPropertyStr(ctx, blockIdx, "inputs", arr);
  126. // outputs
  127. arr = JS_NewArray(ctx);
  128. for (int i = 0; i < NUM_ROWS; i++) {
  129. JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) block->outputs[i], sizeof(float) * block->bufferSize, NULL, NULL, true);
  130. if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) {
  131. WARN("Unable to set property %d of outputs array", i);
  132. }
  133. }
  134. JS_SetPropertyStr(ctx, blockIdx, "outputs", arr);
  135. // knobs
  136. JSValue knobsIdx = JS_NewArrayBuffer(ctx, (uint8_t *) &block->knobs, sizeof(float) * NUM_ROWS, NULL, NULL, true);
  137. JS_SetPropertyStr(ctx, blockIdx, "knobs", knobsIdx);
  138. // switches
  139. JSValue switchesIdx = JS_NewArrayBuffer(ctx, (uint8_t *) &block->switches, sizeof(bool) * NUM_ROWS, NULL, NULL, true);
  140. JS_SetPropertyStr(ctx, blockIdx, "switches", switchesIdx);
  141. // lights
  142. arr = JS_NewArray(ctx);
  143. for (int i = 0; i < NUM_ROWS; i++) {
  144. JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) &block->lights[i], sizeof(float) * 3, NULL, NULL, true);
  145. if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) {
  146. WARN("Unable to set property %d of lights array", i);
  147. }
  148. }
  149. JS_SetPropertyStr(ctx, blockIdx, "lights", arr);
  150. // switchlights
  151. arr = JS_NewArray(ctx);
  152. for (int i = 0; i < NUM_ROWS; i++) {
  153. JSValue buffer = JS_NewArrayBuffer(ctx, (uint8_t *) &block->switchLights[i], sizeof(float) * 3, NULL, NULL, true);
  154. if (JS_SetPropertyUint32(ctx, arr, i, buffer) < 0) {
  155. WARN("Unable to set property %d of switchLights array", i);
  156. }
  157. }
  158. JS_SetPropertyStr(ctx, blockIdx, "switchLights", arr);
  159. }
  160. JS_SetPropertyStr(ctx, global_obj, "block", blockIdx);
  161. // this is a horrible hack to get QuickJS to allocate the correct types
  162. static const std::string updateTypes = R"(
  163. for (var i = 0; i < 6; i++) {
  164. block.inputs[i] = new Float32Array(block.inputs[i]);
  165. block.outputs[i] = new Float32Array(block.outputs[i]);
  166. block.lights[i] = new Float32Array(block.lights[i]);
  167. block.switchLights[i] = new Float32Array(block.switchLights[i]);
  168. }
  169. block.knobs = new Float32Array(block.knobs);
  170. block.switches = new Uint8Array(block.switches);
  171. )";
  172. JSValue hack = JS_Eval(ctx, updateTypes.c_str(), updateTypes.size(), "QuickJS Hack", 0);
  173. if (JS_IsException(hack)) {
  174. std::string errorString = ErrorToString(ctx);
  175. WARN("QuickJS: %s", errorString.c_str());
  176. display(errorString.c_str());
  177. }
  178. JS_FreeValue(ctx, hack);
  179. JS_FreeValue(ctx, val);
  180. JS_FreeValue(ctx, global_obj);
  181. if (JS_IsException(val)) {
  182. std::string errorString = ErrorToString(ctx);
  183. WARN("QuickJS: %s", errorString.c_str());
  184. display(errorString.c_str());
  185. JS_FreeValue(ctx, val);
  186. JS_FreeValue(ctx, blockIdx);
  187. JS_FreeValue(ctx, global_obj);
  188. return -1;
  189. }
  190. return 0;
  191. }
  192. int process() override {
  193. // global object
  194. JSValue global_obj = JS_GetGlobalObject(ctx);
  195. // block
  196. ProcessBlock* block = getProcessBlock();
  197. JSValue blockIdx = JS_GetPropertyStr(ctx, global_obj, "block");
  198. {
  199. // sampleRate
  200. JSValue sampleRate = JS_NewFloat64(ctx, (double) block->sampleRate);
  201. JS_SetPropertyStr(ctx, blockIdx, "sampleRate", sampleRate);
  202. // sampleTime
  203. JSValue sampleTime = JS_NewFloat64(ctx, (double) block->sampleTime);
  204. JS_SetPropertyStr(ctx, blockIdx, "sampleTime", sampleTime);
  205. // bufferSize
  206. JSValue bufferSize = JS_NewInt32(ctx, (double) block->bufferSize);
  207. JS_SetPropertyStr(ctx, blockIdx, "bufferSize", bufferSize);
  208. }
  209. JSValue process = JS_GetPropertyStr(ctx, global_obj, "process");
  210. JSValue val = JS_Call(ctx, process, JS_UNDEFINED, 1, &blockIdx);
  211. if (JS_IsException(val)) {
  212. std::string errorString = ErrorToString(ctx);
  213. WARN("QuickJS: %s", errorString.c_str());
  214. display(errorString.c_str());
  215. JS_FreeValue(ctx, val);
  216. JS_FreeValue(ctx, process);
  217. JS_FreeValue(ctx, blockIdx);
  218. JS_FreeValue(ctx, global_obj);
  219. return -1;
  220. }
  221. JS_FreeValue(ctx, val);
  222. JS_FreeValue(ctx, process);
  223. JS_FreeValue(ctx, global_obj);
  224. return 0;
  225. }
  226. static QuickJSEngine* getQuickJSEngine(JSContext* ctx) {
  227. JSValue global_obj = JS_GetGlobalObject(ctx);
  228. JSValue p = JS_GetPropertyStr(ctx, global_obj, "p");
  229. QuickJSEngine* engine = (QuickJSEngine*) JS_GetOpaque(p, QuickJSEngineClass);
  230. JS_FreeValue(ctx, p);
  231. JS_FreeValue(ctx, global_obj);
  232. return engine;
  233. }
  234. static JSValue native_console_log(JSContext* ctx, JSValueConst this_val,
  235. int argc, JSValueConst *argv) {
  236. if (argc) {
  237. const char *s = JS_ToCString(ctx, argv[0]);
  238. INFO("QuickJS: %s", s);
  239. }
  240. return JS_UNDEFINED;
  241. }
  242. static JSValue native_console_debug(JSContext* ctx, JSValueConst this_val,
  243. int argc, JSValueConst *argv) {
  244. if (argc) {
  245. const char *s = JS_ToCString(ctx, argv[0]);
  246. DEBUG("QuickJS: %s", s);
  247. }
  248. return JS_UNDEFINED;
  249. }
  250. static JSValue native_console_info(JSContext* ctx, JSValueConst this_val,
  251. int argc, JSValueConst *argv) {
  252. if (argc) {
  253. const char *s = JS_ToCString(ctx, argv[0]);
  254. INFO("QuickJS: %s", s);
  255. }
  256. return JS_UNDEFINED;
  257. }
  258. static JSValue native_console_warn(JSContext* ctx, JSValueConst this_val,
  259. int argc, JSValueConst *argv) {
  260. if (argc) {
  261. const char *s = JS_ToCString(ctx, argv[0]);
  262. WARN("QuickJS: %s", s);
  263. }
  264. return JS_UNDEFINED;
  265. }
  266. static JSValue native_display(JSContext* ctx, JSValueConst this_val,
  267. int argc, JSValueConst *argv) {
  268. if (argc) {
  269. const char *s = JS_ToCString(ctx, argv[0]);
  270. getQuickJSEngine(ctx)->display(s);
  271. }
  272. return JS_UNDEFINED;
  273. }
  274. };
  275. __attribute__((constructor(1000)))
  276. static void constructor() {
  277. addScriptEngine<QuickJSEngine>("js");
  278. }