quickjs 0.1.3 → 0.1.5

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: 6466e18ce71a5c30009887601f27eea994bb53b055f023de2052fe1dfac5e4f0
4
- data.tar.gz: 80868dca91c750a6686569550f9fed2e053949443aa9805e032d740cbeb6a914
3
+ metadata.gz: 16b9d276d5d05c5f7cf5ff981573283fa990064d55e60b02dc86f773c892c9a0
4
+ data.tar.gz: bf3e2ebee03197fdead070a974d40485c77bab90a7ab5153274a3cfb48a7ea4b
5
5
  SHA512:
6
- metadata.gz: 977dd9ff96e91f4bb340ac3799d65464ab257635a064a56210bacb8f8e0de04010546fde2bd6207455b073c9e88557fc43aa871fd7d8056b9d3fdecb936f445e
7
- data.tar.gz: 7060cbd9b83e6968d39761398ae7a7137efc77f8c662e10e41b77f5f1edaa2bd281e6f90a3ff32ba4f94001ff0d10371e2414c26ffd2e9d6490de725c90c21ec
6
+ metadata.gz: 4f9431993a243c908c905c25a13c8679f17d3404699a87868c6a195f2eeb0b0e2f1c76716bf4564e076a189f20302ef1d6ade01a763e7827fdc5a4229f23be2c
7
+ data.tar.gz: 85505319f063a8f1e5c8c42dc4de7d483f9a3e3deb4599203d2d55d2c79d206d26385f8a139a67a87f753cab67fa74f2a74b0ed6f2a864fd5bd37c2588762881
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ## [0.1.3] - 2024-06-14
1
+ ## [0.1.4] - 2024-06-14
2
2
 
3
3
  - Initial release
