quickjs 0.1.4 → 0.1.6

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: 6fa772eb6185fa2bc744d7bb7b0cdb2024ccaf2d2ce34cb221139aaf2e819371
4
+ data.tar.gz: de4b5651b61eaaafe975ba56ae24b954c28efc9ea0ba2a6fb34f149b62d4aa58
5
5
  SHA512:
6
- metadata.gz: dd1069fadcd70b58c8fe25d88f091304be1956cc43d62f64a389671ef0ed7ee79b53023852bdfe4be321f2aee53d3c00784ef7d3d2d1a329bad370087f388e74
7
- data.tar.gz: cc4b076a5cfa578805c4108377ab148d4a5c4ec78b65fb6247dd0e906fbf083e0762173926bf4f6cbd37e469ad58f474a276df38a11e834d2207c6ad688b73cb
6
+ metadata.gz: 1095f8d04c0c90756506b8bd16a519b207b3b2a0eda64abecec8fb541a0a5a5c0e11240abce18b03e4ee4d7b4c3a53138513218f12afa4d7e46462f3a93680ef
7
+ data.tar.gz: 3237ad5b23217199900665605629c5b69c0333ea5951f3a43cf2e26ac65d6a718ff94f262fdaeb5bf0c3b132e60cb0561d60a121045a26bb201bbfd63e8e3a9c
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, { memory_limit: 1024 ** 3 })
44
44
 
45
45
  # 1MB max stack size
46
- Quickjs.evalCode(code, { memoryLimit: 1024 ** 2 })
46
+ Quickjs.eval_code(code, { max_stack_size: 1024 ** 2 })
47
47
  ```
48
48
 
49
49
  #### Enable built-in modules
@@ -51,11 +51,61 @@ 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
+ ### `Quickjs::VM`: 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 ** 3,
76
+ max_stack_size: 1024 ** 2,
77
+ )
78
+ ```
79
+
80
+ ```rb
81
+ # enable std module
82
+ # https://bellard.org/quickjs/quickjs.html#std-module
83
+ vm = Quickjs::VM.new(
84
+ features: [::Quickjs::MODULE_STD],
85
+ )
86
+
87
+ # enable os module
88
+ # https://bellard.org/quickjs/quickjs.html#os-module
89
+ vm = Quickjs::VM.new(
90
+ features: [::Quickjs::MODULE_OS],
91
+ )
92
+ ```
93
+
94
+ #### Dispose VM explicitly
95
+
96
+ ```rb
97
+ vm.dispose!
98
+ ```
99
+
100
+ #### Define a global function for JS
101
+
102
+ ```rb
103
+ vm = Quickjs::VM.new
104
+ vm.define_function("greetingTo") do |arg1|
105
+ ['Hello!', arg1].join(' ')
106
+ end
107
+
108
+ vm.eval_code("greetingTo('Rick')") #=> 'Hello! Rick'
59
109
  ```
60
110
 
61
111
  ## License
@@ -11,6 +11,7 @@ $srcs = [
11
11
  'cutils.c',
12
12
  'quickjs.c',
13
13
  'quickjs-libc.c',
14
+ 'quickjsrb-runtime-state.c',
14
15
  'quickjsrb.c',
15
16
  ]
16
17
 
