quickjs 0.14.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0430b7f91db5ab0332aaa70759b574b286fcbaef0ae68c1345536256b98e9a8
4
- data.tar.gz: '088306bccab3579af65b1ffade1780141527b1dd562b9400203707fba2464b84'
3
+ metadata.gz: e13c4664328e497bb8c2179807e8c110c4709b6df84a9a60f888e5a822075de2
4
+ data.tar.gz: c43d43f3c0b928798545ecf57bf9956df1912e3867220915dd5b13c8aeb0031f
5
5
  SHA512:
6
- metadata.gz: e876fa5eb3f2937006c1fe1ceea5445b12928f15df06ccfedf644cd5ef5e97774dba79e08210620fa3d8ae4802da2d3d87c8c34340b0125705f330611077a2ae
7
- data.tar.gz: 8e0ab490bf17410f9a648ba98b9fcce7f510a67e1fbbd0d7c383b560ef11d6911f2bea906474671c4f1bb874fe312c0758df2d508366208720dc4c11aff519e7
6
+ metadata.gz: 581463e6df8d5559f9aa1f91e4512b68f745942d771b2246202d3137a378876c6662db800381abd4dc065b4a6a8acb03f247feda4b78aa9ce1fc31cdd6ed6836
7
+ data.tar.gz: cd4f64a7d98a75c5e388b99e1cdbd44c6ad4cfc4cd96f8e23259f12aa8cbe18f8c580ad41633cb65c9f77a6f9732fbb6e042b6805cc034a0c4c060f4ecdb3635
@@ -3,6 +3,7 @@
3
3
  require 'mkmf'
4
4
 
5
5
  $VPATH << "$(srcdir)/quickjs"
6
+ $INCFLAGS << " -I$(srcdir)/quickjs"
6
7
 
7
8
  $srcs = [
8
9
  'dtoa.c',
@@ -21,8 +22,6 @@ $srcs = [
21
22
  'quickjsrb_crypto_subtle.c',
22
23
  ]
23
24
 
24
- append_cflags('-I$(srcdir)/quickjs')
25
-
26
25
  append_cflags('-g')
27
26
  append_cflags('-O2')
28
27
  append_cflags('-Wall')
@@ -26,6 +26,9 @@ const int num_native_errors = sizeof(native_errors) / sizeof(native_errors[0]);
26
26
 
27
27
  static int dispatch_log(VMData *data, const char *severity, VALUE r_row);
28
28
 
29
+ JSValue to_js_value(JSContext *ctx, VALUE r_value);
30
+ VALUE to_rb_value(JSContext *ctx, JSValue j_val);
31
+
29
32
  JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
30
33
  {
31
34
  JSValue j_error = JS_NewError(ctx); // may wanna have custom error class to determine in JS' end
@@ -45,6 +48,20 @@ JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
45
48
  return j_error;
46
49
  }
47
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
+
48
65
  JSValue to_js_value(JSContext *ctx, VALUE r_value)
49
66
  {
50
67
  switch (TYPE(r_value))
@@ -74,6 +91,15 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
74
91
  }
75
92
  case T_SYMBOL:
76
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
+ }
77
103
  VALUE r_str = rb_funcall(r_value, rb_intern("to_s"), 0);
78
104
  char *str = StringValueCStr(r_str);
79
105
 
@@ -83,14 +109,22 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
83
109
  return JS_TRUE;
84
110
  case T_FALSE:
85
111
  return JS_FALSE;
86
- case T_HASH:
87
112
  case T_ARRAY:
88
113
  {
89
- VALUE r_json_str = rb_funcall(r_value, rb_intern("to_json"), 0);
90
- char *str = StringValueCStr(r_json_str);
91
- JSValue j_parsed = JS_ParseJSON(ctx, str, strlen(str), "<quickjsrb.c>");
92
-
93
- 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;
94
128
  }
95
129
  default:
96
130
  {
@@ -157,6 +191,59 @@ VALUE to_r_json(JSContext *ctx, JSValue j_val)
157
191
  return r_str;
158
192
  }
159
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
+
160
247
  VALUE to_rb_value(JSContext *ctx, JSValue j_val)
161
248
  {
162
249
  switch (JS_VALUE_GET_NORM_TAG(j_val))
@@ -178,18 +265,17 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
178
265
  return JS_ToBool(ctx, j_val) > 0 ? Qtrue : Qfalse;
179
266
  }
180
267
  case JS_TAG_STRING:
181
- case JS_TAG_STRING_ROPE:
182
- {
183
- // QuickJS keeps long `s += chunk` chains as a rope (JS_TAG_STRING_ROPE)
184
- // until something materialises them. JS_ToCStringLen flattens ropes
185
- // transparently, so both tags share the same conversion path.
186
- size_t len;
187
- const char *str = JS_ToCStringLen(ctx, &len, j_val);
188
- if (str == NULL)
268
+ {
269
+ int couldntParse;
270
+ VALUE r_result = rb_protect(r_try_json_parse, to_r_json(ctx, j_val), &couldntParse);
271
+ if (couldntParse)
272
+ {
189
273
  return Qnil;
190
- VALUE r_str = rb_utf8_str_new(str, (long)len);
191
- JS_FreeCString(ctx, str);
192
- return r_str;
274
+ }
275
+ else
276
+ {
277
+ return r_result;
278
+ }
193
279
  }
194
280
  case JS_TAG_OBJECT:
195
281
  {
@@ -201,6 +287,18 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
201
287
  return Qnil;
202
288
  }
203
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
+
204
302
  if (JS_IsError(ctx, j_val))
205
303
  {
206
304
  VALUE r_maybe_ruby_error = find_ruby_error(ctx, j_val);
@@ -240,6 +338,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
240
338
  return r_maybe_file;
241
339
  }
242
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.)
243
348
  VALUE r_str = to_r_json(ctx, j_val);
