quickjs 0.12.0 → 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: 64bdf2de37fc8ac24b7acec74dd724226f732a4acbef401d579b9b46e12c86f1
4
- data.tar.gz: 4d76617466a380a038de3684495dc38eacb29be11002b3d6b1536c2c6caf0d80
3
+ metadata.gz: f27d3afc594731cc4df53661889a9787626121923ece35c544b581ef3f52722c
4
+ data.tar.gz: d3869683f9134339d1d2e44617845f8ece614e1503926841fb9d4ad6de3dc7ba
5
5
  SHA512:
6
- metadata.gz: 0fcf3bde7047e39038983326a6d867430e4e5fc63ac65bc6823c44df4222904a8bef385abd12bcadcca5213c9610198a08cdf4f12bc949dc2f7613509a8e46fc
7
- data.tar.gz: af03f93832d2400cbb1418c764fbdaca5b1a51fd232462731b404a897851ff60cb3437c745ab2c03e36a7a5219c3618751075649b1cdc2e9935689b4e218ccf7
6
+ metadata.gz: b5cfcad98315e8821644aa233ea86213f855900506767ccce01cb0c1a653326456e3d00c453d0821117e31eee543ec440ed1e4583dd6b420a62e49ffcb53e080
7
+ data.tar.gz: 7df6cc20b91fce4c14e2a2b96b31bb447ee50eba03498a99e336c8b35d62164a05874ed7753509a4d0c1958761f96c5c85f4de66c6301b79eecbe208dbcf8212
data/Rakefile CHANGED
@@ -71,4 +71,11 @@ end
71
71
 
72
72
  task 'release:guard_clean' => 'polyfills:version:check'
73
73
 
74
- task default: %i[clobber compile test]
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]
@@ -13,7 +13,9 @@ $srcs = [
13
13
  'quickjs-libc.c',
14
14
  'polyfill-intl-en.min.c',
15
15
  'polyfill-file.min.c',
16
+ 'polyfill-encoding.min.c',
16
17
  'quickjsrb.c',
18
+ 'quickjsrb_file.c',
17
19
  ]
18
20
 
19
21
  append_cflags('-I$(srcdir)/quickjs')
@@ -66,6 +68,10 @@ polyfill-file.min.js:
66
68
  $(COPY) $(srcdir)/vendor/$@ $@
67
69
  polyfill-file.min.c: ./qjsc polyfill-file.min.js
68
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
69
75
  COMPILE_POLYFILL
70
76
  conf
71
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
 
@@ -566,6 +669,15 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
566
669
  JSValue j_polyfillFileObject = JS_ReadObject(data->context, &qjsc_polyfill_file_min, qjsc_polyfill_file_min_size, JS_READ_OBJ_BYTECODE);
567
670
  JSValue j_polyfillFileResult = JS_EvalFunction(data->context, j_polyfillFileObject);
568
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);
569
681
  }
570
682
 
571
683
  JSValue j_console = JS_NewObject(data->context);
@@ -740,6 +852,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
740
852
 
741
853
  static VALUE vm_m_logs(VALUE r_self)
742
854
  {
855
+ rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "Quickjs::VM#logs is deprecated; use Quickjs::VM#on_log instead");
856
+
743
857
  VMData *data;
744
858
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
745
859
 
@@ -762,5 +876,6 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
762
876
  rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
763
877
  rb_define_method(r_class_vm, "import", vm_m_import, -1);
764
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);
765
880
  r_define_log_class(r_class_vm);
766
881
  }
@@ -16,24 +16,21 @@ extern const uint32_t qjsc_polyfill_intl_en_min_size;
16
16
  extern const uint8_t qjsc_polyfill_intl_en_min;
17
17
  extern const uint32_t qjsc_polyfill_file_min_size;
18
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;
19
21
 
20
- const char *featureStdId = "feature_std";
21
- const char *featureOsId = "feature_os";
22
- const char *featureTimeoutId = "feature_timeout";
23
- const char *featurePolyfillIntlId = "feature_polyfill_intl";
24
- const char *featurePolyfillFileId = "feature_polyfill_file";
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;
25
28
 
26
- const char *undefinedId = "undefined";
27
- const char *nanId = "NaN";
29
+ extern const char *undefinedId;
30
+ extern const char *nanId;
28
31
 
29
- const char *native_errors[] = {
30
- "SyntaxError",
31
- "TypeError",
32
- "ReferenceError",
33
- "RangeError",
34
- "EvalError",
35
- "URIError",
36
- "AggregateError"};
32
+ extern const char *native_errors[];
33
+ extern const int num_native_errors;
37
34
 
38
35
  #define QUICKJSRB_SYM(id) \
39
36
  (VALUE) { ID2SYM(rb_intern(id)) }
@@ -52,7 +49,9 @@ typedef struct VMData
52
49
  VALUE defined_functions;
53
50
  struct EvalTime *eval_time;
54
51
  VALUE logs;
55
- VALUE alive_errors;
52
+ VALUE log_listener;
53
+ VALUE alive_objects;
54
+ JSValue j_file_proxy_creator;
56
55
  } VMData;
57
56
 
58
57
  static void vm_free(void *ptr)
@@ -60,6 +59,9 @@ static void vm_free(void *ptr)
60
59
  VMData *data = (VMData *)ptr;
61
60
  free(data->eval_time);
62
61
 
62
+ if (!JS_IsUndefined(data->j_file_proxy_creator))
63
+ JS_FreeValue(data->context, data->j_file_proxy_creator);
64
+
63
65
  JSRuntime *runtime = JS_GetRuntime(data->context);
64
66
  JS_SetInterruptHandler(runtime, NULL, NULL);
65
67
  js_std_free_handlers(runtime);
@@ -69,7 +71,7 @@ static void vm_free(void *ptr)
69
71
  xfree(ptr);