@@ -0,0 +1,92 @@
1
+ #include "quickjsrb-runtime-state.h"
2
+
3
+ unsigned int hash(const char *key) {
4
+ unsigned long int value = 0;
5
+ unsigned int i = 0;
6
+ unsigned int key_len = strlen(key);
7
+
8
+ for (; i < key_len; ++i) {
9
+ value = value * 37 + key[i];
10
+ }
11
+ return value % MAX_NUM_OF_PROC;
12
+ }
13
+
14
+ ProcEntry *create_proc_entry(const char *key, VALUE proc) {
15
+ ProcEntry *entry = malloc(sizeof(ProcEntry));
16
+ entry->key = strdup(key);
17
+ entry->proc = proc;
18
+ entry->next = NULL;
19
+ return entry;
20
+ }
21
+
22
+ ProcEntryMap *create_proc_entries() {
23
+ ProcEntryMap *entryMap = malloc(sizeof(ProcEntryMap));
24
+ entryMap->entries = malloc(sizeof(ProcEntry *) * MAX_NUM_OF_PROC);
25
+ for (int i = 0; i < MAX_NUM_OF_PROC; ++i) {
26
+ entryMap->entries[i] = NULL;
27
+ }
28
+ return entryMap;
29
+ }
30
+
31
+ void set_proc(ProcEntryMap *entryMap, const char *key, VALUE proc) {
32
+ unsigned int slot = hash(key);
33
+ ProcEntry *entry = entryMap->entries[slot];
34
+
35
+ if (entry == NULL) {
36
+ entryMap->entries[slot] = create_proc_entry(key, proc);
37
+ return;
38
+ }
39
+
40
+ ProcEntry *prev;
41
+ while (entry != NULL) {
42
+ if (strcmp(entry->key, key) == 0) {
43
+ entry->proc = proc;
44
+ return;
45
+ }
46
+ prev = entry;
47
+ entry = prev->next;
48
+ }
49
+
50
+ prev->next = create_proc_entry(key, proc);
51
+ }
52
+
53
+ VALUE get_proc(ProcEntryMap *entryMap, const char *key) {
54
+ unsigned int slot = hash(key);
55
+
56
+ ProcEntry *entry = entryMap->entries[slot];
57
+
58
+ while (entry != NULL) {
59
+ if (strcmp(entry->key, key) == 0) {
60
+ return entry->proc;
61
+ }
62
+ entry = entry->next;
63
+ }
64
+
65
+ return Qnil;
66
+ }
67
+
68
+ void free_proc_entry_map(ProcEntryMap *entryMap) {
69
+ for (int i = 0; i < MAX_NUM_OF_PROC; ++i) {
70
+ ProcEntry *entry = entryMap->entries[i];
71
+ while (entry != NULL) {
72
+ ProcEntry *temp = entry;
73
+ entry = entry->next;
74
+ free(temp->key);
75
+ free(temp);
76
+ }
77
+ }
78
+ free(entryMap->entries);
79
+ free(entryMap);
80
+ }
81
+
82
+ QuickjsrbRuntimeState *create_quickjsrb_runtime_state() {
83
+ QuickjsrbRuntimeState *state = malloc(sizeof(QuickjsrbRuntimeState));
84
+ state->procs = create_proc_entries();
85
+
86
+ return state;
87
+ }
88
+
89
+ void free_quickjsrb_runtime_state(QuickjsrbRuntimeState *state) {
90
+ free_proc_entry_map(state->procs);
91
+ free(state);
92
+ }
@@ -0,0 +1,22 @@
1
+ #include "ruby.h"
2
+
3
+ #define MAX_NUM_OF_PROC 100
4
+
5
+ typedef struct ProcEntry {
6
+ char *key;
7
+ VALUE proc;
8
+ struct ProcEntry *next;
9
+ } ProcEntry;
10
+
11
+ typedef struct ProcEntryMap {
12
+ ProcEntry **entries;
13
+ } ProcEntryMap;
14
+
15
+ typedef struct QuickjsrbRuntimeState {
16
+ ProcEntryMap *procs;
17
+ } QuickjsrbRuntimeState;
18
+
19
+ QuickjsrbRuntimeState *create_quickjsrb_runtime_state();
20
+ VALUE get_proc(ProcEntryMap *entryMap, const char *key);
21
+ void set_proc(ProcEntryMap *entryMap, const char *key, VALUE proc);
22
+ void free_quickjsrb_runtime_state(QuickjsrbRuntimeState *state);
@@ -1,114 +1,303 @@
1
1
  #include "quickjsrb.h"