244
349
 
245
350
  if (rb_funcall(r_str, rb_intern("=="), 1, rb_str_new2("undefined")))
@@ -770,33 +875,117 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
770
875
  VALUE r_block;
771
876
  rb_scan_args(argc, argv, "10*&", &r_name, &r_flags, &r_block);
772
877
 
773
- if (!(SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING)))
774
- {
775
- rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
776
- }
777
-
778
878
  VMData *data;
779
879
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
780
880
 
781
- VALUE r_name_sym = rb_funcall(r_name, rb_intern("to_sym"), 0);
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");
782
886
 
783
- rb_hash_aset(data->defined_functions, r_name_sym, r_block);
784
- VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
785
- char *funcName = StringValueCStr(r_name_str);
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
+ }
786
893
 
787
- JSValueConst ruby_data[2];
788
- ruby_data[0] = JS_NewString(data->context, funcName);
789
- ruby_data[1] = JS_NewBool(data->context, RTEST(rb_funcall(r_flags, rb_intern("include?"), 1, ID2SYM(rb_intern("async")))));
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);
790
925
 
791
- JSValue j_global = JS_GetGlobalObject(data->context);
792
- JS_SetPropertyStr(
793
- data->context, j_global, funcName,
794
- JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
795
- JS_FreeValue(data->context, j_global);
796
- JS_FreeValue(data->context, ruby_data[0]);
797
- JS_FreeValue(data->context, ruby_data[1]);
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
+ }
798
933
 
799
- return r_name_sym;
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
986
+ {
987
+ rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
988
+ }
800
989
  }
801
990
 
802
991
  static VALUE vm_m_callGlobalFunction(int argc, VALUE *argv, VALUE r_self)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Quickjs
6
+ class Function
7
+ def initialize(source)
8
+ @source = source
9
+ end
10
+
11
+ def source
12
+ @source
13
+ end
14
+
15
+ def call(*args, on: nil)
16
+ case on
17
+ when Quickjs::VM
18
+ _call_on(on, args)
19
+ when nil, Hash
20
+ vm = Quickjs::VM.new(**on || {})
21
+ res = _call_on(vm, args)
22
+ vm = nil
23
+ res
24
+ else
25
+ raise ArgumentError, 'on: must be a Quickjs::VM, a Hash of VM options, or nil'
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def _call_on(vm, args)
32
+ args_js = args.map { |a| JSON.generate(a) }.join(', ')
33
+ vm.eval_code("(#{@source})(#{args_js})")
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.14.1"
4
+ VERSION = "0.15.0"
5
5
  end
data/lib/quickjs.rb CHANGED
@@ -5,6 +5,7 @@ require "timeout"
5
5
  require_relative "quickjs/version"
6
6
  require_relative "quickjs/subtle_crypto"
7
7
  require_relative "quickjs/crypto_key"
8
+ require_relative "quickjs/function"
8
9
  require_relative "quickjs/quickjsrb"
9
10
 
10
11
  module Quickjs
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "quickjs-rb-polyfills",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "quickjs-rb-polyfills",
9
- "version": "0.14.0",
9
+ "version": "0.15.0",
10
10
  "dependencies": {
11
11
  "@formatjs/intl-datetimeformat": "^7.3.1",
12
12
  "@formatjs/intl-getcanonicallocales": "^3.2.2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickjs-rb-polyfills",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "build": "rolldown -c rolldown.config.mjs",
data/sig/quickjs.rbs CHANGED
@@ -26,6 +26,7 @@ module Quickjs
26
26
  def call: (String | Symbol name, *untyped args) -> untyped
27
27
 
28
28
  def define_function: (String | Symbol name, *Symbol flags) { (*untyped) -> untyped } -> Symbol
29
+ | (Array[String | Symbol] path, *Symbol flags) { (*untyped) -> untyped } -> Array[Symbol]
29
30
 
30
31
  def import: (String | Array[String] | Hash[Symbol, String] imported, from: String, ?code_to_expose: String?) -> true
31
32
 
@@ -42,6 +43,14 @@ module Quickjs
42
43
  end
43
44
  end
44
45
 
46
+ class Function
47
+ def initialize: (String source) -> void
48
+
49
+ def source: () -> String
50
+
51
+ def call: (*untyped args, ?on: VM | Hash[Symbol, untyped] | nil) -> untyped
52
+ end
53
+
45
54
  class RuntimeError < ::RuntimeError
46
55
  def initialize: (String message, String? js_name) -> void
47
56
 
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.14.1
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk
@@ -90,6 +90,7 @@ files:
90
90
  - ext/quickjsrb/vendor/polyfill-url.min.js
91
91
  - lib/quickjs.rb
92
92
  - lib/quickjs/crypto_key.rb
93
+ - lib/quickjs/function.rb
93
94
  - lib/quickjs/subtle_crypto.rb
94
95
  - lib/quickjs/version.rb
95
96
  - polyfills/check-licenses.mjs