xbintsc 0.3.81 → 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.
Files changed (188) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +16 -5
  3. package/README.zh-CN.md +14 -6
  4. package/dist/src/ast/common.d.ts +23 -0
  5. package/dist/src/cli/main.js +68 -24
  6. package/dist/src/cli/main.js.map +1 -1
  7. package/dist/src/codegen/generator/calls/access.js +6 -2
  8. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  9. package/dist/src/codegen/generator/calls/closures.js +4 -0
  10. package/dist/src/codegen/generator/calls/closures.js.map +1 -1
  11. package/dist/src/codegen/generator/calls/invocation.js +9 -4
  12. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  13. package/dist/src/codegen/generator/context.d.ts +50 -0
  14. package/dist/src/codegen/generator/context.js +124 -13
  15. package/dist/src/codegen/generator/context.js.map +1 -1
  16. package/dist/src/codegen/generator/debug.d.ts +33 -0
  17. package/dist/src/codegen/generator/debug.js +97 -0
  18. package/dist/src/codegen/generator/debug.js.map +1 -0
  19. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  20. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  21. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  22. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  23. package/dist/src/codegen/generator/expressions/primary.js +11 -3
  24. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  25. package/dist/src/codegen/generator/generator.d.ts +3 -1
  26. package/dist/src/codegen/generator/generator.js +3 -1
  27. package/dist/src/codegen/generator/generator.js.map +1 -1
  28. package/dist/src/codegen/generator/module.d.ts +1 -1
  29. package/dist/src/codegen/generator/module.js +82 -12
  30. package/dist/src/codegen/generator/module.js.map +1 -1
  31. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  32. package/dist/src/codegen/generator/numeric.js +127 -0
  33. package/dist/src/codegen/generator/numeric.js.map +1 -0
  34. package/dist/src/codegen/generator/state.d.ts +17 -0
  35. package/dist/src/codegen/generator/state.js +9 -0
  36. package/dist/src/codegen/generator/state.js.map +1 -1
  37. package/dist/src/codegen/generator/statements/control-flow.js +2 -4
  38. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  39. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  40. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  41. package/dist/src/codegen/generator/statements/variables.js +8 -2
  42. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  43. package/dist/src/codegen/generator/tables.d.ts +16 -0
  44. package/dist/src/codegen/generator/tables.js +99 -2
  45. package/dist/src/codegen/generator/tables.js.map +1 -1
  46. package/dist/src/driver/bundler/merge.js +41 -5
  47. package/dist/src/driver/bundler/merge.js.map +1 -1
  48. package/dist/src/driver/compiler.d.ts +23 -4
  49. package/dist/src/driver/compiler.js +46 -57
  50. package/dist/src/driver/compiler.js.map +1 -1
  51. package/dist/src/driver/fingerprint.d.ts +27 -0
  52. package/dist/src/driver/fingerprint.js +88 -0
  53. package/dist/src/driver/fingerprint.js.map +1 -0
  54. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  55. package/dist/src/driver/toolchain-provider.js +15 -15
  56. package/dist/src/driver/toolchain-provider.js.map +1 -1
  57. package/dist/src/driver/toolchain.d.ts +58 -1
  58. package/dist/src/driver/toolchain.js +169 -22
  59. package/dist/src/driver/toolchain.js.map +1 -1
  60. package/dist/src/parser/expressions.js +20 -1
  61. package/dist/src/parser/expressions.js.map +1 -1
  62. package/dist/tests/cli/main.test.js +41 -1
  63. package/dist/tests/cli/main.test.js.map +1 -1
  64. package/dist/tests/codegen/closures.test.js +8 -0
  65. package/dist/tests/codegen/closures.test.js.map +1 -1
  66. package/dist/tests/codegen/expressions.test.js +14 -3
  67. package/dist/tests/codegen/expressions.test.js.map +1 -1
  68. package/dist/tests/codegen/llvm.test.js +23 -0
  69. package/dist/tests/codegen/llvm.test.js.map +1 -1
  70. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  71. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  72. package/dist/tests/driver/toolchain.test.js +62 -3
  73. package/dist/tests/driver/toolchain.test.js.map +1 -1
  74. package/dist/tests/e2e/async-await.test.d.ts +10 -0
  75. package/dist/tests/e2e/async-await.test.js +247 -0
  76. package/dist/tests/e2e/async-await.test.js.map +1 -0
  77. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  78. package/dist/tests/e2e/child-process.test.js +59 -0
  79. package/dist/tests/e2e/child-process.test.js.map +1 -0
  80. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  81. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  82. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  83. package/dist/tests/e2e/error-stack.test.js +94 -0
  84. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  85. package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
  86. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
  87. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  88. package/dist/tests/e2e/string-concat.test.js +102 -0
  89. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  90. package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
  91. package/dist/tests/e2e/string-utf16.test.js +133 -0
  92. package/dist/tests/e2e/string-utf16.test.js.map +1 -0
  93. package/dist/tests/helpers.d.ts +9 -0
  94. package/dist/tests/helpers.js +26 -3
  95. package/dist/tests/helpers.js.map +1 -1
  96. package/dist/tests/parser/expressions.test.js +7 -0
  97. package/dist/tests/parser/expressions.test.js.map +1 -1
  98. package/dist/tests/version.test.d.ts +1 -0
  99. package/dist/tests/version.test.js +24 -0
  100. package/dist/tests/version.test.js.map +1 -0
  101. package/doc/DESIGN.md +20 -12
  102. package/doc/ai/cli.md +2 -0
  103. package/doc/ai/contributing.md +16 -8
  104. package/doc/ai/extensions.md +3 -2
  105. package/doc/ai/language-support.md +15 -12
  106. package/doc/ai/troubleshooting.md +43 -5
  107. package/doc/ai/zh-CN/cli.md +2 -0
  108. package/doc/ai/zh-CN/contributing.md +11 -7
  109. package/doc/ai/zh-CN/extensions.md +2 -1
  110. package/doc/ai/zh-CN/language-support.md +15 -12
  111. package/doc/ai/zh-CN/troubleshooting.md +38 -4
  112. package/doc/implemented.md +16 -13
  113. package/doc/requirements.md +13 -4
  114. package/doc/unimplemented.md +20 -18
  115. package/doc/zh-CN/DESIGN.md +15 -9
  116. package/doc/zh-CN/implemented.md +15 -12
  117. package/doc/zh-CN/requirements.md +12 -3
  118. package/doc/zh-CN/unimplemented.md +19 -17
  119. package/package.json +3 -1
  120. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  121. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  122. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  123. package/runtime/ext_node/child_process/child_process.c +25 -0
  124. package/runtime/ext_node/fs/fd_ops.c +1 -1
  125. package/runtime/rt.h +49 -1
  126. package/runtime/rt_internal.h +136 -4
  127. package/runtime/xt_alloc.c +20 -2
  128. package/runtime/xt_async.c +197 -0
  129. package/runtime/xt_bigint/conversions.inc +1 -1
  130. package/runtime/xt_builtins.c +169 -43
  131. package/runtime/xt_constructors.c +389 -0
  132. package/runtime/xt_containers/access.inc +61 -24
  133. package/runtime/xt_containers/arrays.inc +26 -2
  134. package/runtime/xt_containers/objects.inc +206 -8
  135. package/runtime/xt_coroutine.c +212 -0
  136. package/runtime/xt_generator.c +31 -162
  137. package/runtime/xt_io/exception.inc +171 -2
  138. package/runtime/xt_io/inspect.inc +48 -13
  139. package/runtime/xt_io.c +5 -0
  140. package/runtime/xt_promise.c +62 -17
  141. package/runtime/xt_stdlib/arrays.inc +2 -2
  142. package/runtime/xt_stdlib/dispatch.inc +20 -4
  143. package/runtime/xt_stdlib/strings.inc +98 -29
  144. package/runtime/xt_stdlib2/array-statics.inc +3 -2
  145. package/runtime/xt_stdlib2/arrays.inc +1 -1
  146. package/runtime/xt_stdlib2/error.inc +33 -6
  147. package/runtime/xt_stdlib2/json.inc +46 -16
  148. package/runtime/xt_stdlib2/number-string-statics.inc +24 -13
  149. package/runtime/xt_stdlib2/objects.inc +1 -1
  150. package/runtime/xt_stdlib2/regexp-match.inc +50 -35
  151. package/runtime/xt_stdlib2/set.inc +6 -0
  152. package/runtime/xt_stdlib2/strings.inc +81 -42
  153. package/runtime/xt_symbol.c +1 -1
  154. package/runtime/xt_typed_array/construction.inc +43 -2
  155. package/runtime/xt_typed_array.c +4 -0
  156. package/runtime/xt_values/comparison.inc +2 -2
  157. package/runtime/xt_values/constructors.inc +18 -5
  158. package/runtime/xt_values/conversions.inc +4 -3
  159. package/runtime/xt_values/strings.inc +94 -4
  160. package/runtime/xt_values/utf16.inc +340 -0
  161. package/runtime/xt_values.c +1 -0
  162. package/scripts/bench.ts +172 -0
  163. package/scripts/build-runtime.ts +2 -1
  164. package/scripts/check-version.ts +53 -0
  165. package/src/ast/common.ts +23 -0
  166. package/src/cli/main.ts +68 -26
  167. package/src/codegen/generator/calls/access.ts +7 -2
  168. package/src/codegen/generator/calls/closures.ts +3 -0
  169. package/src/codegen/generator/calls/invocation.ts +9 -3
  170. package/src/codegen/generator/context.ts +129 -18
  171. package/src/codegen/generator/debug.ts +132 -0
  172. package/src/codegen/generator/expressions/assignment.ts +9 -5
  173. package/src/codegen/generator/expressions/operators.ts +47 -8
  174. package/src/codegen/generator/expressions/primary.ts +11 -3
  175. package/src/codegen/generator/generator.ts +18 -2
  176. package/src/codegen/generator/module.ts +83 -13
  177. package/src/codegen/generator/numeric.ts +158 -0
  178. package/src/codegen/generator/state.ts +26 -0
  179. package/src/codegen/generator/statements/control-flow.ts +2 -3
  180. package/src/codegen/generator/statements/dispatch.ts +3 -0
  181. package/src/codegen/generator/statements/variables.ts +8 -2
  182. package/src/codegen/generator/tables.ts +110 -2
  183. package/src/driver/bundler/merge.ts +39 -5
  184. package/src/driver/compiler.ts +65 -55
  185. package/src/driver/fingerprint.ts +85 -0
  186. package/src/driver/toolchain-provider.ts +17 -16
  187. package/src/driver/toolchain.ts +210 -21
  188. package/src/parser/expressions.ts +20 -1