2
+ #include "quickjsrb-runtime-state.h"
3
+
4
+ struct qvmdata {
5
+ char alive;
6
+ struct JSContext *context;
7
+ struct QuickjsrbRuntimeState *state;
8
+ };
9
+
10
+ void qvm_free(void* data)
11
+ {
12
+ free(data);
13
+ }
14
+
15
+ size_t qvm_size(const void* data)
16
+ {
17
+ return sizeof(data);
18
+ }
19
+
20
+ static const rb_data_type_t qvm_type = {
21
+ .wrap_struct_name = "qvm",
22
+ .function = {
23
+ .dmark = NULL,
24
+ .dfree = qvm_free,
25
+ .dsize = qvm_size,
26
+ },
27
+ .data = NULL,
28
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
29
+ };
30
+
31
+ VALUE qvm_alloc(VALUE self)
32
+ {
33
+ struct qvmdata *data;
34
+
35
+ return TypedData_Make_Struct(self, struct qvmdata, &qvm_type, data);
36
+ }
37
+
38
+ static JSValue js_quickjsrb_call_global (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
39
+ struct qvmdata *data = JS_GetContextOpaque(ctx);
40
+ JSValue maybeFuncName = JS_ToString(ctx, argv[0]);
41
+ const char *funcName = JS_ToCString(ctx, maybeFuncName);
42
+
43
+ VALUE proc = get_proc(data->state->procs, funcName);
44
+ if (proc == Qnil) {
45
+ return JS_NewString(ctx, "missing");
46
+ }
47
+ JS_FreeValue(ctx, maybeFuncName);
48
+
49
+ // TODO: support multiple args
50
+ JSValue maybeString;
51
+ VALUE r_array = rb_ary_new2(argc);
52
+ for (int i = 1; i < argc; ++i) {
53
+ maybeString = JS_ToString(ctx, argv[i]);
54
+ const char *msg = JS_ToCString(ctx, maybeString);
55
+ rb_ary_push(r_array, rb_str_new2(msg));
56
+ }
57
+ JS_FreeValue(ctx, maybeString);
58
+
59
+ VALUE r_result = rb_funcall(proc, rb_intern("call"), argc - 1, r_array);
60
+ char *result = StringValueCStr(r_result);
61
+ return JS_NewString(ctx, result);
62
+ }
2
63
 
3
64
  VALUE rb_mQuickjs;
4
- VALUE rb_mQuickjsValue;
5
65
  const char *undefinedId = "undefined";
6
66
  const char *nanId = "NaN";
7
67
 
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");
68
+ const char *featureStdId = "feature_std";
69
+ const char *featureOsId = "feature_os";
41
70
 
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);
71
+ VALUE to_rb_value (JSValue jsv, JSContext *ctx) {
72
+ switch(JS_VALUE_GET_NORM_TAG(jsv)) {
73
+ case JS_TAG_INT: {
74
+ int int_res = 0;
75
+ JS_ToInt32(ctx, &int_res, jsv);
76
+ return INT2NUM(int_res);
46
77
  }
78
+ case JS_TAG_FLOAT64: {
79
+ if (JS_VALUE_IS_NAN(jsv)) {
80
+ return ID2SYM(rb_intern(nanId));
81
+ }
82
+ double double_res;
83
+ JS_ToFloat64(ctx, &double_res, jsv);
84
+ return DBL2NUM(double_res);
85
+ }
86
+ case JS_TAG_BOOL: {
87
+ return JS_ToBool(ctx, jsv) > 0 ? Qtrue : Qfalse;
88
+ }
89
+ case JS_TAG_STRING: {
90
+ JSValue maybeString = JS_ToString(ctx, jsv);
91
+ const char *msg = JS_ToCString(ctx, maybeString);
92
+ return rb_str_new2(msg);
93
+ }
94
+ case JS_TAG_OBJECT: {
95
+ int promiseState = JS_PromiseState(ctx, jsv);
96
+ if (promiseState == JS_PROMISE_FULFILLED || promiseState == JS_PROMISE_PENDING) {
97
+ return to_rb_value(js_std_await(ctx, jsv), ctx);
98
+ } else if (promiseState == JS_PROMISE_REJECTED) {
99
+ return to_rb_value(JS_Throw(ctx, JS_PromiseResult(ctx, jsv)), ctx);
100
+ }
47
101
 
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)) {
56
102
  JSValue global = JS_GetGlobalObject(ctx);
57
103
  JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
58
104
  JSValue stringifyFunc = JS_GetPropertyStr(ctx, jsonClass, "stringify");
59
- JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &res);
105
+ JSValue strigified = JS_Call(ctx, stringifyFunc, jsonClass, 1, &jsv);
60
106
 
61
107
  const char *msg = JS_ToCString(ctx, strigified);
62
108
  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
109
 
66
110
  JS_FreeValue(ctx, global);
67
111
  JS_FreeValue(ctx, strigified);
68
112
  JS_FreeValue(ctx, stringifyFunc);
69
113
  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);
