quickjs 0.13.0 → 0.15.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.
@@ -1,5 +1,6 @@
1
1
  #include "quickjsrb.h"
2
2
  #include "quickjsrb_file.h"
3
+ #include "quickjsrb_crypto.h"
3
4
 
4
5
  const char *featureStdId = "feature_std";
5
6
  const char *featureOsId = "feature_os";
@@ -7,6 +8,8 @@ const char *featureTimeoutId = "feature_timeout";
7
8
  const char *featurePolyfillIntlId = "feature_polyfill_intl";
8
9
  const char *featurePolyfillFileId = "feature_polyfill_file";
9
10
  const char *featurePolyfillEncodingId = "feature_polyfill_encoding";
11
+ const char *featurePolyfillUrlId = "feature_polyfill_url";
12
+ const char *featurePolyfillCryptoId = "feature_polyfill_crypto";
10
13
 
11
14
  const char *undefinedId = "undefined";
12
15
  const char *nanId = "NaN";
@@ -23,6 +26,9 @@ const int num_native_errors = sizeof(native_errors) / sizeof(native_errors[0]);
23
26
 
24
27
  static int dispatch_log(VMData *data, const char *severity, VALUE r_row);
25
28
 
29
+ JSValue to_js_value(JSContext *ctx, VALUE r_value);
30
+ VALUE to_rb_value(JSContext *ctx, JSValue j_val);
31
+
26
32
  JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
27
33
  {
28
34
  JSValue j_error = JS_NewError(ctx); // may wanna have custom error class to determine in JS' end
@@ -42,6 +48,20 @@ JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
42
48
  return j_error;
43
49
  }
44
50
 
51
+ typedef struct
52
+ {
53
+ JSContext *ctx;
54
+ JSValue j_obj;
55
+ } RbHashToJsArg;
56
+
57
+ static int rb_hash_entry_to_js(VALUE r_key, VALUE r_val, VALUE extra)
58
+ {
59
+ RbHashToJsArg *arg = (RbHashToJsArg *)extra;
60
+ VALUE r_key_str = rb_funcall(r_key, rb_intern("to_s"), 0);
61
+ JS_SetPropertyStr(arg->ctx, arg->j_obj, StringValueCStr(r_key_str), to_js_value(arg->ctx, r_val));
62
+ return ST_CONTINUE;
63
+ }
64
+
45
65
  JSValue to_js_value(JSContext *ctx, VALUE r_value)
46
66
  {
47
67
  switch (TYPE(r_value))
@@ -71,6 +91,15 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
71
91
  }
72
92
  case T_SYMBOL:
73
93
  {
94
+ if (r_value == QUICKJSRB_SYM(undefinedId))
95
+ return JS_UNDEFINED;
96
+ if (r_value == QUICKJSRB_SYM(nanId))
97
+ {
98
+ JSValue j_global = JS_GetGlobalObject(ctx);
99
+ JSValue j_nan = JS_GetPropertyStr(ctx, j_global, "NaN");
100
+ JS_FreeValue(ctx, j_global);
101
+ return j_nan;
102
+ }
74
103
  VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0);
75
104
  char *str = StringValueCStr(r_str);
76
105
 
@@ -80,14 +109,22 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
80
109
  return JS_TRUE;
81
110
  case T_FALSE:
82
111
  return JS_FALSE;
83
- case T_HASH:
84
112
  case T_ARRAY:
85
113
  {
86
- VALUE r_json_str = rb_funcall(r_value, rb_intern("to_json"), 0);
87
- char *str = StringValueCStr(r_json_str);
88
- JSValue j_parsed = JS_ParseJSON(ctx, str, strlen(str), "<quickjsrb.c>");
89
-
90
- return j_parsed;
114
+ int len = RARRAY_LEN(r_value);
115
+ JSValue j_arr = JS_NewArray(ctx);
116
+ for (int i = 0; i < len; i++)
117
+ {
118
+ JS_SetPropertyUint32(ctx, j_arr, (uint32_t)i, to_js_value(ctx, RARRAY_AREF(r_value, i)));
119
+ }
120
+ return j_arr;
121
+ }
122
+ case T_HASH:
123
+ {
124
+ JSValue j_obj = JS_NewObject(ctx);
125
+ RbHashToJsArg arg = {ctx, j_obj};
126
+ rb_hash_foreach(r_value, rb_hash_entry_to_js, (VALUE)&arg);
127
+ return j_obj;
91
128
  }
