quickjs 0.1.9 → 0.1.11

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: f0461f98dbe3171e88609b060aa9fb414ba881b0977d983d6ec7897726a0559c
4
- data.tar.gz: 84ed4cf19c1d5191e9cd57b789cc3138691f7ad2059b515a2ce4280051105c46
3
+ metadata.gz: 81c0e4a716d292ddbfd58f559f37ddb1d86adb7059779f29bfa0a2b8a8180214
4
+ data.tar.gz: f45ff31a1913c4ab807ddd5d8f08c17c9bc58236b09308f31f5adbf5d9241ffe
5
5
  SHA512:
6
- metadata.gz: c98d7daf116e5513a19b99e4ef387f3b1b60389c3bb9f91822cc1d437bf87cc72041cd97c7cdf2a8a8cb55a86df93a3cf7fe472733017ae4a315831397a56cd5
7
- data.tar.gz: f177d0e31b8d6499fcf922bd85dc594aa2acd0bf8d93d696256c4a3a7a1595eeb874df7e14e8447694a3045b3f2b9d8279633550e535fc8f257311c613c043b6
6
+ metadata.gz: 24c0349dc9e911936932a78e3c7aa0ac75ea36eb776258ae8d8d20d215eaa79a2d79a64c0b1eb0878182033eee8945f7e3f65dd1272e5290a332ac07bde7b7a9
7
+ data.tar.gz: e7e472520ece228398482ea753157ffc4bf08b2f446b56dd2439b27a9c1006ffc9439ca5d1eb27015e6fa19b68e5663d82339c082b82fa085235e8c8108b5d3b
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,15 +97,14 @@ vm = Quickjs::VM.new(
94
97
  vm = Quickjs::VM.new(
95
98
  timeout_msec: 1_000,
96
99
  )
97
- ```
98
100
 
99
- #### Dispose VM explicitly
100
-
101
- ```rb
102
- vm.dispose!
101
+ # enable timeout features `setTimeout`, `clearTimeout`
102
+ vm = Quickjs::VM.new(
103
+ features: [::Quickjs::FEATURES_TIMEOUT],
104
+ )
103
105
  ```
104
106
 
105
- #### Define a global function for JS
107
+ #### ⚡️ Define a global function for JS by Ruby
106
108
 
107
109
  ```rb
108
110
  vm = Quickjs::VM.new
@@ -116,4 +118,5 @@ vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'
116
118
  ## License
117
119
 
118
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
+
119
122
  For otherwise, [the MIT License, Copyright 2024 by Kengo Hamasaki](/LICENSE).
@@ -1,27 +1,35 @@
1
1
  #include "quickjsrb.h"
2
2
 
3
- typedef struct EvalTime {
3
+ typedef struct EvalTime
4
+ {
4
5
  clock_t limit;
5
6
  clock_t started_at;
6
7
  } EvalTime;
7
8
 
8
- typedef struct VMData {
9
- char alive;
9
+ typedef struct VMData
10
+ {
10
11
  struct JSContext *context;
11
12
  VALUE defined_functions;
12
13
  struct EvalTime *eval_time;
13
14
  } VMData;
14
15
 
15
- static void vm_free(void* ptr)
16
+ static void vm_free(void *ptr)
16
17
  {
17
18
  VMData *data = (VMData *)ptr;
18
19
  free(data->eval_time);
20
+
21
+ JSRuntime *runtime = JS_GetRuntime(data->context);
22
+ JS_SetInterruptHandler(runtime, NULL, NULL);
23
+ js_std_free_handlers(runtime);
24
+ JS_FreeContext(data->context);
25
+ JS_FreeRuntime(runtime);
26
+
19
27
  xfree(ptr);
20
28
  }
21
29
 
22
- size_t vm_size(const void* data)
30
+ size_t vm_size(const void *data)
23
31
  {
24
- return sizeof(data);
32
+ return sizeof(VMData);
25
33
  }
26
34
 
27
35
  static void vm_mark(void *ptr)
@@ -31,25 +39,27 @@ static void vm_mark(void *ptr)
31
39
  }
32
40
 
33
41
  static const rb_data_type_t vm_type = {
34
- .wrap_struct_name = "quickjsvm",
35
- .function = {
36
- .dmark = vm_mark,
37
- .dfree = vm_free,
38
- .dsize = vm_size,
39
- },
40
- .data = NULL,
41
- .flags = RUBY_TYPED_FREE_IMMEDIATELY,
42
+ .wrap_struct_name = "quickjsvm",
43
+ .function = {
44
+ .dmark = vm_mark,
45
+ .dfree = vm_free,
46
+ .dsize = vm_size,
47
+ },
48
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
42
49
  };
43
50
 
44
- static VALUE vm_alloc(VALUE self)
51
+ static VALUE vm_alloc(VALUE r_self)
45
52
  {
46
53
  VMData *data;
47
- VALUE obj = TypedData_Make_Struct(self, VMData, &vm_type, data);
54
+ VALUE obj = TypedData_Make_Struct(r_self, VMData, &vm_type, data);
48
55
  data->defined_functions = rb_hash_new();
49
56
 
50
57
  EvalTime *eval_time = malloc(sizeof(EvalTime));
51
58
  data->eval_time = eval_time;
52
59
 
60
+ JSRuntime *runtime = JS_NewRuntime();
61
+ data->context = JS_NewContext(runtime);
62
+
53
63
  return obj;
54
64
  }
55
65
 
@@ -59,156 +69,190 @@ const char *nanId = "NaN";
59
69
 
60
70
  const char *featureStdId = "feature_std";
61
71
  const char *featureOsId = "feature_os";
72
+ const char *featureOsTimeoutId = "feature_os_timeout";
62
73
 
63
- JSValue to_js_value(JSContext *ctx, VALUE r_value) {
64
- switch (TYPE(r_value)) {
65
- case T_NIL:
66
- return JS_NULL;
67
- case T_FIXNUM:
68
- case T_FLOAT: {
69
- VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0, NULL);
70
- char *str = StringValueCStr(r_str);
71
- JSValue global = JS_GetGlobalObject(ctx);
72
- JSValue numberClass = JS_GetPropertyStr(ctx, global, "Number");
73
- JSValue j_str = JS_NewString(ctx, str);
74
- JSValue stringified = JS_Call(ctx, numberClass, JS_UNDEFINED, 1, &j_str);
75
- JS_FreeValue(ctx, global);
76
- JS_FreeValue(ctx, numberClass);
77
- JS_FreeValue(ctx, j_str);
78
-
79
- return stringified;
80
- }
81
- case T_STRING: {
82
- char *str = StringValueCStr(r_value);
74
+ JSValue to_js_value(JSContext *ctx, VALUE r_value)
75
+ {
76
+ if (RTEST(rb_funcall(
77
+ r_value,
78
+ rb_intern("is_a?"),
79
+ 1, rb_const_get(rb_cClass, rb_intern("Exception")))))
80
+ {
81
+ VALUE r_str = rb_funcall(r_value, rb_intern("message"), 0, NULL);
82
+ char *str = StringValueCStr(r_str);
83
+ JSValue j_error = JS_NewError(ctx);
84
+ JSValue j_str = JS_NewString(ctx, str);
85
+ JS_SetPropertyStr(ctx, j_error, "message", j_str);
86
+ return JS_Throw(ctx, j_error);
87
+ }
83
88
 
84
- return JS_NewString(ctx, str);
85
- }
86
- case T_SYMBOL: {
87
- VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0, NULL);
88
- char *str = StringValueCStr(r_str);
89
+ switch (TYPE(r_value))
90
+ {
91
+ case T_NIL:
92
+ return JS_NULL;
93
+ case T_FIXNUM:
94
+ case T_FLOAT:
95
+ {
96
+ VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0, NULL);
97
+ char *str = StringValueCStr(r_str);
98
+ JSValue j_global = JS_GetGlobalObject(ctx);
99
+ JSValue j_numberClass = JS_GetPropertyStr(ctx, j_global, "Number");
100
+ JSValue j_str = JS_NewString(ctx, str);
101
+ JSValue j_stringified = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, &j_str);
102
+ JS_FreeValue(ctx, j_global);
103
+ JS_FreeValue(ctx, j_numberClass);
104
+ JS_FreeValue(ctx, j_str);
105
+
106
+ return j_stringified;
107
+ }
108
+ case T_STRING:
109
+ {
110
+ char *str = StringValueCStr(r_value);
89
111
 
90
- return JS_NewString(ctx, str);
91
- }
92
- case T_TRUE:
93
- return JS_TRUE;
94
- case T_FALSE:
95
- return JS_FALSE;
96
- case T_HASH:
97
- case T_ARRAY: {
98
- VALUE r_json_str = rb_funcall(r_value, rb_intern("to_json"), 0, NULL);
99
- char *str = StringValueCStr(r_json_str);
100
- JSValue global = JS_GetGlobalObject(ctx);
101
- JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
102
- JSValue parseFunc = JS_GetPropertyStr(ctx, jsonClass, "parse");
103
- JSValue j_str = JS_NewString(ctx, str);
104
- JSValue stringified = JS_Call(ctx, parseFunc, jsonClass, 1, &j_str);
105
- JS_FreeValue(ctx, global);
106
- JS_FreeValue(ctx, jsonClass);
107
- JS_FreeValue(ctx, parseFunc);
108
- JS_FreeValue(ctx, j_str);
109
-
110
- return stringified;
111
- }
112
- default: {
113
- VALUE r_inspect_str = rb_funcall(r_value, rb_intern("inspect"), 0, NULL);
114
- char *str = StringValueCStr(r_inspect_str);
112
+ return JS_NewString(ctx, str);
113
+ }
114
+ case T_SYMBOL:
115
+ {
116
+ VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0, NULL);
117
+ char *str = StringValueCStr(r_str);
115
118
 
116
- return JS_NewString(ctx, str);
117
- }
119
+ return JS_NewString(ctx, str);
120
+ }
121
+ case T_TRUE:
122
+ return JS_TRUE;
123
+ case T_FALSE:
124
+ return JS_FALSE;
125
+ case T_HASH:
126
+ case T_ARRAY:
127
+ {
128
+ VALUE r_json_str = rb_funcall(r_value, rb_intern("to_json"), 0, NULL);
129
+ char *str = StringValueCStr(r_json_str);
130
+ JSValue j_global = JS_GetGlobalObject(ctx);
131
+ JSValue j_jsonClass = JS_GetPropertyStr(ctx, j_global, "JSON");
132
+ JSValue j_parseFunc = JS_GetPropertyStr(ctx, j_jsonClass, "parse");
133
+ JSValue j_str = JS_NewString(ctx, str);
134
+ JSValue j_stringified = JS_Call(ctx, j_parseFunc, j_jsonClass, 1, &j_str);
135
+ JS_FreeValue(ctx, j_global);
136
+ JS_FreeValue(ctx, j_jsonClass);
137
+ JS_FreeValue(ctx, j_parseFunc);
138
+ JS_FreeValue(ctx, j_str);
139
+
140
+ return j_stringified;
141
+ }
142
+ default:
143
+ {
144
+ VALUE r_inspect_str = rb_funcall(r_value, rb_intern("inspect"), 0, NULL);
145
+ char *str = StringValueCStr(r_inspect_str);
146
+
147
+ return JS_NewString(ctx, str);
148
+ }
118
149
  }
