quickjs 0.14.0 → 0.15.1
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 +32 -2
- data/ext/quickjsrb/extconf.rb +1 -2
- data/ext/quickjsrb/quickjsrb.c +272 -63
- data/lib/quickjs/function.rb +36 -0
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +4 -1
- data/polyfills/package-lock.json +2 -2
- data/polyfills/package.json +1 -1
- data/sig/quickjs.rbs +10 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54fe50175028245be99548bdb43a1884ceb956f77a999fca6c4cdf9ea9085a13
|
|
4
|
+
data.tar.gz: ddbf8d46cfec89db687899ec083e7ef2859ebd4ff9d711c9a18801d15c8d4ad0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce86220556d6e2bbf06f2da42227876f972a991e4beca36ecce89efcfc84f11ee6d62172813442368af11632f33f970db97535eab435f3fa6046be955c98529d
|
|
7
|
+
data.tar.gz: 255c23efa72230afbac3760e1cd8efd8ba29038263371ed4a4bc591dfbc58da9e48e2524bde82151cb8b10d14cda87255170fe0c46377efec8cb114ea8e9a855
|
data/README.md
CHANGED
|
@@ -40,6 +40,13 @@ Quickjs.eval_code(code,
|
|
|
40
40
|
)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
#### Filename
|
|
44
|
+
|
|
45
|
+
```rb
|
|
46
|
+
# Label shown in JS stack traces (default: "<code>")
|
|
47
|
+
Quickjs.eval_code(code, filename: 'my_script.js')
|
|
48
|
+
```
|
|
49
|
+
|
|
43
50
|
#### Timeout
|
|
44
51
|
|
|
45
52
|
```rb
|
|
@@ -133,6 +140,23 @@ end
|
|
|
133
140
|
vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'
|
|
134
141
|
```
|
|
135
142
|
|
|
143
|
+
Pass an `Array` as the name to register the function on an existing JS object (the last element is the method name; preceding elements are the object path):
|
|
144
|
+
|
|
145
|
+
```rb
|
|
146
|
+
vm = Quickjs::VM.new
|
|
147
|
+
vm.eval_code("const myLib = {}")
|
|
148
|
+
vm.define_function(["myLib", "greetingTo"]) { |name| "Hello, #{name}!" }
|
|
149
|
+
|
|
150
|
+
vm.eval_code("myLib.greetingTo('Rick')") #=> 'Hello! Rick'
|
|
151
|
+
|
|
152
|
+
# Deeply nested
|
|
153
|
+
vm.eval_code("const a = { b: { c: {} } }")
|
|
154
|
+
vm.define_function(["a", "b", "c", "double"]) { |x| x * 2 }
|
|
155
|
+
vm.eval_code("a.b.c.double(21)") #=> 42
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`define_function` returns the registered name as a `Symbol` (or an `Array` of `Symbol`s for array paths).
|
|
159
|
+
|
|
136
160
|
A Ruby exception raised inside the block is catchable in JS as an `Error`, and propagates back to Ruby as the original exception type if uncaught in JS.
|
|
137
161
|
|
|
138
162
|
```rb
|
|
@@ -177,14 +201,20 @@ vm.eval_code('console.log("hello", 42)')
|
|
|
177
201
|
| `string` | ↔ | `String` | |
|
|
178
202
|
| `true` / `false` | ↔ | `true` / `false` | |
|
|
179
203
|
| `null` | ↔ | `nil` | |
|
|
180
|
-
| `Array` | ↔ | `Array` |
|
|
181
|
-
| `Object` | ↔ | `Hash` |
|
|
204
|
+
| `Array` | ↔ | `Array` | recursively converted |
|
|
205
|
+
| `Object` | ↔ | `Hash` | recursively converted; keys are always `String` |
|
|
206
|
+
| `function` | → | `Quickjs::Function` — `.source`, `.call(*args, on:)` | |
|
|
182
207
|
| `undefined` | → | `Quickjs::Value::UNDEFINED` | |
|
|
183
208
|
| `NaN` | → | `Quickjs::Value::NAN` | |
|
|
184
209
|
| `Blob` | → | `Quickjs::Blob` — `.size`, `.type`, `.content` | requires `POLYFILL_FILE` |
|
|
185
210
|
| `File` | → | `Quickjs::File` — `.name`, `.last_modified` + Blob attrs | requires `POLYFILL_FILE` |
|
|
186
211
|
| `File` proxy | ← | `::File` | requires `POLYFILL_FILE`; applies to `define_function` return values |
|
|
187
212
|
|
|
213
|
+
## Acknowledgements
|
|
214
|
+
|
|
215
|
+
- [@ursm](https://github.com/ursm) — for continuous contributions improving performance and developer experience
|
|
216
|
+
- [@persona-id](https://github.com/persona-id) — for providing real-world use cases that shape the direction of this project
|
|
217
|
+
|
|
188
218
|
## License
|
|
189
219
|
|
|
190
220
|
- `ext/quickjsrb/quickjs`
|
data/ext/quickjsrb/extconf.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'mkmf'
|
|
4
4
|
|
|
5
5
|
$VPATH << "$(srcdir)/quickjs"
|
|
6
|
+
$INCFLAGS << " -I$(srcdir)/quickjs"
|
|
6
7
|
|
|
7
8
|
$srcs = [
|
|
8
9
|
'dtoa.c',
|
|
@@ -21,8 +22,6 @@ $srcs = [
|
|
|
21
22
|
'quickjsrb_crypto_subtle.c',
|
|
22
23
|
]
|
|
23
24
|
|
|
24
|
-
append_cflags('-I$(srcdir)/quickjs')
|
|
25
|
-
|
|
26
25
|
append_cflags('-g')
|
|
27
26
|
append_cflags('-O2')
|
|
28
27
|
append_cflags('-Wall')
|
data/ext/quickjsrb/quickjsrb.c
CHANGED
|
@@ -26,6 +26,9 @@ const int num_native_errors = sizeof(native_errors) / sizeof(native_errors[0]);
|
|
|
26
26
|
|
|
27
27
|
static int dispatch_log(VMData *data, const char *severity, VALUE r_row);
|
|
28
28
|
|
|
29
|
+
JSValue to_js_value(JSContext *ctx, VALUE r_value);
|
|
30
|
+
VALUE to_rb_value(JSContext *ctx, JSValue j_val);
|
|
31
|
+
|
|
29
32
|
JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
|
|
30
33
|
{
|
|
31
34
|
JSValue j_error = JS_NewError(ctx); // may wanna have custom error class to determine in JS' end
|
|
@@ -45,6 +48,33 @@ JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
|
|
|
45
48
|
return j_error;
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
typedef struct
|
|
52
|
+
{
|
|
53
|
+
JSContext *ctx;
|
|
54
|
+
JSValue j_obj;
|
|
55
|
+
} RbHashToJsArg;
|
|
56
|
+
|
|
57
|
+
static int rb_hash_entry_to_js(VALUE r_key, VALUE r_val, VALUE extra)
|
|
58
|
+
{
|
|
59
|
+
RbHashToJsArg *arg = (RbHashToJsArg *)extra;
|
|
60
|
+
const char *key_cstr;
|
|
61
|
+
if (SYMBOL_P(r_key))
|
|
62
|
+
{
|
|
63
|
+
key_cstr = rb_id2name(SYM2ID(r_key));
|
|
64
|
+
}
|
|
65
|
+
else if (RB_TYPE_P(r_key, T_STRING))
|
|
66
|
+
{
|
|
67
|
+
key_cstr = StringValueCStr(r_key);
|
|
68
|
+
}
|
|
69
|
+
else
|
|
70
|
+
{
|
|
71
|
+
VALUE r_key_str = rb_funcall(r_key, rb_intern("to_s"), 0);
|
|
72
|
+
key_cstr = StringValueCStr(r_key_str);
|
|
73
|
+
}
|
|
74
|
+
JS_SetPropertyStr(arg->ctx, arg->j_obj, key_cstr, to_js_value(arg->ctx, r_val));
|
|
75
|
+
return ST_CONTINUE;
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
49
79
|
{
|
|
50
80
|
switch (TYPE(r_value))
|
|
@@ -52,45 +82,57 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
52
82
|
case T_NIL:
|
|
53
83
|
return JS_NULL;
|
|
54
84
|
case T_FIXNUM:
|
|
85
|
+
return JS_NewInt64(ctx, NUM2LL(r_value));
|
|
55
86
|
case T_FLOAT:
|
|
87
|
+
return JS_NewFloat64(ctx, NUM2DBL(r_value));
|
|
88
|
+
case T_BIGNUM:
|
|
56
89
|
{
|
|
57
90
|
VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0);
|
|
58
|
-
|
|
91
|
+
JSValue j_str = JS_NewStringLen(ctx, RSTRING_PTR(r_str), RSTRING_LEN(r_str));
|
|
59
92
|
JSValue j_global = JS_GetGlobalObject(ctx);
|
|
60
93
|
JSValue j_numberClass = JS_GetPropertyStr(ctx, j_global, "Number");
|
|
61
|
-
JSValue
|
|
62
|
-
JSValue j_stringified = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, (JSValueConst *)&j_str);
|
|
63
|
-
JS_FreeValue(ctx, j_global);
|
|
64
|
-
JS_FreeValue(ctx, j_numberClass);
|
|
94
|
+
JSValue j_num = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, (JSValueConst *)&j_str);
|
|
65
95
|
JS_FreeValue(ctx, j_str);
|
|
66
|
-
|
|
67
|
-
|
|
96
|
+
JS_FreeValue(ctx, j_numberClass);
|
|
97
|
+
JS_FreeValue(ctx, j_global);
|
|
98
|
+
return j_num;
|
|
68
99
|
}
|
|
69
100
|
case T_STRING:
|
|
70
|
-
|
|
71
|
-
char *str = StringValueCStr(r_value);
|
|
72
|
-
|
|
73
|
-
return JS_NewString(ctx, str);
|
|
74
|
-
}
|
|
101
|
+
return JS_NewStringLen(ctx, RSTRING_PTR(r_value), RSTRING_LEN(r_value));
|
|
75
102
|
case T_SYMBOL:
|
|
76
103
|
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
104
|
+
if (r_value == QUICKJSRB_SYM(undefinedId))
|
|
105
|
+
return JS_UNDEFINED;
|
|
106
|
+
if (r_value == QUICKJSRB_SYM(nanId))
|
|
107
|
+
{
|
|
108
|
+
JSValue j_global = JS_GetGlobalObject(ctx);
|
|
109
|
+
JSValue j_nan = JS_GetPropertyStr(ctx, j_global, "NaN");
|
|
110
|
+
JS_FreeValue(ctx, j_global);
|
|
111
|
+
return j_nan;
|
|
112
|
+
}
|
|
113
|
+
const char *name = rb_id2name(SYM2ID(r_value));
|
|
114
|
+
return JS_NewString(ctx, name);
|
|
81
115
|
}
|
|
82
116
|
case T_TRUE:
|
|
83
117
|
return JS_TRUE;
|
|
84
118
|
case T_FALSE:
|
|
85
119
|
return JS_FALSE;
|
|
86
|
-
case T_HASH:
|
|
87
120
|
case T_ARRAY:
|
|
88
121
|
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
122
|
+
int len = RARRAY_LEN(r_value);
|
|
123
|
+
JSValue j_arr = JS_NewArray(ctx);
|
|
124
|
+
for (int i = 0; i < len; i++)
|
|
125
|
+
{
|
|
126
|
+
JS_SetPropertyUint32(ctx, j_arr, (uint32_t)i, to_js_value(ctx, RARRAY_AREF(r_value, i)));
|
|
127
|
+
}
|
|
128
|
+
return j_arr;
|
|
129
|
+
}
|
|
130
|
+
case T_HASH:
|
|
131
|
+
{
|
|
132
|
+
JSValue j_obj = JS_NewObject(ctx);
|
|
133
|
+
RbHashToJsArg arg = {ctx, j_obj};
|
|
134
|
+
rb_hash_foreach(r_value, rb_hash_entry_to_js, (VALUE)&arg);
|
|
135
|
+
return j_obj;
|
|
94
136
|
}
|
|
95
137
|
default:
|
|
96
138
|
{
|
|
@@ -100,10 +142,7 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
100
142
|
if (!JS_IsUndefined(data->j_file_proxy_creator))
|
|
101
143
|
return quickjsrb_file_to_js(ctx, r_value);
|
|
102
144
|
}
|
|
103
|
-
if (
|
|
104
|
-
r_value,
|
|
105
|
-
rb_intern("is_a?"),
|
|
106
|
-
1, rb_const_get(rb_cClass, rb_intern("Exception")))))
|
|
145
|
+
if (rb_obj_is_kind_of(r_value, rb_eException))
|
|
107
146
|
{
|
|
108
147
|
return j_error_from_ruby_error(ctx, r_value);
|
|
109
148
|
}
|
|
@@ -157,6 +196,59 @@ VALUE to_r_json(JSContext *ctx, JSValue j_val)
|
|
|
157
196
|
return r_str;
|
|
158
197
|
}
|
|
159
198
|
|
|
199
|
+
static int js_is_plain_object(JSContext *ctx, JSValue j_val)
|
|
200
|
+
{
|
|
201
|
+
JSValue j_proto = JS_GetPrototype(ctx, j_val);
|
|
202
|
+
if (JS_IsNull(j_proto))
|
|
203
|
+
return 1; // Object.create(null)
|
|
204
|
+
JSValue j_global = JS_GetGlobalObject(ctx);
|
|
205
|
+
JSValue j_Object = JS_GetPropertyStr(ctx, j_global, "Object");
|
|
206
|
+
JSValue j_Object_proto = JS_GetPropertyStr(ctx, j_Object, "prototype");
|
|
207
|
+
int result = JS_StrictEq(ctx, j_proto, j_Object_proto);
|
|
208
|
+
JS_FreeValue(ctx, j_proto);
|
|
209
|
+
JS_FreeValue(ctx, j_global);
|
|
210
|
+
JS_FreeValue(ctx, j_Object);
|
|
211
|
+
JS_FreeValue(ctx, j_Object_proto);
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static VALUE js_array_to_rb(JSContext *ctx, JSValue j_val)
|
|
216
|
+
{
|
|
217
|
+
JSValue j_length = JS_GetPropertyStr(ctx, j_val, "length");
|
|
218
|
+
uint32_t length = 0;
|
|
219
|
+
JS_ToUint32(ctx, &length, j_length);
|
|
220
|
+
JS_FreeValue(ctx, j_length);
|
|
221
|
+
|
|
222
|
+
VALUE r_array = rb_ary_new_capa(length);
|
|
223
|
+
for (uint32_t i = 0; i < length; i++)
|
|
224
|
+
{
|
|
225
|
+
JSValue j_elem = JS_GetPropertyUint32(ctx, j_val, i);
|
|
226
|
+
rb_ary_push(r_array, to_rb_value(ctx, j_elem));
|
|
227
|
+
JS_FreeValue(ctx, j_elem);
|
|
228
|
+
}
|
|
229
|
+
return r_array;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static VALUE js_plain_object_to_rb(JSContext *ctx, JSValue j_val)
|
|
233
|
+
{
|
|
234
|
+
JSPropertyEnum *ptab;
|
|
235
|
+
uint32_t plen;
|
|
236
|
+
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, j_val, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0)
|
|
237
|
+
return rb_hash_new();
|
|
238
|
+
|
|
239
|
+
VALUE r_hash = rb_hash_new();
|
|
240
|
+
for (uint32_t i = 0; i < plen; i++)
|
|
241
|
+
{
|
|
242
|
+
const char *key = JS_AtomToCString(ctx, ptab[i].atom);
|
|
243
|
+
JSValue j_prop = JS_GetProperty(ctx, j_val, ptab[i].atom);
|
|
244
|
+
rb_hash_aset(r_hash, rb_str_new2(key), to_rb_value(ctx, j_prop));
|
|
245
|
+
JS_FreeCString(ctx, key);
|
|
246
|
+
JS_FreeValue(ctx, j_prop);
|
|
247
|
+
}
|
|
248
|
+
JS_FreePropertyEnum(ctx, ptab, plen);
|
|
249
|
+
return r_hash;
|
|
250
|
+
}
|
|
251
|
+
|
|
160
252
|
VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
161
253
|
{
|
|
162
254
|
switch (JS_VALUE_GET_NORM_TAG(j_val))
|
|
@@ -179,16 +271,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
179
271
|
}
|
|
180
272
|
case JS_TAG_STRING:
|
|
181
273
|
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
{
|
|
274
|
+
size_t len;
|
|
275
|
+
const char *str = JS_ToCStringLen(ctx, &len, j_val);
|
|
276
|
+
if (str == NULL)
|
|
186
277
|
return Qnil;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return r_result;
|
|
191
|
-
}
|
|
278
|
+
VALUE r_str = rb_utf8_str_new(str, (long)len);
|
|
279
|
+
JS_FreeCString(ctx, str);
|
|
280
|
+
return r_str;
|
|
192
281
|
}
|
|
193
282
|
case JS_TAG_OBJECT:
|
|
194
283
|
{
|
|
@@ -200,6 +289,18 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
200
289
|
return Qnil;
|
|
201
290
|
}
|
|
202
291
|
|
|
292
|
+
if (JS_IsFunction(ctx, j_val))
|
|
293
|
+
{
|
|
294
|
+
JSValue j_toStringFunc = JS_GetPropertyStr(ctx, j_val, "toString");
|
|
295
|
+
JSValue j_source = JS_Call(ctx, j_toStringFunc, j_val, 0, NULL);
|
|
296
|
+
JS_FreeValue(ctx, j_toStringFunc);
|
|
297
|
+
const char *source = JS_ToCString(ctx, j_source);
|
|
298
|
+
JS_FreeValue(ctx, j_source);
|
|
299
|
+
VALUE r_source = rb_str_new2(source);
|
|
300
|
+
JS_FreeCString(ctx, source);
|
|
301
|
+
return rb_funcall(rb_path2class("Quickjs::Function"), rb_intern("new"), 1, r_source);
|
|
302
|
+
}
|
|
303
|
+
|
|
203
304
|
if (JS_IsError(ctx, j_val))
|
|
204
305
|
{
|
|
205
306
|
VALUE r_maybe_ruby_error = find_ruby_error(ctx, j_val);
|
|
@@ -239,6 +340,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
239
340
|
return r_maybe_file;
|
|
240
341
|
}
|
|
241
342
|
|
|
343
|
+
if (JS_IsArray(ctx, j_val))
|
|
344
|
+
return js_array_to_rb(ctx, j_val);
|
|
345
|
+
|
|
346
|
+
if (js_is_plain_object(ctx, j_val))
|
|
347
|
+
return js_plain_object_to_rb(ctx, j_val);
|
|
348
|
+
|
|
349
|
+
// Fallback: non-plain objects (Date, RegExp, Map, class instances, functions, etc.)
|
|
242
350
|
VALUE r_str = to_r_json(ctx, j_val);
|
|
243
351
|
|
|
244
352
|
if (rb_funcall(r_str, rb_intern("=="), 1, rb_str_new2("undefined")))
|
|
@@ -376,16 +484,19 @@ static VALUE r_try_call_proc(VALUE r_try_args)
|
|
|
376
484
|
|
|
377
485
|
static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, int _magic, JSValue *func_data)
|
|
378
486
|
{
|
|
379
|
-
|
|
487
|
+
// func_data[0] holds the Ruby Symbol ID for the defined function (stored by
|
|
488
|
+
// vm_m_defineGlobalFunction). Looking up by ID avoids a JS_ToCString +
|
|
489
|
+
// rb_intern round-trip on every call.
|
|
490
|
+
int64_t key_id;
|
|
491
|
+
JS_ToInt64(ctx, &key_id, func_data[0]);
|
|
380
492
|
|
|
381
493
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
382
|
-
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM(
|
|
494
|
+
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM((ID)key_id));
|
|
383
495
|
// Shouldn't happen
|
|
384
496
|
if (r_proc == Qnil)
|
|
385
497
|
{
|
|
386
|
-
return JS_ThrowReferenceError(ctx, "Proc
|
|
498
|
+
return JS_ThrowReferenceError(ctx, "Proc is not defined");
|
|
387
499
|
}
|
|
388
|
-
JS_FreeCString(ctx, funcName);
|
|
389
500
|
|
|
390
501
|
VALUE r_call_args = rb_ary_new();
|
|
391
502
|
rb_ary_push(r_call_args, r_proc);
|
|
@@ -736,22 +847,36 @@ static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
|
|
|
736
847
|
return result;
|
|
737
848
|
}
|
|
738
849
|
|
|
739
|
-
static VALUE vm_m_evalCode(VALUE
|
|
850
|
+
static VALUE vm_m_evalCode(int argc, VALUE *argv, VALUE r_self)
|
|
740
851
|
{
|
|
741
852
|
VMData *data;
|
|
742
853
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
743
854
|
|
|
855
|
+
VALUE r_code, r_opts;
|
|
856
|
+
rb_scan_args(argc, argv, "1:", &r_code, &r_opts);
|
|
857
|
+
|
|
744
858
|
if (!RB_TYPE_P(r_code, T_STRING))
|
|
745
859
|
{
|
|
746
860
|
VALUE r_code_class = rb_class_name(CLASS_OF(r_code));
|
|
747
861
|
rb_raise(rb_eTypeError, "JavaScript code must be a String, got %s", StringValueCStr(r_code_class));
|
|
748
862
|
}
|
|
749
863
|
|
|
864
|
+
const char *filename = "<code>";
|
|
865
|
+
if (!NIL_P(r_opts))
|
|
866
|
+
{
|
|
867
|
+
VALUE r_filename = rb_hash_aref(r_opts, ID2SYM(rb_intern("filename")));
|
|
868
|
+
if (!NIL_P(r_filename))
|
|
869
|
+
{
|
|
870
|
+
Check_Type(r_filename, T_STRING);
|
|
871
|
+
filename = StringValueCStr(r_filename);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
750
875
|
clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
|
|
751
876
|
JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
|
|
752
877
|
|
|
753
|
-
|
|
754
|
-
JSValue j_codeResult = JS_Eval(data->context,
|
|
878
|
+
StringValue(r_code);
|
|
879
|
+
JSValue j_codeResult = JS_Eval(data->context, RSTRING_PTR(r_code), RSTRING_LEN(r_code), filename, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
|
|
755
880
|
JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
|
|
756
881
|
// JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
|
|
757
882
|
// Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
|
|
@@ -769,33 +894,117 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
769
894
|
VALUE r_block;
|
|
770
895
|
rb_scan_args(argc, argv, "10*&", &r_name, &r_flags, &r_block);
|
|
771
896
|
|
|
772
|
-
if (!(SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING)))
|
|
773
|
-
{
|
|
774
|
-
rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
|
|
775
|
-
}
|
|
776
|
-
|
|
777
897
|
VMData *data;
|
|
778
898
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
779
899
|
|
|
780
|
-
|
|
900
|
+
if (RB_TYPE_P(r_name, T_ARRAY))
|
|
901
|
+
{
|
|
902
|
+
long path_len = RARRAY_LEN(r_name);
|
|
903
|
+
if (path_len < 1)
|
|
904
|
+
rb_raise(rb_eArgError, "function's path array must not be empty");
|
|
781
905
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
906
|
+
for (long i = 0; i < path_len; i++)
|
|
907
|
+
{
|
|
908
|
+
VALUE r_seg = RARRAY_AREF(r_name, i);
|
|
909
|
+
if (!(SYMBOL_P(r_seg) || RB_TYPE_P(r_seg, T_STRING)))
|
|
910
|
+
rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
|
|
911
|
+
}
|
|
785
912
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
913
|
+
// Build internal lookup key by joining path segments with "."
|
|
914
|
+
// e.g. ["myLib", "hello"] -> :"myLib.hello"
|
|
915
|
+
VALUE r_segs = rb_ary_new();
|
|
916
|
+
for (long i = 0; i < path_len; i++)
|
|
917
|
+
rb_ary_push(r_segs, rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_s"), 0));
|
|
918
|
+
VALUE r_key_str = rb_funcall(r_segs, rb_intern("join"), 1, rb_str_new2("."));
|
|
919
|
+
VALUE r_key_sym = rb_funcall(r_key_str, rb_intern("to_sym"), 0);
|
|
920
|
+
rb_hash_aset(data->defined_functions, r_key_sym, r_block);
|
|
921
|
+
|
|
922
|
+
VALUE r_func_seg_str = rb_funcall(RARRAY_AREF(r_name, path_len - 1), rb_intern("to_s"), 0);
|
|
923
|
+
char *funcName = StringValueCStr(r_func_seg_str);
|
|
924
|
+
|
|
925
|
+
JSValueConst ruby_data[2];
|
|
926
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_key_sym));
|
|
927
|
+
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
928
|
+
|
|
929
|
+
// Resolve the parent object to attach the function to.
|
|
930
|
+
// For a single-element array, parent is the global object.
|
|
931
|
+
// For multi-element arrays, traverse path[0..n-2] using JS_Eval for the first
|
|
932
|
+
// segment (so lexical const/let bindings are resolved, not just global properties)
|
|
933
|
+
// and JS_GetPropertyStr for subsequent segments.
|
|
934
|
+
JSValue j_parent;
|
|
935
|
+
if (path_len == 1)
|
|
936
|
+
{
|
|
937
|
+
j_parent = JS_GetGlobalObject(data->context);
|
|
938
|
+
}
|
|
939
|
+
else
|
|
940
|
+
{
|
|
941
|
+
VALUE r_first_str = rb_funcall(RARRAY_AREF(r_name, 0), rb_intern("to_s"), 0);
|
|
942
|
+
const char *first_seg = StringValueCStr(r_first_str);
|
|
943
|
+
j_parent = JS_Eval(data->context, first_seg, strlen(first_seg), "<vm>", JS_EVAL_TYPE_GLOBAL);
|
|
789
944
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
945
|
+
if (JS_IsException(j_parent) || !JS_IsObject(j_parent))
|
|
946
|
+
{
|
|
947
|
+
JS_FreeValue(data->context, j_parent);
|
|
948
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
949
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
950
|
+
rb_raise(rb_eArgError, "cannot define function: '%s' is not an object", first_seg);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
for (long i = 1; i < path_len - 1; i++)
|
|
954
|
+
{
|
|
955
|
+
VALUE r_seg_str = rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_s"), 0);
|
|
956
|
+
JSValue j_next = JS_GetPropertyStr(data->context, j_parent, StringValueCStr(r_seg_str));
|
|
957
|
+
JS_FreeValue(data->context, j_parent);
|
|
958
|
+
|
|
959
|
+
if (JS_IsException(j_next) || !JS_IsObject(j_next))
|
|
960
|
+
{
|
|
961
|
+
JS_FreeValue(data->context, j_next);
|
|
962
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
963
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
964
|
+
rb_raise(rb_eArgError, "cannot define function: '%s' is not an object", StringValueCStr(r_seg_str));
|
|
965
|
+
}
|
|
966
|
+
j_parent = j_next;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
JS_SetPropertyStr(
|
|
971
|
+
data->context, j_parent, funcName,
|
|
972
|
+
JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
|
|
973
|
+
JS_FreeValue(data->context, j_parent);
|
|
974
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
975
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
976
|
+
|
|
977
|
+
VALUE r_result = rb_ary_new();
|
|
978
|
+
for (long i = 0; i < path_len; i++)
|
|
979
|
+
rb_ary_push(r_result, rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_sym"), 0));
|
|
980
|
+
return r_result;
|
|
981
|
+
}
|
|
982
|
+
else if (SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING))
|
|
983
|
+
{
|
|
984
|
+
VALUE r_name_sym = rb_funcall(r_name, rb_intern("to_sym"), 0);
|
|
797
985
|
|
|
798
|
-
|
|
986
|
+
rb_hash_aset(data->defined_functions, r_name_sym, r_block);
|
|
987
|
+
VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
|
|
988
|
+
char *funcName = StringValueCStr(r_name_str);
|
|
989
|
+
|
|
990
|
+
JSValueConst ruby_data[2];
|
|
991
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_name_sym));
|
|
992
|
+
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
993
|
+
|
|
994
|
+
JSValue j_global = JS_GetGlobalObject(data->context);
|
|
995
|
+
JS_SetPropertyStr(
|
|
996
|
+
data->context, j_global, funcName,
|
|
997
|
+
JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
|
|
998
|
+
JS_FreeValue(data->context, j_global);
|
|
999
|
+
JS_FreeValue(data->context, ruby_data[0]);
|
|
1000
|
+
JS_FreeValue(data->context, ruby_data[1]);
|
|
1001
|
+
|
|
1002
|
+
return r_name_sym;
|
|
1003
|
+
}
|
|
1004
|
+
else
|
|
1005
|
+
{
|
|
1006
|
+
rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
|
|
1007
|
+
}
|
|
799
1008
|
}
|
|
800
1009
|
|
|
801
1010
|
static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
@@ -1006,7 +1215,7 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
1006
1215
|
VALUE r_class_vm = rb_define_class_under(r_module_quickjs, "VM", rb_cObject);
|
|
1007
1216
|
rb_define_alloc_func(r_class_vm, vm_alloc);
|
|
1008
1217
|
rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
|
|
1009
|
-
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, 1);
|
|
1218
|
+
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, -1);
|
|
1010
1219
|
rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
|
|
1011
1220
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
1012
1221
|
rb_define_method(r_class_vm, "import", vm_m_import, -1);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Quickjs
|
|
6
|
+
class Function
|
|
7
|
+
def initialize(source)
|
|
8
|
+
@source = source
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def source
|
|
12
|
+
@source
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(*args, on: nil)
|
|
16
|
+
case on
|
|
17
|
+
when Quickjs::VM
|
|
18
|
+
_call_on(on, args)
|
|
19
|
+
when nil, Hash
|
|
20
|
+
vm = Quickjs::VM.new(**on || {})
|
|
21
|
+
res = _call_on(vm, args)
|
|
22
|
+
vm = nil
|
|
23
|
+
res
|
|
24
|
+
else
|
|
25
|
+
raise ArgumentError, 'on: must be a Quickjs::VM, a Hash of VM options, or nil'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def _call_on(vm, args)
|
|
32
|
+
args_js = args.map { |a| JSON.generate(a) }.join(', ')
|
|
33
|
+
vm.eval_code("(#{@source})(#{args_js})")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/quickjs/version.rb
CHANGED
data/lib/quickjs.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "timeout"
|
|
|
5
5
|
require_relative "quickjs/version"
|
|
6
6
|
require_relative "quickjs/subtle_crypto"
|
|
7
7
|
require_relative "quickjs/crypto_key"
|
|
8
|
+
require_relative "quickjs/function"
|
|
8
9
|
require_relative "quickjs/quickjsrb"
|
|
9
10
|
|
|
10
11
|
module Quickjs
|
|
@@ -17,8 +18,10 @@ module Quickjs
|
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def eval_code(code, overwrite_opts = {})
|
|
21
|
+
eval_opts = {}
|
|
22
|
+
eval_opts[:filename] = overwrite_opts.delete(:filename) if overwrite_opts.key?(:filename)
|
|
20
23
|
vm = Quickjs::VM.new(**overwrite_opts)
|
|
21
|
-
res = vm.eval_code(code)
|
|
24
|
+
res = vm.eval_code(code, **eval_opts)
|
|
22
25
|
vm = nil
|
|
23
26
|
res
|
|
24
27
|
end
|
data/polyfills/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickjs-rb-polyfills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "quickjs-rb-polyfills",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.15.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@formatjs/intl-datetimeformat": "^7.3.1",
|
|
12
12
|
"@formatjs/intl-getcanonicallocales": "^3.2.2",
|
data/polyfills/package.json
CHANGED
data/sig/quickjs.rbs
CHANGED
|
@@ -21,11 +21,12 @@ module Quickjs
|
|
|
21
21
|
class VM
|
|
22
22
|
def initialize: (?features: Array[Symbol], ?memory_limit: Integer, ?max_stack_size: Integer, ?timeout_msec: Integer) -> void
|
|
23
23
|
|
|
24
|
-
def eval_code: (String code) -> untyped
|
|
24
|
+
def eval_code: (String code, ?filename: String) -> untyped
|
|
25
25
|
|
|
26
26
|
def call: (String | Symbol name, *untyped args) -> untyped
|
|
27
27
|
|
|
28
28
|
def define_function: (String | Symbol name, *Symbol flags) { (*untyped) -> untyped } -> Symbol
|
|
29
|
+
| (Array[String | Symbol] path, *Symbol flags) { (*untyped) -> untyped } -> Array[Symbol]
|
|
29
30
|
|
|
30
31
|
def import: (String | Array[String] | Hash[Symbol, String] imported, from: String, ?code_to_expose: String?) -> true
|
|
31
32
|
|
|
@@ -42,6 +43,14 @@ module Quickjs
|
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
class Function
|
|
47
|
+
def initialize: (String source) -> void
|
|
48
|
+
|
|
49
|
+
def source: () -> String
|
|
50
|
+
|
|
51
|
+
def call: (*untyped args, ?on: VM | Hash[Symbol, untyped] | nil) -> untyped
|
|
52
|
+
end
|
|
53
|
+
|
|
45
54
|
class RuntimeError < ::RuntimeError
|
|
46
55
|
def initialize: (String message, String? js_name) -> void
|
|
47
56
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quickjs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hmsk
|
|
@@ -90,6 +90,7 @@ files:
|
|
|
90
90
|
- ext/quickjsrb/vendor/polyfill-url.min.js
|
|
91
91
|
- lib/quickjs.rb
|
|
92
92
|
- lib/quickjs/crypto_key.rb
|
|
93
|
+
- lib/quickjs/function.rb
|
|
93
94
|
- lib/quickjs/subtle_crypto.rb
|
|
94
95
|
- lib/quickjs/version.rb
|
|
95
96
|
- polyfills/check-licenses.mjs
|