92
129
  default:
93
130
  {
@@ -154,6 +191,59 @@ VALUE to_r_json(JSContext *ctx, JSValue j_val)
154
191
  return r_str;
155
192
  }
156
193
 
194
+ static int js_is_plain_object(JSContext *ctx, JSValue j_val)
195
+ {
196
+ JSValue j_proto = JS_GetPrototype(ctx, j_val);
197
+ if (JS_IsNull(j_proto))
198
+ return 1; // Object.create(null)
199
+ JSValue j_global = JS_GetGlobalObject(ctx);
200
+ JSValue j_Object = JS_GetPropertyStr(ctx, j_global, "Object");
201
+ JSValue j_Object_proto = JS_GetPropertyStr(ctx, j_Object, "prototype");
202
+ int result = JS_StrictEq(ctx, j_proto, j_Object_proto);
203
+ JS_FreeValue(ctx, j_proto);
204
+ JS_FreeValue(ctx, j_global);
205
+ JS_FreeValue(ctx, j_Object);
206
+ JS_FreeValue(ctx, j_Object_proto);
207
+ return result;
208
+ }
209
+
210
+ static VALUE js_array_to_rb(JSContext *ctx, JSValue j_val)
211
+ {
212
+ JSValue j_length = JS_GetPropertyStr(ctx, j_val, "length");
213
+ uint32_t length = 0;
214
+ JS_ToUint32(ctx, &length, j_length);
215
+ JS_FreeValue(ctx, j_length);
216
+
217
+ VALUE r_array = rb_ary_new_capa(length);
218
+ for (uint32_t i = 0; i < length; i++)
219
+ {
220
+ JSValue j_elem = JS_GetPropertyUint32(ctx, j_val, i);
221
+ rb_ary_push(r_array, to_rb_value(ctx, j_elem));
222
+ JS_FreeValue(ctx, j_elem);
223
+ }
224
+ return r_array;
225
+ }
226
+
227
+ static VALUE js_plain_object_to_rb(JSContext *ctx, JSValue j_val)
228
+ {
229
+ JSPropertyEnum *ptab;
230
+ uint32_t plen;
231
+ if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, j_val, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0)
232
+ return rb_hash_new();
233
+
234
+ VALUE r_hash = rb_hash_new();
235
+ for (uint32_t i = 0; i < plen; i++)
236
+ {
237
+ const char *key = JS_AtomToCString(ctx, ptab[i].atom);
238
+ JSValue j_prop = JS_GetProperty(ctx, j_val, ptab[i].atom);
239
+ rb_hash_aset(r_hash, rb_str_new2(key), to_rb_value(ctx, j_prop));
240
+ JS_FreeCString(ctx, key);
241
+ JS_FreeValue(ctx, j_prop);
242
+ }
243
+ JS_FreePropertyEnum(ctx, ptab, plen);
244
+ return r_hash;
245
+ }
246
+
157
247
  VALUE to_rb_value(JSContext *ctx, JSValue j_val)
158
248
  {
159
249
  switch (JS_VALUE_GET_NORM_TAG(j_val))
@@ -197,6 +287,18 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
197
287
  return Qnil;
198
288
  }
199
289
 