119
150
  }
120
151
 
121
- VALUE to_rb_value(JSValue jsv, JSContext *ctx) {
122
- switch(JS_VALUE_GET_NORM_TAG(jsv)) {
123
- case JS_TAG_INT: {
152
+ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
153
+ {
154
+ switch (JS_VALUE_GET_NORM_TAG(j_val))
155
+ {
156
+ case JS_TAG_INT:
157
+ {
124
158
  int int_res = 0;
125
- JS_ToInt32(ctx, &int_res, jsv);
159
+ JS_ToInt32(ctx, &int_res, j_val);
126
160
  return INT2NUM(int_res);
127
161
  }
128
- case JS_TAG_FLOAT64: {
129
- if (JS_VALUE_IS_NAN(jsv)) {
162
+ case JS_TAG_FLOAT64:
163
+ {
164
+ if (JS_VALUE_IS_NAN(j_val))
165
+ {
130
166
  return ID2SYM(rb_intern(nanId));
131
167
  }
132
168
  double double_res;
133
- JS_ToFloat64(ctx, &double_res, jsv);
169
+ JS_ToFloat64(ctx, &double_res, j_val);
134
170
  return DBL2NUM(double_res);
135
171
  }
136
- case JS_TAG_BOOL: {
137
- return JS_ToBool(ctx, jsv) > 0 ? Qtrue : Qfalse;
172
+ case JS_TAG_BOOL:
173
+ {
174
+ return JS_ToBool(ctx, j_val) > 0 ? Qtrue : Qfalse;
138
175
  }
139
- case JS_TAG_STRING: {
140
- JSValue maybeString = JS_ToString(ctx, jsv);
141
- const char *msg = JS_ToCString(ctx, maybeString);
142
- JS_FreeValue(ctx, maybeString);
143
- return rb_str_new2(msg);
176
+ case JS_TAG_STRING:
177
+ {
178
+ const char *msg = JS_ToCString(ctx, j_val);
179
+ VALUE r_str = rb_str_new2(msg);
180
+ JS_FreeCString(ctx, msg);
181
+ return r_str;
144
182
  }
145
- case JS_TAG_OBJECT: {
146
- int promiseState = JS_PromiseState(ctx, jsv);
147
- if (promiseState == JS_PROMISE_FULFILLED || promiseState == JS_PROMISE_PENDING) {
148
- JSValue awaited = js_std_await(ctx, jsv);
149
- VALUE rb_awaited = to_rb_value(awaited, ctx); // TODO: should have timeout
150
- JS_FreeValue(ctx, awaited);
151
- return rb_awaited;
152
- } else if (promiseState == JS_PROMISE_REJECTED) {
153
- JSValue promiseResult = JS_PromiseResult(ctx, jsv);
154
- JSValue throw = JS_Throw(ctx, promiseResult);
155
- JS_FreeValue(ctx, promiseResult);
156
- VALUE rb_errored = to_rb_value(throw, ctx);
157
- JS_FreeValue(ctx, throw);
158
- return rb_errored;
183
+ case JS_TAG_OBJECT:
184
+ {
185
+ int promiseState = JS_PromiseState(ctx, j_val);
186
+ if (promiseState != -1)
187
+ {
188
+ VALUE r_error_message = rb_str_new2("cannot translate a Promise to Ruby. await within JavaScript's end");
189
+ rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, r_error_message));
190
+ return Qnil;
159
191
  }
160
192
 
161
- JSValue global = JS_GetGlobalObject(ctx);
162
- JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
163
- JSValue stringifyFunc = JS_GetPropertyStr(ctx, jsonClass, "stringify");
164
- JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &jsv);
193
+ JSValue j_global = JS_GetGlobalObject(ctx);
194
+ JSValue j_jsonClass = JS_GetPropertyStr(ctx, j_global, "JSON");
195
+ JSValue j_stringifyFunc = JS_GetPropertyStr(ctx, j_jsonClass, "stringify");
196
+ JSValue j_strigified = JS_Call(ctx, j_stringifyFunc, j_jsonClass, 1, &j_val);
165
197
 
166
- const char *msg = JS_ToCString(ctx, strigified);
167
- VALUE rbString = rb_str_new2(msg);
198
+ const char *msg = JS_ToCString(ctx, j_strigified);
199
+ VALUE r_str = rb_str_new2(msg);
200
+ JS_FreeCString(ctx, msg);
168
201
 
169
- JS_FreeValue(ctx, global);
170
- JS_FreeValue(ctx, strigified);
171
- JS_FreeValue(ctx, stringifyFunc);
172
- JS_FreeValue(ctx, jsonClass);
202
+ JS_FreeValue(ctx, j_global);
203
+ JS_FreeValue(ctx, j_strigified);
204
+ JS_FreeValue(ctx, j_stringifyFunc);
205
+ JS_FreeValue(ctx, j_jsonClass);
173
206
 
174
- return rb_funcall(rb_const_get(rb_cClass, rb_intern("JSON")), rb_intern("parse"), 1, rbString);
207
+ return rb_funcall(rb_const_get(rb_cClass, rb_intern("JSON")), rb_intern("parse"), 1, r_str);
175
208
  }
176
209
  case JS_TAG_NULL:
177
210
  return Qnil;
178
211
  case JS_TAG_UNDEFINED:
179
212
  return ID2SYM(rb_intern(undefinedId));
180
- case JS_TAG_EXCEPTION: {
181
- JSValue exceptionVal = JS_GetException(ctx);
182
- if (JS_IsError(ctx, exceptionVal)) {
183
- JSValue jsErrorClassName = JS_GetPropertyStr(ctx, exceptionVal, "name");
184
- const char *errorClassName = JS_ToCString(ctx, jsErrorClassName);
185
-
186
- JSValue jsErrorClassMessage = JS_GetPropertyStr(ctx, exceptionVal, "message");
187
- const char *errorClassMessage = JS_ToCString(ctx, jsErrorClassMessage);
188
-
189
- JS_FreeValue(ctx, jsErrorClassMessage);
190
- JS_FreeValue(ctx, jsErrorClassName);
191
-
192
- rb_raise(rb_eRuntimeError, "%s: %s", errorClassName, errorClassMessage);
193
- } else {
194
- const char *errorMessage = JS_ToCString(ctx, exceptionVal);
195
-
196
- rb_raise(rb_eRuntimeError, "%s", errorMessage);
213
+ case JS_TAG_EXCEPTION:
214
+ {
215
+ JSValue j_exceptionVal = JS_GetException(ctx);
216
+ if (JS_IsError(ctx, j_exceptionVal))
217
+ {
218
+ JSValue j_errorClassName = JS_GetPropertyStr(ctx, j_exceptionVal, "name");
219
+ const char *errorClassName = JS_ToCString(ctx, j_errorClassName);
220
+
221
+ JSValue j_errorClassMessage = JS_GetPropertyStr(ctx, j_exceptionVal, "message");
222
+ const char *errorClassMessage = JS_ToCString(ctx, j_errorClassMessage);
223
+
224
+ JS_FreeValue(ctx, j_errorClassMessage);
225
+ JS_FreeValue(ctx, j_errorClassName);
226
+
227
+ VALUE r_error_message = rb_sprintf("%s: %s", errorClassName, errorClassMessage);
228
+ JS_FreeCString(ctx, errorClassName);
229
+ JS_FreeCString(ctx, errorClassMessage);
230
+ JS_FreeValue(ctx, j_exceptionVal);
231
+ rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, r_error_message));
232
+ }
233
+ else
234
+ {
235
+ const char *errorMessage = JS_ToCString(ctx, j_exceptionVal);
236
+ VALUE r_error_message = rb_sprintf("%s", errorMessage);
237
+
238
+ JS_FreeCString(ctx, errorMessage);
239
+ JS_FreeValue(ctx, j_exceptionVal);
240
+ rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, r_error_message));
197
241
  }
198
-
199
- JS_FreeValue(ctx, exceptionVal);
200
242
  return Qnil;
201
243
  }
202
- case JS_TAG_BIG_INT: {
203
- JSValue toStringFunc = JS_GetPropertyStr(ctx, jsv, "toString");
204
- JSValue strigified = JS_Call(ctx, toStringFunc, jsv, 0, NULL);
205
-
206
- const char *msg = JS_ToCString(ctx, strigified);
207
- VALUE rbString = rb_str_new2(msg);
208
- JS_FreeValue(ctx, strigified);
209
- JS_FreeValue(ctx, toStringFunc);
210
-
211
- return rb_funcall(rbString, rb_intern("to_i"), 0, NULL);
244
+ case JS_TAG_BIG_INT:
245
+ {
246
+ JSValue j_toStringFunc = JS_GetPropertyStr(ctx, j_val, "toString");
247
+ JSValue j_strigified = JS_Call(ctx, j_toStringFunc, j_val, 0, NULL);
248
+
249
+ const char *msg = JS_ToCString(ctx, j_strigified);
250
+ VALUE r_str = rb_str_new2(msg);
251
+ JS_FreeValue(ctx, j_toStringFunc);
252
+ JS_FreeValue(ctx, j_strigified);
253
+ JS_FreeCString(ctx, msg);
254
+
255
+ return rb_funcall(r_str, rb_intern("to_i"), 0, NULL);
212
256
  }
213
257
  case JS_TAG_BIG_FLOAT:
214
258
  case JS_TAG_BIG_DECIMAL:
@@ -218,48 +262,60 @@ VALUE to_rb_value(JSValue jsv, JSContext *ctx) {
218
262
  }
219
263
  }
220
264
 
221
- static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int _argc, JSValueConst *argv) {
265
+ static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int _argc, JSValueConst *argv)
266
+ {
222
267
  VMData *data = JS_GetContextOpaque(ctx);
223
- JSValue maybeFuncName = JS_ToString(ctx, argv[0]);
224
- const char *funcName = JS_ToCString(ctx, maybeFuncName);
225
- JS_FreeValue(ctx, maybeFuncName);
268
+ JSValue j_maybeFuncName = JS_ToString(ctx, argv[0]);
269
+ const char *funcName = JS_ToCString(ctx, j_maybeFuncName);
270
+ JS_FreeValue(ctx, j_maybeFuncName);
226
271
 
227
- VALUE proc = rb_hash_aref(data->defined_functions, rb_str_new2(funcName));
228
- if (proc == Qnil) { // Shouldn't happen
272
+ VALUE r_proc = rb_hash_aref(data->defined_functions, rb_str_new2(funcName));
273
+ if (r_proc == Qnil)
274
+ { // Shouldn't happen
229
275
  return JS_ThrowReferenceError(ctx, "Proc `%s` is not defined", funcName);
230
276
  }
277
+ JS_FreeCString(ctx, funcName);
278
+
279
+ VALUE r_result = rb_funcall(
280
+ rb_const_get(rb_cClass, rb_intern("Quickjs")),
281
+ rb_intern("_with_timeout"),
282
+ 3,
283
+ ULONG2NUM(data->eval_time->limit * 1000 / CLOCKS_PER_SEC),
284
+ r_proc,
285
+ to_rb_value(ctx, argv[1]));
231
286
 
232
- // TODO: cover timeout for calling proc
233
- VALUE r_result = rb_apply(proc, rb_intern("call"), to_rb_value(argv[1], ctx));
234
287
  return to_js_value(ctx, r_result);
235
288
  }
236
289
 
237
- static VALUE vm_m_initialize(int argc, VALUE* argv, VALUE self)
290
+ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
238
291
  {
239
292
  VALUE r_opts;
240
293
  rb_scan_args(argc, argv, ":", &r_opts);
241
- if (NIL_P(r_opts)) r_opts = rb_hash_new();
242
-
243
- VALUE r_memoryLimit = rb_hash_aref(r_opts, ID2SYM(rb_intern("memory_limit")));
244
- if (NIL_P(r_memoryLimit)) r_memoryLimit = UINT2NUM(1024 * 1024 * 128);
245
- VALUE r_maxStackSize = rb_hash_aref(r_opts, ID2SYM(rb_intern("max_stack_size")));
246
- if (NIL_P(r_maxStackSize)) r_maxStackSize = UINT2NUM(1024 * 1024 * 4);
294
+ if (NIL_P(r_opts))
295
+ r_opts = rb_hash_new();
296
+
297
+ VALUE r_memory_limit = rb_hash_aref(r_opts, ID2SYM(rb_intern("memory_limit")));
298
+ if (NIL_P(r_memory_limit))
299
+ r_memory_limit = UINT2NUM(1024 * 1024 * 128);
300
+ VALUE r_max_stack_size = rb_hash_aref(r_opts, ID2SYM(rb_intern("max_stack_size")));
301
+ if (NIL_P(r_max_stack_size))
302
+ r_max_stack_size = UINT2NUM(1024 * 1024 * 4);
247
303
  VALUE r_features = rb_hash_aref(r_opts, ID2SYM(rb_intern("features")));
248
- if (NIL_P(r_features)) r_features = rb_ary_new();
304
+ if (NIL_P(r_features))
305
+ r_features = rb_ary_new();
249
306
  VALUE r_timeout_msec = rb_hash_aref(r_opts, ID2SYM(rb_intern("timeout_msec")));
250
- if (NIL_P(r_timeout_msec)) r_timeout_msec= UINT2NUM(100);
307
+ if (NIL_P(r_timeout_msec))
308
+ r_timeout_msec = UINT2NUM(100);
251
309
 
252
310
  VMData *data;
253
- TypedData_Get_Struct(self, VMData, &vm_type, data);
311
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
254
312
 
255
- JSRuntime *runtime = JS_NewRuntime();
256
- data->context = JS_NewContext(runtime);
257
313
  data->eval_time->limit = (clock_t)(CLOCKS_PER_SEC * NUM2UINT(r_timeout_msec) / 1000);
258
- data->alive = 1;
259
314
  JS_SetContextOpaque(data->context, data);
315
+ JSRuntime *runtime = JS_GetRuntime(data->context);
260
316
 
261
- JS_SetMemoryLimit(runtime, NUM2UINT(r_memoryLimit));
262
- JS_SetMaxStackSize(runtime, NUM2UINT(r_maxStackSize));
317
+ JS_SetMemoryLimit(runtime, NUM2UINT(r_memory_limit));
318
+ JS_SetMaxStackSize(runtime, NUM2UINT(r_max_stack_size));
263
319
 
264
320
  JS_AddIntrinsicBigFloat(data->context);
265
321
  JS_AddIntrinsicBigDecimal(data->context);
@@ -270,110 +326,117 @@ static VALUE vm_m_initialize(int argc, VALUE* argv, VALUE self)
270
326
  JS_SetModuleLoaderFunc(runtime, NULL, js_module_loader, NULL);
271
327
  js_std_init_handlers(runtime);
272
328
 
273
- if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureStdId))))) {
329
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureStdId)))))
330
+ {
274
331
  js_init_module_std(data->context, "std");
275
332
  const char *enableStd = "import * as std from 'std';\n"
276
- "globalThis.std = std;\n";
277
- JSValue stdEval = JS_Eval(data->context, enableStd, strlen(enableStd), "<vm>", JS_EVAL_TYPE_MODULE);
278
- JS_FreeValue(data->context, stdEval);
333
+ "globalThis.std = std;\n";
334
+ JSValue j_stdEval = JS_Eval(data->context, enableStd, strlen(enableStd), "<vm>", JS_EVAL_TYPE_MODULE);
335
+ JS_FreeValue(data->context, j_stdEval);
279
336
  }
