quickjs 0.11.2 → 0.13.0.pre
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 +4 -4
- data/CLAUDE.md +62 -0
- data/README.md +7 -1
- data/Rakefile +61 -2
- data/ext/quickjsrb/extconf.rb +11 -0
- data/ext/quickjsrb/quickjsrb.c +128 -6
- data/ext/quickjsrb/quickjsrb.h +35 -31
- data/ext/quickjsrb/quickjsrb_file.c +332 -0
- data/ext/quickjsrb/quickjsrb_file.h +16 -0
- data/ext/quickjsrb/vendor/polyfill-encoding.min.js +1 -0
- data/ext/quickjsrb/vendor/polyfill-file.min.js +1 -0
- data/ext/quickjsrb/vendor/polyfill-intl-en.min.js +10 -9
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +8 -0
- data/polyfills/package-lock.json +465 -0
- data/polyfills/package.json +18 -0
- data/polyfills/rolldown.config.mjs +28 -0
- data/polyfills/src/encoding.js +231 -0
- data/polyfills/src/file.js +218 -0
- data/polyfills/src/intl-en.js +15 -0
- data/sig/quickjs.rbs +69 -1
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f27d3afc594731cc4df53661889a9787626121923ece35c544b581ef3f52722c
|
|
4
|
+
data.tar.gz: d3869683f9134339d1d2e44617845f8ece614e1503926841fb9d4ad6de3dc7ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5cfcad98315e8821644aa233ea86213f855900506767ccce01cb0c1a653326456e3d00c453d0821117e31eee543ec440ed1e4583dd6b420a62e49ffcb53e080
|
|
7
|
+
data.tar.gz: 7df6cc20b91fce4c14e2a2b96b31bb447ee50eba03498a99e336c8b35d62164a05874ed7753509a4d0c1958761f96c5c85f4de66c6301b79eecbe208dbcf8212
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
quickjs.rb is a Ruby gem wrapping QuickJS (a lightweight JavaScript interpreter) via a C extension. It lets Ruby programs evaluate JavaScript code without a full Node.js runtime.
|
|
8
|
+
|
|
9
|
+
## Build & Test Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bundle exec rake # Full cycle: clobber → compile → test
|
|
13
|
+
bundle exec rake compile # Compile C extension only
|
|
14
|
+
bundle exec rake test # Run all tests
|
|
15
|
+
bundle exec ruby -Itest:lib test/quickjs_test.rb # Run single test file
|
|
16
|
+
bundle exec ruby -Itest:lib test/quickjs_test.rb -n test_name # Run single test
|
|
17
|
+
rake polyfills:build # Rebuild Intl polyfill bundle (requires npm)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
QuickJS source lives as a git submodule under `ext/quickjsrb/quickjs/` — clone with `--recurse-submodules`.
|
|
21
|
+
|
|
22
|
+
## Design Principles
|
|
23
|
+
|
|
24
|
+
- **Never modify QuickJS core** — the engine is a git submodule; all customization is implemented externally in our C extension or Ruby layer
|
|
25
|
+
- **Prefer Ruby over C** — don't rush to write C code; use Ruby where it provides better extendability and maintainability
|
|
26
|
+
- **Security-conscious C layer** — avoid adding flexible C-level features that introduce security risks; expose QuickJS's own options as-is rather than inventing new attack surface
|
|
27
|
+
- **Keep the default footprint small** — don't increase bundle size or add JavaScript overhead (e.g. polyfills) by default; additional capabilities should be opt-in via feature flags
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
**C Extension** (`ext/quickjsrb/`):
|
|
32
|
+
- `quickjsrb.c` / `quickjsrb.h` — Core extension: creates QuickJS runtime/context, handles Ruby↔JS value conversion, timeout interrupts, and Ruby function bridging into JS
|
|
33
|
+
- Value conversion uses JSON serialization for complex types (objects/arrays); direct conversion for primitives
|
|
34
|
+
- `VMData` struct holds the JS context, defined Ruby functions, timeout state, console logs, and error references
|
|
35
|
+
- Ruby GC integration via `vm_mark`, `vm_free`, `vm_compact` callbacks
|
|
36
|
+
|
|
37
|
+
**Ruby layer** (`lib/quickjs/`):
|
|
38
|
+
- `Quickjs::VM` — Persistent JS runtime with `eval_code`, `import`, `define_function`
|
|
39
|
+
- `Quickjs.eval_code` — Convenience method for one-shot evaluation
|
|
40
|
+
- Exception hierarchy maps JS error types (SyntaxError, TypeError, etc.) to Ruby classes under `Quickjs::`
|
|
41
|
+
- Special values: `Quickjs::Value::UNDEFINED` and `Quickjs::Value::NAN` represent JS undefined/NaN
|
|
42
|
+
|
|
43
|
+
**Feature flags** (passed to `VM.new` via `features:` array):
|
|
44
|
+
- `:feature_std`, `:feature_os` — QuickJS std/os modules
|
|
45
|
+
- `:feature_timeout` — setTimeout/setInterval via CRuby threads
|
|
46
|
+
- `:feature_polyfill_intl` — Intl API polyfill (DateTimeFormat, NumberFormat, PluralRules, Locale)
|
|
47
|
+
|
|
48
|
+
**Polyfills** (`polyfills/`):
|
|
49
|
+
- Built from FormatJS packages via rolldown, output minified JS embedded as C source
|
|
50
|
+
- Polyfill version must match gem version (enforced during `rake release`)
|
|
51
|
+
|
|
52
|
+
## Testing
|
|
53
|
+
|
|
54
|
+
Tests use minitest with `describe`/`it` blocks. Key test files:
|
|
55
|
+
- `test/quickjs_test.rb` — Main test suite (value conversion, errors, VM features, ESM imports, function definitions)
|
|
56
|
+
- `test/quickjs_polyfill_test.rb` — Intl polyfill tests
|
|
57
|
+
|
|
58
|
+
## Build Notes
|
|
59
|
+
|
|
60
|
+
- `extconf.rb` compiles with `-DNDEBUG` to avoid conflicts with Ruby 4.0 GC assertions
|
|
61
|
+
- Symbol visibility is hidden by default (`-fvisibility=hidden`)
|
|
62
|
+
- CI matrix: Ruby 3.2/3.3/3.4/4.0 × Ubuntu/macOS
|
data/README.md
CHANGED
|
@@ -63,6 +63,9 @@ vm = Quickjs.eval_code(features: [::Quickjs::FEATURE_TIMEOUT])
|
|
|
63
63
|
|
|
64
64
|
# Inject the polyfill of Intl
|
|
65
65
|
vm = Quickjs.eval_code(features: [::Quickjs::POLYFILL_INTL])
|
|
66
|
+
|
|
67
|
+
# Inject the polyfill of Blob and File (W3C File API)
|
|
68
|
+
vm = Quickjs.eval_code(features: [::Quickjs::POLYFILL_FILE])
|
|
66
69
|
```
|
|
67
70
|
|
|
68
71
|
</details>
|
|
@@ -103,6 +106,9 @@ vm = Quickjs::VM.new(features: [::Quickjs::FEATURE_TIMEOUT])
|
|
|
103
106
|
|
|
104
107
|
# Inject the polyfill of Intl
|
|
105
108
|
vm = Quickjs::VM.new(features: [::Quickjs::POLYFILL_INTL])
|
|
109
|
+
|
|
110
|
+
# Inject the polyfill of Blob and File (W3C File API)
|
|
111
|
+
vm = Quickjs::VM.new(features: [::Quickjs::POLYFILL_FILE])
|
|
106
112
|
```
|
|
107
113
|
|
|
108
114
|
#### VM timeout
|
|
@@ -163,7 +169,7 @@ vm.logs.last.raw #=> ['log me', nil]
|
|
|
163
169
|
|
|
164
170
|
- `ext/quickjsrb/quickjs`
|
|
165
171
|
- [MIT License Copyright (c) 2017-2021 by Fabrice Bellard and Charlie Gordon](https://github.com/bellard/quickjs/blob/6e2e68fd0896957f92eb6c242a2e048c1ef3cae0/LICENSE).
|
|
166
|
-
- `ext/quickjsrb/vendor/polyfill-intl-en.min.js` (bundled and minified)
|
|
172
|
+
- `ext/quickjsrb/vendor/polyfill-intl-en.min.js` ([bundled and minified from `polyfills/`](https://github.com/hmsk/quickjs.rb/tree/main/polyfills))
|
|
167
173
|
- MIT License Copyright (c) 2023 FormatJS
|
|
168
174
|
- [@formatjs/intl-getcanonicallocales](https://github.com/formatjs/formatjs/blob/main/packages/intl-getcanonicallocales/LICENSE.md)
|
|
169
175
|
- [@formatjs/intl-locale](https://github.com/formatjs/formatjs/blob/main/packages/intl-locale/LICENSE.md)
|
data/Rakefile
CHANGED
|
@@ -11,7 +11,7 @@ end
|
|
|
11
11
|
|
|
12
12
|
require 'rake/extensiontask'
|
|
13
13
|
|
|
14
|
-
task build: :compile
|
|
14
|
+
task build: [:compile, 'polyfills:version:warn']
|
|
15
15
|
|
|
16
16
|
GEMSPEC = Gem::Specification.load('quickjs.gemspec')
|
|
17
17
|
|
|
@@ -19,4 +19,63 @@ Rake::ExtensionTask.new('quickjsrb', GEMSPEC) do |ext|
|
|
|
19
19
|
ext.lib_dir = 'lib/quickjs'
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
def check_polyfill_version!
|
|
23
|
+
require 'json'
|
|
24
|
+
require_relative 'lib/quickjs/version'
|
|
25
|
+
|
|
26
|
+
package = JSON.parse(File.read(File.expand_path('polyfills/package.json', __dir__)))
|
|
27
|
+
return if package['version'] == Quickjs::VERSION
|
|
28
|
+
|
|
29
|
+
yield package['version'], Quickjs::VERSION
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
namespace :polyfills do
|
|
33
|
+
desc 'Build polyfill bundles with rolldown and recompile'
|
|
34
|
+
task build: :clobber do
|
|
35
|
+
require 'json'
|
|
36
|
+
require_relative 'lib/quickjs/version'
|
|
37
|
+
|
|
38
|
+
polyfills_dir = File.expand_path('polyfills', __dir__)
|
|
39
|
+
package_json_path = File.join(polyfills_dir, 'package.json')
|
|
40
|
+
package = JSON.parse(File.read(package_json_path))
|
|
41
|
+
old_version = package['version']
|
|
42
|
+
package['version'] = Quickjs::VERSION
|
|
43
|
+
File.write(package_json_path, "#{JSON.pretty_generate(package)}\n")
|
|
44
|
+
if old_version != Quickjs::VERSION
|
|
45
|
+
warn "\n⚠️ polyfills/package.json version was #{old_version}, updated to #{Quickjs::VERSION}\n\n"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Dir.chdir(polyfills_dir) do
|
|
49
|
+
sh 'npm install' unless File.exist?('node_modules/.package-lock.json') &&
|
|
50
|
+
File.mtime('node_modules/.package-lock.json') >= File.mtime('package.json')
|
|
51
|
+
sh 'npx rolldown -c rolldown.config.mjs'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Rake::Task[:compile].invoke
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
namespace :version do
|
|
58
|
+
task :check do
|
|
59
|
+
check_polyfill_version! do |pkg_v, gem_v|
|
|
60
|
+
abort "polyfills/package.json version (#{pkg_v}) does not match gem version (#{gem_v}). Run `rake polyfills:build` first."
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
task :warn do
|
|
65
|
+
check_polyfill_version! do |pkg_v, gem_v|
|
|
66
|
+
warn "⚠️ polyfills/package.json version (#{pkg_v}) does not match gem version (#{gem_v}). Run `rake polyfills:build` to sync."
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
task 'release:guard_clean' => 'polyfills:version:check'
|
|
73
|
+
|
|
74
|
+
namespace :rbs do
|
|
75
|
+
desc 'Validate RBS type definitions'
|
|
76
|
+
task :validate do
|
|
77
|
+
sh RbConfig.ruby, '-S', 'rbs', '-I', 'sig', 'validate'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
task default: %i[clobber compile test rbs:validate]
|
data/ext/quickjsrb/extconf.rb
CHANGED
|
@@ -12,7 +12,10 @@ $srcs = [
|
|
|
12
12
|
'quickjs.c',
|
|
13
13
|
'quickjs-libc.c',
|
|
14
14
|
'polyfill-intl-en.min.c',
|
|
15
|
+
'polyfill-file.min.c',
|
|
16
|
+
'polyfill-encoding.min.c',
|
|
15
17
|
'quickjsrb.c',
|
|
18
|
+
'quickjsrb_file.c',
|
|
16
19
|
]
|
|
17
20
|
|
|
18
21
|
append_cflags('-I$(srcdir)/quickjs')
|
|
@@ -61,6 +64,14 @@ polyfill-intl-en.min.js:
|
|
|
61
64
|
$(COPY) $(srcdir)/vendor/$@ $@
|
|
62
65
|
polyfill-intl-en.min.c: ./qjsc polyfill-intl-en.min.js
|
|
63
66
|
./qjsc $(POLYFILL_OPTS) -c -M polyfill/intl-en.so,intlen -m -o $@ polyfill-intl-en.min.js
|
|
67
|
+
polyfill-file.min.js:
|
|
68
|
+
$(COPY) $(srcdir)/vendor/$@ $@
|
|
69
|
+
polyfill-file.min.c: ./qjsc polyfill-file.min.js
|
|
70
|
+
./qjsc -fno-string-normalize -fno-eval -fno-proxy -fno-module-loader -c -M polyfill/file.so,file -m -o $@ polyfill-file.min.js
|
|
71
|
+
polyfill-encoding.min.js:
|
|
72
|
+
$(COPY) $(srcdir)/vendor/$@ $@
|
|
73
|
+
polyfill-encoding.min.c: ./qjsc polyfill-encoding.min.js
|
|
74
|
+
./qjsc -fno-string-normalize -fno-eval -fno-proxy -fno-module-loader -c -M polyfill/encoding.so,encoding -m -o $@ polyfill-encoding.min.js
|
|
64
75
|
COMPILE_POLYFILL
|
|
65
76
|
conf
|
|
66
77
|
end
|
data/ext/quickjsrb/quickjsrb.c
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
#include "quickjsrb.h"
|
|
2
|
+
#include "quickjsrb_file.h"
|
|
3
|
+
|
|
4
|
+
const char *featureStdId = "feature_std";
|
|
5
|
+
const char *featureOsId = "feature_os";
|
|
6
|
+
const char *featureTimeoutId = "feature_timeout";
|
|
7
|
+
const char *featurePolyfillIntlId = "feature_polyfill_intl";
|
|
8
|
+
const char *featurePolyfillFileId = "feature_polyfill_file";
|
|
9
|
+
const char *featurePolyfillEncodingId = "feature_polyfill_encoding";
|
|
10
|
+
|
|
11
|
+
const char *undefinedId = "undefined";
|
|
12
|
+
const char *nanId = "NaN";
|
|
13
|
+
|
|
14
|
+
const char *native_errors[] = {
|
|
15
|
+
"SyntaxError",
|
|
16
|
+
"TypeError",
|
|
17
|
+
"ReferenceError",
|
|
18
|
+
"RangeError",
|
|
19
|
+
"EvalError",
|
|
20
|
+
"URIError",
|
|
21
|
+
"AggregateError"};
|
|
22
|
+
const int num_native_errors = sizeof(native_errors) / sizeof(native_errors[0]);
|
|
23
|
+
|
|
24
|
+
static int dispatch_log(VMData *data, const char *severity, VALUE r_row);
|
|
2
25
|
|
|
3
26
|
JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
|
|
4
27
|
{
|
|
@@ -10,7 +33,7 @@ JSValue j_error_from_ruby_error(JSContext *ctx, VALUE r_error)
|
|
|
10
33
|
|
|
11
34
|
// Keep the error alive in VMData to prevent GC before find_ruby_error retrieves it
|
|
12
35
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
13
|
-
rb_hash_aset(data->
|
|
36
|
+
rb_hash_aset(data->alive_objects, r_object_id, r_error);
|
|
14
37
|
|
|
15
38
|
VALUE r_exception_message = rb_funcall(r_error, rb_intern("message"), 0);
|
|
16
39
|
const char *errorMessage = StringValueCStr(r_exception_message);
|
|
@@ -68,6 +91,12 @@ JSValue to_js_value(JSContext *ctx, VALUE r_value)
|
|
|
68
91
|
}
|
|
69
92
|
default:
|
|
70
93
|
{
|
|
94
|
+
if (rb_obj_is_kind_of(r_value, rb_cFile))
|
|
95
|
+
{
|
|
96
|
+
VMData *data = JS_GetContextOpaque(ctx);
|
|
97
|
+
if (!JS_IsUndefined(data->j_file_proxy_creator))
|
|
98
|
+
return quickjsrb_file_to_js(ctx, r_value);
|
|
99
|
+
}
|
|
71
100
|
if (TYPE(r_value) == T_OBJECT && RTEST(rb_funcall(
|
|
72
101
|
r_value,
|
|
73
102
|
rb_intern("is_a?"),
|
|
@@ -95,8 +124,8 @@ VALUE find_ruby_error(JSContext *ctx, JSValue j_error)
|
|
|
95
124
|
{
|
|
96
125
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
97
126
|
VALUE r_key = INT2NUM(errorOriginalRubyObjectId);
|
|
98
|
-
VALUE r_error = rb_hash_aref(data->
|
|
99
|
-
rb_hash_delete(data->
|
|
127
|
+
VALUE r_error = rb_hash_aref(data->alive_objects, r_key);
|
|
128
|
+
rb_hash_delete(data->alive_objects, r_key);
|
|
100
129
|
return r_error;
|
|
101
130
|
}
|
|
102
131
|
}
|
|
@@ -177,6 +206,36 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
177
206
|
}
|
|
178
207
|
// will support other errors like just returning an instance of Error
|
|
179
208
|
}
|
|
209
|
+
|
|
210
|
+
// Check for Ruby object proxy (e.g., File proxy with rb_object_id on target)
|
|
211
|
+
{
|
|
212
|
+
JSValue j_rb_id = JS_GetPropertyStr(ctx, j_val, "rb_object_id");
|
|
213
|
+
if (JS_VALUE_GET_NORM_TAG(j_rb_id) == JS_TAG_INT || JS_VALUE_GET_NORM_TAG(j_rb_id) == JS_TAG_FLOAT64)
|
|
214
|
+
{
|
|
215
|
+
int64_t object_id;
|
|
216
|
+
JS_ToInt64(ctx, &object_id, j_rb_id);
|
|
217
|
+
JS_FreeValue(ctx, j_rb_id);
|
|
218
|
+
if (object_id > 0)
|
|
219
|
+
{
|
|
220
|
+
VMData *data = JS_GetContextOpaque(ctx);
|
|
221
|
+
VALUE r_obj = rb_hash_aref(data->alive_objects, LONG2NUM(object_id));
|
|
222
|
+
if (!NIL_P(r_obj) && !rb_obj_is_kind_of(r_obj, rb_eException))
|
|
223
|
+
return r_obj;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else
|
|
227
|
+
{
|
|
228
|
+
JS_FreeValue(ctx, j_rb_id);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// JS File → Quickjs::File
|
|
233
|
+
{
|
|
234
|
+
VALUE r_maybe_file = quickjsrb_try_convert_js_file(ctx, j_val);
|
|
235
|
+
if (!NIL_P(r_maybe_file))
|
|
236
|
+
return r_maybe_file;
|
|
237
|
+
}
|
|
238
|
+
|
|
180
239
|
VALUE r_str = to_r_json(ctx, j_val);
|
|
181
240
|
|
|
182
241
|
if (rb_funcall(r_str, rb_intern("=="), 1, rb_str_new2("undefined")))
|
|
@@ -227,7 +286,7 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
227
286
|
|
|
228
287
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
229
288
|
VALUE r_headline = rb_str_new2(headline);
|
|
230
|
-
|
|
289
|
+
dispatch_log(data, "error", rb_ary_new3(1, r_log_body_new(r_headline, r_headline)));
|
|
231
290
|
|
|
232
291
|
JS_FreeValue(ctx, j_errorClassMessage);
|
|
233
292
|
JS_FreeValue(ctx, j_errorClassName);
|
|
@@ -269,7 +328,7 @@ VALUE to_rb_value(JSContext *ctx, JSValue j_val)
|
|
|
269
328
|
|
|
270
329
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
271
330
|
VALUE r_headline = rb_str_new2(headline);
|
|
272
|
-
|
|
331
|
+
dispatch_log(data, "error", rb_ary_new3(1, r_log_body_new(r_headline, r_headline)));
|
|
273
332
|
|
|
274
333
|
free(headline);
|
|
275
334
|
|
|
@@ -418,6 +477,43 @@ static JSValue js_quickjsrb_set_timeout(JSContext *ctx, JSValueConst _this, int
|
|
|
418
477
|
return JS_UNDEFINED;
|
|
419
478
|
}
|
|
420
479
|
|
|
480
|
+
static VALUE r_try_call_listener(VALUE r_args)
|
|
481
|
+
{
|
|
482
|
+
VALUE r_listener = RARRAY_AREF(r_args, 0);
|
|
483
|
+
VALUE r_log = RARRAY_AREF(r_args, 1);
|
|
484
|
+
return rb_funcall(r_listener, rb_intern("call"), 1, r_log);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
static int dispatch_log(VMData *data, const char *severity, VALUE r_row)
|
|
488
|
+
{
|
|
489
|
+
VALUE r_log = r_log_new(severity, r_row);
|
|
490
|
+
if (!NIL_P(data->log_listener))
|
|
491
|
+
{
|
|
492
|
+
VALUE r_args = rb_ary_new3(2, data->log_listener, r_log);
|
|
493
|
+
int error;
|
|
494
|
+
rb_protect(r_try_call_listener, r_args, &error);
|
|
495
|
+
if (error)
|
|
496
|
+
return error;
|
|
497
|
+
}
|
|
498
|
+
else
|
|
499
|
+
{
|
|
500
|
+
rb_ary_push(data->logs, r_log);
|
|
501
|
+
}
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
static VALUE vm_m_on_log(VALUE r_self)
|
|
506
|
+
{
|
|
507
|
+
rb_need_block();
|
|
508
|
+
|
|
509
|
+
VMData *data;
|
|
510
|
+
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
511
|
+
|
|
512
|
+
data->log_listener = rb_block_proc();
|
|
513
|
+
|
|
514
|
+
return Qnil;
|
|
515
|
+
}
|
|
516
|
+
|
|
421
517
|
static JSValue js_quickjsrb_log(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, const char *severity)
|
|
422
518
|
{
|
|
423
519
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
@@ -467,7 +563,14 @@ static JSValue js_quickjsrb_log(JSContext *ctx, JSValueConst _this, int argc, JS
|
|
|
467
563
|
rb_ary_push(r_row, r_log_body_new(r_raw, r_c));
|
|
468
564
|
}
|
|
469
565
|
|
|
470
|
-
|
|
566
|
+
int error = dispatch_log(data, severity, r_row);
|
|
567
|
+
if (error)
|
|
568
|
+
{
|
|
569
|
+
VALUE r_error = rb_errinfo();
|
|
570
|
+
rb_set_errinfo(Qnil);
|
|
571
|
+
JSValue j_error = j_error_from_ruby_error(ctx, r_error);
|
|
572
|
+
return JS_Throw(ctx, j_error);
|
|
573
|
+
}
|
|
471
574
|
return JS_UNDEFINED;
|
|
472
575
|
}
|
|
473
576
|
|
|
@@ -561,6 +664,22 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
|
|
|
561
664
|
JS_FreeValue(data->context, j_polyfillIntlResult);
|
|
562
665
|
}
|
|
563
666
|
|
|
667
|
+
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillFileId))))
|
|
668
|
+
{
|
|
669
|
+
JSValue j_polyfillFileObject = JS_ReadObject(data->context, &qjsc_polyfill_file_min, qjsc_polyfill_file_min_size, JS_READ_OBJ_BYTECODE);
|
|
670
|
+
JSValue j_polyfillFileResult = JS_EvalFunction(data->context, j_polyfillFileObject);
|
|
671
|
+
JS_FreeValue(data->context, j_polyfillFileResult);
|
|
672
|
+
|
|
673
|
+
quickjsrb_init_file_proxy(data);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillEncodingId))))
|
|
677
|
+
{
|
|
678
|
+
JSValue j_polyfillEncodingObject = JS_ReadObject(data->context, &qjsc_polyfill_encoding_min, qjsc_polyfill_encoding_min_size, JS_READ_OBJ_BYTECODE);
|
|
679
|
+
JSValue j_polyfillEncodingResult = JS_EvalFunction(data->context, j_polyfillEncodingObject);
|
|
680
|
+
JS_FreeValue(data->context, j_polyfillEncodingResult);
|
|
681
|
+
}
|
|
682
|
+
|
|
564
683
|
JSValue j_console = JS_NewObject(data->context);
|
|
565
684
|
JS_SetPropertyStr(
|
|
566
685
|
data->context, j_console, "log",
|
|
@@ -733,6 +852,8 @@ static VALUE vm_m_import(int argc, VALUE *argv, VALUE r_self)
|
|
|
733
852
|
|
|
734
853
|
static VALUE vm_m_logs(VALUE r_self)
|
|
735
854
|
{
|
|
855
|
+
rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "Quickjs::VM#logs is deprecated; use Quickjs::VM#on_log instead");
|
|
856
|
+
|
|
736
857
|
VMData *data;
|
|
737
858
|
TypedData_Get_Struct(r_self, VMData, &vm_type, data);
|
|
738
859
|
|
|
@@ -755,5 +876,6 @@ RUBY_FUNC_EXPORTED void Init_quickjsrb(void)
|
|
|
755
876
|
rb_define_method(r_class_vm, "define_function", vm_m_defineGlobalFunction, -1);
|
|
756
877
|
rb_define_method(r_class_vm, "import", vm_m_import, -1);
|
|
757
878
|
rb_define_method(r_class_vm, "logs", vm_m_logs, 0);
|
|
879
|
+
rb_define_method(r_class_vm, "on_log", vm_m_on_log, 0);
|
|
758
880
|
r_define_log_class(r_class_vm);
|
|
759
881
|
}
|
data/ext/quickjsrb/quickjsrb.h
CHANGED
|
@@ -14,23 +14,23 @@
|
|
|
14
14
|
|
|
15
15
|
extern const uint32_t qjsc_polyfill_intl_en_min_size;
|
|
16
16
|
extern const uint8_t qjsc_polyfill_intl_en_min;
|
|
17
|
+
extern const uint32_t qjsc_polyfill_file_min_size;
|
|
18
|
+
extern const uint8_t qjsc_polyfill_file_min;
|
|
19
|
+
extern const uint32_t qjsc_polyfill_encoding_min_size;
|
|
20
|
+
extern const uint8_t qjsc_polyfill_encoding_min;
|
|
17
21
|
|
|
18
|
-
const char *featureStdId
|
|
19
|
-
const char *featureOsId
|
|
20
|
-
const char *featureTimeoutId
|
|
21
|
-
const char *featurePolyfillIntlId
|
|
22
|
+
extern const char *featureStdId;
|
|
23
|
+
extern const char *featureOsId;
|
|
24
|
+
extern const char *featureTimeoutId;
|
|
25
|
+
extern const char *featurePolyfillIntlId;
|
|
26
|
+
extern const char *featurePolyfillFileId;
|
|
27
|
+
extern const char *featurePolyfillEncodingId;
|
|
22
28
|
|
|
23
|
-
const char *undefinedId
|
|
24
|
-
const char *nanId
|
|
29
|
+
extern const char *undefinedId;
|
|
30
|
+
extern const char *nanId;
|
|
25
31
|
|
|
26
|
-
const char *native_errors[]
|
|
27
|
-
|
|
28
|
-
"TypeError",
|
|
29
|
-
"ReferenceError",
|
|
30
|
-
"RangeError",
|
|
31
|
-
"EvalError",
|
|
32
|
-
"URIError",
|
|
33
|
-
"AggregateError"};
|
|
32
|
+
extern const char *native_errors[];
|
|
33
|
+
extern const int num_native_errors;
|
|
34
34
|
|
|
35
35
|
#define QUICKJSRB_SYM(id) \
|
|
36
36
|
(VALUE) { ID2SYM(rb_intern(id)) }
|
|
@@ -49,7 +49,9 @@ typedef struct VMData
|
|
|
49
49
|
VALUE defined_functions;
|
|
50
50
|
struct EvalTime *eval_time;
|
|
51
51
|
VALUE logs;
|
|
52
|
-
VALUE
|
|
52
|
+
VALUE log_listener;
|
|
53
|
+
VALUE alive_objects;
|
|
54
|
+
JSValue j_file_proxy_creator;
|
|
53
55
|
} VMData;
|
|
54
56
|
|
|
55
57
|
static void vm_free(void *ptr)
|
|
@@ -57,6 +59,9 @@ static void vm_free(void *ptr)
|
|
|
57
59
|
VMData *data = (VMData *)ptr;
|
|
58
60
|
free(data->eval_time);
|
|
59
61
|
|
|
62
|
+
if (!JS_IsUndefined(data->j_file_proxy_creator))
|
|
63
|
+
JS_FreeValue(data->context, data->j_file_proxy_creator);
|
|
64
|
+
|
|
60
65
|
JSRuntime *runtime = JS_GetRuntime(data->context);
|
|
61
66
|
JS_SetInterruptHandler(runtime, NULL, NULL);
|
|
62
67
|
js_std_free_handlers(runtime);
|
|
@@ -66,7 +71,7 @@ static void vm_free(void *ptr)
|
|
|
66
71
|
xfree(ptr);
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
size_t vm_size(const void *data)
|
|
74
|
+
static size_t vm_size(const void *data)
|
|
70
75
|
{
|
|
71
76
|
return sizeof(VMData);
|
|
72
77
|
}
|
|
@@ -76,7 +81,8 @@ static void vm_mark(void *ptr)
|
|
|
76
81
|
VMData *data = (VMData *)ptr;
|
|
77
82
|
rb_gc_mark_movable(data->defined_functions);
|
|
78
83
|
rb_gc_mark_movable(data->logs);
|
|
79
|
-
rb_gc_mark_movable(data->
|
|
84
|
+
rb_gc_mark_movable(data->log_listener);
|
|
85
|
+
rb_gc_mark_movable(data->alive_objects);
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
static void vm_compact(void *ptr)
|
|
@@ -84,7 +90,8 @@ static void vm_compact(void *ptr)
|
|
|
84
90
|
VMData *data = (VMData *)ptr;
|
|
85
91
|
data->defined_functions = rb_gc_location(data->defined_functions);
|
|
86
92
|
data->logs = rb_gc_location(data->logs);
|
|
87
|
-
data->
|
|
93
|
+
data->log_listener = rb_gc_location(data->log_listener);
|
|
94
|
+
data->alive_objects = rb_gc_location(data->alive_objects);
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
static const rb_data_type_t vm_type = {
|
|
@@ -104,7 +111,9 @@ static VALUE vm_alloc(VALUE r_self)
|
|
|
104
111
|
VALUE obj = TypedData_Make_Struct(r_self, VMData, &vm_type, data);
|
|
105
112
|
data->defined_functions = rb_hash_new();
|
|
106
113
|
data->logs = rb_ary_new();
|
|
107
|
-
data->
|
|
114
|
+
data->log_listener = Qnil;
|
|
115
|
+
data->alive_objects = rb_hash_new();
|
|
116
|
+
data->j_file_proxy_creator = JS_UNDEFINED;
|
|
108
117
|
|
|
109
118
|
EvalTime *eval_time = malloc(sizeof(EvalTime));
|
|
110
119
|
data->eval_time = eval_time;
|
|
@@ -129,17 +138,12 @@ static char *random_string()
|
|
|
129
138
|
|
|
130
139
|
static bool is_native_error_name(const char *error_name)
|
|
131
140
|
{
|
|
132
|
-
|
|
133
|
-
int numStrings = sizeof(native_errors) / sizeof(native_errors[0]);
|
|
134
|
-
for (int i = 0; i < numStrings; i++)
|
|
141
|
+
for (int i = 0; i < num_native_errors; i++)
|
|
135
142
|
{
|
|
136
143
|
if (strcmp(native_errors[i], error_name) == 0)
|
|
137
|
-
|
|
138
|
-
is_native_error = true;
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
144
|
+
return true;
|
|
141
145
|
}
|
|
142
|
-
return
|
|
146
|
+
return false;
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
// Constants
|
|
@@ -150,6 +154,8 @@ static void r_define_constants(VALUE r_parent_class)
|
|
|
150
154
|
rb_define_const(r_parent_class, "MODULE_OS", QUICKJSRB_SYM(featureOsId));
|
|
151
155
|
rb_define_const(r_parent_class, "FEATURE_TIMEOUT", QUICKJSRB_SYM(featureTimeoutId));
|
|
152
156
|
rb_define_const(r_parent_class, "POLYFILL_INTL", QUICKJSRB_SYM(featurePolyfillIntlId));
|
|
157
|
+
rb_define_const(r_parent_class, "POLYFILL_FILE", QUICKJSRB_SYM(featurePolyfillFileId));
|
|
158
|
+
rb_define_const(r_parent_class, "POLYFILL_ENCODING", QUICKJSRB_SYM(featurePolyfillEncodingId));
|
|
153
159
|
|
|
154
160
|
VALUE rb_cQuickjsValue = rb_define_class_under(r_parent_class, "Value", rb_cObject);
|
|
155
161
|
rb_define_const(rb_cQuickjsValue, "UNDEFINED", QUICKJSRB_SYM(undefinedId));
|
|
@@ -221,7 +227,7 @@ static VALUE r_log_body_new(VALUE r_raw, VALUE r_c)
|
|
|
221
227
|
#define QUICKJSRB_ERROR_FOR(name) \
|
|
222
228
|
(VALUE) { rb_const_get(rb_const_get(rb_cClass, rb_intern("Quickjs")), rb_intern(name)) }
|
|
223
229
|
|
|
224
|
-
VALUE vm_m_initialize_quickjs_error(VALUE self, VALUE r_message, VALUE r_js_name)
|
|
230
|
+
static VALUE vm_m_initialize_quickjs_error(VALUE self, VALUE r_message, VALUE r_js_name)
|
|
225
231
|
{
|
|
226
232
|
rb_call_super(1, &r_message);
|
|
227
233
|
rb_iv_set(self, "@js_name", r_js_name);
|
|
@@ -235,9 +241,7 @@ static void r_define_exception_classes(VALUE r_parent_class)
|
|
|
235
241
|
rb_define_method(r_runtime_error, "initialize", vm_m_initialize_quickjs_error, 2);
|
|
236
242
|
rb_define_attr(r_runtime_error, "js_name", 1, 0);
|
|
237
243
|
|
|
238
|
-
|
|
239
|
-
int numStrings = sizeof(native_errors) / sizeof(native_errors[0]);
|
|
240
|
-
for (int i = 0; i < numStrings; i++)
|
|
244
|
+
for (int i = 0; i < num_native_errors; i++)
|
|
241
245
|
{
|
|
242
246
|
rb_define_class_under(r_parent_class, native_errors[i], r_runtime_error);
|
|
243
247
|
}
|