290
+ if (JS_IsFunction(ctx, j_val))
291
+ {
292
+ JSValue j_toStringFunc = JS_GetPropertyStr(ctx, j_val, "toString");
293
+ JSValue j_source = JS_Call(ctx, j_toStringFunc, j_val, 0, NULL);
294
+ JS_FreeValue(ctx, j_toStringFunc);
295
+ const char *source = JS_ToCString(ctx, j_source);
296
+ JS_FreeValue(ctx, j_source);
297
+ VALUE r_source = rb_str_new2(source);
298
+ JS_FreeCString(ctx, source);
299
+ return rb_funcall(rb_path2class("Quickjs::Function"), rb_intern("new"), 1, r_source);
300
+ }
301
+
200
302
  if (JS_IsError(ctx, j_val))
201
303
  {
202
304
  VALUE r_maybe_ruby_error = find_ruby_error(ctx, j_val);
@@ -236,6 +338,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
236
338
  return r_maybe_file;
237
339
  }
238
340
 
341
+ if (JS_IsArray(ctx, j_val))
342
+ return js_array_to_rb(ctx, j_val);
343
+
344
+ if (js_is_plain_object(ctx, j_val))
345
+ return js_plain_object_to_rb(ctx, j_val);
346
+
347
+ // Fallback: non-plain objects (Date, RegExp, Map, class instances, functions, etc.)
239
348
  VALUE r_str = to_r_json(ctx, j_val);
240
349
 
241
350
  if (rb_funcall(r_str, rb_intern("=="), 1, rb_str_new2("undefined")))
@@ -486,20 +595,14 @@ static VALUE r_try_call_listener(VALUE r_args)
486
595
 
487
596
  static int dispatch_log(VMData *data, const char *severity, VALUE r_row)
488
597
  {
598
+ if (NIL_P(data->log_listener))
599
+ return 0;
600
+
489
601
  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;
602
+ VALUE r_args = rb_ary_new3(2, data->log_listener, r_log);
603
+ int error;
604
+ rb_protect(r_try_call_listener, r_args, &error);
605
+ return error;
503
606
  }
504
607
 
505
608
  static VALUE vm_m_on_log(VALUE r_self)
@@ -680,6 +783,18 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
680
783
  JS_FreeValue(data->context, j_polyfillEncodingResult);
681
784
  }
682
785
 
786
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillUrlId))))
787
+ {
788
+ JSValue j_polyfillUrlObject = JS_ReadObject(data->context, &qjsc_polyfill_url_min, qjsc_polyfill_url_min_size, JS_READ_OBJ_BYTECODE);
789
+ JSValue j_polyfillUrlResult = JS_EvalFunction(data->context, j_polyfillUrlObject);
790
+ JS_FreeValue(data->context, j_polyfillUrlResult);
791
+ }
792
+
793
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillCryptoId))))
794
+ {
795
+ quickjsrb_init_crypto(data->context, j_global);
796
+ }
797
+
683
798
  JSValue j_console = JS_NewObject(data->context);
684
799
  JS_SetPropertyStr(
685
800
  data->context, j_console, "log",
@@ -713,6 +828,20 @@ static int interrupt_handler(JSRuntime *runtime, void *opaque)
713
828
  return elapsed_ms >= eval_time->limit_ms ? 1 : 0;
714
829
  }
715
830
 
831
+ static VALUE to_rb_return_value(JSContext *ctx, JSValue j_val)
832
+ {
833
+ if (JS_VALUE_GET_NORM_TAG(j_val) == JS_TAG_OBJECT && JS_PromiseState(ctx, j_val) != -1)
834
+ {
835
+ JS_FreeValue(ctx, j_val);
836
+ VALUE r_error_message = rb_str_new2("An unawaited Promise was returned to the top-level");
837
+ rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_NO_AWAIT_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
838
+ return Qnil;
839
+ }
840
+ VALUE result = to_rb_value(ctx, j_val);
841
+ JS_FreeValue(ctx, j_val);
842
+ return result;
843
+ }
844
+
716
845
  static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
717
846
  {
718
847
  VMData *data;
@@ -730,26 +859,11 @@ static VALUE vm_m_evalCode(VALUE r_self, VALUE r_code)
730
859
  char *code = StringValueCStr(r_code);
731
860
  JSValue j_codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_ASYNC);
732
861
  JSValue j_awaitedResult = js_std_await(data->context, j_codeResult); // This frees j_codeResult
862
+ // JS_EVAL_FLAG_ASYNC wraps the result in {value, done} — extract the actual value
863
+ // Free j_awaitedResult before to_rb_return_value because it may raise (longjmp), which would skip cleanup
733
864
  JSValue j_returnedValue = JS_GetPropertyStr(data->context, j_awaitedResult, "value");
734
- // Do this by rescuing to_rb_value
735
- if (JS_VALUE_GET_NORM_TAG(j_returnedValue) == JS_TAG_OBJECT && JS_PromiseState(data->context, j_returnedValue) != -1)
736
- {
737
- JS_FreeValue(data->context, j_returnedValue);
738
- JS_FreeValue(data->context, j_awaitedResult);
739
- VALUE r_error_message = rb_str_new2("An unawaited Promise was returned to the top-level");
740
- rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_NO_AWAIT_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
741
- return Qnil;
742
- }
743
- else
744
- {
745
- // Free j_awaitedResult before to_rb_value because to_rb_value may
746
- // raise (longjmp) for JS exceptions, which would skip any cleanup
747
- // after it and leak JS GC objects.
748
- JS_FreeValue(data->context, j_awaitedResult);
749
- VALUE result = to_rb_value(data->context, j_returnedValue);
750
- JS_FreeValue(data->context, j_returnedValue);
751
- return result;
752
- }
865
+ JS_FreeValue(data->context, j_awaitedResult);
866
+ return to_rb_return_value(data->context, j_returnedValue);
753
867
  }
754
868
 
755
869
  static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
@@ -761,33 +875,253 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
761
875
  VALUE r_block;
762
876
  rb_scan_args(argc, argv, "10*&", &r_name, &r_flags, &r_block);
763
877
 
