quickjs 0.1.5 → 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: 16b9d276d5d05c5f7cf5ff981573283fa990064d55e60b02dc86f773c892c9a0
4
- data.tar.gz: bf3e2ebee03197fdead070a974d40485c77bab90a7ab5153274a3cfb48a7ea4b
3
+ metadata.gz: 6fa772eb6185fa2bc744d7bb7b0cdb2024ccaf2d2ce34cb221139aaf2e819371
4
+ data.tar.gz: de4b5651b61eaaafe975ba56ae24b954c28efc9ea0ba2a6fb34f149b62d4aa58
5
5
  SHA512:
6
- metadata.gz: 4f9431993a243c908c905c25a13c8679f17d3404699a87868c6a195f2eeb0b0e2f1c76716bf4564e076a189f20302ef1d6ade01a763e7827fdc5a4229f23be2c
7
- data.tar.gz: 85505319f063a8f1e5c8c42dc4de7d483f9a3e3deb4599203d2d55d2c79d206d26385f8a139a67a87f753cab67fa74f2a74b0ed6f2a864fd5bd37c2588762881
6
+ metadata.gz: 1095f8d04c0c90756506b8bd16a519b207b3b2a0eda64abecec8fb541a0a5a5c0e11240abce18b03e4ee4d7b4c3a53138513218f12afa4d7e46462f3a93680ef
7
+ data.tar.gz: 3237ad5b23217199900665605629c5b69c0333ea5951f3a43cf2e26ac65d6a718ff94f262fdaeb5bf0c3b132e60cb0561d60a121045a26bb201bbfd63e8e3a9c
data/README.md CHANGED
@@ -40,10 +40,10 @@ Quickjs.eval_code("Number('whatever')") #=> :NaN (Quickjs::Value::NAN)
40
40
 
41
41
  ```rb
42
42
  # 1GB memory limit
43
- Quickjs.eval_code(code, { memoryLimit: 1024 ** 3 })
43
+ Quickjs.eval_code(code, { memory_limit: 1024 ** 3 })
44
44
 
45
45
  # 1MB max stack size
46
- Quickjs.eval_code(code, { memoryLimit: 1024 ** 2 })
46
+ Quickjs.eval_code(code, { max_stack_size: 1024 ** 2 })
47
47
  ```
48
48
 
49
49
  #### Enable built-in modules
@@ -58,7 +58,7 @@ Quickjs.eval_code(code, { features: [Quickjs::MODULE_STD] })
58
58
  Quickjs.eval_code(code, { features: [Quickjs::MODULE_OS] })
59
59
  ```
60
60
 
61
- ### Maintain a consistent VM/runtime
61
+ ### `Quickjs::VM`: Maintain a consistent VM/runtime
62
62
 
63
63
  ```rb
64
64
  vm = Quickjs::VM.new
@@ -72,15 +72,20 @@ vm.eval_code('a.b;') #=> "d"
72
72
 
73
73
  ```rb
74
74
  vm = Quickjs::VM.new(
75
- memory_limit: 1024 * 1024,
76
- max_stack_size: 1024 * 1024,
75
+ memory_limit: 1024 ** 3,
76
+ max_stack_size: 1024 ** 2,
77
77
  )
78
78
  ```
79
79
 
80
80
  ```rb
81
+ # enable std module
82
+ # https://bellard.org/quickjs/quickjs.html#std-module
81
83
  vm = Quickjs::VM.new(
82
84
  features: [::Quickjs::MODULE_STD],
83
85
  )
86
+
87
+ # enable os module
88
+ # https://bellard.org/quickjs/quickjs.html#os-module
84
89
  vm = Quickjs::VM.new(
85
90
  features: [::Quickjs::MODULE_OS],
86
91
  )
@@ -92,6 +97,17 @@ vm = Quickjs::VM.new(
92
97
  vm.dispose!
93
98
  ```
