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.
@@ -0,0 +1,231 @@
1
+ // WHATWG TextEncoder/TextDecoder polyfill for QuickJS
2
+ // Spec: https://encoding.spec.whatwg.org/
3
+
4
+ class TextEncoder {
5
+ get encoding() {
6
+ return "utf-8";
7
+ }
8
+
9
+ encode(input = "") {
10
+ const str = String(input);
11
+ const bytes = [];
12
+ for (let i = 0; i < str.length; i++) {
13
+ let code = str.charCodeAt(i);
14
+
15
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
16
+ const next = str.charCodeAt(i + 1);
17
+ if (next >= 0xdc00 && next <= 0xdfff) {
18
+ code = (code - 0xd800) * 0x400 + (next - 0xdc00) + 0x10000;
19
+ i++;
20
+ }
21
+ }
22
+
23
+ if (code <= 0x7f) {
24
+ bytes.push(code);
25
+ } else if (code <= 0x7ff) {
26
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
27
+ } else if (code <= 0xffff) {
28
+ bytes.push(
29
+ 0xe0 | (code >> 12),
30
+ 0x80 | ((code >> 6) & 0x3f),
31
+ 0x80 | (code & 0x3f)
32
+ );
33
+ } else {
34
+ bytes.push(
35
+ 0xf0 | (code >> 18),
36
+ 0x80 | ((code >> 12) & 0x3f),
37
+ 0x80 | ((code >> 6) & 0x3f),
38
+ 0x80 | (code & 0x3f)
39
+ );
40
+ }
41
+ }
42
+ return new Uint8Array(bytes);
43
+ }
44
+
45
+ encodeInto(source, destination) {
46
+ const str = String(source);
47
+ let read = 0;
48
+ let written = 0;
49
+
50
+ for (let i = 0; i < str.length; i++) {
51
+ let code = str.charCodeAt(i);
52
+
53
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
54
+ const next = str.charCodeAt(i + 1);
55
+ if (next >= 0xdc00 && next <= 0xdfff) {
56
+ if (written + 4 > destination.length) break;
57
+ code = (code - 0xd800) * 0x400 + (next - 0xdc00) + 0x10000;
58
+ destination[written++] = 0xf0 | (code >> 18);
59
+ destination[written++] = 0x80 | ((code >> 12) & 0x3f);
60
+ destination[written++] = 0x80 | ((code >> 6) & 0x3f);
61
+ destination[written++] = 0x80 | (code & 0x3f);
62
+ read += 2;
63
+ i++;
64
+ continue;
65
+ }
66
+ }
67
+
68
+ let byteCount;
69
+ if (code <= 0x7f) byteCount = 1;
70
+ else if (code <= 0x7ff) byteCount = 2;
71
+ else byteCount = 3;
72
+
73
+ if (written + byteCount > destination.length) break;
74
+
75
+ if (byteCount === 1) {
76
+ destination[written++] = code;
77
+ } else if (byteCount === 2) {
78
+ destination[written++] = 0xc0 | (code >> 6);
79
+ destination[written++] = 0x80 | (code & 0x3f);
80
+ } else {
81
+ destination[written++] = 0xe0 | (code >> 12);
82
+ destination[written++] = 0x80 | ((code >> 6) & 0x3f);
83
+ destination[written++] = 0x80 | (code & 0x3f);
84
+ }
85
+ read++;
86
+ }
87
+
88
+ return { read, written };
89
+ }
90
+ }
91
+
92
+ const UTF8_LABELS = [
93
+ "unicode-1-1-utf-8", "unicode11utf8", "unicode20utf8",
94
+ "utf-8", "utf8", "x-unicode20utf8",
95
+ ];
96
+
97
+ function normalizeEncodingLabel(label) {
98
+ const normalized = label.trim().toLowerCase();
99
+ if (UTF8_LABELS.includes(normalized)) return "utf-8";
100
+ return null;
101
+ }
102
+
103
+ class TextDecoder {
104
+ #encoding;
105
+ #fatal;
106
+ #ignoreBOM;
107
+
108
+ constructor(label = "utf-8", options = {}) {
109
+ const normalized = normalizeEncodingLabel(String(label));
110
+ if (!normalized) {
111
+ throw new RangeError(`The "${label}" encoding is not supported.`);
112
+ }
113
+ this.#encoding = normalized;
114
+ this.#fatal = Boolean(options.fatal);
115
+ this.#ignoreBOM = Boolean(options.ignoreBOM);
116
+ }
117
+
118
+ get encoding() {
119
+ return this.#encoding;
120
+ }
121
+
122
+ get fatal() {
123
+ return this.#fatal;
124
+ }
125
+
126
+ get ignoreBOM() {
127
+ return this.#ignoreBOM;
128
+ }
129
+
130
+ decode(input, options = {}) {
131
+ if (input === undefined || input === null) return "";
132
+
133
+ let bytes;
134
+ if (input instanceof ArrayBuffer) {
135
+ bytes = new Uint8Array(input);
136
+ } else if (ArrayBuffer.isView(input)) {
137
+ bytes = new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
138
+ } else {
139
+ throw new TypeError(
140
+ "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"
141
+ );
142
+ }
143
+
144
+ let start = 0;
145
+ if (
146
+ !this.#ignoreBOM &&
147
+ bytes.length >= 3 &&
148
+ bytes[0] === 0xef &&
149
+ bytes[1] === 0xbb &&
150
+ bytes[2] === 0xbf
151
+ ) {
152
+ start = 3;
153
+ }
154
+
155
+ return decodeUTF8(bytes, start, this.#fatal);
156
+ }
157
+ }
158
+
159
+ function decodeUTF8(bytes, start, fatal) {
160
+ let str = "";
161
+ let i = start;
162
+ while (i < bytes.length) {
163
+ const b = bytes[i];
164
+ let code, byteLen;
165
+
166
+ if (b <= 0x7f) {
167
+ code = b;
168
+ byteLen = 1;
169
+ } else if ((b & 0xe0) === 0xc0) {
170
+ if (i + 1 >= bytes.length || (bytes[i + 1] & 0xc0) !== 0x80) {
171
+ if (fatal) throw new TypeError("The encoded data was not valid.");
172
+ str += "\ufffd";
173
+ i++;
174
+ continue;
175
+ }
176
+ code = ((b & 0x1f) << 6) | (bytes[i + 1] & 0x3f);
177
+ byteLen = 2;
178
+ } else if ((b & 0xf0) === 0xe0) {
179
+ if (
180
+ i + 2 >= bytes.length ||
181
+ (bytes[i + 1] & 0xc0) !== 0x80 ||
182
+ (bytes[i + 2] & 0xc0) !== 0x80
183
+ ) {
184
+ if (fatal) throw new TypeError("The encoded data was not valid.");
185
+ str += "\ufffd";
186
+ i++;
187
+ continue;
188
+ }
189
+ code =
190
+ ((b & 0x0f) << 12) |
191
+ ((bytes[i + 1] & 0x3f) << 6) |
192
+ (bytes[i + 2] & 0x3f);
193
+ byteLen = 3;
194
+ } else if ((b & 0xf8) === 0xf0) {
195
+ if (
196
+ i + 3 >= bytes.length ||
197
+ (bytes[i + 1] & 0xc0) !== 0x80 ||
198
+ (bytes[i + 2] & 0xc0) !== 0x80 ||
199
+ (bytes[i + 3] & 0xc0) !== 0x80
200
+ ) {
201
+ if (fatal) throw new TypeError("The encoded data was not valid.");
202
+ str += "\ufffd";
203
+ i++;
204
+ continue;
205
+ }
206
+ code =
207
+ ((b & 0x07) << 18) |
208
+ ((bytes[i + 1] & 0x3f) << 12) |
209
+ ((bytes[i + 2] & 0x3f) << 6) |
210
+ (bytes[i + 3] & 0x3f);
211
+ byteLen = 4;
212
+ } else {
213
+ if (fatal) throw new TypeError("The encoded data was not valid.");
214
+ str += "\ufffd";
215
+ i++;
216
+ continue;
217
+ }
218
+
219
+ if (code <= 0xffff) {
220
+ str += String.fromCharCode(code);
221
+ } else {
222
+ code -= 0x10000;
223
+ str += String.fromCharCode(0xd800 + (code >> 10), 0xdc00 + (code & 0x3ff));
224
+ }
225
+ i += byteLen;
226
+ }
227
+ return str;
228
+ }
229
+
230
+ globalThis.TextEncoder = TextEncoder;
231
+ globalThis.TextDecoder = TextDecoder;
@@ -0,0 +1,218 @@
1
+ // W3C Blob and File polyfill for QuickJS
2
+ // Spec: https://www.w3.org/TR/FileAPI/
3
+
4
+ class Blob {
5
+ #bytes;
6
+ #type;
7
+
8
+ constructor(blobParts, options) {
9
+ this.#type = normalizeType(options?.type ?? "");
10
+
11
+ if (!blobParts) {
12
+ this.#bytes = new Uint8Array(0);
13
+ return;
14
+ }
15
+
16
+ if (!Array.isArray(blobParts)) {
17
+ throw new TypeError(
18
+ "Failed to construct 'Blob': The provided value cannot be converted to a sequence."
19
+ );
20
+ }
21
+
22
+ const parts = [];
23
+ let totalLength = 0;
24
+
25
+ for (const part of blobParts) {
26
+ let bytes;
27
+ if (typeof part === "string") {
28
+ bytes = encodeUTF8(part);
29
+ } else if (part instanceof ArrayBuffer) {
30
+ bytes = new Uint8Array(part);
31
+ } else if (ArrayBuffer.isView(part)) {
32
+ bytes = new Uint8Array(part.buffer, part.byteOffset, part.byteLength);
33
+ } else if (part instanceof Blob) {
34
+ bytes = part.#bytes;
35
+ } else {
36
+ bytes = encodeUTF8(String(part));
37
+ }
38
+ parts.push(bytes);
39
+ totalLength += bytes.byteLength;
40
+ }
41
+
42
+ const merged = new Uint8Array(totalLength);
43
+ let offset = 0;
44
+ for (const part of parts) {
45
+ merged.set(part, offset);
46
+ offset += part.byteLength;
47
+ }
48
+ this.#bytes = merged;
49
+ }
50
+
51
+ get size() {
52
+ return this.#bytes.byteLength;
53
+ }
54
+
55
+ get type() {
56
+ return this.#type;
57
+ }
58
+
59
+ slice(start, end, contentType) {
60
+ const size = this.size;
61
+ let relStart = start === undefined ? 0 : clampIndex(start, size);
62
+ let relEnd = end === undefined ? size : clampIndex(end, size);
63
+ const span = Math.max(relEnd - relStart, 0);
64
+
65
+ const sliced = new Blob();
66
+ sliced.#bytes = this.#bytes.slice(relStart, relStart + span);
67
+ sliced.#type = normalizeType(contentType ?? "");
68
+ return sliced;
69
+ }
70
+
71
+ text() {
72
+ return Promise.resolve(decodeUTF8(this.#bytes));
73
+ }
74
+
75
+ arrayBuffer() {
76
+ return Promise.resolve(this.#bytes.buffer.slice(this.#bytes.byteOffset, this.#bytes.byteOffset + this.#bytes.byteLength));
77
+ }
78
+
79
+ toString() {
80
+ return "[object Blob]";
81
+ }
82
+
83
+ get [Symbol.toStringTag]() {
84
+ return "Blob";
85
+ }
86
+ }
87
+
88
+ function normalizeType(type) {
89
+ // Spec: type must be lowercase ASCII without 0x20-7E exclusions
90
+ if (/[^\x20-\x7E]/.test(type)) {
91
+ return "";
92
+ }
93
+ return type.toLowerCase();
94
+ }
95
+
96
+ function clampIndex(index, size) {
97
+ if (index < 0) {
98
+ return Math.max(size + index, 0);
99
+ }
100
+ return Math.min(index, size);
101
+ }
102
+
103
+ function encodeUTF8(str) {
104
+ // Manual UTF-8 encoding for QuickJS (no TextEncoder)
105
+ const utf8 = [];
106
+ for (let i = 0; i < str.length; i++) {
107
+ let code = str.charCodeAt(i);
108
+
109
+ // Handle surrogate pairs
110
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
111
+ const next = str.charCodeAt(i + 1);
112
+ if (next >= 0xdc00 && next <= 0xdfff) {
113
+ code = (code - 0xd800) * 0x400 + (next - 0xdc00) + 0x10000;
114
+ i++;
115
+ }
116
+ }
117
+
118
+ if (code <= 0x7f) {
119
+ utf8.push(code);
120
+ } else if (code <= 0x7ff) {
121
+ utf8.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
122
+ } else if (code <= 0xffff) {
123
+ utf8.push(
124
+ 0xe0 | (code >> 12),
125
+ 0x80 | ((code >> 6) & 0x3f),
126
+ 0x80 | (code & 0x3f)
127
+ );
128
+ } else {
129
+ utf8.push(
130
+ 0xf0 | (code >> 18),
131
+ 0x80 | ((code >> 12) & 0x3f),
132
+ 0x80 | ((code >> 6) & 0x3f),
133
+ 0x80 | (code & 0x3f)
134
+ );
135
+ }
136
+ }
137
+ return new Uint8Array(utf8);
138
+ }
139
+
140
+ function decodeUTF8(bytes) {
141
+ // Manual UTF-8 decoding for QuickJS (no TextDecoder)
142
+ let str = "";
143
+ let i = 0;
144
+ while (i < bytes.length) {
145
+ let code;
146
+ const b = bytes[i];
147
+ if (b <= 0x7f) {
148
+ code = b;
149
+ i++;
150
+ } else if ((b & 0xe0) === 0xc0) {
151
+ code = ((b & 0x1f) << 6) | (bytes[i + 1] & 0x3f);
152
+ i += 2;
153
+ } else if ((b & 0xf0) === 0xe0) {
154
+ code =
155
+ ((b & 0x0f) << 12) |
156
+ ((bytes[i + 1] & 0x3f) << 6) |
157
+ (bytes[i + 2] & 0x3f);
158
+ i += 3;
159
+ } else {
160
+ code =
161
+ ((b & 0x07) << 18) |
162
+ ((bytes[i + 1] & 0x3f) << 12) |
163
+ ((bytes[i + 2] & 0x3f) << 6) |
164
+ (bytes[i + 3] & 0x3f);
165
+ i += 4;
166
+ }
167
+
168
+ if (code <= 0xffff) {
169
+ str += String.fromCharCode(code);
170
+ } else {
171
+ // Encode as surrogate pair
172
+ code -= 0x10000;
173
+ str += String.fromCharCode(0xd800 + (code >> 10), 0xdc00 + (code & 0x3ff));
174
+ }
175
+ }
176
+ return str;
177
+ }
178
+
179
+ class File extends Blob {
180
+ #name;
181
+ #lastModified;
182
+
183
+ constructor(fileBits, fileName, options) {
184
+ if (arguments.length < 2) {
185
+ throw new TypeError(
186
+ "Failed to construct 'File': 2 arguments required, but only " +
187
+ arguments.length +
188
+ " present."
189
+ );
190
+ }
191
+
192
+ super(fileBits, options);
193
+ this.#name = String(fileName);
194
+ this.#lastModified =
195
+ options?.lastModified !== undefined
196
+ ? Number(options.lastModified)
197
+ : Date.now();
198
+ }
199
+
200
+ get name() {
201
+ return this.#name;
202
+ }
203
+
204
+ get lastModified() {
205
+ return this.#lastModified;
206
+ }
207
+
208
+ toString() {
209
+ return "[object File]";
210
+ }
211
+
212
+ get [Symbol.toStringTag]() {
213
+ return "File";
214
+ }
215
+ }
216
+
217
+ globalThis.Blob = Blob;
218
+ globalThis.File = File;
@@ -0,0 +1,15 @@
1
+ // FormatJS Intl polyfills for QuickJS (no native Intl support)
2
+ // Order matters: getCanonicalLocales and Locale are deps of the others
3
+
4
+ import "@formatjs/intl-getcanonicallocales/polyfill-force.js";
5
+ import "@formatjs/intl-locale/polyfill-force.js";
6
+
7
+ import "@formatjs/intl-pluralrules/polyfill-force.js";
8
+ import "@formatjs/intl-pluralrules/locale-data/en";
9
+
10
+ import "@formatjs/intl-numberformat/polyfill-force.js";
11
+ import "@formatjs/intl-numberformat/locale-data/en";
12
+
13
+ import "@formatjs/intl-datetimeformat/polyfill-force.js";
14
+ import "@formatjs/intl-datetimeformat/locale-data/en";
15
+ import "@formatjs/intl-datetimeformat/add-all-tz.js";
data/sig/quickjs.rbs CHANGED
@@ -1,4 +1,72 @@
1
1
  module Quickjs
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
3
+
4
+ MODULE_STD: Symbol
5
+ MODULE_OS: Symbol
6
+ FEATURE_TIMEOUT: Symbol
7
+ POLYFILL_INTL: Symbol
8
+ POLYFILL_FILE: Symbol
9
+ POLYFILL_HTML_BASE64: Symbol
10
+
11
+ def self.eval_code: (String code, ?Hash[Symbol, untyped] overwrite_opts) -> untyped
12
+
13
+ class Value
14
+ UNDEFINED: Symbol
15
+ NAN: Symbol
16
+ end
17
+
18
+ class VM
19
+ def initialize: (?features: Array[Symbol], ?memory_limit: Integer, ?max_stack_size: Integer, ?timeout_msec: Integer) -> void
20
+
21
+ def eval_code: (String code) -> untyped
22
+
23
+ def define_function: (String | Symbol name, *Symbol flags) { (*untyped) -> untyped } -> Symbol
24
+
25
+ def import: (String | Array[String] | Hash[Symbol, String] imported, from: String, ?code_to_expose: String?) -> true
26
+
27
+ def logs: () -> Array[Log]
28
+
29
+ class Log
30
+ attr_reader severity: Symbol
31
+
32
+ def raw: () -> Array[untyped]
33
+
34
+ def to_s: () -> String
35
+
36
+ def inspect: () -> String
37
+ end
38
+ end
39
+
40
+ class RuntimeError < ::RuntimeError
41
+ def initialize: (String message, String? js_name) -> void
42
+
43
+ attr_reader js_name: String?
44
+ end
45
+
46
+ class SyntaxError < RuntimeError
47
+ end
48
+
49
+ class TypeError < RuntimeError
50
+ end
51
+
52
+ class ReferenceError < RuntimeError
53
+ end
54
+
55
+ class RangeError < RuntimeError
56
+ end
57
+
58
+ class EvalError < RuntimeError
59
+ end
60
+
61
+ class URIError < RuntimeError
62
+ end
63
+
64
+ class AggregateError < RuntimeError
65
+ end
66
+
67
+ class InterruptedError < RuntimeError
68
+ end
69
+
70
+ class NoAwaitError < RuntimeError
71
+ end
4
72
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quickjs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.13.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmsk
@@ -46,6 +46,7 @@ extensions:
46
46
  extra_rdoc_files: []
47
47
  files:
48
48
  - CHANGELOG.md
49
+ - CLAUDE.md
49
50
  - LICENSE
50
51
  - README.md
51
52
  - Rakefile
@@ -77,9 +78,19 @@ files:
77
78
  - ext/quickjsrb/quickjs/unicode_gen_def.h
78
79
  - ext/quickjsrb/quickjsrb.c
79
80
  - ext/quickjsrb/quickjsrb.h
81
+ - ext/quickjsrb/quickjsrb_file.c
82
+ - ext/quickjsrb/quickjsrb_file.h
83
+ - ext/quickjsrb/vendor/polyfill-encoding.min.js
84
+ - ext/quickjsrb/vendor/polyfill-file.min.js
80
85
  - ext/quickjsrb/vendor/polyfill-intl-en.min.js
81
86
  - lib/quickjs.rb
82
87
  - lib/quickjs/version.rb
88
+ - polyfills/package-lock.json
89
+ - polyfills/package.json
90
+ - polyfills/rolldown.config.mjs
91
+ - polyfills/src/encoding.js
92
+ - polyfills/src/file.js
93
+ - polyfills/src/intl-en.js
83
94
  - sig/quickjs.rbs
84
95
  homepage: https://github.com/hmsk/quickjs.rb
85
96
  licenses: