quickjs 0.11.2 → 0.13.0.pre

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29ad9419055f853b82d642f0c5404d24d476dcc3d0d8e7da4364f06a819b10a3
4
- data.tar.gz: 77b07d4c214663c8fd716ab2c9dda3d88c327f3f77b0d350f2987d506a70677e
3
+ metadata.gz: f27d3afc594731cc4df53661889a9787626121923ece35c544b581ef3f52722c
4
+ data.tar.gz: d3869683f9134339d1d2e44617845f8ece614e1503926841fb9d4ad6de3dc7ba
5
5
  SHA512:
6
- metadata.gz: 75ba55882ce0d5371e3ba3d584849d012c06a955d230e7b9975266852753dae3db45244fc8b9bf967f86ba25fff52d7c6d8118bab131f9e8b61bb421356597c0
7
- data.tar.gz: 97964de7f1ef9f20c548cc8e69da09fe058112193593bb433e8b93e3e42424582f111a306df13df78dd583442dd712bc71e6da8ff0ce17116f19618f8aa803d2
6
+ metadata.gz: b5cfcad98315e8821644aa233ea86213f855900506767ccce01cb0c1a653326456e3d00c453d0821117e31eee543ec440ed1e4583dd6b420a62e49ffcb53e080
7
+ data.tar.gz: 7df6cc20b91fce4c14e2a2b96b31bb447ee50eba03498a99e336c8b35d62164a05874ed7753509a4d0c1958761f96c5c85f4de66c6301b79eecbe208dbcf8212
data/CLAUDE.md ADDED
@@ -0,0 +1,62 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ quickjs.rb is a Ruby gem wrapping QuickJS (a lightweight JavaScript interpreter) via a C extension. It lets Ruby programs evaluate JavaScript code without a full Node.js runtime.
8
+
9
+ ## Build & Test Commands
10
+
11
+ ```bash
12
+ bundle exec rake # Full cycle: clobber → compile → test
13
+ bundle exec rake compile # Compile C extension only
14
+ bundle exec rake test # Run all tests
15
+ bundle exec ruby -Itest:lib test/quickjs_test.rb # Run single test file
16
+ bundle exec ruby -Itest:lib test/quickjs_test.rb -n test_name # Run single test
17
+ rake polyfills:build # Rebuild Intl polyfill bundle (requires npm)
18
+ ```
19
+
20
+ QuickJS source lives as a git submodule under `ext/quickjsrb/quickjs/` — clone with `--recurse-submodules`.
21
+
22
+ ## Design Principles
23
+
24
+ - **Never modify QuickJS core** — the engine is a git submodule; all customization is implemented externally in our C extension or Ruby layer
25
+ - **Prefer Ruby over C** — don't rush to write C code; use Ruby where it provides better extendability and maintainability
26
+ - **Security-conscious C layer** — avoid adding flexible C-level features that introduce security risks; expose QuickJS's own options as-is rather than inventing new attack surface
27
+ - **Keep the default footprint small** — don't increase bundle size or add JavaScript overhead (e.g. polyfills) by default; additional capabilities should be opt-in via feature flags
28
+
29
+ ## Architecture
30
+
31
+ **C Extension** (`ext/quickjsrb/`):
32
+ - `quickjsrb.c` / `quickjsrb.h` — Core extension: creates QuickJS runtime/context, handles Ruby↔JS value conversion, timeout interrupts, and Ruby function bridging into JS
33
+ - Value conversion uses JSON serialization for complex types (objects/arrays); direct conversion for primitives
34
+ - `VMData` struct holds the JS context, defined Ruby functions, timeout state, console logs, and error references
35
+ - Ruby GC integration via `vm_mark`, `vm_free`, `vm_compact` callbacks
36
+
37
+ **Ruby layer** (`lib/quickjs/`):
38
+ - `Quickjs::VM` — Persistent JS runtime with `eval_code`, `import`, `define_function`
39
+ - `Quickjs.eval_code` — Convenience method for one-shot evaluation
40
+ - Exception hierarchy maps JS error types (SyntaxError, TypeError, etc.) to Ruby classes under `Quickjs::`
41
+ - Special values: `Quickjs::Value::UNDEFINED` and `Quickjs::Value::NAN` represent JS undefined/NaN
42
+
43
+ **Feature flags** (passed to `VM.new` via `features:` array):
44
+ - `:feature_std`, `:feature_os` — QuickJS std/os modules
45
+ - `:feature_timeout` — setTimeout/setInterval via CRuby threads
46
+ - `:feature_polyfill_intl` — Intl API polyfill (DateTimeFormat, NumberFormat, PluralRules, Locale)
47
+
48
+ **Polyfills** (`polyfills/`):
49
+ - Built from FormatJS packages via rolldown, output minified JS embedded as C source
50
+ - Polyfill version must match gem version (enforced during `rake release`)
51
+
52
+ ## Testing
53
+
54
+ Tests use minitest with `describe`/`it` blocks. Key test files:
55
+ - `test/quickjs_test.rb` — Main test suite (value conversion, errors, VM features, ESM imports, function definitions)
56
+ - `test/quickjs_polyfill_test.rb` — Intl polyfill tests
57
+
58
+ ## Build Notes
59
+
60
+ - `extconf.rb` compiles with `-DNDEBUG` to avoid conflicts with Ruby 4.0 GC assertions
61
+ - Symbol visibility is hidden by default (`-fvisibility=hidden`)
62
+ - CI matrix: Ruby 3.2/3.3/3.4/4.0 × Ubuntu/macOS
data/README.md CHANGED
@@ -63,6 +63,9 @@ vm = Quickjs.eval_code(features: [::Quickjs::FEATURE_TIMEOUT])
63
63
 
64
64
  # Inject the polyfill of Intl
65
65
  vm = Quickjs.eval_code(features: [::Quickjs::POLYFILL_INTL])
66
+
67
+ # Inject the polyfill of Blob and File (W3C File API)
68
+ vm = Quickjs.eval_code(features: [::Quickjs::POLYFILL_FILE])
66
69
  ```
67
70
 
68
71
  </details>
@@ -103,6 +106,9 @@ vm = Quickjs::VM.new(features: [::Quickjs::FEATURE_TIMEOUT])
103
106
 
104
107
  # Inject the polyfill of Intl
105
108
  vm = Quickjs::VM.new(features: [::Quickjs::POLYFILL_INTL])
109
+
110
+ # Inject the polyfill of Blob and File (W3C File API)
111
+ vm = Quickjs::VM.new(features: [::Quickjs::POLYFILL_FILE])
106
112
  ```
107
113
 
108
114
  #### VM timeout
@@ -163,7 +169,7 @@ vm.logs.last.raw #=> ['log me', nil]
163
169
 
164
170
  - `ext/quickjsrb/quickjs`
165
171
  - [MIT License Copyright (c) 2017-2021 by Fabrice Bellard and Charlie Gordon](https://github.com/bellard/quickjs/blob/6e2e68fd0896957f92eb6c242a2e048c1ef3cae0/LICENSE).
166
- - `ext/quickjsrb/vendor/polyfill-intl-en.min.js` (bundled and minified)
172
+ - `ext/quickjsrb/vendor/polyfill-intl-en.min.js` ([bundled and minified from `polyfills/`](https://github.com/hmsk/quickjs.rb/tree/main/polyfills))
167
173
  - MIT License Copyright (c) 2023 FormatJS
168
174
  - [@formatjs/intl-getcanonicallocales](https://github.com/formatjs/formatjs/blob/main/packages/intl-getcanonicallocales/LICENSE.md)
169
175
  - [@formatjs/intl-locale](https://github.com/formatjs/formatjs/blob/main/packages/intl-locale/LICENSE.md)
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ end
11
11
 
12
12
  require 'rake/extensiontask'
13
13
 
14
- task build: :compile
14
+ task build: [:compile, 'polyfills:version:warn']
15
15
 
16
16
  GEMSPEC = Gem::Specification.load('quickjs.gemspec')
17
17
 
@@ -19,4 +19,63 @@ Rake::ExtensionTask.new('quickjsrb', GEMSPEC) do |ext|
19
19
  ext.lib_dir = 'lib/quickjs'
20
20
  end
21
21
 
22
- task default: %i[clobber compile test]
22
+ def check_polyfill_version!
23
+ require 'json'
24
+ require_relative 'lib/quickjs/version'
25
+
26
+ package = JSON.parse(File.read(File.expand_path('polyfills/package.json', __dir__)))
27
+ return if package['version'] == Quickjs::VERSION
28
+
29
+ yield package['version'], Quickjs::VERSION
30
+ end
31
+
32
+ namespace :polyfills do
33
+ desc 'Build polyfill bundles with rolldown and recompile'
34
+ task build: :clobber do
35
+ require 'json'
36
+ require_relative 'lib/quickjs/version'
37
+
38
+ polyfills_dir = File.expand_path('polyfills', __dir__)
39
+ package_json_path = File.join(polyfills_dir, 'package.json')
40
+ package = JSON.parse(File.read(package_json_path))
41
+ old_version = package['version']
42
+ package['version'] = Quickjs::VERSION
43
+ File.write(package_json_path, "#{JSON.pretty_generate(package)}\n")
44
+ if old_version != Quickjs::VERSION
45
+ warn "\n⚠️ polyfills/package.json version was #{old_version}, updated to #{Quickjs::VERSION}\n\n"
46
+ end
47
+
48
+ Dir.chdir(polyfills_dir) do
49
+ sh 'npm install' unless File.exist?('node_modules/.package-lock.json') &&
50
+ File.mtime('node_modules/.package-lock.json') >= File.mtime('package.json')
51
+ sh 'npx rolldown -c rolldown.config.mjs'
52
+ end
53
+
54
+ Rake::Task[:compile].invoke
55
+ end
56
+
57
+ namespace :version do
58
+ task :check do
59
+ check_polyfill_version! do |pkg_v, gem_v|
60
+ abort "polyfills/package.json version (#{pkg_v}) does not match gem version (#{gem_v}). Run `rake polyfills:build` first."
61
+ end
62
+ end
63
+
64
+ task :warn do
65
+ check_polyfill_version! do |pkg_v, gem_v|
66
+ warn "⚠️ polyfills/package.json version (#{pkg_v}) does not match gem version (#{gem_v}). Run `rake polyfills:build` to sync."
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ task 'release:guard_clean' => 'polyfills:version:check'
73
+
74
+ namespace :rbs do
75
+ desc 'Validate RBS type definitions'
76
+ task :validate do
77
+ sh RbConfig.ruby, '-S', 'rbs', '-I', 'sig', 'validate'
78
+ end
79
+ end
80
+
81
+ task default: %i[clobber compile test rbs:validate]
@@ -12,7 +12,10 @@ $srcs = [
12
12
  'quickjs.c',
13
13
  'quickjs-libc.c',
14
14
  'polyfill-intl-en.min.c',
15
+ 'polyfill-file.min.c',
16
+ 'polyfill-encoding.min.c',
15
17
  'quickjsrb.c',
18
+ 'quickjsrb_file.c',
16
19
  ]
17
20
 
18
21
  append_cflags('-I$(srcdir)/quickjs')
@@ -61,6 +64,14 @@ polyfill-intl-en.min.js:
61
64
  $(COPY) $(srcdir)/vendor/$@ $@
62
65
  polyfill-intl-en.min.c: ./qjsc polyfill-intl-en.min.js
63
66
  ./qjsc $(POLYFILL_OPTS) -c -M polyfill/intl-en.so,intlen -m -o $@ polyfill-intl-en.min.js
67
+ polyfill-file.min.js:
68
+ $(COPY) $(srcdir)/vendor/$@ $@
69
+ polyfill-file.min.c: ./qjsc polyfill-file.min.js
70
+ ./qjsc -fno-string-normalize -fno-eval -fno-proxy -fno-module-loader -c -M polyfill/file.so,file -m -o $@ polyfill-file.min.js
71
+ polyfill-encoding.min.js:
72
+ $(COPY) $(srcdir)/vendor/$@ $@
73
+ polyfill-encoding.min.c: ./qjsc polyfill-encoding.min.js
74
+ ./qjsc -fno-string-normalize -fno-eval -fno-proxy -fno-module-loader -c -M polyfill/encoding.so,encoding -m -o $@ polyfill-encoding.min.js
64
75
  COMPILE_POLYFILL
65
76
  conf
66
77
  end
@@ -1,4 +1,27 @@
1
1
  #include "quickjsrb.h"
2
+ #include "quickjsrb_file.h"
3
+
4
+ const char *featureStdId = "feature_std";
5
+ const char *featureOsId = "feature_os";
6
+ const char *featureTimeoutId = "feature_timeout";
7
+ const char *featurePolyfillIntlId = "feature_polyfill_intl";
8
+ const char *featurePolyfillFileId = "feature_polyfill_file";
9
+ const char *featurePolyfillEncodingId = "feature_polyfill_encoding";
10
+
11
+ const char *undefinedId = "undefined";
12
+ const char *nanId = "NaN";
13
+
14
+ const char *native_errors[] = {
15
+ "SyntaxError",
16
+ "TypeError",
17
+ "ReferenceError",
18
+ "RangeError",
19
+ "EvalError",
20
+ "URIError",
21
+ "AggregateError"};
22
+ const int num_native_errors = sizeof(native_errors) / sizeof(native_errors[0]);
23
+
24
+ static int dispatch_log(VMData *data, const char *severity, VALUE r_row);
2
25
 
3
26
  JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
4
27
  {
@@ -10,7 +33,7 @@ JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
10
33
 
11
34
  // Keep the error alive in VMData to prevent GC before find_ruby_error retrieves it
12
35
  VMData *data = JS_GetContextOpaque(ctx);
13
- rb_hash_aset(data->alive_errors, r_object_id, r_error);
36
+ rb_hash_aset(data->alive_objects, r_object_id, r_error);
14
37
 
15
38
  VALUE r_exception_message = rb_funcall(r_error, rb_intern("message"), 0);
16
39
  const char *errorMessage = StringValueCStr(r_exception_message);
@@ -68,6 +91,12 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
68
91
  }
69
92
  default:
70
93
  {
94
+ if (rb_obj_is_kind_of(r_value, rb_cFile))
95
+ {
96
+ VMData *data = JS_GetContextOpaque(ctx);
97
+ if (!JS_IsUndefined(data->j_file_proxy_creator))
98
+ return quickjsrb_file_to_js(ctx, r_value);
99
+ }
71
100
  if (TYPE(r_value) == T_OBJECT && RTEST(rb_funcall(
72
101
  r_value,
73
102
  rb_intern("is_a?"),
@@ -95,8 +124,8 @@ VALUE find_ruby_error(JSContext *ctx, JSValue j_error)
95
124
  {
96
125
  VMData *data = JS_GetContextOpaque(ctx);
97
126
  VALUE r_key = INT2NUM(errorOriginalRubyObjectId);
98
- VALUE r_error = rb_hash_aref(data->alive_errors, r_key);
99
- rb_hash_delete(data->alive_errors, r_key);
127
+ VALUE r_error = rb_hash_aref(data->alive_objects, r_key);
128
+ rb_hash_delete(data->alive_objects, r_key);
100
129
  return r_error;
101
130
  }
102
131
  }
@@ -177,6 +206,36 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
177
206
  }
178
207
  // will support other errors like just returning an instance of Error
179
208
  }
209
+
210
+ // Check for Ruby object proxy (e.g., File proxy with rb_object_id on target)
211
+ {
212
+ JSValue j_rb_id = JS_GetPropertyStr(ctx, j_val, "rb_object_id");
213
+ if (JS_VALUE_GET_NORM_TAG(j_rb_id) == JS_TAG_INT || JS_VALUE_GET_NORM_TAG(j_rb_id) == JS_TAG_FLOAT64)
214
+ {
215
+ int64_t object_id;
216
+ JS_ToInt64(ctx, &object_id, j_rb_id);
217
+ JS_FreeValue(ctx, j_rb_id);
218
+ if (object_id > 0)
219
+ {
220
+ VMData *data = JS_GetContextOpaque(ctx);
221
+ VALUE r_obj = rb_hash_aref(data->alive_objects, LONG2NUM(object_id));
222
+ if (!NIL_P(r_obj) && !rb_obj_is_kind_of(r_obj, rb_eException))
223
+ return r_obj;
224
+ }
225
+ }
226
+ else
227
+ {
228
+ JS_FreeValue(ctx, j_rb_id);
229
+ }
230
+ }
231
+
232
+ // JS File → Quickjs::File
233
+ {
234
+ VALUE r_maybe_file = quickjsrb_try_convert_js_file(ctx, j_val);
235
+ if (!NIL_P(r_maybe_file))
236
+ return r_maybe_file;
237
+ }
238
+
180
239
  VALUE r_str = to_r_json(ctx, j_val);
181
240
 
182
241
  if (rb_funcall(r_str, rb_intern("=="), 1, rb_str_new2("undefined")))
@@ -227,7 +286,7 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
227
286
 
228
287
  VMData *data = JS_GetContextOpaque(ctx);
229
288
  VALUE r_headline = rb_str_new2(headline);
230
- rb_ary_push(data->logs, r_log_new("error", rb_ary_new3(1, r_log_body_new(r_headline, r_headline))));
289
+ dispatch_log(data, "error", rb_ary_new3(1, r_log_body_new(r_headline, r_headline)));
231
290
 
232
291
  JS_FreeValue(ctx, j_errorClassMessage);
233
292
  JS_FreeValue(ctx, j_errorClassName);
@@ -269,7 +328,7 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
269
328
 
270
329
  VMData *data = JS_GetContextOpaque(ctx);
271
330
  VALUE r_headline = rb_str_new2(headline);
272
- rb_ary_push(data->logs, r_log_new("error", rb_ary_new3(1, r_log_body_new(r_headline, r_headline))));
331
+ dispatch_log(data, "error", rb_ary_new3(1, r_log_body_new(r_headline, r_headline)));
273
332
 
274
333
  free(headline);
275
334
 
@@ -418,6 +477,43 @@ static JSValue js_quickjsrb_set_timeout(JSContext *ctx, JSValueConst _this, int
418
477
  return JS_UNDEFINED;
419
478
  }
420
479
 
480
+ static VALUE r_try_call_listener(VALUE r_args)
481
+ {
482
+ VALUE r_listener = RARRAY_AREF(r_args, 0);
483
+ VALUE r_log = RARRAY_AREF(r_args, 1);
484
+ return rb_funcall(r_listener, rb_intern("call"), 1, r_log);
485
+ }
486
+
487
+ static int dispatch_log(VMData *data, const char *severity, VALUE r_row)
488
+ {
489
+ VALUE r_log = r_log_new(severity, r_row);
490
+ if (!NIL_P(data->log_listener))
491
+ {
492
+ VALUE r_args = rb_ary_new3(2, data->log_listener, r_log);
493
+ int error;
494
+ rb_protect(r_try_call_listener, r_args, &error);
495
+ if (error)
496
+ return error;
497
+ }
498
+ else
499
+ {
500
+ rb_ary_push(data->logs, r_log);
501
+ }
502
+ return 0;
503
+ }
504
+
505
+ static VALUE vm_m_on_log(VALUE r_self)
506
+ {
507
+ rb_need_block();
508
+
509
+ VMData *data;
510
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
511
+
512
+ data->log_listener = rb_block_proc();
513
+
514
+ return Qnil;
515
+ }
516
+
421
517
  static JSValue js_quickjsrb_log(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, const char *severity)
422
518
  {
423
519
  VMData *data = JS_GetContextOpaque(ctx);
@@ -467,7 +563,14 @@ static JSValue js_quickjsrb_log(JSContext *ctx, JSValueConst _this, int argc, JS
467
563
  rb_ary_push(r_row, r_log_body_new(r_raw, r_c));
468
564
  }
469
565
 
470
- rb_ary_push(data->logs, r_log_new(severity, r_row));
566
+ int error = dispatch_log(data, severity, r_row);
567
+ if (error)
568
+ {
569
+ VALUE r_error = rb_errinfo();
570
+ rb_set_errinfo(Qnil);
571
+ JSValue j_error = j_error_from_ruby_error(ctx, r_error);
572
+ return JS_Throw(ctx, j_error);
573
+ }
471
574
  return JS_UNDEFINED;
472
575
  }
473
576
 
@@ -561,6 +664,22 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
561
664
  JS_FreeValue(data->context, j_polyfillIntlResult);
562
665
  }
563
666
 
667
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillFileId))))
668
+ {
669
+ JSValue j_polyfillFileObject = JS_ReadObject(data->context, &qjsc_polyfill_file_min, qjsc_polyfill_file_min_size, JS_READ_OBJ_BYTECODE);
670
+ JSValue j_polyfillFileResult = JS_EvalFunction(data->context, j_polyfillFileObject);
671
+ JS_FreeValue(data->context, j_polyfillFileResult);
672
+
673
+ quickjsrb_init_file_proxy(data);
674
+ }
675
+
676
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillEncodingId))))
677
+ {
678
+ JSValue j_polyfillEncodingObject = JS_ReadObject(data->context, &qjsc_polyfill_encoding_min, qjsc_polyfill_encoding_min_size, JS_READ_OBJ_BYTECODE);
679
+ JSValue j_polyfillEncodingResult = JS_EvalFunction(data->context, j_polyfillEncodingObject);
680
+ JS_FreeValue(data->context, j_polyfillEncodingResult);
681
+ }
682
+
564
683
  JSValue j_console = JS_NewObject(data->context);
565
684
  JS_SetPropertyStr(
566
685
  data->context, j_console, "log",
@@ -733,6 +852,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
733
852
 
734
853
  static VALUE vm_m_logs(VALUE r_self)
735
854
  {
855
+ rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "Quickjs::VM#logs is deprecated; use Quickjs::VM#on_log instead");
856
+
736
857
  VMData *data;
737
858
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
738
859
 
@@ -755,5 +876,6 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
755
876
  rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
756
877
  rb_define_method(r_class_vm, "import", vm_m_import, -1);
757
878
  rb_define_method(r_class_vm, "logs", vm_m_logs, 0);
879
+ rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
758
880
  r_define_log_class(r_class_vm);
759
881
  }
@@ -14,23 +14,23 @@
14
14
 
15
15
  extern const uint32_t qjsc_polyfill_intl_en_min_size;
16
16
  extern const uint8_t qjsc_polyfill_intl_en_min;
17
+ extern const uint32_t qjsc_polyfill_file_min_size;
18
+ extern const uint8_t qjsc_polyfill_file_min;
19
+ extern const uint32_t qjsc_polyfill_encoding_min_size;
20
+ extern const uint8_t qjsc_polyfill_encoding_min;
17
21
 
18
- const char *featureStdId = "feature_std";
19
- const char *featureOsId = "feature_os";
20
- const char *featureTimeoutId = "feature_timeout";
21
- const char *featurePolyfillIntlId = "feature_polyfill_intl";
22
+ extern const char *featureStdId;
23
+ extern const char *featureOsId;
24
+ extern const char *featureTimeoutId;
25
+ extern const char *featurePolyfillIntlId;
26
+ extern const char *featurePolyfillFileId;
27
+ extern const char *featurePolyfillEncodingId;
22
28
 
23
- const char *undefinedId = "undefined";
24
- const char *nanId = "NaN";
29
+ extern const char *undefinedId;
30
+ extern const char *nanId;
25
31
 
26
- const char *native_errors[] = {
27
- "SyntaxError",
28
- "TypeError",
29
- "ReferenceError",
30
- "RangeError",
31
- "EvalError",
32
- "URIError",
33
- "AggregateError"};
32
+ extern const char *native_errors[];
33
+ extern const int num_native_errors;
34
34
 
35
35
  #define QUICKJSRB_SYM(id) \
36
36
  (VALUE) { ID2SYM(rb_intern(id)) }
@@ -49,7 +49,9 @@ typedef struct VMData
49
49
  VALUE defined_functions;
50
50
  struct EvalTime *eval_time;
51
51
  VALUE logs;
52
- VALUE alive_errors;
52
+ VALUE log_listener;
53
+ VALUE alive_objects;
54
+ JSValue j_file_proxy_creator;
53
55
  } VMData;
54
56
 
55
57
  static void vm_free(void *ptr)
@@ -57,6 +59,9 @@ static void vm_free(void *ptr)
57
59
  VMData *data = (VMData *)ptr;
58
60
  free(data->eval_time);
59
61
 
62
+ if (!JS_IsUndefined(data->j_file_proxy_creator))
63
+ JS_FreeValue(data->context, data->j_file_proxy_creator);
64
+
60
65
  JSRuntime *runtime = JS_GetRuntime(data->context);
