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 +4 -4
- data/README.md +10 -1
- data/ext/quickjsrb/quickjsrb.c +353 -140
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +14 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 362c16217cefda63b3d8a4e0c41bdf4036c24e49540e5c223efc0751d5c84e44
|
4
|
+
data.tar.gz: 0415d93639765ffc7191964418e3a12f3c5fd51a547e854ff68141f1239c8e91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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).
|
data/ext/quickjsrb/quickjsrb.c
CHANGED
@@ -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
|
53
|
+
static VALUE vm_alloc(VALUE r_self)
|
52
54
|
{
|
53
55
|
VMData *data;
|
54
|
-
VALUE obj = TypedData_Make_Struct(
|
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
|
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
|
85
|
-
JSValue
|
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
|
88
|
-
JS_FreeValue(ctx,
|
89
|
-
JS_FreeValue(ctx,
|
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
|
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
|
117
|
-
JSValue
|
118
|
-
JSValue
|
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
|
121
|
-
JS_FreeValue(ctx,
|
122
|
-
JS_FreeValue(ctx,
|
123
|
-
JS_FreeValue(ctx,
|
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
|
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(
|
157
|
+
VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
139
158
|
{
|
140
|
-
switch (JS_VALUE_GET_NORM_TAG(
|
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,
|
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(
|
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,
|
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,
|
179
|
+
return JS_ToBool(ctx, j_val) > 0 ? Qtrue : Qfalse;
|
161
180
|
}
|
162
181
|
case JS_TAG_STRING:
|
163
182
|
{
|
164
|
-
|
165
|
-
|
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
|
186
|
+
return r_str;
|
169
187
|
}
|
170
188
|
case JS_TAG_OBJECT:
|
171
189
|
{
|
172
|
-
int promiseState = JS_PromiseState(ctx,
|
173
|
-
if (promiseState
|
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
|
-
|
183
|
-
|
184
|
-
|
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
|
191
|
-
JSValue
|
192
|
-
JSValue
|
193
|
-
JSValue
|
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,
|
196
|
-
VALUE
|
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,
|
200
|
-
JS_FreeValue(ctx,
|
201
|
-
JS_FreeValue(ctx,
|
202
|
-
JS_FreeValue(ctx,
|
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,
|
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
|
213
|
-
if (JS_IsError(ctx,
|
220
|
+
JSValue j_exceptionVal = JS_GetException(ctx);
|
221
|
+
if (JS_IsError(ctx, j_exceptionVal))
|
214
222
|
{
|
215
|
-
JSValue
|
216
|
-
const char *errorClassName = JS_ToCString(ctx,
|
217
|
-
|
218
|
-
JSValue
|
219
|
-
const char *errorClassMessage = JS_ToCString(ctx,
|
220
|
-
|
221
|
-
JS_FreeValue(ctx,
|
222
|
-
JS_FreeValue(ctx,
|
223
|
-
|
224
|
-
VALUE
|
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,
|
228
|
-
|
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,
|
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,
|
237
|
-
rb_exc_raise(rb_exc_new_str(
|
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
|
244
|
-
JSValue
|
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,
|
247
|
-
VALUE
|
248
|
-
JS_FreeValue(ctx,
|
249
|
-
JS_FreeValue(ctx,
|
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(
|
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
|
266
|
-
const char *funcName = JS_ToCString(ctx,
|
267
|
-
JS_FreeValue(ctx,
|
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
|
270
|
-
if (
|
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
|
-
|
277
|
-
|
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
|
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
|
289
|
-
if (NIL_P(
|
290
|
-
|
291
|
-
VALUE
|
292
|
-
if (NIL_P(
|
293
|
-
|
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(
|
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(
|
309
|
-
JS_SetMaxStackSize(runtime, NUM2UINT(
|
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
|
326
|
-
JS_FreeValue(data->context,
|
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
|
335
|
-
JS_FreeValue(data->context,
|
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
|
-
|
339
|
-
JSValue
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
JS_SetPropertyStr(data->context,
|
345
|
-
|
346
|
-
|
347
|
-
|
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
|
474
|
+
static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
|
357
475
|
{
|
358
476
|
VMData *data;
|
359
|
-
TypedData_Get_Struct(
|
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
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
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
|
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(
|
509
|
+
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
378
510
|
|
379
511
|
if (rb_block_given_p())
|
380
512
|
{
|
381
|
-
VALUE
|
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
|
-
|
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
|
520
|
+
snprintf(result, length + 1, template, funcName, funcName);
|
391
521
|
|
392
|
-
JSValue
|
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,
|
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
|
-
|
410
|
-
|
411
|
-
rb_define_const(
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
rb_define_method(
|
417
|
-
rb_define_method(
|
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
|
}
|
data/lib/quickjs/version.rb
CHANGED
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)
|
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.
|
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-
|
11
|
+
date: 2024-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|