quickjs 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8eba12c1c34fab81270517de72da6a8b3c9b561d521a42b80c14411c23555684
4
- data.tar.gz: 4bced848c986a41c787306e799b40622d97dff999994b64e615cd3b1c79e586e
3
+ metadata.gz: 362c16217cefda63b3d8a4e0c41bdf4036c24e49540e5c223efc0751d5c84e44
4
+ data.tar.gz: 0415d93639765ffc7191964418e3a12f3c5fd51a547e854ff68141f1239c8e91
5
5
  SHA512:
6
- metadata.gz: 3598ef1f8b5bcbeedf916c101387477b8318e6550a1aa52b6fa4ddb269e261447768428eb4b644e2a0a9cc64ae084830c511cb077b7e25e292eb5d8e84a96460
7
- data.tar.gz: 686a93a0a00d747c99d8a0a0f0072a2d0451cc6bfdeeeb512f02817288636d7192a2e2dda52d0e9bf05b92ebb1930f503ca7ce2251c8419ca568c66f825bca1f
6
+ metadata.gz: d03afbf875aa53a7bbda02ed4ad84bab4f264b77446373f352c0c19d0d6c7cf824e3fd8b5c108cb0dd13872b8f8e53b12488052bd5f80190287c685f8f5bd6b0
7
+ data.tar.gz: 2fa7eae61259f74229e1ee71c2dd4a6016ef7f26cb1e47cdbe2646b871bb7a0eb41c8e74b3805d4471e5e953153fab2f8ab14f17e934969264cb231ae5a08556
data/README.md CHANGED
@@ -56,6 +56,9 @@ Quickjs.eval_code(code, { features: [Quickjs::MODULE_STD] })
56
56
  # enable os module
57
57
  # https://bellard.org/quickjs/quickjs.html#os-module
58
58
  Quickjs.eval_code(code, { features: [Quickjs::MODULE_OS] })
59
+
60
+ # enable timeout features `setTimeout`, `clearTimeout`
61
+ Quickjs.eval_code(code, { features: [Quickjs::FEATURES_TIMEOUT] })
59
62
  ```
60
63
 
61
64
  ### `Quickjs::VM`: Maintain a consistent VM/runtime
@@ -94,9 +97,14 @@ vm = Quickjs::VM.new(
94
97
  vm = Quickjs::VM.new(
95
98
  timeout_msec: 1_000,
96
99
  )
100
+
101
+ # enable timeout features `setTimeout`, `clearTimeout`
102
+ vm = Quickjs::VM.new(
103
+ features: [::Quickjs::FEATURES_TIMEOUT],
104
+ )
97
105
  ```
98
106
 
99
- #### Define a global function for JS
107
+ #### ⚡️ Define a global function for JS by Ruby
100
108
 