61
66
  JS_SetInterruptHandler(runtime, NULL, NULL);
62
67
  js_std_free_handlers(runtime);
@@ -66,7 +71,7 @@ static void vm_free(void *ptr)
66
71
  xfree(ptr);
67
72
  }
68
73
 
69
- size_t vm_size(const void *data)
74
+ static size_t vm_size(const void *data)
70
75
  {
71
76
  return sizeof(VMData);
72
77
  }
@@ -76,7 +81,8 @@ static void vm_mark(void *ptr)
76
81
  VMData *data = (VMData *)ptr;
77
82
  rb_gc_mark_movable(data->defined_functions);
78
83
  rb_gc_mark_movable(data->logs);
79
- rb_gc_mark_movable(data->alive_errors);
84
+ rb_gc_mark_movable(data->log_listener);
85
+ rb_gc_mark_movable(data->alive_objects);
80
86
  }
81
87
 
82
88
  static void vm_compact(void *ptr)
@@ -84,7 +90,8 @@ static void vm_compact(void *ptr)
84
90
  VMData *data = (VMData *)ptr;
85
91
  data->defined_functions = rb_gc_location(data->defined_functions);
86
92
  data->logs = rb_gc_location(data->logs);
87
- data->alive_errors = rb_gc_location(data->alive_errors);
93
+ data->log_listener = rb_gc_location(data->log_listener);
94
+ data->alive_objects = rb_gc_location(data->alive_objects);
88
95
  }