@@ -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
- int bytes = 0;
42
- if (code < 0x800) { tmp[bytes++] = (char)(0xC0 | (code >> 6)); tmp[bytes++] = (char)(0x80 | (code & 0x3F)); }
43
- else { tmp[bytes++] = (char)(0xE0 | (code >> 12)); tmp[bytes++] = (char)(0x80 | ((code >> 6) & 0x3F)); tmp[bytes++] = (char)(0x80 | (code & 0x3F)); }
44
- if (length + (size_t)bytes + 1 > capacity) { capacity = (length + (size_t)bytes + 1) * 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
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;
@@ -120,7 +137,7 @@ xt_value xt_json_parse(int32_t argc, xt_value *argv) {
120
137
  xt_value input = xt_to_string(xt_arg_at(argc, argv, 0));
121
138
  xt_string *s = xt_as_string(input);
122
139
  if (!s) return XT_UNDEFINED;
123
- xt_json_cursor cursor = {s->data, s->length, 0};
140
+ xt_json_cursor cursor = {xt_string_bytes(s), s->length, 0};
124
141
  return xt_json_parse_value(&cursor);
125
142
  }
126
143
 
@@ -193,15 +210,28 @@ static void xt_json_stringify_value(xt_value value, int indent, int depth, xt_va
193
210
  }
194
211
  if (XT_IS_STRING(value)) {
195
212
  xt_string *s = xt_as_string(value);
213
+ const char *bytes = xt_string_bytes(s);
196
214
  *out = xt_add(*out, xt_string_from_cstr("\""));
197
- for (uint32_t i = 0; i < s->length; i++) {
198
- char ch = s->data[i];
199
- if (ch == '"') *out = xt_add(*out, xt_string_from_cstr("\\\""));
200
- else if (ch == '\\') *out = xt_add(*out, xt_string_from_cstr("\\\\"));
201
- else if (ch == '\n') *out = xt_add(*out, xt_string_from_cstr("\\n"));
202
- else if (ch == '\r') *out = xt_add(*out, xt_string_from_cstr("\\r"));
203
- else if (ch == '\t') *out = xt_add(*out, xt_string_from_cstr("\\t"));
204
- else *out = xt_add(*out, xt_string_new(&ch, 1));
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;
205
235
  }
206
236
  *out = xt_add(*out, xt_string_from_cstr("\""));
207
237
  return;
@@ -215,7 +245,7 @@ xt_value xt_json_stringify(int32_t argc, xt_value *argv) {
215
245
  int indent = 0;
216
246
  xt_value space = xt_arg_at(argc, argv, 1);
217
247
  if (XT_IS_NUMBER(space)) indent = xt_to_int32(space);
218
- else if (XT_IS_STRING(space)) indent = (int)((xt_string *)XT_GET_PTR(space))->length;
248
+ else if (XT_IS_STRING(space)) indent = (int)xt_string_utf16_length((xt_string *)XT_GET_PTR(space));
219
249
  if (indent < 0) indent = 0;
220
250
  if (indent > 10) indent = 10;
221
251
  xt_value value = xt_arg_at(argc, argv, 0);
@@ -41,22 +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 || strcmp(fn, "fromCodePoint") == 0) {
45
- xt_string *out = xt_string_alloc((size_t)argc * 4 + 1);
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();
46
58
  size_t length = 0;
47
59
  for (int32_t i = 0; i < argc; i++) {
48
- unsigned code = (unsigned)xt_to_int32(argv[i]);
49
- char buffer[4];
50
- int bytes = 0;
51
- if (code < 0x80) buffer[bytes++] = (char)code;
52
- else if (code < 0x800) { buffer[bytes++] = (char)(0xC0 | (code >> 6)); buffer[bytes++] = (char)(0x80 | (code & 0x3F)); }
53
- else if (code < 0x10000) { buffer[bytes++] = (char)(0xE0 | (code >> 12)); buffer[bytes++] = (char)(0x80 | ((code >> 6) & 0x3F)); buffer[bytes++] = (char)(0x80 | (code & 0x3F)); }
54
- else { buffer[bytes++] = (char)(0xF0 | (code >> 18)); buffer[bytes++] = (char)(0x80 | ((code >> 12) & 0x3F)); buffer[bytes++] = (char)(0x80 | ((code >> 6) & 0x3F)); buffer[bytes++] = (char)(0x80 | (code & 0x3F)); }
55
- memcpy(out->data + length, buffer, (size_t)bytes);
56
- 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);
57
67
  }
58
- out->length = (uint32_t)length;
59
- return XT_FROM_PTR(XT_TAG_STRING, out);
68
+ xt_value result = xt_string_new(out, length);
69
+ free(out);
70
+ return result;
60
71
  }
61
72
  if (strcmp(fn, "raw") == 0) {
62
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 ? XT_UNDEFINED : proto);
67
+ return xt_object_new_with_proto(proto == XT_NULL ? XT_NULL : proto);
68
68
  }
