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 +4 -4
- data/README.md +9 -6
- data/ext/quickjsrb/quickjsrb.c +278 -216
- 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: 81c0e4a716d292ddbfd58f559f37ddb1d86adb7059779f29bfa0a2b8a8180214
|
4
|
+
data.tar.gz: f45ff31a1913c4ab807ddd5d8f08c17c9bc58236b09308f31f5adbf5d9241ffe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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).
|
data/ext/quickjsrb/quickjsrb.c
CHANGED
@@ -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
|
-
|
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*
|
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*
|
30
|
+
size_t vm_size(const void *data)
|
23
31
|
{
|
24
|
-
return sizeof(
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
51
|
+
static VALUE vm_alloc(VALUE r_self)
|
45
52
|
{
|
46
53
|
VMData *data;
|
47
|
-
VALUE obj = TypedData_Make_Struct(
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
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(
|
122
|
-
|
123
|
-
|
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,
|
159
|
+
JS_ToInt32(ctx, &int_res, j_val);
|
126
160
|
return INT2NUM(int_res);
|
127
161
|
}
|
128
|
-
case JS_TAG_FLOAT64:
|
129
|
-
|
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,
|
169
|
+
JS_ToFloat64(ctx, &double_res, j_val);
|
134
170
|
return DBL2NUM(double_res);
|
135
171
|
}
|
136
|
-
case JS_TAG_BOOL:
|
137
|
-
|
172
|
+
case JS_TAG_BOOL:
|
173
|
+
{
|
174
|
+
return JS_ToBool(ctx, j_val) > 0 ? Qtrue : Qfalse;
|
138
175
|
}
|
139
|
-
case JS_TAG_STRING:
|
140
|
-
|
141
|
-
const char *msg = JS_ToCString(ctx,
|
142
|
-
|
143
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
162
|
-
JSValue
|
163
|
-
JSValue
|
164
|
-
JSValue
|
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,
|
167
|
-
VALUE
|
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,
|
170
|
-
JS_FreeValue(ctx,
|
171
|
-
JS_FreeValue(ctx,
|
172
|
-
JS_FreeValue(ctx,
|
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,
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
204
|
-
JSValue
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
JS_FreeValue(ctx,
|
210
|
-
|
211
|
-
|
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
|
224
|
-
const char *funcName = JS_ToCString(ctx,
|
225
|
-
JS_FreeValue(ctx,
|
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
|
228
|
-
if (
|
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*
|
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))
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
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))
|
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))
|
307
|
+
if (NIL_P(r_timeout_msec))
|
308
|
+
r_timeout_msec = UINT2NUM(100);
|
251
309
|
|
252
310
|
VMData *data;
|
253
|
-
TypedData_Get_Struct(
|
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(
|
262
|
-
JS_SetMaxStackSize(runtime, NUM2UINT(
|
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
|
-
|
277
|
-
JSValue
|
278
|
-
JS_FreeValue(data->context,
|
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
|
-
|
285
|
-
JSValue
|
286
|
-
JS_FreeValue(data->context,
|
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
|
291
|
-
JS_FreeValue(data->context,
|
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
|
294
|
-
JSValue
|
295
|
-
JS_SetPropertyStr(data->context,
|
296
|
-
JS_FreeValue(data->context,
|
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
|
365
|
+
return r_self;
|
299
366
|
}
|
300
367
|
|
301
|
-
static int interrupt_handler(JSRuntime *runtime, void *opaque)
|
302
|
-
|
303
|
-
|
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
|
374
|
+
static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
|
307
375
|
{
|
308
376
|
VMData *data;
|
309
|
-
TypedData_Get_Struct(
|
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
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|
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(
|
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
|
-
|
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*
|
420
|
+
char *result = (char *)malloc(length + 1);
|
344
421
|
snprintf(result, length + 1, template, funcName, funcName, funcName, funcName);
|
345
422
|
|
346
|
-
JSValue
|
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
|
}
|
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
|
+
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.
|
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-
|
11
|
+
date: 2024-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|