764
- if (!(SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING)))
878
+ VMData *data;
879
+ TypedData_Get_Struct(r_self, VMData, &vm_type, data);
880
+
881
+ if (RB_TYPE_P(r_name, T_ARRAY))
882
+ {
883
+ long path_len = RARRAY_LEN(r_name);
884
+ if (path_len < 1)
885
+ rb_raise(rb_eArgError, "function's path array must not be empty");
886
+
887
+ for (long i = 0; i < path_len; i++)
888
+ {
889
+ VALUE r_seg = RARRAY_AREF(r_name, i);
890
+ if (!(SYMBOL_P(r_seg) || RB_TYPE_P(r_seg, T_STRING)))
891
+ rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
892
+ }
893
+
894
+ // Build internal lookup key by joining path segments with "."
895
+ // e.g. ["myLib", "hello"] -> :"myLib.hello"
896
+ VALUE r_segs = rb_ary_new();
897
+ for (long i = 0; i < path_len; i++)
898
+ rb_ary_push(r_segs, rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_s"), 0));
899
+ VALUE r_key_str = rb_funcall(r_segs, rb_intern("join"), 1, rb_str_new2("."));
900
+ VALUE r_key_sym = rb_funcall(r_key_str, rb_intern("to_sym"), 0);
901
+ rb_hash_aset(data->defined_functions, r_key_sym, r_block);
902
+
903
+ VALUE r_func_seg_str = rb_funcall(RARRAY_AREF(r_name, path_len - 1), rb_intern("to_s"), 0);
904
+ char *funcName = StringValueCStr(r_func_seg_str);
905
+
906
+ JSValueConst ruby_data[2];
907
+ ruby_data[0] = JS_NewString(data->context, StringValueCStr(r_key_str));
908
+ ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
909
+
910
+ // Resolve the parent object to attach the function to.
911
+ // For a single-element array, parent is the global object.
912
+ // For multi-element arrays, traverse path[0..n-2] using JS_Eval for the first
913
+ // segment (so lexical const/let bindings are resolved, not just global properties)
914
+ // and JS_GetPropertyStr for subsequent segments.
915
+ JSValue j_parent;
916
+ if (path_len == 1)
917
+ {
918
+ j_parent = JS_GetGlobalObject(data->context);
919
+ }
920
+ else
921
+ {
922
+ VALUE r_first_str = rb_funcall(RARRAY_AREF(r_name, 0), rb_intern("to_s"), 0);
923
+ const char *first_seg = StringValueCStr(r_first_str);
924
+ j_parent = JS_Eval(data->context, first_seg, strlen(first_seg), "<vm>", JS_EVAL_TYPE_GLOBAL);
925
+
926
+ if (JS_IsException(j_parent) || !JS_IsObject(j_parent))
927
+ {
928
+ JS_FreeValue(data->context, j_parent);
929
+ JS_FreeValue(data->context, ruby_data[0]);
930
+ JS_FreeValue(data->context, ruby_data[1]);
931
+ rb_raise(rb_eArgError, "cannot define function: '%s' is not an object", first_seg);
932
+ }
933
+
934
+ for (long i = 1; i < path_len - 1; i++)
935
+ {
936
+ VALUE r_seg_str = rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_s"), 0);
937
+ JSValue j_next = JS_GetPropertyStr(data->context, j_parent, StringValueCStr(r_seg_str));
938
+ JS_FreeValue(data->context, j_parent);
939
+
940
+ if (JS_IsException(j_next) || !JS_IsObject(j_next))
941
+ {
942
+ JS_FreeValue(data->context, j_next);
943
+ JS_FreeValue(data->context, ruby_data[0]);
944
+ JS_FreeValue(data->context, ruby_data[1]);
945
+ rb_raise(rb_eArgError, "cannot define function: '%s' is not an object", StringValueCStr(r_seg_str));
946
+ }
947
+ j_parent = j_next;
948
+ }
949
+ }
950
+
951
+ JS_SetPropertyStr(
952
+ data->context, j_parent, funcName,
953
+ JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
954
+ JS_FreeValue(data->context, j_parent);
955
+ JS_FreeValue(data->context, ruby_data[0]);
956
+ JS_FreeValue(data->context, ruby_data[1]);
957
+
958
+ VALUE r_result = rb_ary_new();
959
+ for (long i = 0; i < path_len; i++)
960
+ rb_ary_push(r_result, rb_funcall(RARRAY_AREF(r_name, i), rb_intern("to_sym"), 0));
961
+ return r_result;
962
+ }
963
+ else if (SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING))
964
+ {
965
+ VALUE r_name_sym = rb_funcall(r_name, rb_intern("to_sym"), 0);
966
+
967
+ rb_hash_aset(data->defined_functions, r_name_sym, r_block);
968
+ VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
969
+ char *funcName = StringValueCStr(r_name_str);
970
+
971
+ JSValueConst ruby_data[2];
972
+ ruby_data[0] = JS_NewString(data->context, funcName);
973
+ ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
974
+
975
+ JSValue j_global = JS_GetGlobalObject(data->context);
976
+ JS_SetPropertyStr(
977
+ data->context, j_global, funcName,
978
+ JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
979
+ JS_FreeValue(data->context, j_global);
980
+ JS_FreeValue(data->context, ruby_data[0]);
981
+ JS_FreeValue(data->context, ruby_data[1]);
982
+
983
+ return r_name_sym;
984
+ }
985
+ else
765
986
  {
766
987
  rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
767
988
  }
989
+ }
990
+
991
+ static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
992
+ {
993
+ if (argc < 1)
994
+ rb_raise(rb_eArgError, "wrong number of arguments (given 0, expected 1+)");
995
+
996
+ VALUE r_name = argv[0];
768
997
 
769
998
  VMData *data;
770
999
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
771
1000
 
772
- VALUE r_name_sym = rb_funcall(r_name, rb_intern("to_sym"), 0);
1001
+ JSValue j_this = JS_UNDEFINED;
1002
+ JSValue j_func;
773
1003
 
774
- rb_hash_aset(data->defined_functions, r_name_sym, r_block);
775
- VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
776
- char *funcName = StringValueCStr(r_name_str);
1004
+ VALUE r_path;
1005
+ if (SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING))
1006
+ {
1007
+ VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
1008
+ const char *name_str = StringValueCStr(r_name_str);
1009
+ size_t name_len = strlen(name_str);
1010
+ const char *last_bracket = strrchr(name_str, '[');
1011
+ const char *last_dot = strrchr(name_str, '.');
777
1012
 
778
- JSValueConst ruby_data[2];
779
- ruby_data[0] = JS_NewString(data->context, funcName);
780
- ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
1013
+ if (last_bracket != NULL && last_bracket != name_str && name_str[name_len - 1] == ']')
1014
+ {
1015
+ // Bracket notation: 'a["key"]' or 'a.b["key"]' or 'a[0]'
1016
+ // Split into parent expression and the bracketed key
1017
+ VALUE r_parent = rb_str_new(name_str, last_bracket - name_str);
1018
+ const char *key_start = last_bracket + 1;
1019
+ size_t key_len = name_len - (key_start - name_str) - 1; // exclude ']'
1020
+ // Strip surrounding quotes for string keys: 'a["b"]' → key = b
1021
+ if (key_len >= 2 &&
1022
+ ((key_start[0] == '\'' && key_start[key_len - 1] == '\'') ||
1023
+ (key_start[0] == '"' && key_start[key_len - 1] == '"')))
1024
+ {
1025
+ key_start++;
1026
+ key_len -= 2;
1027
+ }
1028
+ VALUE r_key = rb_str_new(key_start, key_len);
1029
+ r_path = rb_ary_new3(2, r_parent, r_key);
1030
+ }
1031
+ else if (last_dot != NULL && last_dot != name_str)
1032
+ {
1033
+ // Dot notation: 'a.b.c' → ['a.b', 'c'] so the parent becomes `this`
1034
+ VALUE r_parent = rb_str_new(name_str, last_dot - name_str);
1035
+ VALUE r_key = rb_str_new2(last_dot + 1);
1036
+ r_path = rb_ary_new3(2, r_parent, r_key);
1037
+ }
1038
+ else
1039
+ {
1040
+ r_path = rb_ary_new3(1, r_name_str);
1041
+ }
1042
+ }
1043
+ else
1044
+ {
1045
+ rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
1046
+ }
781
1047
 
782
- JSValue j_global = JS_GetGlobalObject(data->context);
783
- JS_SetPropertyStr(
784
- data->context, j_global, funcName,
785
- JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
786
- JS_FreeValue(data->context, j_global);
787
- JS_FreeValue(data->context, ruby_data[0]);
788
- JS_FreeValue(data->context, ruby_data[1]);
1048
+ {
1049
+ long path_len = RARRAY_LEN(r_path);
1050
+
1051
+ VALUE r_first = RARRAY_AREF(r_path, 0);
1052
+ if (!(SYMBOL_P(r_first) || RB_TYPE_P(r_first, T_STRING)))
1053
+ rb_raise(rb_eTypeError, "function path elements should be Symbols or Strings");
1054
+
1055
+ VALUE r_first_str = rb_funcall(r_first, rb_intern("to_s"), 0);
1056
+ const char *first_seg = StringValueCStr(r_first_str);
1057
+
1058
+ // JS_Eval accesses both global object properties and lexical (const/let) bindings
1059
+ JSValue j_cur = JS_Eval(data->context, first_seg, strlen(first_seg), "<vm>", JS_EVAL_TYPE_GLOBAL);
1060
+ if (JS_IsException(j_cur))
1061
+ return to_rb_value(data->context, j_cur); // raises
1062
+
1063
+ for (long i = 1; i < path_len; i++)
1064
+ {
1065
+ VALUE r_seg = RARRAY_AREF(r_path, i);
1066
+ if (!(SYMBOL_P(r_seg) || RB_TYPE_P(r_seg, T_STRING)))
1067
+ {
1068
+ JS_FreeValue(data->context, j_cur);
1069
+ JS_FreeValue(data->context, j_this);
1070
+ rb_raise(rb_eTypeError, "function path elements should be Symbols or Strings");
1071
+ }
1072
+ VALUE r_seg_str = rb_funcall(r_seg, rb_intern("to_s"), 0);
1073
+ const char *seg = StringValueCStr(r_seg_str);
1074
+
1075
+ JSValue j_next = JS_GetPropertyStr(data->context, j_cur, seg);
1076
+ if (JS_IsException(j_next))
1077
+ {
1078
+ JS_FreeValue(data->context, j_cur);
1079
+ JS_FreeValue(data->context, j_this);
1080
+ return to_rb_value(data->context, j_next); // raises
1081
+ }
1082
+
1083
+ JS_FreeValue(data->context, j_this);
1084
+ j_this = j_cur;
1085
+ j_cur = j_next;
1086
+ }
1087
+
1088
+ j_func = j_cur;
1089
+ }
1090
+
1091
+ if (!JS_IsFunction(data->context, j_func))
1092
+ {
1093
+ JS_FreeValue(data->context, j_func);
1094
+ JS_FreeValue(data->context, j_this);
1095
+ VALUE r_error_message = rb_str_new2("given path is not a function");
1096
+ rb_exc_raise(rb_funcall(QUICKJSRB_ERROR_FOR(QUICKJSRB_ROOT_RUNTIME_ERROR), rb_intern("new"), 2, r_error_message, Qnil));
1097
+ return Qnil;
1098
+ }
1099
+
1100
+ int nargs = argc - 1;
1101
+ JSValue *j_args = NULL;
1102
+ if (nargs > 0)
1103
+ {
1104
+ j_args = (JSValue *)malloc(sizeof(JSValue) * nargs);
1105
+ for (int i = 0; i < nargs; i++)
1106
+ j_args[i] = to_js_value(data->context, argv[i + 1]);
1107
+ }
789
1108
 
790
- return r_name_sym;
1109
+ clock_gettime(CLOCK_MONOTONIC, &data->eval_time->started_at);
1110
+ JS_SetInterruptHandler(JS_GetRuntime(data->context), interrupt_handler, data->eval_time);
1111
+
1112
+ JSValue j_result = JS_Call(data->context, j_func, j_this, nargs, (JSValueConst *)j_args);
1113
+
1114
+ JS_FreeValue(data->context, j_func);
1115
+ JS_FreeValue(data->context, j_this);
1116
+ if (j_args)
1117
+ {
1118
+ for (int i = 0; i < nargs; i++)
1119
+ JS_FreeValue(data->context, j_args[i]);
1120
+ free(j_args);
1121
+ }
1122
+
1123
+ // js_std_await handles both async (promise) and sync results; frees j_result
1124
+ return to_rb_return_value(data->context, js_std_await(data->context, j_result));
791
1125
  }
792
1126
 
793
1127
  static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
@@ -850,16 +1184,6 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
850
1184
  return Qtrue;
851
1185
  }
