xbintsc 0.3.87 → 0.5.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.
- package/AGENTS.md +8 -4
- package/README.md +4 -3
- package/README.zh-CN.md +2 -2
- package/dist/src/codegen/generator/calls/access.js +6 -2
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/calls/closures.js +4 -0
- package/dist/src/codegen/generator/calls/closures.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +4 -3
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +6 -1
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +0 -1
- package/dist/src/codegen/generator/module.js +10 -10
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +4 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +1 -3
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +9 -0
- package/dist/src/codegen/generator/tables.js +74 -2
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +2 -2
- package/dist/src/driver/compiler.js +4 -1
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/tests/codegen/closures.test.js +8 -0
- package/dist/tests/codegen/closures.test.js.map +1 -1
- package/dist/tests/e2e/async-await.test.d.ts +10 -0
- package/dist/tests/e2e/async-await.test.js +247 -0
- package/dist/tests/e2e/async-await.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
- package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
- package/dist/tests/e2e/string-utf16.test.js +133 -0
- package/dist/tests/e2e/string-utf16.test.js.map +1 -0
- package/doc/ai/contributing.md +9 -7
- package/doc/ai/extensions.md +3 -2
- package/doc/ai/language-support.md +11 -8
- package/doc/ai/troubleshooting.md +4 -3
- package/doc/ai/zh-CN/contributing.md +5 -5
- package/doc/ai/zh-CN/extensions.md +2 -1
- package/doc/ai/zh-CN/language-support.md +11 -8
- package/doc/ai/zh-CN/troubleshooting.md +3 -2
- package/doc/implemented.md +15 -12
- package/doc/unimplemented.md +11 -10
- package/doc/zh-CN/implemented.md +14 -11
- package/doc/zh-CN/unimplemented.md +11 -10
- package/package.json +1 -1
- package/runtime/rt.h +32 -0
- package/runtime/rt_internal.h +99 -10
- package/runtime/xt_alloc.c +6 -0
- package/runtime/xt_async.c +197 -0
- package/runtime/xt_builtins.c +40 -36
- package/runtime/xt_constructors.c +389 -0
- package/runtime/xt_containers/access.inc +47 -11
- package/runtime/xt_containers/arrays.inc +26 -2
- package/runtime/xt_containers/objects.inc +202 -5
- package/runtime/xt_coroutine.c +212 -0
- package/runtime/xt_generator.c +31 -162
- package/runtime/xt_io/inspect.inc +35 -2
- package/runtime/xt_promise.c +62 -17
- package/runtime/xt_stdlib/dispatch.inc +18 -2
- package/runtime/xt_stdlib/strings.inc +73 -16
- package/runtime/xt_stdlib2/array-statics.inc +3 -3
- package/runtime/xt_stdlib2/error.inc +11 -0
- package/runtime/xt_stdlib2/json.inc +44 -15
- package/runtime/xt_stdlib2/number-string-statics.inc +24 -15
- package/runtime/xt_stdlib2/objects.inc +1 -1
- package/runtime/xt_stdlib2/regexp-match.inc +2 -2
- package/runtime/xt_stdlib2/set.inc +6 -0
- package/runtime/xt_stdlib2/strings.inc +70 -40
- package/runtime/xt_typed_array/construction.inc +43 -2
- package/runtime/xt_typed_array.c +4 -0
- package/runtime/xt_values/constructors.inc +8 -0
- package/runtime/xt_values/strings.inc +19 -3
- package/runtime/xt_values/utf16.inc +340 -0
- package/runtime/xt_values.c +1 -0
- package/src/codegen/generator/calls/access.ts +7 -2
- package/src/codegen/generator/calls/closures.ts +3 -0
- package/src/codegen/generator/calls/invocation.ts +4 -3
- package/src/codegen/generator/expressions/primary.ts +6 -1
- package/src/codegen/generator/module.ts +10 -12
- package/src/codegen/generator/state.ts +4 -0
- package/src/codegen/generator/statements/control-flow.ts +1 -2
- package/src/codegen/generator/tables.ts +84 -2
- package/src/driver/compiler.ts +4 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* -- string methods ------------------------------------------------------- */
|
|
2
2
|
|
|
3
|
+
/* Byte-level search. `from` is a byte offset; returns a byte offset or -1. */
|
|
3
4
|
static int32_t xt_string_find(xt_string *haystack, xt_string *needle, int32_t from) {
|
|
4
5
|
if (from < 0) from = 0;
|
|
5
6
|
if ((uint32_t)from > haystack->length) return -1;
|
|
@@ -12,44 +13,67 @@ static int32_t xt_string_find(xt_string *haystack, xt_string *needle, int32_t fr
|
|
|
12
13
|
return -1;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
/* JavaScript indexes strings in UTF-16 code units; the byte search above is
|
|
17
|
+
* still correct because every JS index maps to a code-point boundary. */
|
|
15
18
|
static xt_value xt_string_char_at(xt_value value, xt_value indexValue) {
|
|
16
19
|
xt_string *s = xt_as_string(value);
|
|
17
20
|
if (!s) return xt_string_from_cstr("");
|
|
18
21
|
int32_t index = xt_to_int32(indexValue);
|
|
19
|
-
if (index < 0 || (uint32_t)index >= s
|
|
20
|
-
return
|
|
22
|
+
if (index < 0 || (uint32_t)index >= xt_string_utf16_length(s)) return xt_string_from_cstr("");
|
|
23
|
+
return xt_string_unit_value(s, index);
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
static xt_value xt_string_char_code_at(xt_value value, xt_value indexValue) {
|
|
24
27
|
xt_string *s = xt_as_string(value);
|
|
25
28
|
if (!s) return xt_number(NAN);
|
|
26
29
|
int32_t index = xt_to_int32(indexValue);
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
int32_t unit = xt_string_code_unit(s, index);
|
|
31
|
+
if (unit < 0) return xt_number(NAN);
|
|
32
|
+
return xt_number((double)unit);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static xt_value xt_string_code_point_at_value(xt_value value, xt_value indexValue) {
|
|
36
|
+
xt_string *s = xt_as_string(value);
|
|
37
|
+
if (!s) return XT_UNDEFINED;
|
|
38
|
+
int32_t index = xt_to_int32(indexValue);
|
|
39
|
+
int32_t code = xt_string_code_point_at(s, index);
|
|
40
|
+
if (code < 0) return XT_UNDEFINED;
|
|
41
|
+
return xt_number((double)code);
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
static xt_value xt_string_index_of(xt_value value, xt_value needleValue, xt_value fromValue) {
|
|
32
45
|
xt_string *s = xt_as_string(value);
|
|
33
46
|
if (!s) return xt_number(-1);
|
|
34
47
|
xt_string *needle = xt_as_string(xt_to_string(needleValue));
|
|
48
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
35
49
|
int32_t from = fromValue == XT_UNDEFINED ? 0 : xt_to_int32(fromValue);
|
|
36
|
-
|
|
50
|
+
if (from < 0) from = 0;
|
|
51
|
+
if (from > units) from = units;
|
|
52
|
+
/* A start position inside a surrogate pair leaves a lone low half; slice the
|
|
53
|
+
* tail so a search for the whole pair correctly fails. */
|
|
54
|
+
if (!xt_string_is_boundary(s, from)) {
|
|
55
|
+
xt_string *tail = xt_as_string(xt_string_slice_units(s, from, units));
|
|
56
|
+
int32_t at = xt_string_find(tail, needle, 0);
|
|
57
|
+
return xt_number(at < 0 ? -1.0 : (double)(from + xt_string_unit_offset(tail, at)));
|
|
58
|
+
}
|
|
59
|
+
int32_t at = xt_string_find(s, needle, xt_string_byte_offset(s, from));
|
|
60
|
+
return xt_number(at < 0 ? -1.0 : (double)xt_string_unit_offset(s, at));
|
|
37
61
|
}
|
|
38
62
|
|
|
39
63
|
static xt_value xt_string_slice(xt_value value, xt_value startValue, xt_value endValue) {
|
|
40
64
|
xt_string *s = xt_as_string(value);
|
|
41
65
|
if (!s) return xt_string_from_cstr("");
|
|
42
|
-
int32_t len = (int32_t)s
|
|
66
|
+
int32_t len = (int32_t)xt_string_utf16_length(s);
|
|
43
67
|
int32_t start = startValue == XT_UNDEFINED ? 0 : xt_to_int32(startValue);
|
|
44
68
|
int32_t end = endValue == XT_UNDEFINED ? len : xt_to_int32(endValue);
|
|
45
69
|
xt_normalize_slice(len, &start, &end);
|
|
46
|
-
return
|
|
70
|
+
return xt_string_slice_units(s, start, end);
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
static xt_value xt_string_substring(xt_value value, xt_value startValue, xt_value endValue) {
|
|
50
74
|
xt_string *s = xt_as_string(value);
|
|
51
75
|
if (!s) return xt_string_from_cstr("");
|
|
52
|
-
int32_t len = (int32_t)s
|
|
76
|
+
int32_t len = (int32_t)xt_string_utf16_length(s);
|
|
53
77
|
int32_t start = startValue == XT_UNDEFINED ? 0 : xt_to_int32(startValue);
|
|
54
78
|
int32_t end = endValue == XT_UNDEFINED ? len : xt_to_int32(endValue);
|
|
55
79
|
if (start < 0) start = 0;
|
|
@@ -61,13 +85,13 @@ static xt_value xt_string_substring(xt_value value, xt_value startValue, xt_valu
|
|
|
61
85
|
start = end;
|
|
62
86
|
end = swap;
|
|
63
87
|
}
|
|
64
|
-
return
|
|
88
|
+
return xt_string_slice_units(s, start, end);
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
static xt_value xt_string_substr(xt_value value, xt_value startValue, xt_value lengthValue) {
|
|
68
92
|
xt_string *s = xt_as_string(value);
|
|
69
93
|
if (!s) return xt_string_from_cstr("");
|
|
70
|
-
int32_t len = (int32_t)s
|
|
94
|
+
int32_t len = (int32_t)xt_string_utf16_length(s);
|
|
71
95
|
int32_t start = startValue == XT_UNDEFINED ? 0 : xt_to_int32(startValue);
|
|
72
96
|
if (start < 0) start += len;
|
|
73
97
|
if (start < 0) start = 0;
|
|
@@ -75,7 +99,7 @@ static xt_value xt_string_substr(xt_value value, xt_value startValue, xt_value l
|
|
|
75
99
|
int32_t length = lengthValue == XT_UNDEFINED ? len - start : xt_to_int32(lengthValue);
|
|
76
100
|
if (length < 0) length = 0;
|
|
77
101
|
if (start + length > len) length = len - start;
|
|
78
|
-
return
|
|
102
|
+
return xt_string_slice_units(s, start, start + length);
|
|
79
103
|
}
|
|
80
104
|
|
|
81
105
|
static xt_value xt_string_split(xt_value value, xt_value separatorValue, xt_value limitValue) {
|
|
@@ -92,7 +116,10 @@ static xt_value xt_string_split(xt_value value, xt_value separatorValue, xt_valu
|
|
|
92
116
|
const char *bytes = xt_string_bytes(s);
|
|
93
117
|
const char *sepBytes = xt_string_bytes(sep);
|
|
94
118
|
if (sep->length == 0) {
|
|
95
|
-
|
|
119
|
+
/* Splitting on the empty string yields one string per UTF-16 code unit,
|
|
120
|
+
* so an emoji becomes its two surrogate halves. */
|
|
121
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
122
|
+
for (int32_t i = 0; i < units; i++) xt_array_push(result, xt_string_unit_value(s, i));
|
|
96
123
|
} else {
|
|
97
124
|
uint32_t start = 0;
|
|
98
125
|
uint32_t i = 0;
|
|
@@ -122,6 +149,7 @@ static xt_value xt_string_case(xt_value value, int upper) {
|
|
|
122
149
|
unsigned char c = (unsigned char)bytes[i];
|
|
123
150
|
mutable[i] = (char)(upper ? toupper((int)c) : tolower((int)c));
|
|
124
151
|
}
|
|
152
|
+
xt_string_finish(out);
|
|
125
153
|
return XT_FROM_PTR(XT_TAG_STRING, out);
|
|
126
154
|
}
|
|
127
155
|
|
|
@@ -153,6 +181,7 @@ static xt_value xt_string_replace(xt_value value, xt_value searchValue, xt_value
|
|
|
153
181
|
memcpy(mutable + at, repl, replacement->length);
|
|
154
182
|
memcpy(mutable + at + replacement->length, text + at + search->length,
|
|
155
183
|
s->length - (size_t)at - search->length);
|
|
184
|
+
xt_string_finish(out);
|
|
156
185
|
return XT_FROM_PTR(XT_TAG_STRING, out);
|
|
157
186
|
}
|
|
158
187
|
|
|
@@ -168,23 +197,51 @@ static xt_value xt_string_repeat(xt_value value, xt_value countValue) {
|
|
|
168
197
|
const char *bytes = xt_string_bytes(s);
|
|
169
198
|
char *mutable = xt_string_mutable(out);
|
|
170
199
|
for (int32_t i = 0; i < count; i++) memcpy(mutable + (size_t)i * s->length, bytes, s->length);
|
|
200
|
+
xt_string_finish(out);
|
|
171
201
|
return XT_FROM_PTR(XT_TAG_STRING, out);
|
|
172
202
|
}
|
|
173
203
|
|
|
174
|
-
static xt_value xt_string_starts_with(xt_value value, xt_value searchValue) {
|
|
204
|
+
static xt_value xt_string_starts_with(xt_value value, xt_value searchValue, xt_value positionValue) {
|
|
175
205
|
xt_string *s = xt_as_string(value);
|
|
176
206
|
if (!s) return XT_FALSE;
|
|
177
207
|
xt_string *search = xt_as_string(xt_to_string(searchValue));
|
|
208
|
+
const char *sBytes = xt_string_bytes(s);
|
|
209
|
+
const char *searchBytes = xt_string_bytes(search);
|
|
210
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
211
|
+
int32_t position = positionValue == XT_UNDEFINED ? 0 : xt_to_int32(positionValue);
|
|
212
|
+
if (position < 0) position = 0;
|
|
213
|
+
if (position > units) position = units;
|
|
178
214
|
if (search->length > s->length) return XT_FALSE;
|
|
179
|
-
|
|
215
|
+
if (!xt_string_is_boundary(s, position)) {
|
|
216
|
+
xt_string *tail = xt_as_string(xt_string_slice_units(s, position, units));
|
|
217
|
+
if (search->length > tail->length) return XT_FALSE;
|
|
218
|
+
return xt_bool(memcmp(xt_string_bytes(tail), searchBytes, search->length) == 0);
|
|
219
|
+
}
|
|
220
|
+
int32_t at = xt_string_byte_offset(s, position);
|
|
221
|
+
if ((uint32_t)at + search->length > s->length) return XT_FALSE;
|
|
222
|
+
return xt_bool(memcmp(sBytes + at, searchBytes, search->length) == 0);
|
|
180
223
|
}
|
|
181
224
|
|
|
182
|
-
static xt_value xt_string_ends_with(xt_value value, xt_value searchValue) {
|
|
225
|
+
static xt_value xt_string_ends_with(xt_value value, xt_value searchValue, xt_value endValue) {
|
|
183
226
|
xt_string *s = xt_as_string(value);
|
|
184
227
|
if (!s) return XT_FALSE;
|
|
185
228
|
xt_string *search = xt_as_string(xt_to_string(searchValue));
|
|
229
|
+
const char *sBytes = xt_string_bytes(s);
|
|
230
|
+
const char *searchBytes = xt_string_bytes(search);
|
|
231
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
232
|
+
int32_t end = endValue == XT_UNDEFINED ? units : xt_to_int32(endValue);
|
|
233
|
+
if (end < 0) end = 0;
|
|
234
|
+
if (end > units) end = units;
|
|
186
235
|
if (search->length > s->length) return XT_FALSE;
|
|
187
|
-
|
|
236
|
+
if (!xt_string_is_boundary(s, end)) {
|
|
237
|
+
xt_string *prefix = xt_as_string(xt_string_slice_units(s, 0, end));
|
|
238
|
+
if (search->length > prefix->length) return XT_FALSE;
|
|
239
|
+
return xt_bool(memcmp(xt_string_bytes(prefix) + prefix->length - search->length,
|
|
240
|
+
searchBytes, search->length) == 0);
|
|
241
|
+
}
|
|
242
|
+
int32_t at = xt_string_byte_offset(s, end);
|
|
243
|
+
if ((uint32_t)at < search->length) return XT_FALSE;
|
|
244
|
+
return xt_bool(memcmp(sBytes + at - search->length, searchBytes, search->length) == 0);
|
|
188
245
|
}
|
|
189
246
|
|
|
190
247
|
static xt_value xt_string_concat_args(xt_value value, int32_t argc, xt_value *argv) {
|
|
@@ -18,9 +18,9 @@ xt_value xt_array_static(xt_value name, int32_t argc, xt_value *argv) {
|
|
|
18
18
|
}
|
|
19
19
|
} else if (XT_IS_STRING(source)) {
|
|
20
20
|
xt_string *s = xt_as_string(source);
|
|
21
|
-
|
|
22
|
-
for (uint32_t i = 0; i <
|
|
23
|
-
xt_value item =
|
|
21
|
+
uint32_t points = xt_string_code_point_count(s);
|
|
22
|
+
for (uint32_t i = 0; i < points; i++) {
|
|
23
|
+
xt_value item = xt_string_code_point_value(s, (int32_t)i);
|
|
24
24
|
if (XT_IS_FUNCTION(mapper)) { xt_value args[2] = {item, xt_number((double)i)}; item = xt_closure_call(mapper, 2, args); }
|
|
25
25
|
xt_array_push(out, item);
|
|
26
26
|
}
|
|
@@ -26,6 +26,8 @@ static xt_value xt_error_proto_for(const char *name) {
|
|
|
26
26
|
xt_string_from_cstr("Error"));
|
|
27
27
|
xt_object_set(xt_error_root_proto, xt_string_from_cstr("message"),
|
|
28
28
|
xt_string_from_cstr(""));
|
|
29
|
+
xt_value constructor = xt_error_constructor(xt_string_from_cstr("Error"));
|
|
30
|
+
xt_object_set(xt_error_root_proto, xt_string_from_cstr("constructor"), constructor);
|
|
29
31
|
}
|
|
30
32
|
if (strcmp(name, "Error") == 0) return xt_error_root_proto;
|
|
31
33
|
xt_value key = xt_string_from_cstr(name);
|
|
@@ -41,6 +43,8 @@ static xt_value xt_error_proto_for(const char *name) {
|
|
|
41
43
|
xt_object_set(proto, xt_string_from_cstr("name"), key);
|
|
42
44
|
xt_object_set(proto, xt_string_from_cstr("message"), xt_string_from_cstr(""));
|
|
43
45
|
xt_object_set(registry, key, proto);
|
|
46
|
+
xt_value constructor = xt_error_constructor(key);
|
|
47
|
+
xt_object_set(proto, xt_string_from_cstr("constructor"), constructor);
|
|
44
48
|
return proto;
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -162,6 +166,13 @@ xt_value xt_aggregate_error_ctor(int32_t argc, xt_value *argv) {
|
|
|
162
166
|
return xt_make_error("AggregateError", argc, argv);
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
/* Dispatch used by the first-class constructor factory (`const E = TypeError`). */
|
|
170
|
+
xt_value xt_error_ctor_by_name(xt_value nameValue, int32_t argc, xt_value *argv) {
|
|
171
|
+
const char *name = XT_IS_STRING(nameValue) ? xt_string_data(nameValue) : "Error";
|
|
172
|
+
if (!xt_error_is_family(name)) name = "Error";
|
|
173
|
+
return xt_make_error(name, argc, argv);
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
/* `super(message)` inside a subclass of the builtin Error family: initialize
|
|
166
177
|
* the already-allocated instance instead of creating a fresh object. The
|
|
167
178
|
* parent's name selects the default `name` (`TypeError`, ...). */
|
|
@@ -36,14 +36,31 @@ static xt_value xt_json_parse_string(xt_json_cursor *c) {
|
|
|
36
36
|
else if (h >= 'a' && h <= 'f') code |= (unsigned)(h - 'a' + 10);
|
|
37
37
|
else if (h >= 'A' && h <= 'F') code |= (unsigned)(h - 'A' + 10);
|
|
38
38
|
}
|
|
39
|
+
/* A high surrogate followed by `\u` and a low surrogate denotes one
|
|
40
|
+
* supplementary code point, exactly like a JavaScript string. */
|
|
41
|
+
if (code >= 0xD800 && code <= 0xDBFF && c->pos + 5 < c->length &&
|
|
42
|
+
c->data[c->pos] == '\\' && c->data[c->pos + 1] == 'u') {
|
|
43
|
+
unsigned low = 0;
|
|
44
|
+
int valid = 1;
|
|
45
|
+
for (int i = 0; i < 4; i++) {
|
|
46
|
+
char h = c->data[c->pos + 2 + (size_t)i];
|
|
47
|
+
low <<= 4;
|
|
48
|
+
if (h >= '0' && h <= '9') low |= (unsigned)(h - '0');
|
|
49
|
+
else if (h >= 'a' && h <= 'f') low |= (unsigned)(h - 'a' + 10);
|
|
50
|
+
else if (h >= 'A' && h <= 'F') low |= (unsigned)(h - 'A' + 10);
|
|
51
|
+
else valid = 0;
|
|
52
|
+
}
|
|
53
|
+
if (valid && low >= 0xDC00 && low <= 0xDFFF) {
|
|
54
|
+
code = 0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00);
|
|
55
|
+
c->pos += 6;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
39
58
|
if (code >= 0x80) {
|
|
40
59
|
char tmp[4];
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
memcpy(out + length, tmp, (size_t)bytes);
|
|
46
|
-
length += (size_t)bytes;
|
|
60
|
+
size_t bytes = xt_utf8_encode(code, tmp);
|
|
61
|
+
if (length + bytes + 1 > capacity) { capacity = (length + bytes + 1) * 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
|
|
62
|
+
memcpy(out + length, tmp, bytes);
|
|
63
|
+
length += bytes;
|
|
47
64
|
continue;
|
|
48
65
|
}
|
|
49
66
|
ch = (char)code;
|
|
@@ -195,14 +212,26 @@ static void xt_json_stringify_value(xt_value value, int indent, int depth, xt_va
|
|
|
195
212
|
xt_string *s = xt_as_string(value);
|
|
196
213
|
const char *bytes = xt_string_bytes(s);
|
|
197
214
|
*out = xt_add(*out, xt_string_from_cstr("\""));
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
else
|
|
215
|
+
size_t offset = 0;
|
|
216
|
+
while (offset < s->length) {
|
|
217
|
+
size_t width = 0;
|
|
218
|
+
uint32_t code = xt_utf8_decode(bytes + offset, s->length - offset, &width);
|
|
219
|
+
const char *escape = NULL;
|
|
220
|
+
char buffer[8];
|
|
221
|
+
if (code == '"') escape = "\\\"";
|
|
222
|
+
else if (code == '\\') escape = "\\\\";
|
|
223
|
+
else if (code == '\b') escape = "\\b";
|
|
224
|
+
else if (code == '\f') escape = "\\f";
|
|
225
|
+
else if (code == '\n') escape = "\\n";
|
|
226
|
+
else if (code == '\r') escape = "\\r";
|
|
227
|
+
else if (code == '\t') escape = "\\t";
|
|
228
|
+
else if (code < 0x20 || (code >= 0xD800 && code <= 0xDFFF)) {
|
|
229
|
+
snprintf(buffer, sizeof(buffer), "\\u%04x", (unsigned)code);
|
|
230
|
+
escape = buffer;
|
|
231
|
+
}
|
|
232
|
+
if (escape) *out = xt_add(*out, xt_string_from_cstr(escape));
|
|
233
|
+
else *out = xt_add(*out, xt_string_new(bytes + offset, width));
|
|
234
|
+
offset += width;
|
|
206
235
|
}
|
|
207
236
|
*out = xt_add(*out, xt_string_from_cstr("\""));
|
|
208
237
|
return;
|
|
@@ -216,7 +245,7 @@ xt_value xt_json_stringify(int32_t argc, xt_value *argv) {
|
|
|
216
245
|
int indent = 0;
|
|
217
246
|
xt_value space = xt_arg_at(argc, argv, 1);
|
|
218
247
|
if (XT_IS_NUMBER(space)) indent = xt_to_int32(space);
|
|
219
|
-
else if (XT_IS_STRING(space)) indent = (int)((xt_string *)XT_GET_PTR(space))
|
|
248
|
+
else if (XT_IS_STRING(space)) indent = (int)xt_string_utf16_length((xt_string *)XT_GET_PTR(space));
|
|
220
249
|
if (indent < 0) indent = 0;
|
|
221
250
|
if (indent > 10) indent = 10;
|
|
222
251
|
xt_value value = xt_arg_at(argc, argv, 0);
|
|
@@ -41,24 +41,33 @@ xt_value xt_number_static(xt_value name, int32_t argc, xt_value *argv) {
|
|
|
41
41
|
xt_value xt_string_static(xt_value name, int32_t argc, xt_value *argv) {
|
|
42
42
|
const char *fn = xt_string_data(name);
|
|
43
43
|
if (!fn) return XT_UNDEFINED;
|
|
44
|
-
if (strcmp(fn, "fromCharCode") == 0
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if (strcmp(fn, "fromCharCode") == 0) {
|
|
45
|
+
/* `fromCharCode` takes UTF-16 code units (modulo 65536) and joins adjacent
|
|
46
|
+
* high/low surrogates into one code point, like JavaScript. */
|
|
47
|
+
if (argc <= 0) return xt_string_from_cstr("");
|
|
48
|
+
uint16_t *units = (uint16_t *)malloc(sizeof(uint16_t) * (size_t)argc);
|
|
49
|
+
if (!units) abort();
|
|
50
|
+
for (int32_t i = 0; i < argc; i++) units[i] = (uint16_t)(xt_to_int32(argv[i]) & 0xFFFF);
|
|
51
|
+
xt_value result = xt_string_from_units(units, (size_t)argc);
|
|
52
|
+
free(units);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
if (strcmp(fn, "fromCodePoint") == 0) {
|
|
56
|
+
char *out = (char *)malloc((size_t)argc * 4 + 1);
|
|
57
|
+
if (!out) abort();
|
|
47
58
|
size_t length = 0;
|
|
48
59
|
for (int32_t i = 0; i < argc; i++) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
memcpy(mutable + length, buffer, (size_t)bytes);
|
|
57
|
-
length += (size_t)bytes;
|
|
60
|
+
int32_t code = xt_to_int32(argv[i]);
|
|
61
|
+
if (code < 0 || code > 0x10FFFF) {
|
|
62
|
+
free(out);
|
|
63
|
+
xt_throw(xt_string_from_cstr("RangeError: Invalid code point"));
|
|
64
|
+
return XT_UNDEFINED;
|
|
65
|
+
}
|
|
66
|
+
length += xt_utf8_encode((uint32_t)code, out + length);
|
|
58
67
|
}
|
|
59
|
-
|
|
60
|
-
out
|
|
61
|
-
return
|
|
68
|
+
xt_value result = xt_string_new(out, length);
|
|
69
|
+
free(out);
|
|
70
|
+
return result;
|
|
62
71
|
}
|
|
63
72
|
if (strcmp(fn, "raw") == 0) {
|
|
64
73
|
xt_value strings = xt_arg_at(argc, argv, 0);
|
|
@@ -64,7 +64,7 @@ xt_value xt_object_static(xt_value name, int32_t argc, xt_value *argv) {
|
|
|
64
64
|
}
|
|
65
65
|
if (strcmp(fn, "create") == 0) {
|
|
66
66
|
xt_value proto = xt_arg_at(argc, argv, 0);
|
|
67
|
-
return xt_object_new_with_proto(proto == XT_NULL ?
|
|
67
|
+
return xt_object_new_with_proto(proto == XT_NULL ? XT_NULL : proto);
|
|
68
68
|
}
|
|
69
69
|
return XT_UNDEFINED;
|
|
70
70
|
}
|
|
@@ -203,7 +203,7 @@ xt_value xt_regexp_exec(xt_value target, xt_value input) {
|
|
|
203
203
|
}
|
|
204
204
|
xt_array_push(out, groupValue);
|
|
205
205
|
}
|
|
206
|
-
xt_set(out, xt_string_from_cstr("index"), xt_number((double)matches[0].rm_so));
|
|
206
|
+
xt_set(out, xt_string_from_cstr("index"), xt_number((double)xt_string_unit_offset(text, matches[0].rm_so)));
|
|
207
207
|
xt_set(out, xt_string_from_cstr("input"), textValue);
|
|
208
208
|
xt_set(out, xt_string_from_cstr("groups"), XT_UNDEFINED);
|
|
209
209
|
free(matches);
|
|
@@ -351,7 +351,7 @@ xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replac
|
|
|
351
351
|
}
|
|
352
352
|
xt_array_push(argArray, groupValue);
|
|
353
353
|
}
|
|
354
|
-
xt_array_push(argArray, xt_number((double)start));
|
|
354
|
+
xt_array_push(argArray, xt_number((double)xt_string_unit_offset(text, start)));
|
|
355
355
|
xt_array_push(argArray, xt_to_string(value));
|
|
356
356
|
xt_value produced = xt_closure_call(replacementValue, jsTotal + 3, xt_as_array(argArray)->items);
|
|
357
357
|
xt_string *producedText = xt_as_string(xt_to_string(produced));
|
|
@@ -17,6 +17,8 @@ int32_t xt_set_size(xt_value value) {
|
|
|
17
17
|
xt_value xt_iter_length(xt_value value) {
|
|
18
18
|
if (xt_is_map(value)) return xt_number((double)xt_map_size(value));
|
|
19
19
|
if (xt_is_set(value)) return xt_number((double)xt_set_size(value));
|
|
20
|
+
/* Strings iterate by code point, not by UTF-16 unit. */
|
|
21
|
+
if (XT_IS_STRING(value)) return xt_number((double)xt_string_code_point_count(xt_as_string(value)));
|
|
20
22
|
return xt_array_length(value);
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -70,12 +72,16 @@ xt_value xt_iter_has(xt_value value, xt_value index) {
|
|
|
70
72
|
if (xt_is_generator(value)) return xt_bool(xt_generator_has_next(value));
|
|
71
73
|
if (xt_is_map(value)) return xt_bool(xt_to_int32(index) < xt_map_size(value));
|
|
72
74
|
if (xt_is_set(value)) return xt_bool(xt_to_int32(index) < xt_set_size(value));
|
|
75
|
+
if (XT_IS_STRING(value))
|
|
76
|
+
return xt_bool(xt_to_int32(index) <
|
|
77
|
+
(int32_t)xt_string_code_point_count(xt_as_string(value)));
|
|
73
78
|
return xt_bool(xt_to_int32(index) < xt_to_int32(xt_array_length(value)));
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
xt_value xt_iter_value(xt_value value, xt_value index) {
|
|
77
82
|
if (xt_is_iterator_state(value)) return ((xt_iterator_state *)XT_GET_PTR(value))->cached;
|
|
78
83
|
if (xt_is_generator(value)) return xt_generator_iter_value(value);
|
|
84
|
+
if (XT_IS_STRING(value)) return xt_string_code_point_value(xt_as_string(value), xt_to_int32(index));
|
|
79
85
|
if (xt_is_map(value)) {
|
|
80
86
|
xt_map *map = (xt_map *)XT_GET_PTR(value);
|
|
81
87
|
int64_t i = (int64_t)xt_to_int32(index);
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
/* -- string methods ------------------------------------------------------- */
|
|
2
2
|
|
|
3
|
+
/* `padStart`/`padEnd` measure the target length in UTF-16 code units and may
|
|
4
|
+
* cut the pad string through a surrogate pair. */
|
|
3
5
|
static xt_value xt_string_pad(xt_value value, xt_value lengthValue, xt_value padValue, int start) {
|
|
4
6
|
xt_string *s = xt_as_string(value);
|
|
5
7
|
if (!s) return value;
|
|
6
8
|
int32_t target = xt_to_int32(lengthValue);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
10
|
+
if (target <= units) return value;
|
|
11
|
+
xt_value padSymbol = padValue == XT_UNDEFINED ? xt_string_from_cstr(" ") : padValue;
|
|
12
|
+
xt_string *pad = xt_as_string(xt_to_string(padSymbol));
|
|
9
13
|
if (!pad || pad->length == 0) return value;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
memcpy(mutable, text, s->length);
|
|
21
|
-
for (size_t i = 0; i < needed; i++) mutable[s->length + i] = padBytes[i % pad->length];
|
|
14
|
+
int32_t padUnits = (int32_t)xt_string_utf16_length(pad);
|
|
15
|
+
if (padUnits <= 0) return value;
|
|
16
|
+
uint32_t needed = (uint32_t)(target - units);
|
|
17
|
+
xt_value padding = xt_string_from_cstr("");
|
|
18
|
+
uint32_t filled = 0;
|
|
19
|
+
while (filled < needed) {
|
|
20
|
+
uint32_t remaining = needed - filled;
|
|
21
|
+
int32_t chunk = remaining < (uint32_t)padUnits ? (int32_t)remaining : padUnits;
|
|
22
|
+
padding = xt_add(padding, xt_string_slice_units(pad, 0, chunk));
|
|
23
|
+
filled += (uint32_t)chunk;
|
|
22
24
|
}
|
|
23
|
-
return
|
|
25
|
+
return start ? xt_add(padding, value) : xt_add(value, padding);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
static xt_value xt_string_trim_side(xt_value value, int start) {
|
|
@@ -37,10 +39,11 @@ static xt_value xt_string_trim_side(xt_value value, int start) {
|
|
|
37
39
|
static xt_value xt_string_at(xt_value value, xt_value indexValue) {
|
|
38
40
|
xt_string *s = xt_as_string(value);
|
|
39
41
|
if (!s) return XT_UNDEFINED;
|
|
42
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
40
43
|
int32_t index = indexValue == XT_UNDEFINED ? 0 : xt_to_int32(indexValue);
|
|
41
|
-
if (index < 0) index +=
|
|
42
|
-
if (index < 0 ||
|
|
43
|
-
return
|
|
44
|
+
if (index < 0) index += units;
|
|
45
|
+
if (index < 0 || index >= units) return XT_UNDEFINED;
|
|
46
|
+
return xt_string_unit_value(s, index);
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_value replacementValue) {
|
|
@@ -49,7 +52,18 @@ static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_v
|
|
|
49
52
|
if (!s) return value;
|
|
50
53
|
xt_string *search = xt_as_string(xt_to_string(searchValue));
|
|
51
54
|
xt_string *replacement = xt_as_string(xt_to_string(replacementValue));
|
|
52
|
-
if (!search || search->length == 0)
|
|
55
|
+
if (!search || search->length == 0) {
|
|
56
|
+
/* An empty search inserts the replacement before every UTF-16 code unit
|
|
57
|
+
* and once at the end. */
|
|
58
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
59
|
+
xt_value replacementText = xt_to_string(replacementValue);
|
|
60
|
+
xt_value result = replacementText;
|
|
61
|
+
for (int32_t i = 0; i < units; i++) {
|
|
62
|
+
result = xt_add(result, xt_string_unit_value(s, i));
|
|
63
|
+
result = xt_add(result, replacementText);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
53
67
|
const char *text = xt_string_bytes(s);
|
|
54
68
|
const char *needle = xt_string_bytes(search);
|
|
55
69
|
const char *repl = xt_string_bytes(replacement);
|
|
@@ -74,6 +88,33 @@ static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_v
|
|
|
74
88
|
return result;
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
/* `limit` is a UTF-16 index; the last possible match starts at or before it. */
|
|
92
|
+
static xt_value xt_string_last_index_of(xt_string *s, xt_string *needle, int32_t limit) {
|
|
93
|
+
if (!s || !needle) return xt_number(-1);
|
|
94
|
+
const char *text = xt_string_bytes(s);
|
|
95
|
+
const char *need = xt_string_bytes(needle);
|
|
96
|
+
int32_t units = (int32_t)xt_string_utf16_length(s);
|
|
97
|
+
if (limit < 0) limit = 0;
|
|
98
|
+
if (limit > units) limit = units;
|
|
99
|
+
if (needle->length == 0) return xt_number((double)limit);
|
|
100
|
+
/* The low half of a surrogate pair is a match candidate of its own, but a
|
|
101
|
+
* byte search from the pair start cannot see it. */
|
|
102
|
+
if (!xt_string_is_boundary(s, limit)) {
|
|
103
|
+
xt_string *tail = xt_as_string(xt_string_slice_units(s, limit, units));
|
|
104
|
+
if (needle->length <= tail->length &&
|
|
105
|
+
memcmp(xt_string_bytes(tail), need, needle->length) == 0)
|
|
106
|
+
return xt_number((double)limit);
|
|
107
|
+
}
|
|
108
|
+
if (needle->length > s->length) return xt_number(-1);
|
|
109
|
+
int32_t i = xt_string_byte_offset(s, limit);
|
|
110
|
+
if (i > (int32_t)s->length - (int32_t)needle->length) i = (int32_t)s->length - (int32_t)needle->length;
|
|
111
|
+
for (; i >= 0; i--) {
|
|
112
|
+
if (((unsigned char)text[i] & 0xC0) == 0x80) continue; /* not a code-point boundary */
|
|
113
|
+
if (memcmp(text + i, need, needle->length) == 0) return xt_number((double)xt_string_unit_offset(s, i));
|
|
114
|
+
}
|
|
115
|
+
return xt_number(-1);
|
|
116
|
+
}
|
|
117
|
+
|
|
77
118
|
xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc, xt_value *argv, int *handled) {
|
|
78
119
|
*handled = 1;
|
|
79
120
|
if (strcmp(method, "at") == 0) return xt_string_at(target, xt_arg_at(argc, argv, 0));
|
|
@@ -93,34 +134,22 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
|
|
|
93
134
|
xt_value regexp = xt_arg_at(argc, argv, 0);
|
|
94
135
|
if (xt_is_regexp(regexp)) {
|
|
95
136
|
int32_t start = 0;
|
|
96
|
-
if (xt_regexp_find(regexp, target, 0, &start, NULL))
|
|
137
|
+
if (xt_regexp_find(regexp, target, 0, &start, NULL)) {
|
|
138
|
+
xt_string *text = xt_as_string(xt_to_string(target));
|
|
139
|
+
return xt_number((double)xt_string_unit_offset(text, start));
|
|
140
|
+
}
|
|
97
141
|
return xt_number(-1);
|
|
98
142
|
}
|
|
99
143
|
*handled = 0;
|
|
100
144
|
return XT_UNDEFINED;
|
|
101
145
|
}
|
|
102
|
-
if (strcmp(method, "replaceAll") == 0) {
|
|
103
|
-
xt_value searchValue = xt_arg_at(argc, argv, 0);
|
|
104
|
-
if (xt_is_regexp(searchValue)) return xt_regexp_replace(target, searchValue, xt_arg_at(argc, argv, 1));
|
|
105
|
-
*handled = 0;
|
|
106
|
-
return XT_UNDEFINED;
|
|
107
|
-
}
|
|
108
146
|
if (strcmp(method, "lastIndexOf") == 0) {
|
|
109
147
|
xt_string *s = xt_as_string(target);
|
|
110
148
|
xt_string *needle = xt_as_string(xt_to_string(xt_arg_at(argc, argv, 0)));
|
|
111
149
|
if (!s || !needle) return xt_number(-1);
|
|
112
|
-
int32_t limit = (argc >= 2 && argv[1] != XT_UNDEFINED) ? xt_to_int32(argv[1])
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (nlen == 0) return xt_number((double)(limit < 0 ? 0 : limit));
|
|
116
|
-
const char *text = xt_string_bytes(s);
|
|
117
|
-
const char *need = xt_string_bytes(needle);
|
|
118
|
-
int32_t i = limit;
|
|
119
|
-
if (i > (int32_t)s->length - nlen) i = (int32_t)s->length - nlen;
|
|
120
|
-
for (; i >= 0; i--) {
|
|
121
|
-
if (memcmp(text + i, need, (size_t)nlen) == 0) return xt_number((double)i);
|
|
122
|
-
}
|
|
123
|
-
return xt_number(-1);
|
|
150
|
+
int32_t limit = (argc >= 2 && argv[1] != XT_UNDEFINED) ? xt_to_int32(argv[1])
|
|
151
|
+
: (int32_t)xt_string_utf16_length(s);
|
|
152
|
+
return xt_string_last_index_of(s, needle, limit);
|
|
124
153
|
}
|
|
125
154
|
if (strcmp(method, "localeCompare") == 0) {
|
|
126
155
|
xt_string *a = xt_as_string(target);
|
|
@@ -132,8 +161,9 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
|
|
|
132
161
|
xt_string *s = xt_as_string(target);
|
|
133
162
|
if (!s) return XT_UNDEFINED;
|
|
134
163
|
int32_t index = xt_to_int32(xt_arg_at(argc, argv, 0));
|
|
135
|
-
|
|
136
|
-
|
|
164
|
+
int32_t code = xt_string_code_point_at(s, index);
|
|
165
|
+
if (code < 0) return XT_UNDEFINED;
|
|
166
|
+
return xt_number((double)code);
|
|
137
167
|
}
|
|
138
168
|
if (strcmp(method, "valueOf") == 0 || strcmp(method, "toString") == 0) return target;
|
|
139
169
|
*handled = 0;
|