89
96
 
90
97
  static const rb_data_type_t vm_type = {
@@ -104,7 +111,9 @@ static VALUE vm_alloc(VALUE r_self)
104
111
  VALUE obj = TypedData_Make_Struct(r_self, VMData, &vm_type, data);
105
112
  data->defined_functions = rb_hash_new();
106
113
  data->logs = rb_ary_new();
107
- data->alive_errors = rb_hash_new();
114
+ data->log_listener = Qnil;
115
+ data->alive_objects = rb_hash_new();
116
+ data->j_file_proxy_creator = JS_UNDEFINED;
108
117
 
109
118
  EvalTime *eval_time = malloc(sizeof(EvalTime));
110
119
  data->eval_time = eval_time;
@@ -129,17 +138,12 @@ static char *random_string()
129
138
 
130
139
  static bool is_native_error_name(const char *error_name)
131
140
  {
132
- bool is_native_error = false;
133
- int numStrings = sizeof(native_errors) / sizeof(native_errors[0]);
134
- for (int i = 0; i < numStrings; i++)
141
+ for (int i = 0; i < num_native_errors; i++)
135
142
  {
136
143
  if (strcmp(native_errors[i], error_name) == 0)
137
- {
138
- is_native_error = true;
139
- break;
140
- }
144
+ return true;
141
145
  }
142
- return is_native_error;
146
+ return false;
143
147
  }
144
148
 
145
149
  // Constants
@@ -150,6 +154,8 @@ static void r_define_constants(VALUE r_parent_class)
150
154
  rb_define_const(r_parent_class, "MODULE_OS", QUICKJSRB_SYM(featureOsId));
151
155
  rb_define_const(r_parent_class, "FEATURE_TIMEOUT", QUICKJSRB_SYM(featureTimeoutId));
152
156
  rb_define_const(r_parent_class, "POLYFILL_INTL", QUICKJSRB_SYM(featurePolyfillIntlId));
157
+ rb_define_const(r_parent_class, "POLYFILL_FILE", QUICKJSRB_SYM(featurePolyfillFileId));
158
+ rb_define_const(r_parent_class, "POLYFILL_ENCODING", QUICKJSRB_SYM(featurePolyfillEncodingId));
153
159
 
154
160
  VALUE rb_cQuickjsValue = rb_define_class_under(r_parent_class, "Value", rb_cObject);
155
161
  rb_define_const(rb_cQuickjsValue, "UNDEFINED", QUICKJSRB_SYM(undefinedId));
@@ -221,7 +227,7 @@ static VALUE r_log_body_new(VALUE r_raw, VALUE r_c)
221
227
  #define QUICKJSRB_ERROR_FOR(name) \
222
228
  (VALUE) { rb_const_get(rb_const_get(rb_cClass, rb_intern("Quickjs")), rb_intern(name)) }
223
229
 
224
- VALUE vm_m_initialize_quickjs_error(VALUE self, VALUE r_message, VALUE r_js_name)
230
+ static VALUE vm_m_initialize_quickjs_error(VALUE self, VALUE r_message, VALUE r_js_name)
225
231
  {
226
232
  rb_call_super(1, &r_message);
227
233
  rb_iv_set(self, "@js_name", r_js_name);
@@ -235,9 +241,7 @@ static void r_define_exception_classes(VALUE r_parent_class)
235
241
  rb_define_method(r_runtime_error, "initialize", vm_m_initialize_quickjs_error, 2);
236
242
  rb_define_attr(r_runtime_error, "js_name", 1, 0);
237
243
 
238
- // JS native errors
239
- int numStrings = sizeof(native_errors) / sizeof(native_errors[0]);
240
- for (int i = 0; i < numStrings; i++)
244
+ for (int i = 0; i < num_native_errors; i++)
241
245
  {
242
246
  rb_define_class_under(r_parent_class, native_errors[i], r_runtime_error);
243
247
  }