94
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'
109
+ ```
110
+
95
111
  ## License
96
112
 
97
113
  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).
@@ -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,4 +1,65 @@
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
65
  const char *undefinedId = "undefined";
@@ -31,6 +92,13 @@ VALUE to_rb_value (JSValue jsv, JSContext *ctx) {
31
92
  return rb_str_new2(msg);
32
93
  }
33
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
+ }
101
+
34
102
  JSValue global = JS_GetGlobalObject(ctx);
35
103
  JSValue jsonClass = JS_GetPropertyStr(ctx, global, "JSON");
36
104
  JSValue stringifyFunc = JS_GetPropertyStr(ctx, jsonClass, "stringify");
@@ -50,9 +118,28 @@ VALUE to_rb_value (JSValue jsv, JSContext *ctx) {
50
118
  return Qnil;
51
119
  case JS_TAG_UNDEFINED:
52
120
  return ID2SYM(rb_intern(undefinedId));
53
- case JS_TAG_EXCEPTION:
54
- rb_raise(rb_eRuntimeError, "Something happened by evaluating as JavaScript code");
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);
134
+ } else {
135
+ const char *errorMessage = JS_ToCString(ctx, exceptionVal);
136
+
137
+ rb_raise(rb_eRuntimeError, "%s", errorMessage);
138
+ }
139
+
140
+ JS_FreeValue(ctx, exceptionVal);
55
141
  return Qnil;
142
+ }
56
143
  case JS_TAG_BIG_INT: {
57
144
  JSValue toStringFunc = JS_GetPropertyStr(ctx, jsv, "toString");
58
145
  JSValue strigified = JS_Call(ctx, toStringFunc, jsv, 0, NULL);
@@ -72,39 +159,6 @@ VALUE to_rb_value (JSValue jsv, JSContext *ctx) {
72
159
  }
73
160
  }
74
161
 
75
- struct qvmdata {
76
- struct JSContext *context;
77
- char alive;
78
- };
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
162
  VALUE qvm_m_initialize(int argc, VALUE* argv, VALUE self)
109
163
  {
110
164
  VALUE r_opts;
@@ -120,9 +174,13 @@ VALUE qvm_m_initialize(int argc, VALUE* argv, VALUE self)
120
174
 
121
175
  struct qvmdata *data;
122
176
  TypedData_Get_Struct(self, struct qvmdata, &qvm_type, data);
177
+
178
+ QuickjsrbRuntimeState *state= create_quickjsrb_runtime_state();
123
179
  JSRuntime *runtime = JS_NewRuntime();
124
180
  data->context = JS_NewContext(runtime);
181
+ data->state = state;
125
182
  data->alive = 1;
183
+ JS_SetContextOpaque(data->context, data);
126
184
 
127
185
  JS_SetMemoryLimit(runtime, NUM2UINT(r_memoryLimit));
128
186
  JS_SetMaxStackSize(runtime, NUM2UINT(r_maxStackSize));
@@ -152,6 +210,15 @@ VALUE qvm_m_initialize(int argc, VALUE* argv, VALUE self)
152
210
  JS_FreeValue(data->context, osEval);
153
211
  }
154
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
+
155
222
  return self;
156
223
  }
157
224
 
@@ -172,6 +239,35 @@ VALUE qvm_m_evalCode(VALUE self, VALUE r_code)
172
239
  return result;
173
240
  }
174
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
+
175
271
  VALUE qvm_m_dispose(VALUE self)
176
272
  {
177
273
  struct qvmdata *data;
@@ -179,6 +275,7 @@ VALUE qvm_m_dispose(VALUE self)
179
275
 
180
276
  JSRuntime *runtime = JS_GetRuntime(data->context);
181
277
  js_std_free_handlers(runtime);
278
+ free_quickjsrb_runtime_state(data->state);
182
279
  JS_FreeContext(data->context);
183
280
  JS_FreeRuntime(runtime);
184
281
  data->alive = 0;
@@ -201,5 +298,6 @@ Init_quickjsrb(void)
201
298
  rb_define_alloc_func(vmClass, qvm_alloc);
202
299
  rb_define_method(vmClass, "initialize", qvm_m_initialize, -1);
203
300
  rb_define_method(vmClass, "eval_code", qvm_m_evalCode, 1);
301
+ rb_define_method(vmClass, "define_function", qvm_m_defineGlobalFunction, 1);
204
302
  rb_define_method(vmClass, "dispose!", qvm_m_dispose, 0);
205
303
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quickjs
4
- VERSION = "0.1.5"
4
+ VERSION = "0.1.6"
5
5
  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.5
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-07-01 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