101
109
  ```rb
102
110
  vm = Quickjs::VM.new
@@ -110,4 +118,5 @@ vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'
110
118
  ## License
111
119
 
112
120
  Every file in `ext/quickjsrb/quickjs` is licensed under [the MIT License Copyright 2017-2021 by Fabrice Bellard and Charlie Goron](/ext/quickjsrb/quickjs/LICENSE).
121
+
113
122
  For otherwise, [the MIT License, Copyright 2024 by Kengo Hamasaki](/LICENSE).
@@ -11,6 +11,7 @@ typedef struct VMData
11
11
  struct JSContext *context;
12
12
  VALUE defined_functions;
13
13
  struct EvalTime *eval_time;
14
+ VALUE logs;
14
15
  } VMData;
15
16
 
16
17
  static void vm_free(void *ptr)
@@ -36,6 +37,7 @@ static void vm_mark(void *ptr)
36
37
  {
37
38
  VMData *data = (VMData *)ptr;
38
39
  rb_gc_mark_movable(data->defined_functions);
40
+ rb_gc_mark_movable(data->logs);
39
41
  }
40
42
 
41
43
  static const rb_data_type_t vm_type = {
@@ -48,11 +50,12 @@ static const rb_data_type_t vm_type = {
48
50
  .flags = RUBY_TYPED_FREE_IMMEDIATELY,
49
51
  };
50
52
 
51
- static VALUE vm_alloc(VALUE self)
53
+ static VALUE vm_alloc(VALUE r_self)
52
54
  {
53
55
  VMData *data;
54
- VALUE obj = TypedData_Make_Struct(self, VMData, &vm_type, data);
56
+ VALUE obj = TypedData_Make_Struct(r_self, VMData, &vm_type, data);
55
57
  data->defined_functions = rb_hash_new();
58
+ data->logs = rb_ary_new();
56
59
 
57
60
  EvalTime *eval_time = malloc(sizeof(EvalTime));
58
61
  data->eval_time = eval_time;
@@ -63,15 +66,31 @@ static VALUE vm_alloc(VALUE self)
63
66
  return obj;
64
67
  }
65
68
 
66
- VALUE rb_mQuickjs;
69
+ VALUE rb_cQuickjsVMLog, rb_cQuickjsSyntaxError, rb_cQuickjsRuntimeError, rb_cQuickjsInterruptedError, rb_cQuickjsNoAwaitError, rb_cQuickjsTypeError, rb_cQuickjsReferenceError, rb_cQuickjsRangeError, rb_cQuickjsEvalError, rb_cQuickjsURIError, rb_cQuickjsAggregateError;
67
70
  const char *undefinedId = "undefined";
68
71
  const char *nanId = "NaN";
69
72
 
70
73
  const char *featureStdId = "feature_std";
71
74
  const char *featureOsId = "feature_os";
75
+ const char *featureOsTimeoutId = "feature_os_timeout";
72
76
 
73
77
  JSValue to_js_value(JSContext *ctx, VALUE r_value)
74
78
  {
79
+ if (RTEST(rb_funcall(
80
+ r_value,
81
+ rb_intern("is_a?"),
82
+ 1, rb_const_get(rb_cClass, rb_intern("Exception")))))
83
+ {
84
+ JSValue j_error = JS_NewError(ctx);
85
+ VALUE r_str = rb_funcall(r_value, rb_intern("message"), 0, NULL);
86
+ char *exceptionMessage = StringValueCStr(r_str);
87
+ VALUE r_exception_name = rb_funcall(rb_funcall(r_value, rb_intern("class"), 0, NULL), rb_intern("name"), 0, NULL);
88
+ char *exceptionName = StringValueCStr(r_exception_name);
89
+ JS_SetPropertyStr(ctx, j_error, "name", JS_NewString(ctx, exceptionName));
90
+ JS_SetPropertyStr(ctx, j_error, "message", JS_NewString(ctx, exceptionMessage));
91
+ return JS_Throw(ctx, j_error);
92
+ }
93
+
75
94
  switch (TYPE(r_value))
76
95
  {
77
96
  case T_NIL:
@@ -81,15 +100,15 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
81
100
  {
82
101
  VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0, NULL);
83
102
  char *str = StringValueCStr(r_str);
84
- JSValue global = JS_GetGlobalObject(ctx);
85
- JSValue numberClass = JS_GetPropertyStr(ctx, global, "Number");
103
+ JSValue j_global = JS_GetGlobalObject(ctx);
104
+ JSValue j_numberClass = JS_GetPropertyStr(ctx, j_global, "Number");
86
105
  JSValue j_str = JS_NewString(ctx, str);
87
- JSValue stringified = JS_Call(ctx, numberClass, JS_UNDEFINED, 1, &j_str);
88
- JS_FreeValue(ctx, global);
89
- JS_FreeValue(ctx, numberClass);
106
+ JSValue j_stringified = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, &j_str);
107
+ JS_FreeValue(ctx, j_global);
108
+ JS_FreeValue(ctx, j_numberClass);
90
109
  JS_FreeValue(ctx, j_str);
91
110
 
92
- return stringified;
111
+ return j_stringified;
93
112
  }
94
113
  case T_STRING:
95
114
  {
@@ -113,17 +132,17 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
113
132
  {
114
133
  VALUE r_json_str = rb_funcall(r_value, rb_intern("to_json"), 0, NULL);
115
134
  char *str = StringValueCStr(r_json_str);
116
- JSValue global = JS_GetGlobalObject(ctx);
117
- JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
118
- JSValue parseFunc = JS_GetPropertyStr(ctx, jsonClass, "parse");
135
+ JSValue j_global = JS_GetGlobalObject(ctx);
136
+ JSValue j_jsonClass = JS_GetPropertyStr(ctx, j_global, "JSON");
137
+ JSValue j_parseFunc = JS_GetPropertyStr(ctx, j_jsonClass, "parse");
119
138
  JSValue j_str = JS_NewString(ctx, str);
120
- JSValue stringified = JS_Call(ctx, parseFunc, jsonClass, 1, &j_str);
121
- JS_FreeValue(ctx, global);
122
- JS_FreeValue(ctx, jsonClass);
123
- JS_FreeValue(ctx, parseFunc);
139
+ JSValue j_stringified = JS_Call(ctx, j_parseFunc, j_jsonClass, 1, &j_str);
140
+ JS_FreeValue(ctx, j_global);
141
+ JS_FreeValue(ctx, j_jsonClass);
142
+ JS_FreeValue(ctx, j_parseFunc);
124
143
  JS_FreeValue(ctx, j_str);
125
144
 
126
- return stringified;
145
+ return j_stringified;
127
146
  }
128
147
  default:
129
148
  {
@@ -135,73 +154,62 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
135
154
  }
136
155
  }
137
156
 
138
- VALUE to_rb_value(JSValue jsv, JSContext *ctx)
157
+ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
139
158
  {
140
- switch (JS_VALUE_GET_NORM_TAG(jsv))
159
+ switch (JS_VALUE_GET_NORM_TAG(j_val))
141
160
  {
142
161
  case JS_TAG_INT:
143
162
  {
144
163
  int int_res = 0;
145
- JS_ToInt32(ctx, &int_res, jsv);
164
+ JS_ToInt32(ctx, &int_res, j_val);
146
165
  return INT2NUM(int_res);
147
166
  }
148
167
  case JS_TAG_FLOAT64:
149
168
  {
150
- if (JS_VALUE_IS_NAN(jsv))
169
+ if (JS_VALUE_IS_NAN(j_val))
151
170
  {
152
171
  return ID2SYM(rb_intern(nanId));
153
172
  }
154
173
  double double_res;
155
- JS_ToFloat64(ctx, &double_res, jsv);
174
+ JS_ToFloat64(ctx, &double_res, j_val);
156
175
  return DBL2NUM(double_res);
157
176
  }
158
177
  case JS_TAG_BOOL:
159
178
  {
160
- return JS_ToBool(ctx, jsv) > 0 ? Qtrue : Qfalse;
179
+ return JS_ToBool(ctx, j_val) > 0 ? Qtrue : Qfalse;
161
180
  }
162
181
  case JS_TAG_STRING:
163
182
  {
164
- JSValue maybeString = JS_ToString(ctx, jsv);
165
- const char *msg = JS_ToCString(ctx, maybeString);
166
- JS_FreeValue(ctx, maybeString);
183
+ const char *msg = JS_ToCString(ctx, j_val);
184
+ VALUE r_str = rb_str_new2(msg);
167
185
  JS_FreeCString(ctx, msg);
168
- return rb_str_new2(msg);
186
+ return r_str;
169
187
  }
170
188
  case JS_TAG_OBJECT:
171
189
  {
172
- int promiseState = JS_PromiseState(ctx, jsv);
173
- if (promiseState == JS_PROMISE_FULFILLED || promiseState == JS_PROMISE_PENDING)
174
- {
175
- JSValue awaited = js_std_await(ctx, jsv);
176
- VALUE rb_awaited = to_rb_value(awaited, ctx); // TODO: should have timeout
177
- JS_FreeValue(ctx, awaited);
178
- return rb_awaited;
179
- }
180
- else if (promiseState == JS_PROMISE_REJECTED)
190
+ int promiseState = JS_PromiseState(ctx, j_val);
191
+ if (promiseState != -1)
181
192
  {
182
- JSValue promiseResult = JS_PromiseResult(ctx, jsv);
183
- JSValue throw = JS_Throw(ctx, promiseResult);
184
- JS_FreeValue(ctx, promiseResult);
185
- VALUE rb_errored = to_rb_value(throw, ctx);
186
- JS_FreeValue(ctx, throw);
187
- return rb_errored;
193
+ VALUE r_error_message = rb_str_new2("cannot translate a Promise to Ruby. await within JavaScript's end");
194
+ rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, r_error_message));
195
+ return Qnil;
188
196
  }
189
197
 
190
- JSValue global = JS_GetGlobalObject(ctx);
191
- JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
192
- JSValue stringifyFunc = JS_GetPropertyStr(ctx, jsonClass, "stringify");
193
- JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &jsv);
198
+ JSValue j_global = JS_GetGlobalObject(ctx);
199
+ JSValue j_jsonClass = JS_GetPropertyStr(ctx, j_global, "JSON");
200
+ JSValue j_stringifyFunc = JS_GetPropertyStr(ctx, j_jsonClass, "stringify");
201
+ JSValue j_strigified = JS_Call(ctx, j_stringifyFunc, j_jsonClass, 1, &j_val);
194
202
 
195
- const char *msg = JS_ToCString(ctx, strigified);
196
- VALUE rbString = rb_str_new2(msg);
203
+ const char *msg = JS_ToCString(ctx, j_strigified);
204
+ VALUE r_str = rb_str_new2(msg);
197
205
  JS_FreeCString(ctx, msg);
198
206
 
199
- JS_FreeValue(ctx, global);
200
- JS_FreeValue(ctx, strigified);
201
- JS_FreeValue(ctx, stringifyFunc);
202
- JS_FreeValue(ctx, jsonClass);
207
+ JS_FreeValue(ctx, j_global);
208
+ JS_FreeValue(ctx, j_strigified);
209
+ JS_FreeValue(ctx, j_stringifyFunc);
210
+ JS_FreeValue(ctx, j_jsonClass);
203
211
 
204
- return rb_funcall(rb_const_get(rb_cClass, rb_intern("JSON")), rb_intern("parse"), 1, rbString);
212
+ return rb_funcall(rb_const_get(rb_cClass, rb_intern("JSON")), rb_intern("parse"), 1, r_str);
205
213
  }
206
214
  case JS_TAG_NULL:
207
215
  return Qnil;
@@ -209,47 +217,95 @@ VALUE to_rb_value(JSValue jsv, JSContext *ctx)
209
217
  return ID2SYM(rb_intern(undefinedId));
210
218
  case JS_TAG_EXCEPTION:
211
219
  {
212
- JSValue exceptionVal = JS_GetException(ctx);
213
- if (JS_IsError(ctx, exceptionVal))
220
+ JSValue j_exceptionVal = JS_GetException(ctx);
221
+ if (JS_IsError(ctx, j_exceptionVal))
214
222
  {
215
- JSValue jsErrorClassName = JS_GetPropertyStr(ctx, exceptionVal, "name");
216
- const char *errorClassName = JS_ToCString(ctx, jsErrorClassName);
217
-
218
- JSValue jsErrorClassMessage = JS_GetPropertyStr(ctx, exceptionVal, "message");
219
- const char *errorClassMessage = JS_ToCString(ctx, jsErrorClassMessage);
220
-
221
- JS_FreeValue(ctx, jsErrorClassMessage);
222
- JS_FreeValue(ctx, jsErrorClassName);
223
-
224
- VALUE rb_errorMessage = rb_sprintf("%s: %s", errorClassName, errorClassMessage);
223
+ JSValue j_errorClassName = JS_GetPropertyStr(ctx, j_exceptionVal, "name");
224
+ const char *errorClassName = JS_ToCString(ctx, j_errorClassName);
225
+
226
+ JSValue j_errorClassMessage = JS_GetPropertyStr(ctx, j_exceptionVal, "message");
227
+ const char *errorClassMessage = JS_ToCString(ctx, j_errorClassMessage);
228
+
229
+ JS_FreeValue(ctx, j_errorClassMessage);
230
+ JS_FreeValue(ctx, j_errorClassName);
231
+
232
+ VALUE r_error_message = rb_sprintf("%s: %s", errorClassName, errorClassMessage);
233
+ VALUE r_error_class = rb_eRuntimeError;
234
+
235
+ if (strcmp(errorClassName, "SyntaxError") == 0)
236
+ {
237
+ r_error_class = rb_cQuickjsSyntaxError;
238
+ r_error_message = rb_str_new2(errorClassMessage);
239
+ }
240
+ else if (strcmp(errorClassName, "TypeError") == 0)
241
+ {
242
+ r_error_class = rb_cQuickjsTypeError;
243
+ r_error_message = rb_str_new2(errorClassMessage);
244
+ }
245
+ else if (strcmp(errorClassName, "ReferenceError") == 0)
246
+ {
247
+ r_error_class = rb_cQuickjsReferenceError;
248
+ r_error_message = rb_str_new2(errorClassMessage);
249
+ }
250
+ else if (strcmp(errorClassName, "RangeError") == 0)
251
+ {
252
+ r_error_class = rb_cQuickjsRangeError;
253
+ r_error_message = rb_str_new2(errorClassMessage);
254
+ }
255
+ else if (strcmp(errorClassName, "EvalError") == 0)
256
+ {
257
+ r_error_class = rb_cQuickjsEvalError;
258
+ r_error_message = rb_str_new2(errorClassMessage);
259
+ }
260
+ else if (strcmp(errorClassName, "URIError") == 0)
261
+ {
262
+ r_error_class = rb_cQuickjsURIError;
263
+ r_error_message = rb_str_new2(errorClassMessage);
264
+ }
265
+ else if (strcmp(errorClassName, "AggregateError") == 0)
266
+ {
267
+ r_error_class = rb_cQuickjsAggregateError;
268
+ r_error_message = rb_str_new2(errorClassMessage);
269
+ }
270
+ else if (strcmp(errorClassName, "InternalError") == 0 && strstr(errorClassMessage, "interrupted") != NULL)
271
+ {
272
+ r_error_class = rb_cQuickjsInterruptedError;
273
+ r_error_message = rb_str_new2("Code evaluation is interrupted by the timeout or something");
274
+ }
275
+ else if (strcmp(errorClassName, "Quickjs::InterruptedError") == 0)
276
+ {
277
+ r_error_class = rb_cQuickjsInterruptedError;
278
+ r_error_message = rb_str_new2(errorClassMessage);
279
+ }
225
280
  JS_FreeCString(ctx, errorClassName);
226
281
  JS_FreeCString(ctx, errorClassMessage);
227
- JS_FreeValue(ctx, exceptionVal);
228
- rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, rb_errorMessage));
282
+ JS_FreeValue(ctx, j_exceptionVal);
283
+
284
+ rb_exc_raise(rb_exc_new_str(r_error_class, r_error_message));
229
285
  }
230
- else
286
+ else // exception without Error object
231
287
  {
232
- const char *errorMessage = JS_ToCString(ctx, exceptionVal);
288
+ const char *errorMessage = JS_ToCString(ctx, j_exceptionVal);
289
+ VALUE r_error_message = rb_sprintf("%s", errorMessage);
233
290
 
234
- VALUE rb_errorMessage = rb_sprintf("%s", errorMessage);
235
291
  JS_FreeCString(ctx, errorMessage);
236
- JS_FreeValue(ctx, exceptionVal);
237
- rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, rb_errorMessage));
292
+ JS_FreeValue(ctx, j_exceptionVal);
293
+ rb_exc_raise(rb_exc_new_str(rb_cQuickjsRuntimeError, r_error_message));
238
294
  }
239
295
  return Qnil;
240
296
  }
241
297
  case JS_TAG_BIG_INT:
242
298
  {
243
- JSValue toStringFunc = JS_GetPropertyStr(ctx, jsv, "toString");
244
- JSValue strigified = JS_Call(ctx, toStringFunc, jsv, 0, NULL);
299
+ JSValue j_toStringFunc = JS_GetPropertyStr(ctx, j_val, "toString");
300
+ JSValue j_strigified = JS_Call(ctx, j_toStringFunc, j_val, 0, NULL);
245
301
 
246
- const char *msg = JS_ToCString(ctx, strigified);
247
- VALUE rbString = rb_str_new2(msg);
248
- JS_FreeValue(ctx, toStringFunc);
249
- JS_FreeValue(ctx, strigified);
302
+ const char *msg = JS_ToCString(ctx, j_strigified);
303
+ VALUE r_str = rb_str_new2(msg);
304
+ JS_FreeValue(ctx, j_toStringFunc);
305
+ JS_FreeValue(ctx, j_strigified);
250
306
  JS_FreeCString(ctx, msg);
251
307
 
252
- return rb_funcall(rbString, rb_intern("to_i"), 0, NULL);
308
+ return rb_funcall(r_str, rb_intern("to_i"), 0, NULL);
253
309
  }
254
310
  case JS_TAG_BIG_FLOAT:
255
311
  case JS_TAG_BIG_DECIMAL:
@@ -262,35 +318,75 @@ VALUE to_rb_value(JSValue jsv, JSContext *ctx)
262
318
  static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int _argc, JSValueConst *argv)
263
319
  {
264
320
  VMData *data = JS_GetContextOpaque(ctx);
265
- JSValue maybeFuncName = JS_ToString(ctx, argv[0]);
266
- const char *funcName = JS_ToCString(ctx, maybeFuncName);
267
- JS_FreeValue(ctx, maybeFuncName);
321
+ JSValue j_maybeFuncName = JS_ToString(ctx, argv[0]);
322
+ const char *funcName = JS_ToCString(ctx, j_maybeFuncName);
323
+ JS_FreeValue(ctx, j_maybeFuncName);
268
324
 
269
- VALUE proc = rb_hash_aref(data->defined_functions, rb_str_new2(funcName));
270
- if (proc == Qnil)
325
+ VALUE r_proc = rb_hash_aref(data->defined_functions, rb_str_new2(funcName));
326
+ if (r_proc == Qnil)
271
327
  { // Shouldn't happen
272
328
  return JS_ThrowReferenceError(ctx, "Proc `%s` is not defined", funcName);
273
329
  }
274
330
  JS_FreeCString(ctx, funcName);
275
331
 
276
- // TODO: cover timeout for calling proc
277
- VALUE r_result = rb_apply(proc, rb_intern("call"), to_rb_value(argv[1], ctx));
332
+ VALUE r_result = rb_funcall(
333
+ rb_const_get(rb_cClass, rb_intern("Quickjs")),
334
+ rb_intern("_with_timeout"),
335
+ 3,
336
+ ULONG2NUM(data->eval_time->limit * 1000 / CLOCKS_PER_SEC),
337
+ r_proc,
338
+ to_rb_value(ctx, argv[1]));
339
+
278
340
  return to_js_value(ctx, r_result);
279
341
  }
280
342
 
281
- static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE self)
343
+ static JSValue js_quickjsrb_log(JSContext *ctx, JSValueConst _this, int _argc, JSValueConst *argv)
344
+ {
345
+ VMData *data = JS_GetContextOpaque(ctx);
346
+ JSValue j_severity = JS_ToString(ctx, argv[0]);
347
+ const char *severity = JS_ToCString(ctx, j_severity);
348
+ JS_FreeValue(ctx, j_severity);
349
+
350
+ VALUE r_log = rb_funcall(rb_cQuickjsVMLog, rb_intern("new"), 0);
351
+ rb_iv_set(r_log, "@severity", ID2SYM(rb_intern(severity)));
352
+
353
+ VALUE r_row = rb_ary_new();
354
+ int i;
355
+ JSValue j_length = JS_GetPropertyStr(ctx, argv[1], "length");
356
+ int count;
357
+ JS_ToInt32(ctx, &count, j_length);
358
+ JS_FreeValue(ctx, j_length);
359
+ for (i = 0; i < count; i++)
360
+ {
361
+ JSValue j_logged = JS_GetPropertyUint32(ctx, argv[1], i);
362
+ const char *body = JS_ToCString(ctx, j_logged);
363
+ VALUE r_loghash = rb_hash_new();
364
+ rb_hash_aset(r_loghash, ID2SYM(rb_intern("c")), rb_str_new2(body));
365
+ rb_ary_push(r_row, r_loghash);
366
+ JS_FreeValue(ctx, j_logged);
367
+ JS_FreeCString(ctx, body);
368
+ }
369
+
370
+ rb_iv_set(r_log, "@row", r_row);
371
+ rb_ary_push(data->logs, r_log);
372
+ JS_FreeCString(ctx, severity);
373
+
374
+ return JS_UNDEFINED;
375
+ }
376
+
377
+ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
282
378
  {
283
379
  VALUE r_opts;
284
380
  rb_scan_args(argc, argv, ":", &r_opts);
285
381
  if (NIL_P(r_opts))
286
382
  r_opts = rb_hash_new();
287
383
 
288
- VALUE r_memoryLimit = rb_hash_aref(r_opts, ID2SYM(rb_intern("memory_limit")));
289
- if (NIL_P(r_memoryLimit))
290
- r_memoryLimit = UINT2NUM(1024 * 1024 * 128);
291
- VALUE r_maxStackSize = rb_hash_aref(r_opts, ID2SYM(rb_intern("max_stack_size")));
292
- if (NIL_P(r_maxStackSize))
293
- r_maxStackSize = UINT2NUM(1024 * 1024 * 4);
384
+ VALUE r_memory_limit = rb_hash_aref(r_opts, ID2SYM(rb_intern("memory_limit")));
385
+ if (NIL_P(r_memory_limit))
386
+ r_memory_limit = UINT2NUM(1024 * 1024 * 128);
387
+ VALUE r_max_stack_size = rb_hash_aref(r_opts, ID2SYM(rb_intern("max_stack_size")));
388
+ if (NIL_P(r_max_stack_size))
389
+ r_max_stack_size = UINT2NUM(1024 * 1024 * 4);
294
390
  VALUE r_features = rb_hash_aref(r_opts, ID2SYM(rb_intern("features")));
295
391
  if (NIL_P(r_features))
296
392
  r_features = rb_ary_new();
@@ -299,20 +395,19 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE self)
299
395
  r_timeout_msec = UINT2NUM(100);
300
396
 
301
397
  VMData *data;
302
- TypedData_Get_Struct(self, VMData, &vm_type, data);
398
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
303
399
 
304
400
  data->eval_time->limit = (clock_t)(CLOCKS_PER_SEC * NUM2UINT(r_timeout_msec) / 1000);
305
401
  JS_SetContextOpaque(data->context, data);
306
402
  JSRuntime *runtime = JS_GetRuntime(data->context);
307
403
 
308
- JS_SetMemoryLimit(runtime, NUM2UINT(r_memoryLimit));
309
- JS_SetMaxStackSize(runtime, NUM2UINT(r_maxStackSize));
404
+ JS_SetMemoryLimit(runtime, NUM2UINT(r_memory_limit));
405
+ JS_SetMaxStackSize(runtime, NUM2UINT(r_max_stack_size));
310
406
 
311
407
  JS_AddIntrinsicBigFloat(data->context);
312
408
  JS_AddIntrinsicBigDecimal(data->context);
313
409
  JS_AddIntrinsicOperators(data->context);
314
410
  JS_EnableBignumExt(data->context, TRUE);
315
- js_std_add_helpers(data->context, 0, NULL);
316
411
 
317
412
  JS_SetModuleLoaderFunc(runtime, NULL, js_module_loader, NULL);
318
413
  js_std_init_handlers(runtime);
@@ -322,8 +417,8 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE self)
322
417
  js_init_module_std(data->context, "std");
323
418
  const char *enableStd = "import * as std from 'std';\n"
324
419
  "globalThis.std = std;\n";
325
- JSValue stdEval = JS_Eval(data->context, enableStd, strlen(enableStd), "<vm>", JS_EVAL_TYPE_MODULE);
326
- JS_FreeValue(data->context, stdEval);
420
+ JSValue j_stdEval = JS_Eval(data->context, enableStd, strlen(enableStd), "<vm>", JS_EVAL_TYPE_MODULE);
421
+ JS_FreeValue(data->context, j_stdEval);
327
422
  }
328
423
 
329
424
  if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureOsId)))))
@@ -331,20 +426,43 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE self)
331
426
  js_init_module_os(data->context, "os");
332
427
  const char *enableOs = "import * as os from 'os';\n"
333
428
  "globalThis.os = os;\n";
334
- JSValue osEval = JS_Eval(data->context, enableOs, strlen(enableOs), "<vm>", JS_EVAL_TYPE_MODULE);
335
- JS_FreeValue(data->context, osEval);
429
+ JSValue j_osEval = JS_Eval(data->context, enableOs, strlen(enableOs), "<vm>", JS_EVAL_TYPE_MODULE);
430
+ JS_FreeValue(data->context, j_osEval);
431
+ }
432
+ else if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureOsTimeoutId)))))
433
+ {
434
+ js_init_module_os(data->context, "_os"); // Better if this is limited just only for setTimeout and clearTimeout
435
+ const char *enableTimeout = "import * as _os from '_os';\n"
436
+ "globalThis.setTimeout = _os.setTimeout;\n"
437
+ "globalThis.clearTimeout = _os.clearTimeout;\n";
438
+ JSValue j_timeoutEval = JS_Eval(data->context, enableTimeout, strlen(enableTimeout), "<vm>", JS_EVAL_TYPE_MODULE);
439
+ JS_FreeValue(data->context, j_timeoutEval);
336
440
  }
337
441
 
338
- const char *setupGlobalRuby = "globalThis.__ruby = {};\n";
339
- JSValue rubyEval = JS_Eval(data->context, setupGlobalRuby, strlen(setupGlobalRuby), "<vm>", JS_EVAL_TYPE_MODULE);
340
- JS_FreeValue(data->context, rubyEval);
341
-
342
- JSValue global = JS_GetGlobalObject(data->context);
343
- JSValue func = JS_NewCFunction(data->context, js_quickjsrb_call_global, "rubyGlobal", 2);
344
- JS_SetPropertyStr(data->context, global, "rubyGlobal", func);
345
- JS_FreeValue(data->context, global);
346
-
347
- return self;
442
+ JSValue j_global = JS_GetGlobalObject(data->context);
443
+ JSValue j_quickjsrbGlobal = JS_NewObject(data->context);
444
+ JS_SetPropertyStr(
445
+ data->context, j_quickjsrbGlobal, "runRubyMethod",
446
+ JS_NewCFunction(data->context, js_quickjsrb_call_global, "runRubyMethod", 2));
447
+
448
+ JS_SetPropertyStr(data->context, j_global, "__quickjsrb", j_quickjsrbGlobal);
449
+
450
+ JSValue j_console = JS_NewObject(data->context);
451
+ JS_SetPropertyStr(
452
+ data->context, j_quickjsrbGlobal, "log",
453
+ JS_NewCFunction(data->context, js_quickjsrb_log, "log", 2));
454
+ JS_SetPropertyStr(data->context, j_global, "console", j_console);
455
+ JS_FreeValue(data->context, j_global);
456
+
457
+ const char *defineLoggers = "console.log = (...args) => __quickjsrb.log('info', args);\n"
458
+ "console.debug = (...args) => __quickjsrb.log('verbose', args);\n"
459
+ "console.info = (...args) => __quickjsrb.log('info', args);\n"
460
+ "console.warn = (...args) => __quickjsrb.log('warning', args);\n"
461
+ "console.error = (...args) => __quickjsrb.log('error', args);\n";
462
+ JSValue j_defineLoggers = JS_Eval(data->context, defineLoggers, strlen(defineLoggers), "<vm>", JS_EVAL_TYPE_GLOBAL);
463
+ JS_FreeValue(data->context, j_defineLoggers);
464
+
465
+ return r_self;
348
466
  }
349
467
 
350
468
  static int interrupt_handler(JSRuntime *runtime, void *opaque)
@@ -353,66 +471,161 @@ static int interrupt_handler(JSRuntime *runtime, void *opaque)
353
471
  return clock() >= eval_time->started_at + eval_time->limit ? 1 : 0;
354
472
  }
355
473
 
356
- static VALUE vm_m_evalCode(VALUE self, VALUE r_code)
474
+ static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
357
475
  {
358
476
  VMData *data;
359
- TypedData_Get_Struct(self, VMData, &vm_type, data);
477
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
360
478
 
361
479
  data->eval_time->started_at = clock();
362
480
  JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
363
481
 
364
482
  char *code = StringValueCStr(r_code);
365
- JSValue codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL);
366
- VALUE result = to_rb_value(codeResult, data->context);
367
-
368
- JS_FreeValue(data->context, codeResult);
369
- return result;
483
+ JSValue j_codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
484
+ JSValue j_awaitedResult = js_std_await(data->context, j_codeResult);
485
+ JSValue j_returnedValue = JS_GetPropertyStr(data->context, j_awaitedResult, "value");
486
+ // Do this by rescuing to_rb_value
487
+ if (JS_VALUE_GET_NORM_TAG(j_returnedValue) == JS_TAG_OBJECT && JS_PromiseState(data->context, j_returnedValue) != -1)
488
+ {
489
+ JS_FreeValue(data->context, j_returnedValue);
490
+ JS_FreeValue(data->context, j_awaitedResult);
491
+ VALUE r_error_message = rb_str_new2("An unawaited Promise was returned to the top-level");
492
+ rb_exc_raise(rb_exc_new_str(rb_cQuickjsNoAwaitError, r_error_message));
493
+ return Qnil;
494
+ }
495
+ else
496
+ {
497
+ VALUE result = to_rb_value(data->context, j_returnedValue);
498
+ JS_FreeValue(data->context, j_returnedValue);
499
+ JS_FreeValue(data->context, j_awaitedResult);
500
+ return result;
501
+ }
370
502
  }
371
503
 
372
- static VALUE vm_m_defineGlobalFunction(VALUE self, VALUE r_name)
504
+ static VALUE vm_m_defineGlobalFunction(VALUE r_self, VALUE r_name)
373
505
  {
374
506
  rb_need_block();
375
507
 
376
508
  VMData *data;
377
- TypedData_Get_Struct(self, VMData, &vm_type, data);
509
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
378
510
 
379
511
  if (rb_block_given_p())
380
512
  {
381
- VALUE proc = rb_block_proc();
382
-
513
+ VALUE r_proc = rb_block_proc();
514
+ rb_hash_aset(data->defined_functions, r_name, r_proc);
383
515
  char *funcName = StringValueCStr(r_name);
384
516
 
385
- rb_hash_aset(data->defined_functions, r_name, proc);
386
-
387
- const char *template = "globalThis.__ruby['%s'] = (...args) => rubyGlobal('%s', args);\nglobalThis['%s'] = globalThis.__ruby['%s'];\n";
388
- int length = snprintf(NULL, 0, template, funcName, funcName, funcName, funcName);
517
+ const char *template = "globalThis['%s'] = (...args) => __quickjsrb.runRubyMethod('%s', args);\n";
518
+ int length = snprintf(NULL, 0, template, funcName, funcName);
389
519
  char *result = (char *)malloc(length + 1);
390
- snprintf(result, length + 1, template, funcName, funcName, funcName, funcName);
520
+ snprintf(result, length + 1, template, funcName, funcName);
391
521
 
392
- JSValue codeResult = JS_Eval(data->context, result, strlen(result), "<vm>", JS_EVAL_TYPE_MODULE);
522
+ JSValue j_codeResult = JS_Eval(data->context, result, strlen(result), "<vm>", JS_EVAL_TYPE_MODULE);
393
523
 
394
524
  free(result);
395
- JS_FreeValue(data->context, codeResult);
525
+ JS_FreeValue(data->context, j_codeResult);
396
526
  return rb_funcall(r_name, rb_intern("to_sym"), 0, NULL);
397
527
  }
398
528
 
399
529
  return Qnil;
400
530
  }
401
531
 
532
+ // WISH: vm.import('hey', from: '...source...') imports just default
533
+ // WISH: vm.import('{ member, member2 }', from: '...source...')
534
+ // WISH: vm.import('{ member as aliasedName }', from: '...source...')
535
+ // WISH: vm.import('defaultMember, { member }', from: '...source...')
536
+ // WISH: vm.import('* as all', from: '...source...')
537
+ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
538
+ {
539
+ VALUE r_import_string, r_opts;
540
+ rb_scan_args(argc, argv, "10:", &r_import_string, &r_opts);
541
+ if (NIL_P(r_opts))
542
+ r_opts = rb_hash_new();
543
+ VALUE r_from = rb_hash_aref(r_opts, ID2SYM(rb_intern("from")));
544
+ if (NIL_P(r_from))
545
+ {
546
+ VALUE r_error_message = rb_str_new2("missing import source");
547
+ rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, r_error_message));
548
+ return Qnil;
549
+ }
550
+
551
+ VMData *data;
552
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
553
+
554
+ char *source = StringValueCStr(r_from);
555
+ char *import_name = StringValueCStr(r_import_string);
556
+ JSValue func = JS_Eval(data->context, source, strlen(source), "mmmodule", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
557
+ js_module_set_import_meta(data->context, func, TRUE, FALSE);
558
+ JS_FreeValue(data->context, func);
559
+
560
+ const char *importAndGlobalizeModule = "import * as %s from 'mmmodule';\n"
561
+ "globalThis['%s'] = %s;\n";
562
+ int length = snprintf(NULL, 0, importAndGlobalizeModule, import_name, import_name, import_name);
563
+ char *result = (char *)malloc(length + 1);
564
+ snprintf(result, length + 1, importAndGlobalizeModule, import_name, import_name, import_name);
565
+
566
+ JSValue j_codeResult = JS_Eval(data->context, result, strlen(result), "<vm>", JS_EVAL_TYPE_MODULE);
567
+ free(result);
568
+ JS_FreeValue(data->context, j_codeResult);
569
+
570
+ return Qtrue;
571
+ }
572
+
573
+ static VALUE vm_m_getLogs(VALUE r_self)
574
+ {
575
+ VMData *data;
576
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
577
+
578
+ return data->logs;
579
+ }
580
+
581
+ static VALUE pick_c(VALUE block_arg, VALUE data, int argc, const VALUE *argv, VALUE blockarg)
582
+ {
583
+ return rb_hash_aref(block_arg, ID2SYM(rb_intern("c")));
584
+ }
585
+
586
+ static VALUE vm_m_to_s(VALUE r_self)
587
+ {
588
+ VALUE row = rb_iv_get(r_self, "@row");
589
+ VALUE r_ary = rb_block_call(row, rb_intern("map"), 0, NULL, pick_c, Qnil);
590
+
591
+ return rb_funcall(r_ary, rb_intern("join"), 1, rb_str_new2(" "));
592
+ }
593
+
402
594
  RUBY_FUNC_EXPORTED void
403
595
  Init_quickjsrb(void)
404
596
  {
405
- rb_mQuickjs = rb_define_module("Quickjs");
597
+ VALUE rb_mQuickjs = rb_define_module("Quickjs");
406
598
  rb_define_const(rb_mQuickjs, "MODULE_STD", ID2SYM(rb_intern(featureStdId)));
407
599
  rb_define_const(rb_mQuickjs, "MODULE_OS", ID2SYM(rb_intern(featureOsId)));
408
-
409
- VALUE valueClass = rb_define_class_under(rb_mQuickjs, "Value", rb_cObject);
410
- rb_define_const(valueClass, "UNDEFINED", ID2SYM(rb_intern(undefinedId)));
411
- rb_define_const(valueClass, "NAN", ID2SYM(rb_intern(nanId)));
412
-
413
- VALUE vmClass = rb_define_class_under(rb_mQuickjs, "VM", rb_cObject);
414
- rb_define_alloc_func(vmClass, vm_alloc);
415
- rb_define_method(vmClass, "initialize", vm_m_initialize, -1);
416
- rb_define_method(vmClass, "eval_code", vm_m_evalCode, 1);
417
- rb_define_method(vmClass, "define_function", vm_m_defineGlobalFunction, 1);
600
+ rb_define_const(rb_mQuickjs, "FEATURES_TIMEOUT", ID2SYM(rb_intern(featureOsTimeoutId)));
601
+
602
+ VALUE rb_cQuickjsValue = rb_define_class_under(rb_mQuickjs, "Value", rb_cObject);
603
+ rb_define_const(rb_cQuickjsValue, "UNDEFINED", ID2SYM(rb_intern(undefinedId)));
604
+ rb_define_const(rb_cQuickjsValue, "NAN", ID2SYM(rb_intern(nanId)));
605
+
606
+ VALUE rb_cQuickjsVM = rb_define_class_under(rb_mQuickjs, "VM", rb_cObject);
607
+ rb_define_alloc_func(rb_cQuickjsVM, vm_alloc);
608
+ rb_define_method(rb_cQuickjsVM, "initialize", vm_m_initialize, -1);
609
+ rb_define_method(rb_cQuickjsVM, "eval_code", vm_m_evalCode, 1);
610
+ rb_define_method(rb_cQuickjsVM, "define_function", vm_m_defineGlobalFunction, 1);
611
+ rb_define_method(rb_cQuickjsVM, "import", vm_m_import, -1);
612
+ rb_define_method(rb_cQuickjsVM, "logs", vm_m_getLogs, 0);
613
+
614
+ rb_cQuickjsVMLog = rb_define_class_under(rb_cQuickjsVM, "Log", rb_cObject);
615
+ rb_define_attr(rb_cQuickjsVMLog, "severity", 1, 0);
616
+ rb_define_method(rb_cQuickjsVMLog, "to_s", vm_m_to_s, 0);
617
+ rb_define_method(rb_cQuickjsVMLog, "inspect", vm_m_to_s, 0);
618
+
619
+ rb_cQuickjsRuntimeError = rb_define_class_under(rb_mQuickjs, "RuntimeError", rb_eRuntimeError);
620
+
621
+ rb_cQuickjsSyntaxError = rb_define_class_under(rb_mQuickjs, "SyntaxError", rb_cQuickjsRuntimeError);
622
+ rb_cQuickjsTypeError = rb_define_class_under(rb_mQuickjs, "TypeError", rb_cQuickjsRuntimeError);
623
+ rb_cQuickjsRangeError = rb_define_class_under(rb_mQuickjs, "RangeError", rb_cQuickjsRuntimeError);
624
+ rb_cQuickjsReferenceError = rb_define_class_under(rb_mQuickjs, "ReferenceError", rb_cQuickjsRuntimeError);
625
+ rb_cQuickjsURIError = rb_define_class_under(rb_mQuickjs, "URIError", rb_cQuickjsRuntimeError);
626
+ rb_cQuickjsEvalError = rb_define_class_under(rb_mQuickjs, "EvalError", rb_cQuickjsRuntimeError);
627
+ rb_cQuickjsAggregateError = rb_define_class_under(rb_mQuickjs, "AggregateError", rb_cQuickjsRuntimeError);
628
+
629
+ rb_cQuickjsInterruptedError = rb_define_class_under(rb_mQuickjs, "InterruptedError", rb_cQuickjsRuntimeError);
630
+ rb_cQuickjsNoAwaitError = rb_define_class_under(rb_mQuickjs, "NoAwaitError", rb_cQuickjsRuntimeError);
418
631
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.1.10"
4
+ VERSION = "0.1.12"
5
5
  end
data/lib/quickjs.rb CHANGED
@@ -1,12 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "timeout"
3
4
  require "json"
4
5
  require_relative "quickjs/version"
5
6
  require_relative "quickjs/quickjsrb"
6
7
 
7
8
  module Quickjs
8
9
  def eval_code(code, overwrite_opts = {})
9
- Quickjs::VM.new(**overwrite_opts).eval_code(code)
10
+ vm = Quickjs::VM.new(**overwrite_opts)
11
+ res = vm.eval_code(code)
12
+ vm = nil
13
+ res
10
14
  end
11
15
  module_function :eval_code
16
+
17
+ def _with_timeout(msec, proc, args)
18
+ Timeout.timeout(msec / 1_000.0) { proc.call(*args) }
19
+ rescue Timeout::Error
20
+ Quickjs::InterruptedError.new('Ruby runtime got timeout')
21
+ rescue => e
22
+ e
23
+ end
24
+ module_function :_with_timeout
12
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quickjs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-17 00:00:00.000000000 Z
11
+ date: 2024-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json