4
4
  - See `test/quickjs_test.rb` for supported features
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # quickjs.rb
2
+
3
+ A Ruby wrapper for [QuickJS](https://bellard.org/quickjs) to run JavaScript codes via Ruby with a smaller footprint.
4
+
5
+ [![Gem Version](https://img.shields.io/gem/v/quickjs?style=for-the-badge)](https://rubygems.org/gems/quickjs) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/hmsk/quickjs.rb/main.yml?style=for-the-badge)](https://github.com/hmsk/quickjs.rb/actions/workflows/main.yml)
6
+
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ gem install quickjs
12
+ ```
13
+
14
+ ```rb
15
+ gem 'quickjs'
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### `Quickjs.eval_code`: Evaluate JavaScript code instantly
21
+
22
+ ```rb
23
+ require 'quickjs'
24
+
25
+ Quickjs.eval_code('const fn = (n, pow) => n ** pow; fn(2,8);') # => 256
26
+ Quickjs.eval_code('const fn = (name) => `Hi, ${name}!`; fn("Itadori");') # => "Hi, Itadori!
27
+ Quickjs.eval_code("const isOne = (n) => 1 === n; func(1);") #=> true (TrueClass)
28
+ Quickjs.eval_code("const isOne = (n) => 1 === n; func(3);") #=> false (FalseClass)
29
+
30
+ # When code returns 'object' for `typeof`, the result is converted via JSON.stringify (JS) -> JSON.parse (Ruby)
31
+ Quickjs.eval_code("[1,2,3]") #=> [1, 2, 3] (Array)
32
+ Quickjs.eval_code("({ a: '1', b: 1 })") #=> { 'a' => '1', 'b' => 1 } (Hash)
33
+
34
+ Quickjs.eval_code("null") #=> nil
35
+ Quickjs.eval_code('const obj = {}; obj.missingKey;') # => :undefined (Quickjs::Value::Undefined)
36
+ Quickjs.eval_code("Number('whatever')") #=> :NaN (Quickjs::Value::NAN)
37
+ ```
38
+
39
+ #### Limit resources
40
+
41
+ ```rb
42
+ # 1GB memory limit
43
+ Quickjs.eval_code(code, { memoryLimit: 1024 ** 3 })
44
+
45
+ # 1MB max stack size
46
+ Quickjs.eval_code(code, { memoryLimit: 1024 ** 2 })
47
+ ```
48
+
49
+ #### Enable built-in modules
50
+
51
+ ```rb
52
+ # enable std module
53
+ # https://bellard.org/quickjs/quickjs.html#std-module
54
+ Quickjs.eval_code(code, { features: [Quickjs::MODULE_STD] })
55
+
56
+ # enable os module
57
+ # https://bellard.org/quickjs/quickjs.html#os-module
58
+ Quickjs.eval_code(code, { features: [Quickjs::MODULE_OS] })
59
+ ```
60
+
61
+ ### Maintain a consistent VM/runtime
62
+
63
+ ```rb
64
+ vm = Quickjs::VM.new
65
+ vm.eval_code('const a = { b: "c" };')
66
+ vm.eval_code('a.b;') #=> "c"
67
+ vm.eval_code('a.b = "d";')
68
+ vm.eval_code('a.b;') #=> "d"
69
+ ```
70
+
71
+ #### Config VM
72
+
73
+ ```rb
74
+ vm = Quickjs::VM.new(
75
+ memory_limit: 1024 * 1024,
76
+ max_stack_size: 1024 * 1024,
77
+ )
78
+ ```
79
+
80
+ ```rb
81
+ vm = Quickjs::VM.new(
82
+ features: [::Quickjs::MODULE_STD],
83
+ )
84
+ vm = Quickjs::VM.new(
85
+ features: [::Quickjs::MODULE_OS],
86
+ )
87
+ ```
88
+
89
+ #### Dispose VM explicitly
90
+
91
+ ```rb
92
+ vm.dispose!
93
+ ```
94
+
95
+ ## License
96
+
97
+ Every file in `ext/quickjsrb/quickjs` is licensed under [the MIT License Copyright 2017-2021 by Fabrice Bellard and Charlie Goron](/ext/quickjsrb/quickjs/LICENSE).
98
+ For otherwise, [the MIT License, Copyright 2024 by Kengo Hamasaki](/LICENSE).
@@ -1,107 +1,205 @@
1
1
  #include "quickjsrb.h"
2
2
 
3
3
  VALUE rb_mQuickjs;
4
- VALUE rb_mQuickjsValue;
5
4
  const char *undefinedId = "undefined";
6
5
  const char *nanId = "NaN";
7
6
 
8
- VALUE rb_module_eval_js_code(
9
- VALUE klass,
10
- VALUE r_code,
11
- VALUE r_memoryLimit,
12
- VALUE r_maxStackSize,
13
- VALUE r_enableStd,
14
- VALUE r_enableOs
15
- ) {
16
- JSRuntime *rt = JS_NewRuntime();
17
- JSContext *ctx = JS_NewContext(rt);
18
-
19
- JS_SetMemoryLimit(rt, NUM2UINT(r_memoryLimit));
20
- JS_SetMaxStackSize(rt, NUM2UINT(r_maxStackSize));
21
-
22
- JS_AddIntrinsicBigFloat(ctx);
23
- JS_AddIntrinsicBigDecimal(ctx);
24
- JS_AddIntrinsicOperators(ctx);
25
- JS_EnableBignumExt(ctx, TRUE);
26
-
27
- JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
28
- js_std_add_helpers(ctx, 0, NULL);
29
-
30
- js_std_init_handlers(rt);
31
- if (r_enableStd == Qtrue) {
32
- js_init_module_std(ctx, "std");
33
- const char *enableStd = "import * as std from 'std';\n"
34
- "globalThis.std = std;\n";
35
- JSValue stdEval = JS_Eval(ctx, enableStd, strlen(enableStd), "<code>", JS_EVAL_TYPE_MODULE);
36
- JS_FreeValue(ctx, stdEval);
37
- }
38
-
39
- if (r_enableOs == Qtrue) {
40
- js_init_module_os(ctx, "os");
7
+ const char *featureStdId = "feature_std";
8
+ const char *featureOsId = "feature_os";
41
9
 
42
- const char *enableOs = "import * as os from 'os';\n"
43
- "globalThis.os = os;\n";
44
- JSValue osEval = JS_Eval(ctx, enableOs, strlen(enableOs), "<code>", JS_EVAL_TYPE_MODULE);
45
- JS_FreeValue(ctx, osEval);
10
+ VALUE to_rb_value (JSValue jsv, JSContext *ctx) {
11
+ switch(JS_VALUE_GET_NORM_TAG(jsv)) {
12
+ case JS_TAG_INT: {
13
+ int int_res = 0;
14
+ JS_ToInt32(ctx, &int_res, jsv);
15
+ return INT2NUM(int_res);
46
16
  }
47
-
48
- char *code = StringValueCStr(r_code);
49
- JSValue res = JS_Eval(ctx, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL);
50
-
51
- VALUE result;
52
- int r = 0;
53
- if (JS_IsException(res)) {
54
- rb_raise(rb_eRuntimeError, "Something happened by evaluating as JavaScript code");
55
- result = Qnil;
56
- } else if (JS_IsObject(res)) {
17
+ case JS_TAG_FLOAT64: {
18
+ if (JS_VALUE_IS_NAN(jsv)) {
19
+ return ID2SYM(rb_intern(nanId));
20
+ }
21
+ double double_res;
22
+ JS_ToFloat64(ctx, &double_res, jsv);
23
+ return DBL2NUM(double_res);
24
+ }
25
+ case JS_TAG_BOOL: {
26
+ return JS_ToBool(ctx, jsv) > 0 ? Qtrue : Qfalse;
27
+ }
28
+ case JS_TAG_STRING: {
29
+ JSValue maybeString = JS_ToString(ctx, jsv);
30
+ const char *msg = JS_ToCString(ctx, maybeString);
31
+ return rb_str_new2(msg);
32
+ }
33
+ case JS_TAG_OBJECT: {
57
34
  JSValue global = JS_GetGlobalObject(ctx);
58
35
  JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
59
36
  JSValue stringifyFunc = JS_GetPropertyStr(ctx, jsonClass, "stringify");
60
- JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &res);
37
+ JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &jsv);
61
38
 
62
39
  const char *msg = JS_ToCString(ctx, strigified);
63
40
  VALUE rbString = rb_str_new2(msg);
64
- VALUE rb_cJson = rb_const_get(rb_cClass, rb_intern("JSON"));
65
- result = rb_funcall(rb_cJson, rb_intern("parse"), 1, rbString);
66
41
 
67
42
  JS_FreeValue(ctx, global);
68
43
  JS_FreeValue(ctx, strigified);
69
44
  JS_FreeValue(ctx, stringifyFunc);
70
45
  JS_FreeValue(ctx, jsonClass);
71
- } else if (JS_VALUE_IS_NAN(res)) {
72
- result = ID2SYM(rb_intern(nanId));
73
- } else if (JS_IsNumber(res)) {
74
- JS_ToInt32(ctx, &r, res);
75
- result = INT2NUM(r);
76
- } else if (JS_IsString(res)) {
77
- JSValue maybeString = JS_ToString(ctx, res);
78
- const char *msg = JS_ToCString(ctx, maybeString);
79
- result = rb_str_new2(msg);
80
- } else if (JS_IsBool(res)) {
81
- result = JS_ToBool(ctx, res) > 0 ? Qtrue : Qfalse;
82
- } else if (JS_IsUndefined(res)) {
83
- result = ID2SYM(rb_intern(undefinedId));
84
- } else if (JS_IsNull(res)) {
85
- result = Qnil;
86
- } else {
87
- result = Qnil;
46
+
47
+ return rb_funcall(rb_const_get(rb_cClass, rb_intern("JSON")), rb_intern("parse"), 1, rbString);
48
+ }
49
+ case JS_TAG_NULL:
50
+ return Qnil;
51
+ case JS_TAG_UNDEFINED:
52
+ return ID2SYM(rb_intern(undefinedId));
53
+ case JS_TAG_EXCEPTION:
54
+ rb_raise(rb_eRuntimeError, "Something happened by evaluating as JavaScript code");
55
+ return Qnil;
56
+ case JS_TAG_BIG_INT: {
57
+ JSValue toStringFunc = JS_GetPropertyStr(ctx, jsv, "toString");
58
+ JSValue strigified = JS_Call(ctx, toStringFunc, jsv, 0, NULL);
59
+
60
+ const char *msg = JS_ToCString(ctx, strigified);
61
+ VALUE rbString = rb_str_new2(msg);
62
+ JS_FreeValue(ctx, strigified);
63
+ JS_FreeValue(ctx, toStringFunc);
64
+
65
+ return rb_funcall(rbString, rb_intern("to_i"), 0, NULL);
66
+ }
67
+ case JS_TAG_BIG_FLOAT:
68
+ case JS_TAG_BIG_DECIMAL:
69
+ case JS_TAG_SYMBOL:
70
+ default:
71
+ return Qnil;
88
72
  }
89
- JS_FreeValue(ctx, res);
73
+ }
90
74
 
91
- js_std_free_handlers(rt);
92
- JS_FreeContext(ctx);
93
- JS_FreeRuntime(rt);
75
+ struct qvmdata {
76
+ struct JSContext *context;
77
+ char alive;
78
+ };
94
79
 
80
+ void qvm_free(void* data)
81
+ {
82
+ free(data);
83
+ }
84
+
85
+ size_t qvm_size(const void* data)
86
+ {
87
+ return sizeof(data);
88
+ }
89
+
90
+ static const rb_data_type_t qvm_type = {
91
+ .wrap_struct_name = "qvm",
92
+ .function = {
93
+ .dmark = NULL,
94
+ .dfree = qvm_free,
95
+ .dsize = qvm_size,
96
+ },
97
+ .data = NULL,
98
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
99
+ };
100
+
101
+ VALUE qvm_alloc(VALUE self)
102
+ {
103
+ struct qvmdata *data;
104
+
105
+ return TypedData_Make_Struct(self, struct qvmdata, &qvm_type, data);
106
+ }
107
+
108
+ VALUE qvm_m_initialize(int argc, VALUE* argv, VALUE self)
109
+ {
110
+ VALUE r_opts;
111
+ rb_scan_args(argc, argv, ":", &r_opts);
112
+ if (NIL_P(r_opts)) r_opts = rb_hash_new();
113
+
114
+ VALUE r_memoryLimit = rb_hash_aref(r_opts, ID2SYM(rb_intern("memory_limit")));
115
+ if (NIL_P(r_memoryLimit)) r_memoryLimit = UINT2NUM(1024 * 1024 * 128);
116
+ VALUE r_maxStackSize = rb_hash_aref(r_opts, ID2SYM(rb_intern("max_stack_size")));
117
+ if (NIL_P(r_maxStackSize)) r_maxStackSize = UINT2NUM(1024 * 1024 * 4);
118
+ VALUE r_features = rb_hash_aref(r_opts, ID2SYM(rb_intern("features")));
119
+ if (NIL_P(r_features)) r_features = rb_ary_new();
120
+
121
+ struct qvmdata *data;
122
+ TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
123
+ JSRuntime *runtime = JS_NewRuntime();
124
+ data->context = JS_NewContext(runtime);
125
+ data->alive = 1;
126
+
127
+ JS_SetMemoryLimit(runtime, NUM2UINT(r_memoryLimit));
128
+ JS_SetMaxStackSize(runtime, NUM2UINT(r_maxStackSize));
129
+
130
+ JS_AddIntrinsicBigFloat(data->context);
131
+ JS_AddIntrinsicBigDecimal(data->context);
132
+ JS_AddIntrinsicOperators(data->context);
133
+ JS_EnableBignumExt(data->context, TRUE);
134
+ js_std_add_helpers(data->context, 0, NULL);
135
+
136
+ JS_SetModuleLoaderFunc(runtime, NULL, js_module_loader, NULL);
137
+ js_std_init_handlers(runtime);
138
+
139
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureStdId))))) {
140
+ js_init_module_std(data->context, "std");
141
+ const char *enableStd = "import * as std from 'std';\n"
142
+ "globalThis.std = std;\n";
143
+ JSValue stdEval = JS_Eval(data->context, enableStd, strlen(enableStd), "<vm>", JS_EVAL_TYPE_MODULE);
144
+ JS_FreeValue(data->context, stdEval);
145
+ }
146
+
147
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureOsId))))) {
148
+ js_init_module_os(data->context, "os");
149
+ const char *enableOs = "import * as os from 'os';\n"
150
+ "globalThis.os = os;\n";
151
+ JSValue osEval = JS_Eval(data->context, enableOs, strlen(enableOs), "<vm>", JS_EVAL_TYPE_MODULE);
152
+ JS_FreeValue(data->context, osEval);
153
+ }
154
+
155
+ return self;
156
+ }
157
+
158
+ VALUE qvm_m_evalCode(VALUE self, VALUE r_code)
159
+ {
160
+ struct qvmdata *data;
161
+ TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
162
+
163
+ if (data->alive < 1) {
164
+ rb_raise(rb_eRuntimeError, "Quickjs::VM was disposed");
165
+ return Qnil;
166
+ }
167
+ char *code = StringValueCStr(r_code);
168
+ JSValue codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL);
169
+ VALUE result = to_rb_value(codeResult, data->context);
170
+
171
+ JS_FreeValue(data->context, codeResult);
95
172
  return result;
