quickjs 0.15.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/quickjsrb.c +60 -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: 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/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
|
}
|
|
@@ -266,16 +271,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
266
271
|
}
|
|
267
272
|
case JS_TAG_STRING:
|
|
268
273
|
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
{
|
|
274
|
+
size_t len;
|
|
275
|
+
const char *str = JS_ToCStringLen(ctx, &len, j_val);
|
|
276
|
+
if (str == NULL)
|
|
273
277
|
return Qnil;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return r_result;
|
|
278
|
-
}
|
|
278
|
+
VALUE r_str = rb_utf8_str_new(str, (long)len);
|
|
279
|
+
JS_FreeCString(ctx, str);
|
|
280
|
+
return r_str;
|
|
279
281
|
}
|
|
280
282
|
case JS_TAG_OBJECT:
|
|
281
283
|
{
|
|
@@ -482,16 +484,19 @@ static VALUE r_try_call_proc(VALUE r_try_args)
|
|
|
482
484
|
|
|
483
485
|
static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, int _magic, JSValue *func_data)
|
|
484
486
|
{
|
|
485
|
-
|
|
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]);
|
|
486
492
|
|
|
487
493
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
488
|
-
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM(
|
|
494
|
+
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM((ID)key_id));
|
|
489
495
|
// Shouldn't happen
|
|
490
496
|
if (r_proc == Qnil)
|
|
491
497
|
{
|
|
492
|
-
return JS_ThrowReferenceError(ctx, "Proc
|
|
498
|
+
return JS_ThrowReferenceError(ctx, "Proc is not defined");
|
|
493
499
|
}
|
|
494
|
-
JS_FreeCString(ctx, funcName);
|
|
495
500
|
|
|
496
501
|
VALUE r_call_args = rb_ary_new();
|
|
497
502
|
rb_ary_push(r_call_args, r_proc);
|
|
@@ -842,22 +847,36 @@ static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
|
|
|
842
847
|
return result;
|
|
843
848
|
}
|
|
844
849
|
|
|
845
|
-
static VALUE vm_m_evalCode(VALUE
|
|
850
|
+
static VALUE vm_m_evalCode(int argc, VALUE *argv, VALUE r_self)
|
|
846
851
|
{
|
|
847
852
|
VMData *data;
|
|
848
853
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
849
854
|
|
|
855
|
+
VALUE r_code, r_opts;
|
|
856
|
+
rb_scan_args(argc, argv, "1:", &r_code, &r_opts);
|
|
857
|
+
|
|
850
858
|
if (!RB_TYPE_P(r_code, T_STRING))
|
|
851
859
|
{
|
|
852
860
|
VALUE r_code_class = rb_class_name(CLASS_OF(r_code));
|
|
853
861
|
rb_raise(rb_eTypeError, "JavaScript code must be a String, got %s", StringValueCStr(r_code_class));
|
|
854
862
|
}
|
|
855
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
|
+
|
|
856
875
|
clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
|
|
857
876
|
JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
|
|
858
877
|
|
|
859
|
-
|
|
860
|
-
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);
|
|
861
880
|
JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
|
|
862
881
|
// JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
|
|
863
882
|
// Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
|
|
@@ -904,7 +923,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
904
923
|
char *funcName = StringValueCStr(r_func_seg_str);
|
|
905
924
|
|
|
906
925
|
JSValueConst ruby_data[2];
|
|
907
|
-
ruby_data[0] =
|
|
926
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_key_sym));
|
|
908
927
|
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
909
928
|
|
|
910
929
|
// Resolve the parent object to attach the function to.
|
|
@@ -969,7 +988,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
969
988
|
char *funcName = StringValueCStr(r_name_str);
|
|
970
989
|
|
|
971
990
|
JSValueConst ruby_data[2];
|
|
972
|
-
ruby_data[0] =
|
|
991
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_name_sym));
|
|
973
992
|
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
974
993
|
|
|
975
994
|
JSValue j_global = JS_GetGlobalObject(data->context);
|
|
@@ -1196,7 +1215,7 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
1196
1215
|
VALUE r_class_vm = rb_define_class_under(r_module_quickjs, "VM", rb_cObject);
|
|
1197
1216
|
rb_define_alloc_func(r_class_vm, vm_alloc);
|
|
1198
1217
|
rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
|
|
1199
|
-
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);
|
|
1200
1219
|
rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
|
|
1201
1220
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
1202
1221
|
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
|
|