280
337
 
281
- if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureOsId))))) {
338
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureOsId)))))
339
+ {
282
340
  js_init_module_os(data->context, "os");
283
341
  const char *enableOs = "import * as os from 'os';\n"
284
- "globalThis.os = os;\n";
285
- JSValue osEval = JS_Eval(data->context, enableOs, strlen(enableOs), "<vm>", JS_EVAL_TYPE_MODULE);
286
- JS_FreeValue(data->context, osEval);
342
+ "globalThis.os = os;\n";
343
+ JSValue j_osEval = JS_Eval(data->context, enableOs, strlen(enableOs), "<vm>", JS_EVAL_TYPE_MODULE);
344
+ JS_FreeValue(data->context, j_osEval);
345
+ }
346
+ else if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureOsTimeoutId)))))
347
+ {
348
+ js_init_module_os(data->context, "_os"); // Better if this is limited just only for setTimeout and clearTimeout
349
+ const char *enableTimeout = "import * as _os from '_os';\n"
350
+ "globalThis.setTimeout = _os.setTimeout;\n"
351
+ "globalThis.clearTimeout = _os.clearTimeout;\n";
352
+ JSValue j_timeoutEval = JS_Eval(data->context, enableTimeout, strlen(enableTimeout), "<vm>", JS_EVAL_TYPE_MODULE);
353
+ JS_FreeValue(data->context, j_timeoutEval);
287
354
  }