96
173
  }
97
174
 
175
+ VALUE qvm_m_dispose(VALUE self)
176
+ {
177
+ struct qvmdata *data;
178
+ TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
179
+
180
+ JSRuntime *runtime = JS_GetRuntime(data->context);
181
+ js_std_free_handlers(runtime);
182
+ JS_FreeContext(data->context);
183
+ JS_FreeRuntime(runtime);
184
+ data->alive = 0;
185
+
186
+ return Qnil;
187
+ }
188
+
98
189
  RUBY_FUNC_EXPORTED void
99
190
  Init_quickjsrb(void)
100
191
  {
101
192
  rb_mQuickjs = rb_define_module("Quickjs");
102
- rb_define_module_function(rb_mQuickjs, "_evalCode", rb_module_eval_js_code, 5);
193
+ rb_define_const(rb_mQuickjs, "MODULE_STD", ID2SYM(rb_intern(featureStdId)));
194
+ rb_define_const(rb_mQuickjs, "MODULE_OS", ID2SYM(rb_intern(featureOsId)));
103
195
 
104
196
  VALUE valueClass = rb_define_class_under(rb_mQuickjs, "Value", rb_cObject);
105
197
  rb_define_const(valueClass, "UNDEFINED", ID2SYM(rb_intern(undefinedId)));
106
198
  rb_define_const(valueClass, "NAN", ID2SYM(rb_intern(nanId)));
199
+
200
+ VALUE vmClass = rb_define_class_under(rb_mQuickjs, "VM", rb_cObject);
201
+ rb_define_alloc_func(vmClass, qvm_alloc);
202
+ rb_define_method(vmClass, "initialize", qvm_m_initialize, -1);
203
+ rb_define_method(vmClass, "eval_code", qvm_m_evalCode, 1);
204
+ rb_define_method(vmClass, "dispose!", qvm_m_dispose, 0);
107
205
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
data/lib/quickjs.rb CHANGED
@@ -5,25 +5,8 @@ require_relative "quickjs/version"
5
5
  require_relative "quickjs/quickjsrb"
6
6
 
7
7
  module Quickjs
8
- FEATURE_STD = :std
9
- FEATURE_OS = :os
10
-
11
- def evalCode(
12
- code,
13
- opts = {
14
- memoryLimit: nil,
15
- maxStackSize: nil,
16
- features: []
17
- }
18
- )
19
-
20
- _evalCode(
21
- code,
22
- opts[:memoryLimit] || 1024 * 1024 * 128,
23
- opts[:maxStackSize] || 1024 * 1024 * 4,
24
- opts[:features].include?(Quickjs::FEATURE_STD),
25
- opts[:features].include?(Quickjs::FEATURE_OS),
26
- )
8
+ def eval_code(code, overwrite_opts = {})
9
+ Quickjs::VM.new(**overwrite_opts).eval_code(code)
27
10
  end
28
- module_function :evalCode
11
+ module_function :eval_code
29
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quickjs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-15 00:00:00.000000000 Z
11
+ date: 2024-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -34,6 +34,7 @@ extra_rdoc_files: []
34
34
  files:
35
35
  - CHANGELOG.md
36
36
  - LICENSE
37
+ - README.md
37
38
  - Rakefile
38
39
  - ext/quickjsrb/extconf.rb
39
40
  - ext/quickjsrb/quickjs/LICENSE