69
69
  return XT_UNDEFINED;
70
70
  }
@@ -109,8 +109,11 @@ static int xt_regexp_run(xt_value regexpValue, xt_value input, int32_t start,
109
109
  if ((uint32_t)start > text->length) return 0;
110
110
  int flags = REG_EXTENDED;
111
111
  xt_string *f = xt_as_string(regexp->flags);
112
- if (f) for (uint32_t i = 0; i < f->length; i++) if (f->data[i] == 'i') flags |= REG_ICASE;
113
- char *translated = xt_regexp_translate_js(pattern->data, jsGroups, groupCount);
112
+ if (f) {
113
+ const char *flagBytes = xt_string_bytes(f);
114
+ for (uint32_t i = 0; i < f->length; i++) if (flagBytes[i] == 'i') flags |= REG_ICASE;
115
+ }
116
+ char *translated = xt_regexp_translate_js(xt_string_bytes(pattern), jsGroups, groupCount);
114
117
  if (!translated) return 0;
115
118
  regex_t compiled;
116
119
  if (regcomp(&compiled, translated, flags) != 0) {
@@ -118,7 +121,7 @@ static int xt_regexp_run(xt_value regexpValue, xt_value input, int32_t start,
118
121
  return 0;
119
122
  }
120
123
  free(translated);
121
- int ok = regexec(&compiled, text->data + start, nmatch, matches, 0) == 0;
124
+ int ok = regexec(&compiled, xt_string_bytes(text) + start, nmatch, matches, 0) == 0;
122
125
  regfree(&compiled);
123
126
  if (!ok) return 0;
124
127
  for (size_t i = 0; i < nmatch; i++) {
@@ -142,7 +145,8 @@ static int xt_regexp_is_global(xt_value regexpValue) {
142
145
  xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
143
146
  xt_string *f = xt_as_string(regexp->flags);
144
147
  if (!f) return 0;
145
- for (uint32_t i = 0; i < f->length; i++) if (f->data[i] == 'g') return 1;
148
+ const char *flags = xt_string_bytes(f);
149
+ for (uint32_t i = 0; i < f->length; i++) if (flags[i] == 'g') return 1;
146
150
  return 0;
147
151
  }
148
152
 
@@ -154,7 +158,7 @@ static int xt_regexp_js_group_count(xt_value regexpValue) {
154
158
  int count = (int)pattern->length + 1;
155
159
  int *groups = (int *)malloc(sizeof(int) * (size_t)count);
156
160
  int groupCount = 0;
157
- char *translated = xt_regexp_translate_js(pattern->data, groups, &groupCount);
161
+ char *translated = xt_regexp_translate_js(xt_string_bytes(pattern), groups, &groupCount);
158
162
  free(translated);
159
163
  int total = 0;
160
164
  for (int i = 0; i < groupCount; i++) if (groups[i] > total) total = groups[i];
@@ -177,6 +181,7 @@ xt_value xt_regexp_exec(xt_value target, xt_value input) {
177
181
  if (!matches || !groups) { free(matches); free(groups); return XT_NULL; }
178
182
  xt_value textValue = xt_to_string(input);
179
183
  xt_string *text = xt_as_string(textValue);
184
+ const char *textBytes = xt_string_bytes(text);
180
185
  int groupCount = 0;
181
186
  if (!xt_regexp_run(target, input, 0, matches, cap, groups, &groupCount)) {
182
187
  free(matches);
@@ -186,19 +191,19 @@ xt_value xt_regexp_exec(xt_value target, xt_value input) {
186
191
  int jsTotal = 0;
187
192
  for (int i = 0; i < groupCount; i++) if (groups[i] > jsTotal) jsTotal = groups[i];
188
193
  xt_value out = xt_array_new(0, NULL);
189
- xt_array_push(out, xt_string_new(text->data + matches[0].rm_so, (int64_t)(matches[0].rm_eo - matches[0].rm_so)));
194
+ xt_array_push(out, xt_string_new(textBytes + matches[0].rm_so, (int64_t)(matches[0].rm_eo - matches[0].rm_so)));
190
195
  for (int g = 1; g <= jsTotal; g++) {
191
196
  xt_value groupValue = XT_UNDEFINED;
192
197
  for (int i = 0; i < groupCount; i++) {
193
198
  if (groups[i] != g) continue;
194
199
  if (matches[i + 1].rm_so >= 0) {
195
- groupValue = xt_string_new(text->data + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
200
+ groupValue = xt_string_new(textBytes + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
196
201
  }
197
202
  break;
198
203
  }
199
204
  xt_array_push(out, groupValue);
200
205
  }
201
- 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)));
202
207
  xt_set(out, xt_string_from_cstr("input"), textValue);
203
208
  xt_set(out, xt_string_from_cstr("groups"), XT_UNDEFINED);
204
209
  free(matches);
@@ -212,6 +217,7 @@ xt_value xt_regexp_match(xt_value input, xt_value regexp) {
212
217
  if (!xt_regexp_is_global(regexp)) return xt_regexp_exec(regexp, input);
213
218
  xt_value textValue = xt_to_string(input);
214
219
  xt_string *text = xt_as_string(textValue);
220
+ const char *textBytes = xt_string_bytes(text);
215
221
  xt_value results = xt_array_new(0, NULL);
216
222
  int32_t cursor = 0;
217
223
  int found = 0;
@@ -220,7 +226,7 @@ xt_value xt_regexp_match(xt_value input, xt_value regexp) {
220
226
  int32_t end = 0;
221
227
  if (!xt_regexp_find(regexp, input, cursor, &start, &end)) break;
222
228
  found = 1;
223
- xt_array_push(results, xt_string_new(text->data + start, (int64_t)(end - start)));
229
+ xt_array_push(results, xt_string_new(textBytes + start, (int64_t)(end - start)));
224
230
  cursor = end > start ? end : end + 1;
225
231
  }
226
232
  return found ? results : XT_NULL;
@@ -241,10 +247,11 @@ static void xt_regexp_append(char **out, size_t *length, size_t *capacity, const
241
247
  static void xt_regexp_append_group(char **out, size_t *length, size_t *capacity,
242
248
  xt_string *text, regmatch_t *matches, int *groups,
243
249
  int groupCount, int group) {
250
+ const char *textBytes = xt_string_bytes(text);
244
251
  for (int i = 0; i < groupCount; i++) {
245
252
  if (groups[i] != group) continue;
246
253
  if (matches[i + 1].rm_so >= 0) {
247
- xt_regexp_append(out, length, capacity, text->data + matches[i + 1].rm_so,
254
+ xt_regexp_append(out, length, capacity, textBytes + matches[i + 1].rm_so,
248
255
  (size_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
249
256
  }
250
257
  return;
@@ -255,32 +262,34 @@ static void xt_regexp_expand_replacement(char **out, size_t *length, size_t *cap
255
262
  xt_string *replacement, xt_string *text,
256
263
  regmatch_t *matches, int *groups, int groupCount,
257
264
  int jsTotal) {
265
+ const char *repl = xt_string_bytes(replacement);
266
+ const char *textBytes = xt_string_bytes(text);
258
267
  size_t start = (size_t)matches[0].rm_so;
259
268
  size_t end = (size_t)matches[0].rm_eo;
260
269
  for (size_t i = 0; i < replacement->length; i++) {
261
- char c = replacement->data[i];
270
+ char c = repl[i];
262
271
  if (c != '$' || i + 1 >= replacement->length) {
263
272
  xt_regexp_append(out, length, capacity, &c, 1);
264
273
  continue;
265
274
  }
266
- char next = replacement->data[i + 1];
275
+ char next = repl[i + 1];
267
276
  if (next == '$') {
268
277
  xt_regexp_append(out, length, capacity, "$", 1);
269
278
  i++;
270
279
  } else if (next == '&') {
271
- xt_regexp_append(out, length, capacity, text->data + start, end - start);
280
+ xt_regexp_append(out, length, capacity, textBytes + start, end - start);
272
281
  i++;
273
282
  } else if (next == '`') {
274
- xt_regexp_append(out, length, capacity, text->data, start);
283
+ xt_regexp_append(out, length, capacity, textBytes, start);
275
284
  i++;
276
285
  } else if (next == '\'') {
277
- xt_regexp_append(out, length, capacity, text->data + end, text->length - end);
286
+ xt_regexp_append(out, length, capacity, textBytes + end, text->length - end);
278
287
  i++;
279
288
  } else if (next >= '0' && next <= '9') {
280
289
  int value = next - '0';
281
290
  int digits = 1;
282
- if (i + 2 < replacement->length && replacement->data[i + 2] >= '0' && replacement->data[i + 2] <= '9') {
283
- int two = value * 10 + (replacement->data[i + 2] - '0');
291
+ if (i + 2 < replacement->length && repl[i + 2] >= '0' && repl[i + 2] <= '9') {
292
+ int two = value * 10 + (repl[i + 2] - '0');
284
293
  if (two <= jsTotal) { value = two; digits = 2; }
285
294
  }
286
295
  if (value >= 1 && value <= jsTotal) {
@@ -299,6 +308,7 @@ static void xt_regexp_expand_replacement(char **out, size_t *length, size_t *cap
299
308
  xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replacementValue) {
300
309
  xt_string *text = xt_as_string(xt_to_string(value));
301
310
  if (!text) return value;
311
+ const char *textBytes = xt_string_bytes(text);
302
312
  int global = xt_regexp_is_global(regexpValue);
303
313
  int useFunction = XT_IS_FUNCTION(replacementValue);
304
314
  xt_string *replacement = NULL;
@@ -324,28 +334,28 @@ xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replac
324
334
  if (!xt_regexp_run(regexpValue, value, cursor, matches, cap, groups, &groupCount)) break;
325
335
  int32_t start = (int32_t)matches[0].rm_so;
326
336
  int32_t end = (int32_t)matches[0].rm_eo;
327
- xt_regexp_append(&out, &length, &capacity, text->data + cursor, (size_t)(start - cursor));
337
+ xt_regexp_append(&out, &length, &capacity, textBytes + cursor, (size_t)(start - cursor));
328
338
  if (useFunction) {
329
339
  /* Accumulate in an `xt_array` so the earlier match/group strings stay
330
340
  reachable while later groups (and the callback itself) allocate. */
331
341
  xt_value argArray = xt_array_new(0, NULL);
332
- xt_array_push(argArray, xt_string_new(text->data + start, (int64_t)(end - start)));
342
+ xt_array_push(argArray, xt_string_new(textBytes + start, (int64_t)(end - start)));
333
343
  for (int g = 1; g <= jsTotal; g++) {
334
344
  xt_value groupValue = XT_UNDEFINED;
335
345
  for (int i = 0; i < groupCount; i++) {
336
346
  if (groups[i] != g) continue;
337
347
  if (matches[i + 1].rm_so >= 0) {
338
- groupValue = xt_string_new(text->data + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
348
+ groupValue = xt_string_new(textBytes + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
339
349
  }
340
350
  break;
341
351
  }
342
352
  xt_array_push(argArray, groupValue);
343
353
  }
344
- xt_array_push(argArray, xt_number((double)start));
354
+ xt_array_push(argArray, xt_number((double)xt_string_unit_offset(text, start)));
345
355
  xt_array_push(argArray, xt_to_string(value));
346
356
  xt_value produced = xt_closure_call(replacementValue, jsTotal + 3, xt_as_array(argArray)->items);
347
357
  xt_string *producedText = xt_as_string(xt_to_string(produced));
348
- if (producedText) xt_regexp_append(&out, &length, &capacity, producedText->data, producedText->length);
358
+ if (producedText) xt_regexp_append(&out, &length, &capacity, xt_string_bytes(producedText), producedText->length);
349
359
  } else {
350
360
  xt_regexp_expand_replacement(&out, &length, &capacity, replacement, text, matches, groups, groupCount, jsTotal);
351
361
  }
@@ -355,7 +365,7 @@ xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replac
355
365
  } else {
356
366
  /* Zero-width match: copy one byte so the scan always progresses. */
357
367
  if (cursor >= (int32_t)text->length) break;
358
- xt_regexp_append(&out, &length, &capacity, text->data + cursor, 1);
368
+ xt_regexp_append(&out, &length, &capacity, textBytes + cursor, 1);
359
369
  cursor++;
360
370
  }
361
371
  if (!global) break;
@@ -367,7 +377,7 @@ xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replac
367
377
  return value;
368
378
  }
369
379
  size_t tail = text->length - (size_t)cursor;
370
- xt_regexp_append(&out, &length, &capacity, text->data + cursor, tail);
380
+ xt_regexp_append(&out, &length, &capacity, textBytes + cursor, tail);
371
381
  xt_value result = xt_string_new(out, (int64_t)length);
372
382
  free(out);
373
383
  free(matches);
@@ -381,6 +391,7 @@ xt_value xt_regexp_split(xt_value value, xt_value regexpValue, xt_value limitVal
381
391
  xt_value result = xt_array_new(0, NULL);
382
392
  xt_string *text = xt_as_string(xt_to_string(value));
383
393
  if (!text) return result;
394
+ const char *textBytes = xt_string_bytes(text);
384
395
  int64_t limit = limitValue == XT_UNDEFINED ? 0x7fffffff : (int64_t)xt_to_int32(limitValue);
385
396
  if (limit <= 0) return result;
386
397
  xt_regexp *regexp = (xt_regexp *)XT_GET_PTR(regexpValue);
@@ -400,14 +411,14 @@ xt_value xt_regexp_split(xt_value value, xt_value regexpValue, xt_value limitVal
400
411
  int32_t end = (int32_t)matches[0].rm_eo;
401
412
  if (start != q) { q++; continue; }
402
413
  if (end == p) { q++; continue; }
403
- xt_array_push(result, xt_string_new(text->data + p, (int64_t)(q - p)));
414
+ xt_array_push(result, xt_string_new(textBytes + p, (int64_t)(q - p)));
404
415
  if ((int64_t)xt_as_array(result)->length >= limit) { free(matches); free(groups); return result; }
405
416
  for (int g = 1; g <= jsTotal; g++) {
406
417
  xt_value groupValue = XT_UNDEFINED;
407
418
  for (int i = 0; i < groupCount; i++) {
408
419
  if (groups[i] != g) continue;
409
420
  if (matches[i + 1].rm_so >= 0) {
410
- groupValue = xt_string_new(text->data + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
421
+ groupValue = xt_string_new(textBytes + matches[i + 1].rm_so, (int64_t)(matches[i + 1].rm_eo - matches[i + 1].rm_so));
411
422
  }
412
423
  break;
413
424
  }
@@ -418,7 +429,7 @@ xt_value xt_regexp_split(xt_value value, xt_value regexpValue, xt_value limitVal
418
429
  q = p;
419
430
  }
420
431
  if ((int64_t)xt_as_array(result)->length < limit) {
421
- xt_array_push(result, xt_string_new(text->data + p, (int64_t)(len - p)));
432
+ xt_array_push(result, xt_string_new(textBytes + p, (int64_t)(len - p)));
422
433
  }
423
434
  free(matches);
424
435
  free(groups);
@@ -431,16 +442,20 @@ xt_value xt_regexp_get_property(xt_value target, xt_value key) {
431
442
  if (!XT_IS_STRING(key)) return XT_UNDEFINED;
432
443
  xt_string *k = xt_as_string(key);
433
444
  xt_string *f = xt_as_string(regexp->flags);
445
+ const char *keyBytes = xt_string_bytes(k);
434
446
  int flag = 0;
435
- if (k->length == 6 && memcmp(k->data, "source", 6) == 0) return regexp->source;
436
- if (k->length == 5 && memcmp(k->data, "flags", 5) == 0) return regexp->flags;
437
- if (k->length == 9 && memcmp(k->data, "lastIndex", 9) == 0) return xt_number(0);
438
- if (k->length == 6 && memcmp(k->data, "global", 6) == 0) flag = 'g';
439
- else if (k->length == 10 && memcmp(k->data, "ignoreCase", 10) == 0) flag = 'i';
440
- else if (k->length == 9 && memcmp(k->data, "multiline", 9) == 0) flag = 'm';
441
- else if (k->length == 6 && memcmp(k->data, "sticky", 6) == 0) flag = 'y';
447
+ if (k->length == 6 && memcmp(keyBytes, "source", 6) == 0) return regexp->source;
448
+ if (k->length == 5 && memcmp(keyBytes, "flags", 5) == 0) return regexp->flags;
449
+ if (k->length == 9 && memcmp(keyBytes, "lastIndex", 9) == 0) return xt_number(0);
450
+ if (k->length == 6 && memcmp(keyBytes, "global", 6) == 0) flag = 'g';
451
+ else if (k->length == 10 && memcmp(keyBytes, "ignoreCase", 10) == 0) flag = 'i';
452
+ else if (k->length == 9 && memcmp(keyBytes, "multiline", 9) == 0) flag = 'm';
453
+ else if (k->length == 6 && memcmp(keyBytes, "sticky", 6) == 0) flag = 'y';
442
454
  if (flag) {
443
- if (f) for (uint32_t i = 0; i < f->length; i++) if (f->data[i] == flag) return XT_TRUE;
455
+ if (f) {
456
+ const char *flags = xt_string_bytes(f);
457
+ for (uint32_t i = 0; i < f->length; i++) if (flags[i] == flag) return XT_TRUE;
458
+ }
444
459
  return XT_FALSE;
445
460
  }
446
461
  return XT_UNDEFINED;
@@ -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,42 +1,49 @@
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
- if (target <= (int32_t)s->length) return value;
8
- xt_string *pad = xt_as_string(xt_to_string(padValue == XT_UNDEFINED ? xt_string_from_cstr(" ") : padValue));
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
- size_t total = (size_t)target;
11
- xt_string *out = xt_string_alloc(total);
12
- size_t needed = total - s->length;
13
- if (start) {
14
- for (size_t i = 0; i < needed; i++) out->data[i] = pad->data[i % pad->length];
15
- memcpy(out->data + needed, s->data, s->length);
16
- } else {
17
- memcpy(out->data, s->data, s->length);
18
- for (size_t i = 0; i < needed; i++) out->data[s->length + i] = pad->data[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;
19
24
  }
20
- return XT_FROM_PTR(XT_TAG_STRING, out);
25
+ return start ? xt_add(padding, value) : xt_add(value, padding);
21
26
  }
22
27
 
23
28
  static xt_value xt_string_trim_side(xt_value value, int start) {
24
29
  xt_string *s = xt_as_string(value);
25
30
  if (!s) return value;
31
+ const char *bytes = xt_string_bytes(s);
26
32
  uint32_t begin = 0;
27
33
  uint32_t end = s->length;
28
- if (start) { while (begin < end && isspace((unsigned char)s->data[begin])) begin++; }
29
- else { while (end > begin && isspace((unsigned char)s->data[end - 1])) end--; }
30
- return xt_string_new(s->data + begin, end - begin);
34
+ if (start) { while (begin < end && isspace((unsigned char)bytes[begin])) begin++; }
35
+ else { while (end > begin && isspace((unsigned char)bytes[end - 1])) end--; }
36
+ return xt_string_new(bytes + begin, end - begin);
31
37
  }
32
38
 
33
39
  static xt_value xt_string_at(xt_value value, xt_value indexValue) {
34
40
  xt_string *s = xt_as_string(value);
35
41
  if (!s) return XT_UNDEFINED;
42
+ int32_t units = (int32_t)xt_string_utf16_length(s);
36
43
  int32_t index = indexValue == XT_UNDEFINED ? 0 : xt_to_int32(indexValue);
37
- if (index < 0) index += (int32_t)s->length;
38
- if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
39
- return xt_string_new(s->data + index, 1);
44
+ if (index < 0) index += units;
45
+ if (index < 0 || index >= units) return XT_UNDEFINED;
46
+ return xt_string_unit_value(s, index);
40
47
  }
41
48
 
42
49
  static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_value replacementValue) {
@@ -45,21 +52,35 @@ static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_v
45
52
  if (!s) return value;
46
53
  xt_string *search = xt_as_string(xt_to_string(searchValue));
47
54
  xt_string *replacement = xt_as_string(xt_to_string(replacementValue));
48
- if (!search || search->length == 0) return value;
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
+ }
67
+ const char *text = xt_string_bytes(s);
68
+ const char *needle = xt_string_bytes(search);
69
+ const char *repl = xt_string_bytes(replacement);
49
70
  size_t capacity = s->length + replacement->length * 4 + 16;
50
71
  char *out = (char *)malloc(capacity);
51
72
  if (!out) abort();
52
73
  size_t length = 0;
53
74
  uint32_t i = 0;
54
75
  while (i < s->length) {
55
- if (i + search->length <= s->length && memcmp(s->data + i, search->data, search->length) == 0) {
76
+ if (i + search->length <= s->length && memcmp(text + i, needle, search->length) == 0) {
56
77
  if (length + replacement->length + 2 > capacity) { capacity = (length + replacement->length + 2) * 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
57
- memcpy(out + length, replacement->data, replacement->length);
78
+ memcpy(out + length, repl, replacement->length);
58
79
  length += replacement->length;
59
80
  i += search->length;
60
81
  } else {
61
82
  if (length + 2 > capacity) { capacity *= 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
62
- out[length++] = s->data[i++];
83
+ out[length++] = text[i++];
63
84
  }
64
85
  }
65
86
  xt_value result = xt_string_new(out, length);
@@ -67,6 +88,33 @@ static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_v
67
88
  return result;
68
89
  }
69
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
+
70
118
  xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc, xt_value *argv, int *handled) {
71
119
  *handled = 1;
72
120
  if (strcmp(method, "at") == 0) return xt_string_at(target, xt_arg_at(argc, argv, 0));
@@ -86,45 +134,36 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
86
134
  xt_value regexp = xt_arg_at(argc, argv, 0);
87
135
  if (xt_is_regexp(regexp)) {
88
136
  int32_t start = 0;
89
- if (xt_regexp_find(regexp, target, 0, &start, NULL)) return xt_number((double)start);
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
+ }
90
141
  return xt_number(-1);
91
142
  }
92
143
  *handled = 0;
93
144
  return XT_UNDEFINED;
94
145
  }
95
- if (strcmp(method, "replaceAll") == 0) {
96
- xt_value searchValue = xt_arg_at(argc, argv, 0);
97
- if (xt_is_regexp(searchValue)) return xt_regexp_replace(target, searchValue, xt_arg_at(argc, argv, 1));
98
- *handled = 0;
99
- return XT_UNDEFINED;
100
- }
101
146
  if (strcmp(method, "lastIndexOf") == 0) {
102
147
  xt_string *s = xt_as_string(target);
103
148
  xt_string *needle = xt_as_string(xt_to_string(xt_arg_at(argc, argv, 0)));
104
149
  if (!s || !needle) return xt_number(-1);
105
- int32_t limit = (argc >= 2 && argv[1] != XT_UNDEFINED) ? xt_to_int32(argv[1]) : (int32_t)s->length;
106
- if (limit > (int32_t)s->length) limit = (int32_t)s->length;
107
- int32_t nlen = (int32_t)needle->length;
108
- if (nlen == 0) return xt_number((double)(limit < 0 ? 0 : limit));
109
- int32_t i = limit;
110
- if (i > (int32_t)s->length - nlen) i = (int32_t)s->length - nlen;
111
- for (; i >= 0; i--) {
112
- if (memcmp(s->data + i, needle->data, (size_t)nlen) == 0) return xt_number((double)i);
113
- }
114
- 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);
115
153
  }
116
154
  if (strcmp(method, "localeCompare") == 0) {
117
155
  xt_string *a = xt_as_string(target);
118
156
  xt_string *b = xt_as_string(xt_to_string(xt_arg_at(argc, argv, 0)));
119
- int cmp = strcmp(a ? a->data : "", b ? b->data : "");
157
+ int cmp = strcmp(a ? xt_string_bytes(a) : "", b ? xt_string_bytes(b) : "");
120
158
  return xt_number(cmp < 0 ? -1 : cmp > 0 ? 1 : 0);
121
159
  }
122
160
  if (strcmp(method, "codePointAt") == 0) {
123
161
  xt_string *s = xt_as_string(target);
124
162
  if (!s) return XT_UNDEFINED;
125
163
  int32_t index = xt_to_int32(xt_arg_at(argc, argv, 0));
126
- if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
127
- return xt_number((double)(unsigned char)s->data[index]);
164
+ int32_t code = xt_string_code_point_at(s, index);
165
+ if (code < 0) return XT_UNDEFINED;
166
+ return xt_number((double)code);
128
167
  }
129
168
  if (strcmp(method, "valueOf") == 0 || strcmp(method, "toString") == 0) return target;
130
169
  *handled = 0;
@@ -98,7 +98,7 @@ xt_value xt_symbol_to_string(xt_value value) {
98
98
  char *buffer = (char *)malloc(8 + length);
99
99
  if (!buffer) abort();
100
100
  memcpy(buffer, "Symbol(", 7);
101
- if (description) memcpy(buffer + 7, description->data, length);
101
+ if (description) memcpy(buffer + 7, xt_string_bytes(description), length);
102
102
  buffer[7 + length] = ')';
103
103
  xt_value result = xt_string_new(buffer, 8 + length);
104
104
  free(buffer);