quickjs 0.1.4 → 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: 3e230674190897ed1edcd6b9918deb3f9f073746f28410bdbe3e20e7622baea0
4
- data.tar.gz: 8aeddfc2188ab5c1c34216a9f0e853339d784d2476967a2309171a654e633cc0
3
+ metadata.gz: 16b9d276d5d05c5f7cf5ff981573283fa990064d55e60b02dc86f773c892c9a0
4
+ data.tar.gz: bf3e2ebee03197fdead070a974d40485c77bab90a7ab5153274a3cfb48a7ea4b
5
5
  SHA512:
6
- metadata.gz: dd1069fadcd70b58c8fe25d88f091304be1956cc43d62f64a389671ef0ed7ee79b53023852bdfe4be321f2aee53d3c00784ef7d3d2d1a329bad370087f388e74
7
- data.tar.gz: cc4b076a5cfa578805c4108377ab148d4a5c4ec78b65fb6247dd0e906fbf083e0762173926bf4f6cbd37e469ad58f474a276df38a11e834d2207c6ad688b73cb
6
+ metadata.gz: 4f9431993a243c908c905c25a13c8679f17d3404699a87868c6a195f2eeb0b0e2f1c76716bf4564e076a189f20302ef1d6ade01a763e7827fdc5a4229f23be2c
7
+ data.tar.gz: 85505319f063a8f1e5c8c42dc4de7d483f9a3e3deb4599203d2d55d2c79d206d26385f8a139a67a87f753cab67fa74f2a74b0ed6f2a864fd5bd37c2588762881
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Ruby wrapper for [QuickJS](https://bellard.org/quickjs) to run JavaScript codes via Ruby with a smaller footprint.
4
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?style=for-the-badge)](https://github.com/hmsk/quickjs.rb/actions/workflows/main.yml)
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
6
 
7
7
 
8
8
  ## Installation
@@ -17,33 +17,33 @@ gem 'quickjs'
17
17
 
18
18
  ## Usage
19
19
 
20
- ### `Quickjs.evalCode`: Evaluate JavaScript code
20
+ ### `Quickjs.eval_code`: Evaluate JavaScript code instantly
21
21
 
22
22
  ```rb
23
23
  require 'quickjs'
24
24
 
25
- Quickjs.evalCode('const fn = (n, pow) => n ** pow; fn(2,8);') # => 256
26
- Quickjs.evalCode('const fn = (name) => `Hi, ${name}!`; fn("Itadori");') # => "Hi, Itadori!
27
- Quickjs.evalCode("const isOne = (n) => 1 === n; func(1);") #=> true (TrueClass)
28
- Quickjs.evalCode("const isOne = (n) => 1 === n; func(3);") #=> false (FalseClass)
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
29
 
30
30
  # When code returns 'object' for `typeof`, the result is converted via JSON.stringify (JS) -> JSON.parse (Ruby)
31
- Quickjs.evalCode("[1,2,3]") #=> [1, 2, 3] (Array)
32
- Quickjs.evalCode("({ a: '1', b: 1 })") #=> { 'a' => '1', 'b' => 1 } (Hash)
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
33
 
34
- Quickjs.evalCode("null") #=> nil
35
- Quickjs.evalCode('const obj = {}; obj.missingKey;') # => :undefined (Quickjs::Value::Undefined)
36
- Quickjs.evalCode("Number('whatever')") #=> :NaN (Quickjs::Value::NAN)
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
37
  ```
38
38
 
39
39
  #### Limit resources
40
40
 
41
41
  ```rb
42
42
  # 1GB memory limit
43
- Quickjs.evalCode(code, { memoryLimit: 1024 ** 3 })
43
+ Quickjs.eval_code(code, { memoryLimit: 1024 ** 3 })
44
44
 
45
45
  # 1MB max stack size
46
- Quickjs.evalCode(code, { memoryLimit: 1024 ** 2 })
46
+ Quickjs.eval_code(code, { memoryLimit: 1024 ** 2 })
47
47
  ```
48
48
 
49
49
  #### Enable built-in modules
@@ -51,11 +51,45 @@ Quickjs.evalCode(code, { memoryLimit: 1024 ** 2 })
51
51
  ```rb
52
52
  # enable std module
53
53
  # https://bellard.org/quickjs/quickjs.html#std-module
54
- Quickjs.evalCode(code, { features: [Quickjs::MODULE_STD] })
54
+ Quickjs.eval_code(code, { features: [Quickjs::MODULE_STD] })
55
55
 
56
56
  # enable os module
57
57
  # https://bellard.org/quickjs/quickjs.html#os-module
58
- Quickjs.evalCode(code, { features: [Quickjs::MODULE_OS] })
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!
59
93
  ```
60
94
 
61
95
  ## License
@@ -1,114 +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
- if (JS_IsException(res)) {
53
- rb_raise(rb_eRuntimeError, "Something happened by evaluating as JavaScript code");
54
- result = Qnil;
55
- } 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: {
56
34
  JSValue global = JS_GetGlobalObject(ctx);
57
35
  JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
58
36
  JSValue stringifyFunc = JS_GetPropertyStr(ctx, jsonClass, "stringify");
59
- JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &res);
37
+ JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &jsv);
60
38
 