852
1186
 
853
- static VALUE vm_m_logs(VALUE r_self)
854
- {
855
- rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "Quickjs::VM#logs is deprecated; use Quickjs::VM#on_log instead");
856
-
857
- VMData *data;
858
- TypedData_Get_Struct(r_self, VMData, &vm_type, data);
859
-
860
- return data->logs;
861
- }
862
-
863
1187
  RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
864
1188
  {
865
1189
  rb_require("json");
@@ -873,9 +1197,9 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
873
1197
  rb_define_alloc_func(r_class_vm, vm_alloc);
874
1198
  rb_define_method(r_class_vm, "initialize", vm_m_initialize, -1);
875
1199
  rb_define_method(r_class_vm, "eval_code", vm_m_evalCode, 1);
1200
+ rb_define_method(r_class_vm, "call", vm_m_callGlobalFunction, -1);
876
1201
  rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
877
1202
  rb_define_method(r_class_vm, "import", vm_m_import, -1);
878
- rb_define_method(r_class_vm, "logs", vm_m_logs, 0);
879
1203
  rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
880
1204
  r_define_log_class(r_class_vm);
881
1205
  }
@@ -18,6 +18,8 @@ extern const uint32_t qjsc_polyfill_file_min_size;
18
18
  extern const uint8_t qjsc_polyfill_file_min;