288
355
 
289
356
  const char *setupGlobalRuby = "globalThis.__ruby = {};\n";
290
- JSValue rubyEval = JS_Eval(data->context, setupGlobalRuby, strlen(setupGlobalRuby), "<vm>", JS_EVAL_TYPE_MODULE);
291
- JS_FreeValue(data->context, rubyEval);
357
+ JSValue j_rubyEval = JS_Eval(data->context, setupGlobalRuby, strlen(setupGlobalRuby), "<vm>", JS_EVAL_TYPE_MODULE);
358
+ JS_FreeValue(data->context, j_rubyEval);
292
359
 
293
- JSValue global = JS_GetGlobalObject(data->context);
294
- JSValue func = JS_NewCFunction(data->context, js_quickjsrb_call_global, "rubyGlobal", 2);
295
- JS_SetPropertyStr(data->context, global, "rubyGlobal", func);
296
- JS_FreeValue(data->context, global);
360
+ JSValue j_global = JS_GetGlobalObject(data->context);
361
+ JSValue j_func = JS_NewCFunction(data->context, js_quickjsrb_call_global, "rubyGlobal", 2);
362
+ JS_SetPropertyStr(data->context, j_global, "rubyGlobal", j_func);
363
+ JS_FreeValue(data->context, j_global);
297
364
 
