quickjs 0.15.0 → 0.16.0
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/CLAUDE.md +8 -0
- data/README.md +52 -2
- data/ext/quickjsrb/quickjsrb.c +152 -50
- data/ext/quickjsrb/quickjsrb.h +4 -0
- 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 +6 -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: e8f0513a5e0a54c24bdc8c20000f8cf9d1611e394683a710a2260ebedb57913a
|
|
4
|
+
data.tar.gz: 335ab02e13a0b8b137342f5895bc821641ac33d6f8370e05b9c24f0ca3ce7416
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6e975069d7b77adf0d2e4ca7d6459c6499e1465155cae563ff3fb87d7f1f8aefe9f3b26191d9736a2c398ee13df48d5ded389b17f54aec56dc2bf4e4ac580a8
|
|
7
|
+
data.tar.gz: b1341d5c2e0ba51e6891752bc6c203fe7c2bfc8e1d76beafaf93129f3fb0bb600c64e786beec42ec6a9cc95336562de739d32f02711422b1f9930dd4b57cbb93
|
data/CLAUDE.md
CHANGED
|
@@ -55,6 +55,14 @@ Tests use minitest with `describe`/`it` blocks. Key test files:
|
|
|
55
55
|
- `test/quickjs_test.rb` — Main test suite (value conversion, errors, VM features, ESM imports, function definitions)
|
|
56
56
|
- `test/quickjs_polyfill_test.rb` — Intl polyfill tests
|
|
57
57
|
|
|
58
|
+
## Release Process
|
|
59
|
+
|
|
60
|
+
1. On `main`, run `bundle exec rake polyfills:build` — rebuilds polyfill bundles and syncs `polyfills/package.json` version to match the gem version
|
|
61
|
+
2. Bump `lib/quickjs/version.rb` and `Gemfile.lock` to the new version
|
|
62
|
+
3. Commit `lib/quickjs/version.rb`, `Gemfile.lock`, `polyfills/package.json`, `polyfills/package-lock.json` as `"prepare vX.Y.Z"` — do NOT push; `rake release` handles that
|
|
63
|
+
4. Run `bundle exec rake release` — tags, pushes to GitHub, and publishes to RubyGems (human step; do not run this as Claude)
|
|
64
|
+
5. Create a GitHub release via `gh release create` with notes following the pattern of previous releases
|
|
65
|
+
|
|
58
66
|
## Build Notes
|
|
59
67
|
|
|
60
68
|
- `extconf.rb` compiles with `-DNDEBUG` to avoid conflicts with Ruby 4.0 GC assertions
|
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
|
|
@@ -122,6 +129,26 @@ vm.import('DefaultExport', from: File.read('exports.esm.js'))
|
|
|
122
129
|
vm.import('* as all', from: File.read('exports.esm.js'))
|
|
123
130
|
```
|
|
124
131
|
|
|
132
|
+
#### `Quickjs::VM#module_loader=`: 🧩 Resolve `import` specifiers from Ruby
|
|
133
|
+
|
|
134
|
+
By default, `import` specifiers that aren't already loaded fall through to QuickJS's filesystem loader. Set a `module_loader` Proc to resolve specifiers in-memory instead — useful when the source code lives in a database, an importmap, or a virtual filesystem.
|
|
135
|
+
|
|
136
|
+
```rb
|
|
137
|
+
vm = Quickjs::VM.new
|
|
138
|
+
modules = {
|
|
139
|
+
'a' => "import { b } from 'b'; export const a = () => `a-${b()}`;",
|
|
140
|
+
'b' => "export const b = () => 'b-result';"
|
|
141
|
+
}
|
|
142
|
+
vm.module_loader = ->(name) { modules[name] }
|
|
143
|
+
|
|
144
|
+
vm.import(['a'], filename: 'a')
|
|
145
|
+
vm.eval_code('a()') #=> 'a-b-result'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The Proc receives the (already normalized) module specifier and returns the module source as a `String`, or `nil` to signal "not found" (which raises `Quickjs::ReferenceError` on the JS side). Pass `nil` to clear a previously set loader.
|
|
149
|
+
|
|
150
|
+
When `module_loader=` is set, pass `filename:` to `import` instead of `from:` to resolve a named specifier directly through the loader — no inline bridge source needed.
|
|
151
|
+
|
|
125
152
|
#### `Quickjs::VM#define_function`: 💎 Define a global function for JS by Ruby
|
|
126
153
|
|
|
127
154
|
```rb
|
|
@@ -133,6 +160,23 @@ end
|
|
|
133
160
|
vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'
|
|
134
161
|
```
|
|
135
162
|
|
|
163
|
+
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):
|
|
164
|
+
|
|
165
|
+
```rb
|
|
166
|
+
vm = Quickjs::VM.new
|
|
167
|
+
vm.eval_code("const myLib = {}")
|
|
168
|
+
vm.define_function(["myLib", "greetingTo"]) { |name| "Hello, #{name}!" }
|
|
169
|
+
|
|
170
|
+
vm.eval_code("myLib.greetingTo('Rick')") #=> 'Hello! Rick'
|
|
171
|
+
|
|
172
|
+
# Deeply nested
|
|
173
|
+
vm.eval_code("const a = { b: { c: {} } }")
|
|
174
|
+
vm.define_function(["a", "b", "c", "double"]) { |x| x * 2 }
|
|
175
|
+
vm.eval_code("a.b.c.double(21)") #=> 42
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`define_function` returns the registered name as a `Symbol` (or an `Array` of `Symbol`s for array paths).
|
|
179
|
+
|
|
136
180
|
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
181
|
|
|
138
182
|
```rb
|
|
@@ -177,14 +221,20 @@ vm.eval_code('console.log("hello", 42)')
|
|
|
177
221
|
| `string` | ↔ | `String` | |
|
|
178
222
|
| `true` / `false` | ↔ | `true` / `false` | |
|
|
179
223
|
| `null` | ↔ | `nil` | |
|
|
180
|
-
| `Array` | ↔ | `Array` |
|
|
181
|
-
| `Object` | ↔ | `Hash` |
|
|
224
|
+
| `Array` | ↔ | `Array` | recursively converted |
|
|
225
|
+
| `Object` | ↔ | `Hash` | recursively converted; keys are always `String` |
|
|
226
|
+
| `function` | → | `Quickjs::Function` — `.source`, `.call(*args, on:)` | |
|
|
182
227
|
| `undefined` | → | `Quickjs::Value::UNDEFINED` | |
|
|
183
228
|
| `NaN` | → | `Quickjs::Value::NAN` | |
|
|
184
229
|
| `Blob` | → | `Quickjs::Blob` — `.size`, `.type`, `.content` | requires `POLYFILL_FILE` |
|
|
185
230
|
| `File` | → | `Quickjs::File` — `.name`, `.last_modified` + Blob attrs | requires `POLYFILL_FILE` |
|
|
186
231
|
| `File` proxy | ← | `::File` | requires `POLYFILL_FILE`; applies to `define_function` return values |
|
|
187
232
|
|
|
233
|
+
## Acknowledgements
|
|
234
|
+
|
|
235
|
+
- [@ursm](https://github.com/ursm) — for continuous contributions improving performance and developer experience
|
|
236
|
+
- [@persona-id](https://github.com/persona-id) — for providing real-world use cases that shape the direction of this project
|
|
237
|
+
|
|
188
238
|
## License
|
|
189
239
|
|
|
190
240
|
- `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
|
{
|
|
@@ -468,6 +470,59 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
468
470
|
}
|
|
469
471
|
}
|
|
470
472
|
|
|
473
|
+
struct module_loader_call_args
|
|
474
|
+
{
|
|
475
|
+
VALUE proc;
|
|
476
|
+
VALUE r_module_name;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
static VALUE r_module_loader_call(VALUE r_args_val)
|
|
480
|
+
{
|
|
481
|
+
struct module_loader_call_args *args = (struct module_loader_call_args *)r_args_val;
|
|
482
|
+
return rb_funcall(args->proc, rb_intern("call"), 1, args->r_module_name);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
static JSModuleDef *quickjsrb_module_loader(JSContext *ctx, const char *module_name, void *opaque, JSValueConst attributes)
|
|
486
|
+
{
|
|
487
|
+
VMData *data = JS_GetContextOpaque(ctx);
|
|
488
|
+
if (NIL_P(data->module_loader))
|
|
489
|
+
return js_module_loader(ctx, module_name, opaque, attributes);
|
|
490
|
+
|
|
491
|
+
struct module_loader_call_args args = {data->module_loader, rb_str_new_cstr(module_name)};
|
|
492
|
+
int state;
|
|
493
|
+
VALUE r_source = rb_protect(r_module_loader_call, (VALUE)&args, &state);
|
|
494
|
+
if (state)
|
|
495
|
+
{
|
|
496
|
+
VALUE r_error = rb_errinfo();
|
|
497
|
+
rb_set_errinfo(Qnil);
|
|
498
|
+
JSValue j_error = j_error_from_ruby_error(ctx, r_error);
|
|
499
|
+
JS_Throw(ctx, j_error);
|
|
500
|
+
return NULL;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (NIL_P(r_source) || r_source == Qfalse)
|
|
504
|
+
{
|
|
505
|
+
JS_ThrowReferenceError(ctx, "module loader returned no source for '%s'", module_name);
|
|
506
|
+
return NULL;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (!RB_TYPE_P(r_source, T_STRING))
|
|
510
|
+
{
|
|
511
|
+
JS_ThrowTypeError(ctx, "module loader must return a String or nil, got %s", rb_obj_classname(r_source));
|
|
512
|
+
return NULL;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
JSValue j_func = JS_Eval(ctx, RSTRING_PTR(r_source), RSTRING_LEN(r_source), module_name,
|
|
516
|
+
JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
517
|
+
if (JS_IsException(j_func))
|
|
518
|
+
return NULL;
|
|
519
|
+
|
|
520
|
+
js_module_set_import_meta(ctx, j_func, FALSE, FALSE);
|
|
521
|
+
JSModuleDef *m = JS_VALUE_GET_PTR(j_func);
|
|
522
|
+
JS_FreeValue(ctx, j_func);
|
|
523
|
+
return m;
|
|
524
|
+
}
|
|
525
|
+
|
|
471
526
|
static VALUE r_try_call_proc(VALUE r_try_args)
|
|
472
527
|
{
|
|
473
528
|
return rb_funcall(
|
|
@@ -482,16 +537,19 @@ static VALUE r_try_call_proc(VALUE r_try_args)
|
|
|
482
537
|
|
|
483
538
|
static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, int _magic, JSValue *func_data)
|
|
484
539
|
{
|
|
485
|
-
|
|
540
|
+
// func_data[0] holds the Ruby Symbol ID for the defined function (stored by
|
|
541
|
+
// vm_m_defineGlobalFunction). Looking up by ID avoids a JS_ToCString +
|
|
542
|
+
// rb_intern round-trip on every call.
|
|
543
|
+
int64_t key_id;
|
|
544
|
+
JS_ToInt64(ctx, &key_id, func_data[0]);
|
|
486
545
|
|
|
487
546
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
488
|
-
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM(
|
|
547
|
+
VALUE r_proc = rb_hash_aref(data->defined_functions, ID2SYM((ID)key_id));
|
|
489
548
|
// Shouldn't happen
|
|
490
549
|
if (r_proc == Qnil)
|
|
491
550
|
{
|
|
492
|
-
return JS_ThrowReferenceError(ctx, "Proc
|
|
551
|
+
return JS_ThrowReferenceError(ctx, "Proc is not defined");
|
|
493
552
|
}
|
|
494
|
-
JS_FreeCString(ctx, funcName);
|
|
495
553
|
|
|
496
554
|
VALUE r_call_args = rb_ary_new();
|
|
497
555
|
rb_ary_push(r_call_args, r_proc);
|
|
@@ -727,7 +785,7 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
|
|
|
727
785
|
JS_SetMemoryLimit(runtime, NUM2UINT(r_memory_limit));
|
|
728
786
|
JS_SetMaxStackSize(runtime, NUM2UINT(r_max_stack_size));
|
|
729
787
|
|
|
730
|
-
JS_SetModuleLoaderFunc2(runtime, NULL,
|
|
788
|
+
JS_SetModuleLoaderFunc2(runtime, NULL, quickjsrb_module_loader, js_module_check_attributes, NULL);
|
|
731
789
|
js_std_init_handlers(runtime);
|
|
732
790
|
|
|
733
791
|
JSValue j_global = JS_GetGlobalObject(data->context);
|
|
@@ -842,22 +900,36 @@ static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
|
|
|
842
900
|
return result;
|
|
843
901
|
}
|
|
844
902
|
|
|
845
|
-
static VALUE vm_m_evalCode(VALUE
|
|
903
|
+
static VALUE vm_m_evalCode(int argc, VALUE *argv, VALUE r_self)
|
|
846
904
|
{
|
|
847
905
|
VMData *data;
|
|
848
906
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
849
907
|
|
|
908
|
+
VALUE r_code, r_opts;
|
|
909
|
+
rb_scan_args(argc, argv, "1:", &r_code, &r_opts);
|
|
910
|
+
|
|
850
911
|
if (!RB_TYPE_P(r_code, T_STRING))
|
|
851
912
|
{
|
|
852
913
|
VALUE r_code_class = rb_class_name(CLASS_OF(r_code));
|
|
853
914
|
rb_raise(rb_eTypeError, "JavaScript code must be a String, got %s", StringValueCStr(r_code_class));
|
|
854
915
|
}
|
|
855
916
|
|
|
917
|
+
const char *filename = "<code>";
|
|
918
|
+
if (!NIL_P(r_opts))
|
|
919
|
+
{
|
|
920
|
+
VALUE r_filename = rb_hash_aref(r_opts, ID2SYM(rb_intern("filename")));
|
|
921
|
+
if (!NIL_P(r_filename))
|
|
922
|
+
{
|
|
923
|
+
Check_Type(r_filename, T_STRING);
|
|
924
|
+
filename = StringValueCStr(r_filename);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
856
928
|
clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
|
|
857
929
|
JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
|
|
858
930
|
|
|
859
|
-
|
|
860
|
-
JSValue j_codeResult = JS_Eval(data->context,
|
|
931
|
+
StringValue(r_code);
|
|
932
|
+
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
933
|
JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
|
|
862
934
|
// JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
|
|
863
935
|
// Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
|
|
@@ -904,7 +976,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
904
976
|
char *funcName = StringValueCStr(r_func_seg_str);
|
|
905
977
|
|
|
906
978
|
JSValueConst ruby_data[2];
|
|
907
|
-
ruby_data[0] =
|
|
979
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_key_sym));
|
|
908
980
|
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
909
981
|
|
|
910
982
|
// Resolve the parent object to attach the function to.
|
|
@@ -969,7 +1041,7 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
969
1041
|
char *funcName = StringValueCStr(r_name_str);
|
|
970
1042
|
|
|
971
1043
|
JSValueConst ruby_data[2];
|
|
972
|
-
ruby_data[0] =
|
|
1044
|
+
ruby_data[0] = JS_NewInt64(data->context, (int64_t)SYM2ID(r_name_sym));
|
|
973
1045
|
ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
|
|
974
1046
|
|
|
975
1047
|
JSValue j_global = JS_GetGlobalObject(data->context);
|
|
@@ -1124,6 +1196,25 @@ static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
|
|
|
1124
1196
|
return to_rb_return_value(data->context, js_std_await(data->context, j_result));
|
|
1125
1197
|
}
|
|
1126
1198
|
|
|
1199
|
+
static VALUE vm_m_set_module_loader(VALUE r_self, VALUE r_loader)
|
|
1200
|
+
{
|
|
1201
|
+
VMData *data;
|
|
1202
|
+
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
1203
|
+
|
|
1204
|
+
if (!NIL_P(r_loader) && !rb_obj_is_kind_of(r_loader, rb_cProc))
|
|
1205
|
+
rb_raise(rb_eTypeError, "module_loader must be a Proc or nil");
|
|
1206
|
+
|
|
1207
|
+
data->module_loader = r_loader;
|
|
1208
|
+
return r_loader;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
static VALUE vm_m_get_module_loader(VALUE r_self)
|
|
1212
|
+
{
|
|
1213
|
+
VMData *data;
|
|
1214
|
+
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
1215
|
+
return data->module_loader;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1127
1218
|
static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
1128
1219
|
{
|
|
1129
1220
|
VALUE r_import_string, r_opts;
|
|
@@ -1131,7 +1222,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
|
1131
1222
|
if (NIL_P(r_opts))
|
|
1132
1223
|
r_opts = rb_hash_new();
|
|
1133
1224
|
VALUE r_from = rb_hash_aref(r_opts, ID2SYM(rb_intern("from")));
|
|
1134
|
-
|
|
1225
|
+
VALUE r_filename = rb_hash_aref(r_opts, ID2SYM(rb_intern("filename")));
|
|
1226
|
+
if (NIL_P(r_from) && NIL_P(r_filename))
|
|
1135
1227
|
{
|
|
1136
1228
|
VALUE r_error_message = rb_str_new2("missing import source");
|
|
1137
1229
|
rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
|
|
@@ -1142,16 +1234,24 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
|
1142
1234
|
VMData *data;
|
|
1143
1235
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
1144
1236
|
|
|
1145
|
-
char *filename
|
|
1146
|
-
|
|
1147
|
-
JSValue module = JS_Eval(data->context, source, strlen(source), filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
1148
|
-
if (JS_IsException(module))
|
|
1237
|
+
char *filename;
|
|
1238
|
+
if (!NIL_P(r_filename))
|
|
1149
1239
|
{
|
|
1240
|
+
filename = StringValueCStr(r_filename);
|
|
1241
|
+
}
|
|
1242
|
+
else
|
|
1243
|
+
{
|
|
1244
|
+
filename = random_string();
|
|
1245
|
+
char *source = StringValueCStr(r_from);
|
|
1246
|
+
JSValue module = JS_Eval(data->context, source, strlen(source), filename, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
1247
|
+
if (JS_IsException(module))
|
|
1248
|
+
{
|
|
1249
|
+
JS_FreeValue(data->context, module);
|
|
1250
|
+
return to_rb_value(data->context, module);
|
|
1251
|
+
}
|
|
1252
|
+
js_module_set_import_meta(data->context, module, TRUE, FALSE);
|
|
1150
1253
|
JS_FreeValue(data->context, module);
|
|
1151
|
-
return to_rb_value(data->context, module);
|
|
1152
1254
|
}
|
|
1153
|
-
js_module_set_import_meta(data->context, module, TRUE, FALSE);
|
|
1154
|
-
JS_FreeValue(data->context, module);
|
|
1155
1255
|
|
|
1156
1256
|
VALUE r_import_settings = rb_funcall(
|
|
1157
1257
|
rb_const_get(rb_cClass, rb_intern("Quickjs")),
|
|
@@ -1196,10 +1296,12 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
1196
1296
|
VALUE r_class_vm = rb_define_class_under(r_module_quickjs, "VM", rb_cObject);
|
|
1197
1297
|
rb_define_alloc_func(r_class_vm, vm_alloc);
|
|
1198
1298
|
rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
|
|
1199
|
-
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, 1);
|
|
1299
|
+
rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, -1);
|
|
1200
1300
|
rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
|
|
1201
1301
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
1202
1302
|
rb_define_method(r_class_vm, "import", vm_m_import, -1);
|
|
1303
|
+
rb_define_method(r_class_vm, "module_loader", vm_m_get_module_loader, 0);
|
|
1304
|
+
rb_define_method(r_class_vm, "module_loader=", vm_m_set_module_loader, 1);
|
|
1203
1305
|
rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
|
|
1204
1306
|
r_define_log_class(r_class_vm);
|
|
1205
1307
|
}
|
data/ext/quickjsrb/quickjsrb.h
CHANGED
|
@@ -54,6 +54,7 @@ typedef struct VMData
|
|
|
54
54
|
struct EvalTime *eval_time;
|
|
55
55
|
VALUE log_listener;
|
|
56
56
|
VALUE alive_objects;
|
|
57
|
+
VALUE module_loader;
|
|
57
58
|
JSValue j_file_proxy_creator;
|
|
58
59
|
} VMData;
|
|
59
60
|
|
|
@@ -85,6 +86,7 @@ static void vm_mark(void *ptr)
|
|
|
85
86
|
rb_gc_mark_movable(data->defined_functions);
|
|
86
87
|
rb_gc_mark_movable(data->log_listener);
|
|
87
88
|
rb_gc_mark_movable(data->alive_objects);
|
|
89
|
+
rb_gc_mark_movable(data->module_loader);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
static void vm_compact(void *ptr)
|
|
@@ -93,6 +95,7 @@ static void vm_compact(void *ptr)
|
|
|
93
95
|
data->defined_functions = rb_gc_location(data->defined_functions);
|
|
94
96
|
data->log_listener = rb_gc_location(data->log_listener);
|
|
95
97
|
data->alive_objects = rb_gc_location(data->alive_objects);
|
|
98
|
+
data->module_loader = rb_gc_location(data->module_loader);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
static const rb_data_type_t vm_type = {
|
|
@@ -113,6 +116,7 @@ static VALUE vm_alloc(VALUE r_self)
|
|
|
113
116
|
data->defined_functions = rb_hash_new();
|
|
114
117
|
data->log_listener = Qnil;
|
|
115
118
|
data->alive_objects = rb_hash_new();
|
|
119
|
+
data->module_loader = Qnil;
|
|
116
120
|
data->j_file_proxy_creator = JS_UNDEFINED;
|
|
117
121
|
|
|
118
122
|
EvalTime *eval_time = malloc(sizeof(EvalTime));
|
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.
|
|
3
|
+
"version": "0.16.0",
|
|
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.16.0",
|
|
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
|
|
|
@@ -29,6 +29,11 @@ module Quickjs
|
|
|
29
29
|
| (Array[String | Symbol] path, *Symbol flags) { (*untyped) -> untyped } -> Array[Symbol]
|
|
30
30
|
|
|
31
31
|
def import: (String | Array[String] | Hash[Symbol, String] imported, from: String, ?code_to_expose: String?) -> true
|
|
32
|
+
| (String | Array[String] | Hash[Symbol, String] imported, filename: String) -> true
|
|
33
|
+
|
|
34
|
+
def module_loader: () -> (^(String) -> String? | nil)
|
|
35
|
+
|
|
36
|
+
def module_loader=: (^(String) -> String? | nil) -> (^(String) -> String? | nil)
|
|
32
37
|
|
|
33
38
|
def on_log: () { (Log) -> void } -> nil
|
|
34
39
|
|