70
72
  }
71
73
 
72
- size_t vm_size(const void *data)
74
+ static size_t vm_size(const void *data)
73
75
  {
74
76
  return sizeof(VMData);
75
77
  }
@@ -79,7 +81,8 @@ static void vm_mark(void *ptr)
79
81
  VMData *data = (VMData *)ptr;
80
82
  rb_gc_mark_movable(data->defined_functions);
81
83
  rb_gc_mark_movable(data->logs);
82
- rb_gc_mark_movable(data->alive_errors);
84
+ rb_gc_mark_movable(data->log_listener);
85
+ rb_gc_mark_movable(data->alive_objects);
83
86
  }
84
87
 
85
88
  static void vm_compact(void *ptr)
@@ -87,7 +90,8 @@ static void vm_compact(void *ptr)
87
90
  VMData *data = (VMData *)ptr;
88
91
  data->defined_functions = rb_gc_location(data->defined_functions);
89
92
  data->logs = rb_gc_location(data->logs);
90
- 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);
91
95
  }
92
96
 
93
97
  static const rb_data_type_t vm_type = {
@@ -107,7 +111,9 @@ static VALUE vm_alloc(VALUE r_self)
107
111
  VALUE obj = TypedData_Make_Struct(r_self, VMData, &vm_type, data);
108
112
  data->defined_functions = rb_hash_new();
109
113
  data->logs = rb_ary_new();
110
- 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;
111
117
 
112
118
  EvalTime *eval_time = malloc(sizeof(EvalTime));
113
119
  data->eval_time = eval_time;
@@ -132,17 +138,12 @@ static char *random_string()
132
138
 
133
139
  static bool is_native_error_name(const char *error_name)
134
140
  {
135
- bool is_native_error = false;
136
- int numStrings = sizeof(native_errors) / sizeof(native_errors[0]);
137
- for (int i = 0; i < numStrings; i++)
141
+ for (int i = 0; i < num_native_errors; i++)
138
142
  {
139
143
  if (strcmp(native_errors[i], error_name) == 0)
140
- {
141
- is_native_error = true;
142
- break;
143
- }
144
+ return true;
144
145
  }
145
- return is_native_error;
146
+ return false;
146
147
  }
147
148
 
148
149
  // Constants
@@ -154,6 +155,7 @@ static void r_define_constants(VALUE r_parent_class)
154
155
  rb_define_const(r_parent_class, "FEATURE_TIMEOUT", QUICKJSRB_SYM(featureTimeoutId));
155
156
  rb_define_const(r_parent_class, "POLYFILL_INTL", QUICKJSRB_SYM(featurePolyfillIntlId));
156
157
  rb_define_const(r_parent_class, "POLYFILL_FILE", QUICKJSRB_SYM(featurePolyfillFileId));
158
+ rb_define_const(r_parent_class, "POLYFILL_ENCODING", QUICKJSRB_SYM(featurePolyfillEncodingId));
157
159
 
158
160
  VALUE rb_cQuickjsValue = rb_define_class_under(r_parent_class, "Value", rb_cObject);
159
161
  rb_define_const(rb_cQuickjsValue, "UNDEFINED", QUICKJSRB_SYM(undefinedId));
@@ -225,7 +227,7 @@ static VALUE r_log_body_new(VALUE r_raw, VALUE r_c)
225
227
  #define QUICKJSRB_ERROR_FOR(name) \
226
228
  (VALUE) { rb_const_get(rb_const_get(rb_cClass, rb_intern("Quickjs")), rb_intern(name)) }
227
229
 
228
- 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)
229
231
  {
230
232
  rb_call_super(1, &r_message);
231
233
  rb_iv_set(self, "@js_name", r_js_name);
@@ -239,9 +241,7 @@ static void r_define_exception_classes(VALUE r_parent_class)
239
241
  rb_define_method(r_runtime_error, "initialize", vm_m_initialize_quickjs_error, 2);
240
242
  rb_define_attr(r_runtime_error, "js_name", 1, 0);
241
243
 
242
- // JS native errors
243
- int numStrings = sizeof(native_errors) / sizeof(native_errors[0]);
244
- for (int i = 0; i < numStrings; i++)
244
+ for (int i = 0; i < num_native_errors; i++)
245
245
  {
246
246
  rb_define_class_under(r_parent_class, native_errors[i], r_runtime_error);
247
247
  }
@@ -0,0 +1,332 @@
1
+ #include "quickjsrb.h"
2
+ #include "quickjsrb_file.h"
3
+
4
+ static VALUE r_find_alive_rb_file(JSContext *ctx, JSValue j_handle)
5
+ {
6
+ int64_t handle;
7
+ JS_ToInt64(ctx, &handle, j_handle);
8
+ VMData *data = JS_GetContextOpaque(ctx);
9
+ return rb_hash_aref(data->alive_objects, LONG2NUM(handle));
10
+ }
11
+
12
+ static JSValue js_ruby_file_name(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
13
+ {
14
+ VALUE r_file = r_find_alive_rb_file(ctx, argv[0]);
15
+ if (NIL_P(r_file))
16
+ return JS_UNDEFINED;
17
+
18
+ VALUE r_path = rb_funcall(r_file, rb_intern("path"), 0);
19
+ VALUE r_basename = rb_funcall(rb_cFile, rb_intern("basename"), 1, r_path);
20
+ return JS_NewString(ctx, StringValueCStr(r_basename));
21
+ }
22
+
23
+ static JSValue js_ruby_file_size(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
24
+ {
25
+ VALUE r_file = r_find_alive_rb_file(ctx, argv[0]);
26
+ if (NIL_P(r_file))
27
+ return JS_UNDEFINED;
28
+
29
+ VALUE r_size = rb_funcall(r_file, rb_intern("size"), 0);
30
+ return JS_NewInt64(ctx, NUM2LONG(r_size));
31
+ }
32
+
33
+ static JSValue js_ruby_file_type(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
34
+ {
35
+ return JS_NewString(ctx, "");
36
+ }
37
+
38
+ static JSValue js_ruby_file_last_modified(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
39
+ {
40
+ VALUE r_file = r_find_alive_rb_file(ctx, argv[0]);
41
+ if (NIL_P(r_file))
42
+ return JS_UNDEFINED;
43
+
44
+ VALUE r_mtime = rb_funcall(r_file, rb_intern("mtime"), 0);
45
+ VALUE r_epoch_f = rb_funcall(r_mtime, rb_intern("to_f"), 0);
46
+ double epoch_ms = NUM2DBL(r_epoch_f) * 1000.0;
47
+ return JS_NewFloat64(ctx, epoch_ms);
48
+ }
49
+
50
+ static JSValue js_ruby_file_text(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
51
+ {
52
+ VALUE r_file = r_find_alive_rb_file(ctx, argv[0]);
53
+ if (NIL_P(r_file))
54
+ return JS_UNDEFINED;
55
+
56
+ JSValue promise, resolving_funcs[2];
57
+ promise = JS_NewPromiseCapability(ctx, resolving_funcs);
58
+ if (JS_IsException(promise))
59
+ return JS_EXCEPTION;
60
+
61
+ rb_funcall(r_file, rb_intern("rewind"), 0);
62
+ VALUE r_content = rb_funcall(r_file, rb_intern("read"), 0);
63
+ JSValue j_content = JS_NewString(ctx, StringValueCStr(r_content));
64
+
65
+ JSValue ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, &j_content);
66
+ JS_FreeValue(ctx, j_content);
67
+ JS_FreeValue(ctx, ret);
68
+ JS_FreeValue(ctx, resolving_funcs[0]);
69
+ JS_FreeValue(ctx, resolving_funcs[1]);
70
+
71
+ return promise;
72
+ }
73
+
74
+ static JSValue js_ruby_file_array_buffer(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
75
+ {
76
+ VALUE r_file = r_find_alive_rb_file(ctx, argv[0]);
77
+ if (NIL_P(r_file))
78
+ return JS_UNDEFINED;
79
+
80
+ JSValue promise, resolving_funcs[2];
81
+ promise = JS_NewPromiseCapability(ctx, resolving_funcs);
82
+ if (JS_IsException(promise))
83
+ return JS_EXCEPTION;
84
+
85
+ rb_funcall(r_file, rb_intern("rewind"), 0);
86
+ VALUE r_content = rb_funcall(r_file, rb_intern("read"), 0);
87
+ rb_funcall(r_content, rb_intern("force_encoding"), 1, rb_str_new_cstr("BINARY"));
88
+ long len = RSTRING_LEN(r_content);
89
+ const char *ptr = RSTRING_PTR(r_content);
90
+
91
+ JSValue j_buf = JS_NewArrayBufferCopy(ctx, (const uint8_t *)ptr, len);
92
+
93
+ JSValue ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, &j_buf);
94
+ JS_FreeValue(ctx, j_buf);
95
+ JS_FreeValue(ctx, ret);
96
+ JS_FreeValue(ctx, resolving_funcs[0]);
97
+ JS_FreeValue(ctx, resolving_funcs[1]);
98
+
99
+ return promise;
100
+ }
101
+
102
+ static JSValue js_ruby_file_slice(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
103
+ {
104
+ VALUE r_file = r_find_alive_rb_file(ctx, argv[0]);
105
+ if (NIL_P(r_file))
106
+ return JS_UNDEFINED;
107
+
108
+ VALUE r_size = rb_funcall(r_file, rb_intern("size"), 0);
109
+ long file_size = NUM2LONG(r_size);
110
+
111
+ long start = 0;
112
+ if (argc > 1 && !JS_IsUndefined(argv[1]))
113
+ {
114
+ int64_t s;
115
+ JS_ToInt64(ctx, &s, argv[1]);
116
+ start = (long)s;
117
+ if (start < 0)
118
+ start = file_size + start;
119
+ if (start < 0)
120
+ start = 0;
121
+ if (start > file_size)
122
+ start = file_size;
123
+ }
124
+
125
+ long end = file_size;
126
+ if (argc > 2 && !JS_IsUndefined(argv[2]))
127
+ {
128
+ int64_t e;
129
+ JS_ToInt64(ctx, &e, argv[2]);
130
+ end = (long)e;
131
+ if (end < 0)
132
+ end = file_size + end;
133
+ if (end < 0)
134
+ end = 0;
135
+ if (end > file_size)
136
+ end = file_size;
137
+ }
138
+
139
+ const char *content_type = "";
140
+ if (argc > 3 && JS_IsString(argv[3]))
141
+ content_type = JS_ToCString(ctx, argv[3]);
142
+
143
+ long len = end > start ? end - start : 0;
144
+
145
+ rb_funcall(r_file, rb_intern("rewind"), 0);
146
+ if (start > 0)
147
+ rb_funcall(r_file, rb_intern("seek"), 1, LONG2NUM(start));
148
+ VALUE r_bytes = rb_funcall(r_file, rb_intern("read"), 1, LONG2NUM(len));
149
+ rb_funcall(r_bytes, rb_intern("force_encoding"), 1, rb_str_new_cstr("BINARY"));
150
+
151
+ JSValue j_buf = JS_NewArrayBufferCopy(ctx, (const uint8_t *)RSTRING_PTR(r_bytes), RSTRING_LEN(r_bytes));
152
+ JSValue j_global = JS_GetGlobalObject(ctx);
153
+ JSValue j_uint8_ctor = JS_GetPropertyStr(ctx, j_global, "Uint8Array");
154
+ JSValue j_uint8 = JS_CallConstructor(ctx, j_uint8_ctor, 1, &j_buf);
155
+
156
+ JSValue j_parts = JS_NewArray(ctx);
157
+ JS_SetPropertyUint32(ctx, j_parts, 0, j_uint8);
158
+
159
+ JSValue j_opts = JS_NewObject(ctx);
160
+ JS_SetPropertyStr(ctx, j_opts, "type", JS_NewString(ctx, content_type));
161
+
162
+ JSValue j_blob_ctor = JS_GetPropertyStr(ctx, j_global, "Blob");
163
+ JSValueConst blob_args[2] = {j_parts, j_opts};
164
+ JSValue j_blob = JS_CallConstructor(ctx, j_blob_ctor, 2, blob_args);
165
+
166
+ if (argc > 3 && JS_IsString(argv[3]))
167
+ JS_FreeCString(ctx, content_type);
168
+
169
+ JS_FreeValue(ctx, j_buf);
170
+ JS_FreeValue(ctx, j_uint8_ctor);
171
+ JS_FreeValue(ctx, j_parts);
172
+ JS_FreeValue(ctx, j_opts);
173
+ JS_FreeValue(ctx, j_blob_ctor);
174
+ JS_FreeValue(ctx, j_global);
175
+
176
+ return j_blob;
177
+ }
178
+
179
+ void quickjsrb_init_file_proxy(VMData *data)
180
+ {
181
+ const char *factory_src =
182
+ "(function(getName, getSize, getType, getLastModified, getText, getArrayBuffer, getSlice) {\n"
183
+ " return function(handle) {\n"
184
+ " var target = Object.create(File.prototype);\n"
185
+ " Object.defineProperty(target, 'rb_object_id', { value: handle, enumerable: false });\n"
186
+ " return new Proxy(target, {\n"
187
+ " getPrototypeOf: function() { return File.prototype; },\n"
188
+ " get: function(target, prop, receiver) {\n"
189
+ " if (prop === 'name') return getName(handle);\n"
190
+ " if (prop === 'size') return getSize(handle);\n"
191
+ " if (prop === 'type') return getType(handle);\n"
192
+ " if (prop === 'lastModified') return getLastModified(handle);\n"
193
+ " if (prop === 'text') return function() { return getText(handle); };\n"
194
+ " if (prop === 'arrayBuffer') return function() { return getArrayBuffer(handle); };\n"
195
+ " if (prop === 'slice') return function(start, end, contentType) { return getSlice(handle, start, end, contentType); };\n"
196
+ " if (prop === Symbol.toStringTag) return 'File';\n"
197
+ " if (prop === 'toString') return function() { return '[object File]'; };\n"
198
+ " return Reflect.get(target, prop, receiver);\n"
199
+ " }\n"
200
+ " });\n"
201
+ " };\n"
202
+ "})";
203
+ JSValue j_factory_fn = JS_Eval(data->context, factory_src, strlen(factory_src), "<file-proxy>", JS_EVAL_TYPE_GLOBAL);
204
+
205
+ JSValue j_helpers[7];
206
+ j_helpers[0] = JS_NewCFunction(data->context, js_ruby_file_name, "__rb_file_name", 1);
207
+ j_helpers[1] = JS_NewCFunction(data->context, js_ruby_file_size, "__rb_file_size", 1);
208
+ j_helpers[2] = JS_NewCFunction(data->context, js_ruby_file_type, "__rb_file_type", 1);
209
+ j_helpers[3] = JS_NewCFunction(data->context, js_ruby_file_last_modified, "__rb_file_last_modified", 1);
210
+ j_helpers[4] = JS_NewCFunction(data->context, js_ruby_file_text, "__rb_file_text", 1);
211
+ j_helpers[5] = JS_NewCFunction(data->context, js_ruby_file_array_buffer, "__rb_file_array_buffer", 1);
212
+ j_helpers[6] = JS_NewCFunction(data->context, js_ruby_file_slice, "__rb_file_slice", 4);
213
+
214
+ data->j_file_proxy_creator = JS_Call(data->context, j_factory_fn, JS_UNDEFINED, 7, j_helpers);
215
+
216
+ JS_FreeValue(data->context, j_factory_fn);
217
+ for (int i = 0; i < 7; i++)
218
+ JS_FreeValue(data->context, j_helpers[i]);
219
+ }
220
+
221
+ JSValue quickjsrb_file_to_js(JSContext *ctx, VALUE r_file)
222
+ {
223
+ VMData *data = JS_GetContextOpaque(ctx);
224
+ VALUE r_object_id = rb_funcall(r_file, rb_intern("object_id"), 0);
225
+ rb_hash_aset(data->alive_objects, r_object_id, r_file);
226
+ JSValue j_handle = JS_NewInt64(ctx, NUM2LONG(r_object_id));
227
+ JSValue j_proxy = JS_Call(ctx, data->j_file_proxy_creator, JS_UNDEFINED, 1, &j_handle);
228
+ JS_FreeValue(ctx, j_handle);
229
+ return j_proxy;
230
+ }
231
+
232
+ static VALUE r_extract_blob_content(JSContext *ctx, JSValue j_val)
233
+ {
234
+ JSValue j_ab_fn = JS_GetPropertyStr(ctx, j_val, "arrayBuffer");
235
+ JSValue j_promise = JS_Call(ctx, j_ab_fn, j_val, 0, NULL);
236
+ JS_FreeValue(ctx, j_ab_fn);
237
+
238
+ JSContext *ctx2;
239
+ while (JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx2) > 0)
240
+ {
241
+ }
242
+
243
+ VALUE r_content = rb_str_new("", 0);
244
+ if (JS_PromiseState(ctx, j_promise) == JS_PROMISE_FULFILLED)
245
+ {
246
+ JSValue j_result = JS_PromiseResult(ctx, j_promise);
247
+ size_t buf_len;
248
+ uint8_t *buf = JS_GetArrayBuffer(ctx, &buf_len, j_result);
249
+ if (buf)
250
+ r_content = rb_str_new((const char *)buf, buf_len);
251
+ JS_FreeValue(ctx, j_result);
252
+ }
253
+ JS_FreeValue(ctx, j_promise);
254
+
255
+ rb_funcall(r_content, rb_intern("force_encoding"), 1, rb_str_new_cstr("BINARY"));
256
+ return r_content;
257
+ }
258
+
259
+ static void r_populate_blob_attrs(JSContext *ctx, JSValue j_val, VALUE r_obj)
260
+ {
261
+ JSValue j_size = JS_GetPropertyStr(ctx, j_val, "size");
262
+ int64_t size_val;
263
+ JS_ToInt64(ctx, &size_val, j_size);
264
+ rb_iv_set(r_obj, "@size", LONG2NUM(size_val));
265
+ JS_FreeValue(ctx, j_size);
266
+
267
+ JSValue j_type = JS_GetPropertyStr(ctx, j_val, "type");
268
+ const char *type_str = JS_ToCString(ctx, j_type);
269
+ rb_iv_set(r_obj, "@type", rb_str_new_cstr(type_str ? type_str : ""));
270
+ JS_FreeCString(ctx, type_str);
271
+ JS_FreeValue(ctx, j_type);
272
+
273
+ rb_iv_set(r_obj, "@content", r_extract_blob_content(ctx, j_val));
274
+ }
275
+
276
+ VALUE quickjsrb_try_convert_js_file(JSContext *ctx, JSValue j_val)
277
+ {
278
+ JSValue j_global = JS_GetGlobalObject(ctx);
279
+
280
+ // Check File first (File extends Blob, so instanceof Blob is also true for Files)
281
+ JSValue j_file_ctor = JS_GetPropertyStr(ctx, j_global, "File");
282
+ if (!JS_IsUndefined(j_file_ctor) && !JS_IsException(j_file_ctor))
283
+ {
284
+ int is_file = JS_IsInstanceOf(ctx, j_val, j_file_ctor);
285
+ JS_FreeValue(ctx, j_file_ctor);
286
+ if (is_file > 0)
287
+ {
288
+ JS_FreeValue(ctx, j_global);
289
+
290
+ VALUE r_quickjs_mod = rb_const_get(rb_cClass, rb_intern("Quickjs"));
291
+ VALUE r_file = rb_funcall(rb_const_get(r_quickjs_mod, rb_intern("File")), rb_intern("new"), 0);
292
+ r_populate_blob_attrs(ctx, j_val, r_file);
293
+
294
+ JSValue j_name = JS_GetPropertyStr(ctx, j_val, "name");
295
+ const char *name_str = JS_ToCString(ctx, j_name);
296
+ rb_iv_set(r_file, "@name", rb_str_new_cstr(name_str ? name_str : ""));
297
+ JS_FreeCString(ctx, name_str);
298
+ JS_FreeValue(ctx, j_name);
299
+
300
+ JSValue j_last_modified = JS_GetPropertyStr(ctx, j_val, "lastModified");
301
+ double last_modified_val;
302
+ JS_ToFloat64(ctx, &last_modified_val, j_last_modified);
303
+ rb_iv_set(r_file, "@last_modified", DBL2NUM(last_modified_val));
304
+ JS_FreeValue(ctx, j_last_modified);
305
+
306
+ return r_file;
307
+ }
308
+ }
309
+ else
310
+ {
311
+ JS_FreeValue(ctx, j_file_ctor);
312
+ }
313
+
314
+ JSValue j_blob_ctor = JS_GetPropertyStr(ctx, j_global, "Blob");
315
+ JS_FreeValue(ctx, j_global);
316
+ if (JS_IsUndefined(j_blob_ctor) || JS_IsException(j_blob_ctor))
317
+ {
318
+ JS_FreeValue(ctx, j_blob_ctor);
319
+ return Qnil;
320
+ }
321
+
322
+ int is_blob = JS_IsInstanceOf(ctx, j_val, j_blob_ctor);
323
+ JS_FreeValue(ctx, j_blob_ctor);
324
+ if (is_blob <= 0)
325
+ return Qnil;
326
+
327
+ VALUE r_quickjs_mod = rb_const_get(rb_cClass, rb_intern("Quickjs"));
328
+ VALUE r_blob = rb_funcall(rb_const_get(r_quickjs_mod, rb_intern("Blob")), rb_intern("new"), 0);
329
+ r_populate_blob_attrs(ctx, j_val, r_blob);
330
+
331
+ return r_blob;
332
+ }
@@ -0,0 +1,16 @@
1
+ #ifndef QUICKJSRB_FILE_H
2
+ #define QUICKJSRB_FILE_H 1
3
+
4
+ // This header is included by quickjsrb.c (which already includes quickjsrb.h)
5
+ // and quickjsrb_file.c (which includes quickjsrb.h before this header).
6
+ // So we rely on VMData, JSValue, etc. being already defined.
7
+
8
+ void quickjsrb_init_file_proxy(VMData *data);
9
+ JSValue quickjsrb_file_to_js(JSContext *ctx, VALUE r_file);
10
+
11
+ // Check if a JS value is a File instance and convert to Quickjs::File
12
+ // Returns Qnil if not a File
13
+ VALUE quickjsrb_try_convert_js_file(JSContext *ctx, JSValue j_val);
14
+
15
+
16
+ #endif /* QUICKJSRB_FILE_H */
@@ -0,0 +1 @@
1
+ (function(){var e=class{get encoding(){return`utf-8`}encode(e=``){let t=String(e),n=[];for(let e=0;e<t.length;e++){let r=t.charCodeAt(e);if(r>=55296&&r<=56319&&e+1<t.length){let n=t.charCodeAt(e+1);n>=56320&&n<=57343&&(r=(r-55296)*1024+(n-56320)+65536,e++)}r<=127?n.push(r):r<=2047?n.push(192|r>>6,128|r&63):r<=65535?n.push(224|r>>12,128|r>>6&63,128|r&63):n.push(240|r>>18,128|r>>12&63,128|r>>6&63,128|r&63)}return new Uint8Array(n)}encodeInto(e,t){let n=String(e),r=0,i=0;for(let e=0;e<n.length;e++){let a=n.charCodeAt(e);if(a>=55296&&a<=56319&&e+1<n.length){let o=n.charCodeAt(e+1);if(o>=56320&&o<=57343){if(i+4>t.length)break;a=(a-55296)*1024+(o-56320)+65536,t[i++]=240|a>>18,t[i++]=128|a>>12&63,t[i++]=128|a>>6&63,t[i++]=128|a&63,r+=2,e++;continue}}let o;if(o=a<=127?1:a<=2047?2:3,i+o>t.length)break;o===1?t[i++]=a:o===2?(t[i++]=192|a>>6,t[i++]=128|a&63):(t[i++]=224|a>>12,t[i++]=128|a>>6&63,t[i++]=128|a&63),r++}return{read:r,written:i}}};let t=[`unicode-1-1-utf-8`,`unicode11utf8`,`unicode20utf8`,`utf-8`,`utf8`,`x-unicode20utf8`];function n(e){let n=e.trim().toLowerCase();return t.includes(n)?`utf-8`:null}var r=class{#e;#t;#n;constructor(e=`utf-8`,t={}){let r=n(String(e));if(!r)throw RangeError(`The "${e}" encoding is not supported.`);this.#e=r,this.#t=!!t.fatal,this.#n=!!t.ignoreBOM}get encoding(){return this.#e}get fatal(){return this.#t}get ignoreBOM(){return this.#n}decode(e,t={}){if(e==null)return``;let n;if(e instanceof ArrayBuffer)n=new Uint8Array(e);else if(ArrayBuffer.isView(e))n=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);else throw TypeError(`The provided value is not of type '(ArrayBuffer or ArrayBufferView)'`);let r=0;return!this.#n&&n.length>=3&&n[0]===239&&n[1]===187&&n[2]===191&&(r=3),i(n,r,this.#t)}};function i(e,t,n){let r=``,i=t;for(;i<e.length;){let t=e[i],a,o;if(t<=127)a=t,o=1;else if((t&224)==192){if(i+1>=e.length||(e[i+1]&192)!=128){if(n)throw TypeError(`The encoded data was not valid.`);r+=`�`,i++;continue}a=(t&31)<<6|e[i+1]&63,o=2}else if((t&240)==224){if(i+2>=e.length||(e[i+1]&192)!=128||(e[i+2]&192)!=128){if(n)throw TypeError(`The encoded data was not valid.`);r+=`�`,i++;continue}a=(t&15)<<12|(e[i+1]&63)<<6|e[i+2]&63,o=3}else if((t&248)==240){if(i+3>=e.length||(e[i+1]&192)!=128||(e[i+2]&192)!=128||(e[i+3]&192)!=128){if(n)throw TypeError(`The encoded data was not valid.`);r+=`�`,i++;continue}a=(t&7)<<18|(e[i+1]&63)<<12|(e[i+2]&63)<<6|e[i+3]&63,o=4}else{if(n)throw TypeError(`The encoded data was not valid.`);r+=`�`,i++;continue}a<=65535?r+=String.fromCharCode(a):(a-=65536,r+=String.fromCharCode(55296+(a>>10),56320+(a&1023))),i+=o}return r}globalThis.TextEncoder=e,globalThis.TextDecoder=r})();
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.12.0"
4
+ VERSION = "0.13.0.pre"
5
5
  end
data/lib/quickjs.rb CHANGED
@@ -5,6 +5,14 @@ require_relative "quickjs/version"
5
5
  require_relative "quickjs/quickjsrb"
6
6
 
7
7
  module Quickjs
8
+ class Blob
9
+ attr_reader :size, :type, :content
10
+ end
11
+
12
+ class File < Blob
13
+ attr_reader :name, :last_modified
14
+ end
15
+
8
16
  def eval_code(code, overwrite_opts = {})
9
17
  vm = Quickjs::VM.new(**overwrite_opts)
10
18
  res = vm.eval_code(code)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "quickjs-rb-polyfills",
3
- "version": "0.12.0",
3
+ "version": "0.13.0.pre",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "quickjs-rb-polyfills",
9
- "version": "0.12.0",
9
+ "version": "0.13.0.pre",
10
10
  "dependencies": {
11
11
  "@formatjs/intl-datetimeformat": "^7.2.1",
12
12
  "@formatjs/intl-getcanonicallocales": "^3.2.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickjs-rb-polyfills",
3
- "version": "0.12.0",
3
+ "version": "0.13.0.pre",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "build": "rolldown -c rolldown.config.mjs"
@@ -17,4 +17,12 @@ export default [
17
17
  minify: true,
18
18
  },
19
19
  }),
20
+ defineConfig({
21
+ input: "src/encoding.js",
22
+ output: {
23
+ file: "../ext/quickjsrb/vendor/polyfill-encoding.min.js",
24
+ format: "iife",
25
+ minify: true,
26
+ },
27
+ }),
20
28
  ];
@@ -0,0 +1,231 @@
1
+ // WHATWG TextEncoder/TextDecoder polyfill for QuickJS
2
+ // Spec: https://encoding.spec.whatwg.org/
3
+
4
+ class TextEncoder {
5
+ get encoding() {
6
+ return "utf-8";
7
+ }
8
+
9
+ encode(input = "") {
10
+ const str = String(input);
11
+ const bytes = [];
12
+ for (let i = 0; i < str.length; i++) {
13
+ let code = str.charCodeAt(i);
14
+
15
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
16
+ const next = str.charCodeAt(i + 1);
17
+ if (next >= 0xdc00 && next <= 0xdfff) {
18
+ code = (code - 0xd800) * 0x400 + (next - 0xdc00) + 0x10000;
19
+ i++;
20
+ }
21
+ }
22
+
23
+ if (code <= 0x7f) {
24
+ bytes.push(code);
25
+ } else if (code <= 0x7ff) {
26
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
27
+ } else if (code <= 0xffff) {
28
+ bytes.push(
29
+ 0xe0 | (code >> 12),
30
+ 0x80 | ((code >> 6) & 0x3f),
31
+ 0x80 | (code & 0x3f)
32
+ );
33
+ } else {
34
+ bytes.push(
35
+ 0xf0 | (code >> 18),
36
+ 0x80 | ((code >> 12) & 0x3f),
37
+ 0x80 | ((code >> 6) & 0x3f),
38
+ 0x80 | (code & 0x3f)
39
+ );
40
+ }
41
+ }
42
+ return new Uint8Array(bytes);
43
+ }
44
+
45
+ encodeInto(source, destination) {
46
+ const str = String(source);
47
+ let read = 0;
48
+ let written = 0;
49
+
50
+ for (let i = 0; i < str.length; i++) {
51
+ let code = str.charCodeAt(i);
52
+
53
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
54
+ const next = str.charCodeAt(i + 1);
55
+ if (next >= 0xdc00 && next <= 0xdfff) {
56
+ if (written + 4 > destination.length) break;
57
+ code = (code - 0xd800) * 0x400 + (next - 0xdc00) + 0x10000;
58
+ destination[written++] = 0xf0 | (code >> 18);
59
+ destination[written++] = 0x80 | ((code >> 12) & 0x3f);
60
+ destination[written++] = 0x80 | ((code >> 6) & 0x3f);
61
+ destination[written++] = 0x80 | (code & 0x3f);
62
+ read += 2;
63
+ i++;
64
+ continue;
65
+ }
66
+ }
67
+
68
+ let byteCount;
69
+ if (code <= 0x7f) byteCount = 1;
70
+ else if (code <= 0x7ff) byteCount = 2;
71
+ else byteCount = 3;
72
+
73
+ if (written + byteCount > destination.length) break;
74
+
75
+ if (byteCount === 1) {
76
+ destination[written++] = code;
77
+ } else if (byteCount === 2) {
78
+ destination[written++] = 0xc0 | (code >> 6);
79
+ destination[written++] = 0x80 | (code & 0x3f);
80
+ } else {
81
+ destination[written++] = 0xe0 | (code >> 12);
82
+ destination[written++] = 0x80 | ((code >> 6) & 0x3f);
83
+ destination[written++] = 0x80 | (code & 0x3f);
84
+ }
85
+ read++;
86
+ }
87
+
88
+ return { read, written };
89
+ }
90
+ }
91
+
92
+ const UTF8_LABELS = [
93
+ "unicode-1-1-utf-8", "unicode11utf8", "unicode20utf8",
94
+ "utf-8", "utf8", "x-unicode20utf8",
95
+ ];
96
+
97
+ function normalizeEncodingLabel(label) {
98
+ const normalized = label.trim().toLowerCase();
99
+ if (UTF8_LABELS.includes(normalized)) return "utf-8";
100
+ return null;
101
+ }
102
+
103
+ class TextDecoder {
104
+ #encoding;
105
+ #fatal;
106
+ #ignoreBOM;
107
+
108
+ constructor(label = "utf-8", options = {}) {
109
+ const normalized = normalizeEncodingLabel(String(label));
110
+ if (!normalized) {
111
+ throw new RangeError(`The "${label}" encoding is not supported.`);
112
+ }
113
+ this.#encoding = normalized;
114
+ this.#fatal = Boolean(options.fatal);
115
+ this.#ignoreBOM = Boolean(options.ignoreBOM);
116
+ }
117
+
118
+ get encoding() {
119
+ return this.#encoding;
120
+ }
121
+
122
+ get fatal() {
123
+ return this.#fatal;
124
+ }
125
+
126
+ get ignoreBOM() {
127
+ return this.#ignoreBOM;
128
+ }
129
+
130
+ decode(input, options = {}) {
131
+ if (input === undefined || input === null) return "";
132
+
133
+ let bytes;
134
+ if (input instanceof ArrayBuffer) {
135
+ bytes = new Uint8Array(input);
136
+ } else if (ArrayBuffer.isView(input)) {
137
+ bytes = new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
138
+ } else {
139
+ throw new TypeError(
140
+ "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"
141
+ );
142
+ }
143
+
144
+ let start = 0;
145
+ if (
146
+ !this.#ignoreBOM &&
147
+ bytes.length >= 3 &&
148
+ bytes[0] === 0xef &&
149
+ bytes[1] === 0xbb &&
150
+ bytes[2] === 0xbf
151
+ ) {
152
+ start = 3;
153
+ }
154
+
155
+ return decodeUTF8(bytes, start, this.#fatal);
156
+ }
157
+ }
158
+
159
+ function decodeUTF8(bytes, start, fatal) {
160
+ let str = "";
161
+ let i = start;
162
+ while (i < bytes.length) {
163
+ const b = bytes[i];
164
+ let code, byteLen;
165
+
166
+ if (b <= 0x7f) {
167
+ code = b;
168
+ byteLen = 1;
169
+ } else if ((b & 0xe0) === 0xc0) {
170
+ if (i + 1 >= bytes.length || (bytes[i + 1] & 0xc0) !== 0x80) {
171
+ if (fatal) throw new TypeError("The encoded data was not valid.");
172
+ str += "\ufffd";
173
+ i++;
174
+ continue;
175
+ }
176
+ code = ((b & 0x1f) << 6) | (bytes[i + 1] & 0x3f);
177
+ byteLen = 2;
178
+ } else if ((b & 0xf0) === 0xe0) {
179
+ if (
180
+ i + 2 >= bytes.length ||
181
+ (bytes[i + 1] & 0xc0) !== 0x80 ||
182
+ (bytes[i + 2] & 0xc0) !== 0x80
183
+ ) {
184
+ if (fatal) throw new TypeError("The encoded data was not valid.");
185
+ str += "\ufffd";
186
+ i++;
187
+ continue;
188
+ }
189
+ code =
190
+ ((b & 0x0f) << 12) |
191
+ ((bytes[i + 1] & 0x3f) << 6) |
192
+ (bytes[i + 2] & 0x3f);
193
+ byteLen = 3;
194
+ } else if ((b & 0xf8) === 0xf0) {
195
+ if (
196
+ i + 3 >= bytes.length ||
197
+ (bytes[i + 1] & 0xc0) !== 0x80 ||
198
+ (bytes[i + 2] & 0xc0) !== 0x80 ||
199
+ (bytes[i + 3] & 0xc0) !== 0x80
200
+ ) {
201
+ if (fatal) throw new TypeError("The encoded data was not valid.");
202
+ str += "\ufffd";
203
+ i++;
204
+ continue;
205
+ }
206
+ code =
207
+ ((b & 0x07) << 18) |
208
+ ((bytes[i + 1] & 0x3f) << 12) |
209
+ ((bytes[i + 2] & 0x3f) << 6) |
210
+ (bytes[i + 3] & 0x3f);
211
+ byteLen = 4;
212
+ } else {
213
+ if (fatal) throw new TypeError("The encoded data was not valid.");
214
+ str += "\ufffd";
215
+ i++;
216
+ continue;
217
+ }
218
+
219
+ if (code <= 0xffff) {
220
+ str += String.fromCharCode(code);
221
+ } else {
222
+ code -= 0x10000;
223
+ str += String.fromCharCode(0xd800 + (code >> 10), 0xdc00 + (code & 0x3ff));
224
+ }
225
+ i += byteLen;
226
+ }
227
+ return str;
228
+ }
229
+
230
+ globalThis.TextEncoder = TextEncoder;
231
+ globalThis.TextDecoder = TextDecoder;
data/sig/quickjs.rbs CHANGED
@@ -1,4 +1,72 @@
1
1
  module Quickjs
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
3
+
4
+ MODULE_STD: Symbol
5
+ MODULE_OS: Symbol
6
+ FEATURE_TIMEOUT: Symbol
7
+ POLYFILL_INTL: Symbol
8
+ POLYFILL_FILE: Symbol
9
+ POLYFILL_HTML_BASE64: Symbol
10
+
11
+ def self.eval_code: (String code, ?Hash[Symbol, untyped] overwrite_opts) -> untyped
12
+
13
+ class Value
14
+ UNDEFINED: Symbol
15
+ NAN: Symbol
16
+ end
17
+
18
+ class VM
19
+ def initialize: (?features: Array[Symbol], ?memory_limit: Integer, ?max_stack_size: Integer, ?timeout_msec: Integer) -> void
20
+
21
+ def eval_code: (String code) -> untyped
22
+
23
+ def define_function: (String | Symbol name, *Symbol flags) { (*untyped) -> untyped } -> Symbol
24
+
25
+ def import: (String | Array[String] | Hash[Symbol, String] imported, from: String, ?code_to_expose: String?) -> true
26
+
27
+ def logs: () -> Array[Log]
28
+
29
+ class Log
30
+ attr_reader severity: Symbol
31
+
32
+ def raw: () -> Array[untyped]
33
+
34
+ def to_s: () -> String
35
+
36
+ def inspect: () -> String
37
+ end
38
+ end
39
+
40
+ class RuntimeError < ::RuntimeError
41
+ def initialize: (String message, String? js_name) -> void
42
+
43
+ attr_reader js_name: String?
44
+ end
45
+
46
+ class SyntaxError < RuntimeError
47
+ end
48
+
49
+ class TypeError < RuntimeError
50
+ end
51
+
52
+ class ReferenceError < RuntimeError
53
+ end
54
+
55
+ class RangeError < RuntimeError
56
+ end
57
+
58
+ class EvalError < RuntimeError
59
+ end
60
+
61
+ class URIError < RuntimeError
62
+ end
63
+
64
+ class AggregateError < RuntimeError
65
+ end
66
+
67
+ class InterruptedError < RuntimeError
68
+ end
69
+
70
+ class NoAwaitError < RuntimeError
71
+ end
4
72
  end
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.12.0
4
+ version: 0.13.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk
@@ -78,6 +78,9 @@ files:
78
78
  - ext/quickjsrb/quickjs/unicode_gen_def.h
79
79
  - ext/quickjsrb/quickjsrb.c
80
80
  - ext/quickjsrb/quickjsrb.h
81
+ - ext/quickjsrb/quickjsrb_file.c
82
+ - ext/quickjsrb/quickjsrb_file.h
83
+ - ext/quickjsrb/vendor/polyfill-encoding.min.js
81
84
  - ext/quickjsrb/vendor/polyfill-file.min.js
82
85
  - ext/quickjsrb/vendor/polyfill-intl-en.min.js
83
86
  - lib/quickjs.rb
@@ -85,6 +88,7 @@ files:
85
88
  - polyfills/package-lock.json
86
89
  - polyfills/package.json
87
90
  - polyfills/rolldown.config.mjs
91
+ - polyfills/src/encoding.js
88
92
  - polyfills/src/file.js
89
93
  - polyfills/src/intl-en.js
90
94
  - sig/quickjs.rbs