quickjs 0.14.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69b86d7c5040dba161c1d6f67cb3eab3fd8a3dc51b30eb6d355c2580dbc29602
4
- data.tar.gz: 5e174baed18d79e0ac18a496f97cd580fd20830a98fb240351eeea46abe57683
3
+ metadata.gz: e13c4664328e497bb8c2179807e8c110c4709b6df84a9a60f888e5a822075de2
4
+ data.tar.gz: c43d43f3c0b928798545ecf57bf9956df1912e3867220915dd5b13c8aeb0031f
5
5
  SHA512:
6
- metadata.gz: 9f7abcc1733fcfe9c51aacf81c9e34b05e7e44142a9db79a8bd4525ba67570f897cd7c1fb200428507876dad35a67b9a3f383711a3b0cfc9ff1b46728cd1c125
7
- data.tar.gz: a15abae6b482f0df7efc89e9b0a3b9d3ca3bab8bb8d04ce7844f60b195a0314d8e12d2b3bbdf8cf01dae763e75945d4e84cf3c1efbe2a99d4cfa525e2279625e
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))
@@ -200,6 +287,18 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
200
287
  return Qnil;
201
288
  }
202
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
+
203
302
  if (JS_IsError(ctx, j_val))
204
303
  {
205
304
  VALUE r_maybe_ruby_error = find_ruby_error(ctx, j_val);
@@ -239,6 +338,13 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
239
338
  return r_maybe_file;
240
339
  }
241
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.)
242
348
  VALUE r_str = to_r_json(ctx, j_val);
243
349
 
244
350
  if (rb_funcall(r_str, rb_intern("=="), 1, rb_str_new2("undefined")))
@@ -769,33 +875,117 @@ static VALUE vm_m_defineGlobalFunction(int argc, VALUE *argv, VALUE r_self)
769
875
  VALUE r_block;
770
876
  rb_scan_args(argc, argv, "10*&", &r_name, &r_flags, &r_block);
771
877
 
772
- if (!(SYMBOL_P(r_name) || RB_TYPE_P(r_name, T_STRING)))
773
- {
774
- rb_raise(rb_eTypeError, "function's name should be a Symbol or a String");
775
- }
776
-
777
878
  VMData *data;
778
879
  TypedData_Get_Struct(r_self, VMData, &vm_type, data);
779
880
 
780
- 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");
781
886
 
782
- rb_hash_aset(data->defined_functions, r_name_sym, r_block);
783
- VALUE r_name_str = rb_funcall(r_name, rb_intern("to_s"), 0);
784
- 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
+ }
785
893
 
786
- JSValueConst ruby_data[2];
787
- ruby_data[0] = JS_NewString(data->context, funcName);
788
- 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);
789
925
 
790
- JSValue j_global = JS_GetGlobalObject(data->context);
791
- JS_SetPropertyStr(
792
- data->context, j_global, funcName,
793
- JS_NewCFunctionData(data->context, js_quickjsrb_call_global, 1, 0, 2, ruby_data));
794
- JS_FreeValue(data->context, j_global);
795
- JS_FreeValue(data->context, ruby_data[0]);
796
- 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
+ }
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);
797
939
 
798
- return r_name_sym;
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
+ }
799
989
  }
800
990
 
801
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.0"
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.0",
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.0
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