114
+
115
+ return rb_funcall(rb_const_get(rb_cClass, rb_intern("JSON")), rb_intern("parse"), 1, rbString);
116
+ }
117
+ case JS_TAG_NULL:
118
+ return Qnil;
119
+ case JS_TAG_UNDEFINED:
120
+ return ID2SYM(rb_intern(undefinedId));
121
+ case JS_TAG_EXCEPTION: {
122
+ JSValue exceptionVal = JS_GetException(ctx);
123
+ if (JS_IsError(ctx, exceptionVal)) {
124
+ JSValue jsErrorClassName = JS_GetPropertyStr(ctx, exceptionVal, "name");
125
+ const char *errorClassName = JS_ToCString(ctx, jsErrorClassName);
126
+
127
+ JSValue jsErrorClassMessage = JS_GetPropertyStr(ctx, exceptionVal, "message");
128
+ const char *errorClassMessage = JS_ToCString(ctx, jsErrorClassMessage);
129
+
130
+ JS_FreeValue(ctx, jsErrorClassMessage);
131
+ JS_FreeValue(ctx, jsErrorClassName);
132
+
133
+ rb_raise(rb_eRuntimeError, "%s: %s", errorClassName, errorClassMessage);
78
134
  } else {
79
- int int_res;
80
- JS_ToInt32(ctx, &int_res, res);
81
- result = INT2NUM(int_res);
135
+ const char *errorMessage = JS_ToCString(ctx, exceptionVal);
136
+
137
+ rb_raise(rb_eRuntimeError, "%s", errorMessage);
82
138
  }
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;
139
+
140
+ JS_FreeValue(ctx, exceptionVal);
141
+ return Qnil;
142
+ }
143
+ case JS_TAG_BIG_INT: {
144
+ JSValue toStringFunc = JS_GetPropertyStr(ctx, jsv, "toString");
145
+ JSValue strigified = JS_Call(ctx, toStringFunc, jsv, 0, NULL);
146
+
147
+ const char *msg = JS_ToCString(ctx, strigified);
148
+ VALUE rbString = rb_str_new2(msg);
149
+ JS_FreeValue(ctx, strigified);
150
+ JS_FreeValue(ctx, toStringFunc);
151
+
152
+ return rb_funcall(rbString, rb_intern("to_i"), 0, NULL);
153
+ }
154
+ case JS_TAG_BIG_FLOAT:
155
+ case JS_TAG_BIG_DECIMAL:
156
+ case JS_TAG_SYMBOL:
157
+ default:
158
+ return Qnil;
95
159
  }