19
19
  extern const uint32_t qjsc_polyfill_encoding_min_size;
20
20
  extern const uint8_t qjsc_polyfill_encoding_min;
21
+ extern const uint32_t qjsc_polyfill_url_min_size;
22
+ extern const uint8_t qjsc_polyfill_url_min;
21
23
 
22
24
  extern const char *featureStdId;
23
25
  extern const char *featureOsId;
@@ -25,6 +27,8 @@ extern const char *featureTimeoutId;
25
27
  extern const char *featurePolyfillIntlId;
26
28
  extern const char *featurePolyfillFileId;
27
29
  extern const char *featurePolyfillEncodingId;
30
+ extern const char *featurePolyfillUrlId;
31
+ extern const char *featurePolyfillCryptoId;
28
32
 
29
33
  extern const char *undefinedId;
30
34
  extern const char *nanId;
@@ -48,7 +52,6 @@ typedef struct VMData
48
52
  struct JSContext *context;
49
53
  VALUE defined_functions;
50
54
  struct EvalTime *eval_time;
51
- VALUE logs;
52
55
  VALUE log_listener;
53
56
  VALUE alive_objects;
54
57
  JSValue j_file_proxy_creator;
@@ -80,7 +83,6 @@ static void vm_mark(void *ptr)
80
83
  {
81
84
  VMData *data = (VMData *)ptr;
82
85
  rb_gc_mark_movable(data->defined_functions);
83
- rb_gc_mark_movable(data->logs);
84
86
  rb_gc_mark_movable(data->log_listener);
85
87
  rb_gc_mark_movable(data->alive_objects);
86
88
  }