298
- return self;
365
+ return r_self;
299
366
  }
300
367
 
301
- static int interrupt_handler(JSRuntime *runtime, void *opaque) {
302
- EvalTime *eval_time = opaque;
303
- return clock() >= eval_time->started_at + eval_time->limit ? 1 : 0;
368
+ static int interrupt_handler(JSRuntime *runtime, void *opaque)
369
+ {
370
+ EvalTime *eval_time = opaque;
371
+ return clock() >= eval_time->started_at + eval_time->limit ? 1 : 0;
304
372
  }
305
373
 
306
- static VALUE vm_m_evalCode(VALUE self, VALUE r_code)
374
+ static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
307
375
  {
308
376
  VMData *data;
309
- TypedData_Get_Struct(self, VMData, &vm_type, data);
310
-
311
- if (data->alive < 1) {
312
- rb_raise(rb_eRuntimeError, "Quickjs::VM was disposed");
313
- return Qnil;
314
- }
377
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
315
378
 
316
379
  data->eval_time->started_at = clock();
317
380
  JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
318
381
 
319
382
  char *code = StringValueCStr(r_code);
320
- JSValue codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL);
321
- VALUE result = to_rb_value(codeResult, data->context);
322
-
323
- JS_FreeValue(data->context, codeResult);
324
- return result;
383
+ JSValue j_codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
384
+ JSValue j_awaitedResult = js_std_await(data->context, j_codeResult);
385
+ JSValue j_returnedValue = JS_GetPropertyStr(data->context, j_awaitedResult, "value");
386
+ // Do this by rescuing to_rb_value
387
+ if (JS_VALUE_GET_NORM_TAG(j_returnedValue) == JS_TAG_OBJECT && JS_PromiseState(data->context, j_returnedValue) != -1)
388
+ {
389
+ JS_FreeValue(data->context, j_returnedValue);
390
+ JS_FreeValue(data->context, j_awaitedResult);
391
+ VALUE r_error_message = rb_str_new2("An unawaited Promise was returned to the top-level");
392
+ rb_exc_raise(rb_exc_new_str(rb_eRuntimeError, r_error_message));
393
+ return Qnil;
394
+ }
395
+ else
396
+ {
397
+ VALUE result = to_rb_value(data->context, j_returnedValue);
398
+ JS_FreeValue(data->context, j_returnedValue);
399
+ JS_FreeValue(data->context, j_awaitedResult);
400
+ return result;
401
+ }
325
402
  }