96
- JS_FreeValue(ctx, res);
160
+ }
161
+
162
+ VALUE qvm_m_initialize(int argc, VALUE* argv, VALUE self)
163
+ {
164
+ VALUE r_opts;
165
+ rb_scan_args(argc, argv, ":", &r_opts);
166
+ if (NIL_P(r_opts)) r_opts = rb_hash_new();
167
+
168
+ VALUE r_memoryLimit = rb_hash_aref(r_opts, ID2SYM(rb_intern("memory_limit")));
169
+ if (NIL_P(r_memoryLimit)) r_memoryLimit = UINT2NUM(1024 * 1024 * 128);
170
+ VALUE r_maxStackSize = rb_hash_aref(r_opts, ID2SYM(rb_intern("max_stack_size")));
171
+ if (NIL_P(r_maxStackSize)) r_maxStackSize = UINT2NUM(1024 * 1024 * 4);
172
+ VALUE r_features = rb_hash_aref(r_opts, ID2SYM(rb_intern("features")));
173
+ if (NIL_P(r_features)) r_features = rb_ary_new();
174
+
175
+ struct qvmdata *data;
176
+ TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
177
+
178
+ QuickjsrbRuntimeState *state= create_quickjsrb_runtime_state();
179
+ JSRuntime *runtime = JS_NewRuntime();
180
+ data->context = JS_NewContext(runtime);
181
+ data->state = state;
182
+ data->alive = 1;
183
+ JS_SetContextOpaque(data->context, data);
184
+
185
+ JS_SetMemoryLimit(runtime, NUM2UINT(r_memoryLimit));
186
+ JS_SetMaxStackSize(runtime, NUM2UINT(r_maxStackSize));
187
+
188
+ JS_AddIntrinsicBigFloat(data->context);
189
+ JS_AddIntrinsicBigDecimal(data->context);
190
+ JS_AddIntrinsicOperators(data->context);
191
+ JS_EnableBignumExt(data->context, TRUE);
192
+ js_std_add_helpers(data->context, 0, NULL);
193
+
194
+ JS_SetModuleLoaderFunc(runtime, NULL, js_module_loader, NULL);
195
+ js_std_init_handlers(runtime);
196
+
197
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureStdId))))) {
198
+ js_init_module_std(data->context, "std");
199
+ const char *enableStd = "import * as std from 'std';\n"
200
+ "globalThis.std = std;\n";
201
+ JSValue stdEval = JS_Eval(data->context, enableStd, strlen(enableStd), "<vm>", JS_EVAL_TYPE_MODULE);
202
+ JS_FreeValue(data->context, stdEval);
203
+ }
204
+
205
+ if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, ID2SYM(rb_intern(featureOsId))))) {
206
+ js_init_module_os(data->context, "os");
207
+ const char *enableOs = "import * as os from 'os';\n"
208
+ "globalThis.os = os;\n";
209
+ JSValue osEval = JS_Eval(data->context, enableOs, strlen(enableOs), "<vm>", JS_EVAL_TYPE_MODULE);
210
+ JS_FreeValue(data->context, osEval);
211
+ }
212
+
213
+ const char *setupGlobalRuby = "globalThis.__ruby = {};\n";
214
+ JSValue rubyEval = JS_Eval(data->context, setupGlobalRuby, strlen(setupGlobalRuby), "<vm>", JS_EVAL_TYPE_MODULE);
215
+ JS_FreeValue(data->context, rubyEval);
216
+
217
+ JSValue global = JS_GetGlobalObject(data->context);
218
+ JSValue func = JS_NewCFunction(data->context, js_quickjsrb_call_global, "rubyGlobal", 2);
219
+ JS_SetPropertyStr(data->context, global, "rubyGlobal", func);
220
+ JS_FreeValue(data->context, global);
221
+
222
+ return self;
223
+ }
224
+
225
+ VALUE qvm_m_evalCode(VALUE self, VALUE r_code)
226
+ {
227
+ struct qvmdata *data;
228
+ TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
97
229
 
98
- js_std_free_handlers(rt);
99
- JS_FreeContext(ctx);
100
- JS_FreeRuntime(rt);
230
+ if (data->alive < 1) {
231
+ rb_raise(rb_eRuntimeError, "Quickjs::VM was disposed");
232
+ return Qnil;
233
+ }
234
+ char *code = StringValueCStr(r_code);
235
+ JSValue codeResult = JS_Eval(data->context, code, strlen(code), "<code>", JS_EVAL_TYPE_GLOBAL);
236
+ VALUE result = to_rb_value(codeResult, data->context);
101
237
 
238
+ JS_FreeValue(data->context, codeResult);
102
239
  return result;
103
240
  }