61
39
  const char *msg = JS_ToCString(ctx, strigified);
62
40
  VALUE rbString = rb_str_new2(msg);
63
- VALUE rb_cJson = rb_const_get(rb_cClass, rb_intern("JSON"));
64
- result = rb_funcall(rb_cJson, rb_intern("parse"), 1, rbString);
65
41
 
66
42
  JS_FreeValue(ctx, global);
67
43
  JS_FreeValue(ctx, strigified);
68
44
  JS_FreeValue(ctx, stringifyFunc);
69
45
  JS_FreeValue(ctx, jsonClass);
70
- } else if (JS_VALUE_IS_NAN(res)) {
71
- result = ID2SYM(rb_intern(nanId));
72
- } else if (JS_IsNumber(res)) {
73
- int tag = JS_VALUE_GET_TAG(res);
74
- if (JS_TAG_IS_FLOAT64(tag)) {
75
- double double_res;
76
- JS_ToFloat64(ctx, &double_res, res);
77
- result = DBL2NUM(double_res);
78
- } else {
79
- int int_res;
80
- JS_ToInt32(ctx, &int_res, res);
81
- result = INT2NUM(int_res);
82
- }
83
- } else if (JS_IsString(res)) {
84
- JSValue maybeString = JS_ToString(ctx, res);
85
- const char *msg = JS_ToCString(ctx, maybeString);
86
- result = rb_str_new2(msg);
87
- } else if (JS_IsBool(res)) {
88
- result = JS_ToBool(ctx, res) > 0 ? Qtrue : Qfalse;
89
- } else if (JS_IsUndefined(res)) {
90
- result = ID2SYM(rb_intern(undefinedId));
91
- } else if (JS_IsNull(res)) {
92
- result = Qnil;
93
- } else {
94
- result = Qnil;
46
+
47
+ return rb_funcall(rb_const_get(rb_cClass, rb_intern("JSON")), rb_intern("parse"), 1, rbString);
95
48
  }
96
- JS_FreeValue(ctx, res);
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);
97
64
 
98
- js_std_free_handlers(rt);
99
- JS_FreeContext(ctx);
100
- JS_FreeRuntime(rt);
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;
72
+ }
73
+ }
74
+
75
+ struct qvmdata {
76
+ struct JSContext *context;
77
+ char alive;
78
+ };
79
+
80
+ void qvm_free(void* data)
81
+ {
82
+ free(data);
83
+ }
101
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);
102
172
  return result;
103
173
  }
104
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
+
105
189
  RUBY_FUNC_EXPORTED void
106
190
  Init_quickjsrb(void)
107
191
  {
108
192
  rb_mQuickjs = rb_define_module("Quickjs");
109
- 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)));
110
195
 
111
196
  VALUE valueClass = rb_define_class_under(rb_mQuickjs, "Value", rb_cObject);
112
197
  rb_define_const(valueClass, "UNDEFINED", ID2SYM(rb_intern(undefinedId)));
113
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);
114
205
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.1.4"
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
- MODULE_STD = :std
9
- MODULE_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::MODULE_STD),
25
- opts[:features].include?(Quickjs::MODULE_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.4
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-19 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