326
403
 
327
- static VALUE vm_m_defineGlobalFunction(VALUE self, VALUE r_name)
404
+ static VALUE vm_m_defineGlobalFunction(VALUE r_self, VALUE r_name)
328
405
  {
329
406
  rb_need_block();
330
407
 
331
408
  VMData *data;
332
- TypedData_Get_Struct(self, VMData, &vm_type, data);
333
-
334
- if (rb_block_given_p()) {
335
- VALUE proc = rb_block_proc();
409
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
336
410
 
411
+ if (rb_block_given_p())
412
+ {
413
+ VALUE r_proc = rb_block_proc();
414
+ rb_hash_aset(data->defined_functions, r_name, r_proc);
337
415
  char *funcName = StringValueCStr(r_name);
338
416
 
339
- rb_hash_aset(data->defined_functions, r_name, proc);
340
-
341
- const char* template = "globalThis.__ruby['%s'] = (...args) => rubyGlobal('%s', args);\nglobalThis['%s'] = globalThis.__ruby['%s'];\n";
417
+ const char *template = "globalThis.__ruby['%s'] = (...args) => rubyGlobal('%s', args);\n"
418
+ "globalThis['%s'] = globalThis.__ruby['%s'];\n";
342
419
  int length = snprintf(NULL, 0, template, funcName, funcName, funcName, funcName);
343
- char* result = (char*)malloc(length + 1);
420
+ char *result = (char *)malloc(length + 1);
344
421
  snprintf(result, length + 1, template, funcName, funcName, funcName, funcName);
345
422
 
346
- JSValue codeResult = JS_Eval(data->context, result, strlen(result), "<vm>", JS_EVAL_TYPE_MODULE);
423
+ JSValue j_codeResult = JS_Eval(data->context, result, strlen(result), "<vm>", JS_EVAL_TYPE_MODULE);
347
424
 
348
- JS_FreeValue(data->context, codeResult);
349
425
  free(result);
426
+ JS_FreeValue(data->context, j_codeResult);
350
427
  return rb_funcall(r_name, rb_intern("to_sym"), 0, NULL);
351
428
  }
352
429
 
353
430
  return Qnil;
354
431
  }
355
432
 
356
- static VALUE vm_m_dispose(VALUE self)
357
- {
358
- VMData *data;
359
- TypedData_Get_Struct(self, VMData, &vm_type, data);
360
-
361
- JSRuntime *runtime = JS_GetRuntime(data->context);
362
- JS_SetInterruptHandler(runtime, NULL, NULL);
363
- js_std_free_handlers(runtime);
364
- JS_FreeContext(data->context);
365
- JS_FreeRuntime(runtime);
366
- data->alive = 0;
367
-
368
- return Qnil;
369
- }
370
-
371
433
  RUBY_FUNC_EXPORTED void
372
434
  Init_quickjsrb(void)
373
435
  {
374
436
  rb_mQuickjs = rb_define_module("Quickjs");
375
437
  rb_define_const(rb_mQuickjs, "MODULE_STD", ID2SYM(rb_intern(featureStdId)));
376
438
  rb_define_const(rb_mQuickjs, "MODULE_OS", ID2SYM(rb_intern(featureOsId)));
439
+ rb_define_const(rb_mQuickjs, "FEATURES_TIMEOUT", ID2SYM(rb_intern(featureOsTimeoutId)));
377
440
 
378
441
  VALUE valueClass = rb_define_class_under(rb_mQuickjs, "Value", rb_cObject);
379
442
  rb_define_const(valueClass, "UNDEFINED", ID2SYM(rb_intern(undefinedId)));
@@ -384,5 +447,4 @@ Init_quickjsrb(void)
384
447
  rb_define_method(vmClass, "initialize", vm_m_initialize, -1);
385
448
  rb_define_method(vmClass, "eval_code", vm_m_evalCode, 1);
386
449
  rb_define_method(vmClass, "define_function", vm_m_defineGlobalFunction, 1);
387
- rb_define_method(vmClass, "dispose!", vm_m_dispose, 0);
388
450
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.1.9"
4
+ VERSION = "0.1.11"
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
+ RuntimeError.new('interrupted')
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.9
4
+ version: 0.1.11
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-15 00:00:00.000000000 Z
11
+ date: 2024-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json