quickjs 0.15.0 → 0.15.2
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/quickjsrb.c +64 -41
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +3 -1
- data/polyfills/package-lock.json +2 -2
- data/polyfills/package.json +1 -1
- data/sig/quickjs.rbs +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8cfe5ee5e8eef08251b2d90c11763cbbfa4c0a5a64e83d201f0e15c576f1c597
|
|
4
|
+
data.tar.gz: 9a59bb09ba65423d6f09a8afb0e0aa2e5c91b89155fbf6c5b1f15065cefbdbd3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ab5cb1e220ffcb01350c1ee43e7167710a5cda902945fee155e292352d7c10bd309959c8816a26bcb54fe0ac8291f910156c8d3ad6f64397b71c2e706a2cac0
|
|
7
|
+
data.tar.gz: acfbac2d942837185453f203debc9280f5a77529307e6ca07f222a26b9377b93fad1531a4db3ccd3e85a18cd264536f73542d92696726ff4d4ed2f3db5c12478
|
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/quickjsrb.c
CHANGED
|
@@ -57,8 +57,21 @@ typedef struct
|
|
|
57
57
|
static int rb_hash_entry_to_js(VALUE r_key, VALUE r_val, VALUE extra)
|
|
58
58
|
{
|
|
59
59
|
RbHashToJsArg *arg = (RbHashToJsArg *)extra;
|
|
60
|
-
|
|
61
|
-
|
|
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));
|
|
62
75
|
return ST_CONTINUE;
|
|
63
76
|
}
|
|
64
77
|
|
|
@@ -69,26 +82,23 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
69
82
|
case T_NIL:
|
|
70
83
|
return JS_NULL;
|
|
71
84
|
case T_FIXNUM:
|
|
85
|
+
return JS_NewInt64(ctx, NUM2LL(r_value));
|
|
72
86
|
case T_FLOAT:
|
|
87
|
+
return JS_NewFloat64(ctx, NUM2DBL(r_value));
|
|
88
|
+
case T_BIGNUM:
|
|
73
89
|
{
|
|
74
90
|
VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0);
|
|
75
|
-
|
|
91
|
+
JSValue j_str = JS_NewStringLen(ctx, RSTRING_PTR(r_str), RSTRING_LEN(r_str));
|
|
76
92
|
JSValue j_global = JS_GetGlobalObject(ctx);
|
|
77
93
|
JSValue j_numberClass = JS_GetPropertyStr(ctx, j_global, "Number");
|
|
78
|
-
JSValue
|
|
79
|
-
JSValue j_stringified = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, (JSValueConst *)&j_str);
|
|
80
|
-
JS_FreeValue(ctx, j_global);
|
|
81
|
-
JS_FreeValue(ctx, j_numberClass);
|
|
94
|
+
JSValue j_num = JS_Call(ctx, j_numberClass, JS_UNDEFINED, 1, (JSValueConst *)&j_str);
|
|
82
95
|
JS_FreeValue(ctx, j_str);
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
JS_FreeValue(ctx, j_numberClass);
|
|
97
|
+
JS_FreeValue(ctx, j_global);
|
|
98
|
+
return j_num;
|
|
85
99
|
}
|
|
86
100
|
case T_STRING:
|
|
87
|
-
|
|
88
|
-
char *str = StringValueCStr(r_value);
|
|
89
|
-
|
|
90
|
-
return JS_NewString(ctx, str);
|
|
91
|
-
}
|
|
101
|
+
return JS_NewStringLen(ctx, RSTRING_PTR(r_value), RSTRING_LEN(r_value));
|
|
92
102
|
case T_SYMBOL:
|
|
93
103
|
{
|
|
94
104
|
if (r_value == QUICKJSRB_SYM(undefinedId))
|
|
@@ -100,10 +110,8 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
100
110
|
JS_FreeValue(ctx, j_global);
|
|
101
111
|
return j_nan;
|
|
102
112
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return JS_NewString(ctx, str);
|
|
113
|
+
const char *name = rb_id2name(SYM2ID(r_value));
|
|
114
|
+
return JS_NewString(ctx, name);
|
|
107
115
|
}
|
|
108
116
|
case T_TRUE:
|
|
109
117
|
return JS_TRUE;
|
|
@@ -134,10 +142,7 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
134
142
|
if (!JS_IsUndefined(data->j_file_proxy_creator))
|
|
135
143
|
return quickjsrb_file_to_js(ctx, r_value);
|
|
136
144
|
}
|
|
137
|
-
if (
|
|
138
|
-
r_value,
|
|
139
|
-
rb_intern("is_a?"),
|
|
140
|
-
1, rb_const_get(rb_cClass, rb_intern("Exception")))))
|
|
145
|
+
if (rb_obj_is_kind_of(r_value, rb_eException))
|
|
141
146
|
{
|
|
142
147
|
return j_error_from_ruby_error(ctx, r_value);
|
|
143
148
|
}
|
|
@@ -265,17 +270,18 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
265
270
|
return JS_ToBool(ctx, j_val) > 0 ? Qtrue : Qfalse;
|
|
266
271
|
}
|
|
267
272
|
case JS_TAG_STRING:
|
|
273
|
+
case JS_TAG_STRING_ROPE:
|
|
268
274
|
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
275
|
+
// QuickJS keeps long `s += chunk` chains as a rope (JS_TAG_STRING_ROPE)
|
|
276
|
+
// until something materialises them. JS_ToCStringLen flattens ropes
|
|
277
|
+
// transparently, so both tags share the same conversion path.
|
|
278
|
+
size_t len;
|
|
279
|
+
const char *str = JS_ToCStringLen(ctx, &len, j_val);
|
|
280
|
+
if (str == NULL)
|
|
273
281
|
return Qnil;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return r_result;
|
|
278
|
-
}
|
|
282
|
+
VALUE r_str = rb_utf8_str_new(str, (long)len);
|
|
283
|
+
JS_FreeCString(ctx, str);
|
|
284
|
+
return r_str;
|
|
279
285
|
}
|
|
280
286
|
case JS_TAG_OBJECT:
|
|
281
287
|
{
|
|
@@ -482,16 +488,19 @@ static VALUE r_try_call_proc(VALUE r_try_args)
|
|
|
482
488
|
|
|
483
489
|
static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, int _magic, JSValue *func_data)
|
|
484
490
|
{
|
|
485
|
-
|
|
491
|
+
// func_data[0] holds the Ruby Symbol ID for the defined function (stored by
|
|
492
|
+
// vm_m_defineGlobalFunction). Looking up by ID avoids a JS_ToCString +
|
|
493
|
+
// rb_intern round-trip on every call.
|
|
494
|
+
int64_t key_id;
|
|
495
|
+
JS_ToInt64(ctx, &key_id, func_data[0]);
|
|
486
496
|
|
|
487
497
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
488
|
-
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM(
|
|
498
|
+
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM((ID)key_id));
|
|
489
499
|
// Shouldn't happen
|
|
490
500
|
if (r_proc == Qnil)
|
|
491
501
|
{
|
|
492
|
-
return JS_ThrowReferenceError(ctx, "Proc
|
|
502
|
+
return JS_ThrowReferenceError(ctx, "Proc is not defined");
|
|
493
503
|
}
|
|
494
|
-
JS_FreeCString(ctx, funcName);
|
|
495
504
|
|
|
496
505
|
VALUE r_call_args = rb_ary_new();
|
|
497
506
|
rb_ary_push(r_call_args, r_proc);
|
|
@@ -842,22 +851,36 @@ static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
|
|
|
842
851
|
return result;
|
|
843
852
|
}
|
|
844
853
|
|
|
845
|
-
static VALUE vm_m_evalCode(VALUE
|
|
854
|
+
static VALUE vm_m_evalCode(int argc, VALUE *argv, VALUE r_self)
|
|
846
855
|
{
|
|
847
856
|
VMData *data;
|
|
848
857
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
849
858
|
|
|
859
|
+
VALUE r_code, r_opts;
|
|
860
|
+
rb_scan_args(argc, argv, "1:", &r_code, &r_opts);
|
|
861
|
+
|
|
850
862
|
if (!RB_TYPE_P(r_code, T_STRING))
|
|
851
863
|
{
|
|
852
864
|
VALUE r_code_class = rb_class_name(CLASS_OF(r_code));
|
|
853
865
|
rb_raise(rb_eTypeError, "JavaScript code must be a String, got %s", StringValueCStr(r_code_class));
|
|
854
866
|
}
|
|
855
867
|
|
|
868
|
+
const char *filename = "<code>";
|
|
869
|
+
if (!NIL_P(r_opts))
|
|
870
|
+
{
|
|
871
|
+
VALUE r_filename = rb_hash_aref(r_opts, ID2SYM(rb_intern("filename")));
|
|
872
|
+
if (!NIL_P(r_filename))
|
|
873
|
+
{
|
|
874
|
+
Check_Type(r_filename, T_STRING);
|
|
875
|
+
filename = StringValueCStr(r_filename);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
856
879
|
clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
|
|
857
880
|
JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
|
|
858
881
|
|
|
859
|
-
|
|
860
|
-
JSValue j_codeResult = JS_Eval(data->context,
|
|
882
|
+
StringValue(r_code);
|
|
883
|
+
JSValue j_codeResult = JS_Eval(data->context, RSTRING_PTR(r_code), RSTRING_LEN(r_code), filename, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
|
|
861
884
|
JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
|
|
862
885
|
// JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
|
|
863
886
|
// Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
|
|
@@ -904,7 +927,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
904
927
|
char *funcName = StringValueCStr(r_func_seg_str);
|
|
905
928
|
|
|
906
929
|
JSValueConst ruby_data[2];
|
|
907
|
-
ruby_data[0] =
|
|
930
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_key_sym));
|
|
908
931
|
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
909
932
|
|
|
910
933
|
// Resolve the parent object to attach the function to.
|
|
@@ -969,7 +992,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
969
992
|
char *funcName = StringValueCStr(r_name_str);
|
|
970
993
|
|
|
971
994
|
JSValueConst ruby_data[2];
|
|
972
|
-
ruby_data[0] =
|
|
995
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_name_sym));
|
|
973
996
|
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
974
997
|
|
|
975
998
|
JSValue j_global = JS_GetGlobalObject(data->context);
|
|
@@ -1196,7 +1219,7 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
1196
1219
|
VALUE r_class_vm = rb_define_class_under(r_module_quickjs, "VM", rb_cObject);
|
|
1197
1220
|
rb_define_alloc_func(r_class_vm, vm_alloc);
|
|
1198
1221
|
rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
|
|
1199
|
-
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, 1);
|
|
1222
|
+
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, -1);
|
|
1200
1223
|
rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
|
|
1201
1224
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
1202
1225
|
rb_define_method(r_class_vm, "import", vm_m_import, -1);
|
data/lib/quickjs/version.rb
CHANGED
data/lib/quickjs.rb
CHANGED
|
@@ -18,8 +18,10 @@ module Quickjs
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def eval_code(code, overwrite_opts = {})
|
|
21
|
+
eval_opts = {}
|
|
22
|
+
eval_opts[:filename] = overwrite_opts.delete(:filename) if overwrite_opts.key?(:filename)
|
|
21
23
|
vm = Quickjs::VM.new(**overwrite_opts)
|
|
22
|
-
res = vm.eval_code(code)
|
|
24
|
+
res = vm.eval_code(code, **eval_opts)
|
|
23
25
|
vm = nil
|
|
24
26
|
res
|
|
25
27
|
end
|
data/polyfills/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickjs-rb-polyfills",
|
|
3
|
-
"version": "0.15.
|
|
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.15.
|
|
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,7 +21,7 @@ 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
|
|