104
241
 
242
+ VALUE qvm_m_defineGlobalFunction(VALUE self, VALUE r_name)
243
+ {
244
+ rb_need_block();
245
+
246
+ struct qvmdata *data;
247
+ TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
248
+
249
+ if (rb_block_given_p()) {
250
+ VALUE proc = rb_block_proc();
251
+
252
+ char *funcName = StringValueCStr(r_name);
253
+
254
+ set_proc(data->state->procs, funcName, proc);
255
+
256
+ const char* template = "globalThis.__ruby['%s'] = (...args) => rubyGlobal('%s', args);\nglobalThis['%s'] = globalThis.__ruby['%s'];\n";
257
+ int length = snprintf(NULL, 0, template, funcName, funcName, funcName, funcName);
258
+ char* result = (char*)malloc(length + 1);
259
+ snprintf(result, length + 1, template, funcName, funcName, funcName, funcName);
260
+
261
+ JSValue codeResult = JS_Eval(data->context, result, strlen(result), "<vm>", JS_EVAL_TYPE_MODULE);
262
+
263
+ JS_FreeValue(data->context, codeResult);
264
+ free(result);
265
+ return rb_funcall(r_name, rb_intern("to_sym"), 0, NULL);
266
+ }
267
+
268
+ return Qnil;
269
+ }
270
+
271
+ VALUE qvm_m_dispose(VALUE self)
272
+ {
273
+ struct qvmdata *data;
274
+ TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
275
+
276
+ JSRuntime *runtime = JS_GetRuntime(data->context);
277
+ js_std_free_handlers(runtime);
278
+ free_quickjsrb_runtime_state(data->state);
279
+ JS_FreeContext(data->context);
280
+ JS_FreeRuntime(runtime);
281
+ data->alive = 0;
282
+
283
+ return Qnil;
284
+ }
285
+
105
286
  RUBY_FUNC_EXPORTED void
106
287
  Init_quickjsrb(void)
107
288
  {
108
289
  rb_mQuickjs = rb_define_module("Quickjs");
109
- rb_define_module_function(rb_mQuickjs, "_evalCode", rb_module_eval_js_code, 5);
290
+ rb_define_const(rb_mQuickjs, "MODULE_STD", ID2SYM(rb_intern(featureStdId)));
291
+ rb_define_const(rb_mQuickjs, "MODULE_OS", ID2SYM(rb_intern(featureOsId)));
110
292
 
111
293
  VALUE valueClass = rb_define_class_under(rb_mQuickjs, "Value", rb_cObject);
112
294
  rb_define_const(valueClass, "UNDEFINED", ID2SYM(rb_intern(undefinedId)));
113
295
  rb_define_const(valueClass, "NAN", ID2SYM(rb_intern(nanId)));
296
+
297
+ VALUE vmClass = rb_define_class_under(rb_mQuickjs, "VM", rb_cObject);
298
+ rb_define_alloc_func(vmClass, qvm_alloc);
299
+ rb_define_method(vmClass, "initialize", qvm_m_initialize, -1);
300
+ rb_define_method(vmClass, "eval_code", qvm_m_evalCode, 1);
301
+ rb_define_method(vmClass, "define_function", qvm_m_defineGlobalFunction, 1);
302
+ rb_define_method(vmClass, "dispose!", qvm_m_dispose, 0);
114
303
  }
@@ -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.6"
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.6
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-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -62,6 +62,8 @@ files:
62
62
  - ext/quickjsrb/quickjs/run-test262.c
63
63
  - ext/quickjsrb/quickjs/unicode_gen.c
64
64
  - ext/quickjsrb/quickjs/unicode_gen_def.h
65
+ - ext/quickjsrb/quickjsrb-runtime-state.c
66
+ - ext/quickjsrb/quickjsrb-runtime-state.h
65
67
  - ext/quickjsrb/quickjsrb.c
66
68
  - ext/quickjsrb/quickjsrb.h
67
69
  - lib/quickjs.rb