@@ -89,7 +91,6 @@ static void vm_compact(void *ptr)
89
91
  {
90
92
  VMData *data = (VMData *)ptr;
91
93
  data->defined_functions = rb_gc_location(data->defined_functions);
92
- data->logs = rb_gc_location(data->logs);
93
94
  data->log_listener = rb_gc_location(data->log_listener);
94
95
  data->alive_objects = rb_gc_location(data->alive_objects);
95
96
  }
@@ -110,7 +111,6 @@ static VALUE vm_alloc(VALUE r_self)
110
111
  VMData *data;
111
112
  VALUE obj = TypedData_Make_Struct(r_self, VMData, &vm_type, data);
112
113
  data->defined_functions = rb_hash_new();
113
- data->logs = rb_ary_new();
114
114
  data->log_listener = Qnil;
115
115
  data->alive_objects = rb_hash_new();
116
116
  data->j_file_proxy_creator = JS_UNDEFINED;
@@ -156,6 +156,8 @@ static void r_define_constants(VALUE r_parent_class)
156
156
  rb_define_const(r_parent_class, "POLYFILL_INTL", QUICKJSRB_SYM(featurePolyfillIntlId));
157
157
  rb_define_const(r_parent_class, "POLYFILL_FILE", QUICKJSRB_SYM(featurePolyfillFileId));
158
158
  rb_define_const(r_parent_class, "POLYFILL_ENCODING", QUICKJSRB_SYM(featurePolyfillEncodingId));
159
+ rb_define_const(r_parent_class, "POLYFILL_URL", QUICKJSRB_SYM(featurePolyfillUrlId));
160
+ rb_define_const(r_parent_class, "POLYFILL_CRYPTO", QUICKJSRB_SYM(featurePolyfillCryptoId));
159
161
 
160
162
  VALUE rb_cQuickjsValue = rb_define_class_under(r_parent_class, "Value", rb_cObject);
161
163
  rb_define_const(rb_cQuickjsValue, "UNDEFINED", QUICKJSRB_SYM(undefinedId));