xbintsc 0.3.79 → 0.3.87

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 (201) hide show
  1. package/README.md +13 -5
  2. package/README.zh-CN.md +13 -6
  3. package/dist/src/ast/common.d.ts +23 -0
  4. package/dist/src/cli/main.js +68 -24
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/invocation.js +5 -1
  7. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  8. package/dist/src/codegen/generator/context.d.ts +50 -0
  9. package/dist/src/codegen/generator/context.js +124 -13
  10. package/dist/src/codegen/generator/context.js.map +1 -1
  11. package/dist/src/codegen/generator/debug.d.ts +33 -0
  12. package/dist/src/codegen/generator/debug.js +97 -0
  13. package/dist/src/codegen/generator/debug.js.map +1 -0
  14. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  15. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  16. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  17. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  18. package/dist/src/codegen/generator/expressions/primary.js +5 -2
  19. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  20. package/dist/src/codegen/generator/generator.d.ts +3 -1
  21. package/dist/src/codegen/generator/generator.js +3 -1
  22. package/dist/src/codegen/generator/generator.js.map +1 -1
  23. package/dist/src/codegen/generator/module.d.ts +1 -0
  24. package/dist/src/codegen/generator/module.js +76 -6
  25. package/dist/src/codegen/generator/module.js.map +1 -1
  26. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  27. package/dist/src/codegen/generator/numeric.js +127 -0
  28. package/dist/src/codegen/generator/numeric.js.map +1 -0
  29. package/dist/src/codegen/generator/state.d.ts +17 -0
  30. package/dist/src/codegen/generator/state.js +5 -0
  31. package/dist/src/codegen/generator/state.js.map +1 -1
  32. package/dist/src/codegen/generator/statements/control-flow.js +1 -1
  33. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  34. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  35. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  36. package/dist/src/codegen/generator/statements/variables.js +8 -2
  37. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  38. package/dist/src/codegen/generator/tables.d.ts +7 -0
  39. package/dist/src/codegen/generator/tables.js +25 -0
  40. package/dist/src/codegen/generator/tables.js.map +1 -1
  41. package/dist/src/driver/bundler/merge.js +41 -5
  42. package/dist/src/driver/bundler/merge.js.map +1 -1
  43. package/dist/src/driver/compiler.d.ts +22 -3
  44. package/dist/src/driver/compiler.js +49 -58
  45. package/dist/src/driver/compiler.js.map +1 -1
  46. package/dist/src/driver/fingerprint.d.ts +27 -0
  47. package/dist/src/driver/fingerprint.js +88 -0
  48. package/dist/src/driver/fingerprint.js.map +1 -0
  49. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  50. package/dist/src/driver/toolchain-provider.js +15 -15
  51. package/dist/src/driver/toolchain-provider.js.map +1 -1
  52. package/dist/src/driver/toolchain.d.ts +58 -1
  53. package/dist/src/driver/toolchain.js +169 -22
  54. package/dist/src/driver/toolchain.js.map +1 -1
  55. package/dist/src/extensions/gui/index.js +10 -2
  56. package/dist/src/extensions/gui/index.js.map +1 -1
  57. package/dist/src/parser/expressions.js +20 -1
  58. package/dist/src/parser/expressions.js.map +1 -1
  59. package/dist/tests/cli/main.test.js +41 -1
  60. package/dist/tests/cli/main.test.js.map +1 -1
  61. package/dist/tests/codegen/expressions.test.js +14 -3
  62. package/dist/tests/codegen/expressions.test.js.map +1 -1
  63. package/dist/tests/codegen/llvm.test.js +23 -0
  64. package/dist/tests/codegen/llvm.test.js.map +1 -1
  65. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  66. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  67. package/dist/tests/driver/toolchain.test.js +62 -3
  68. package/dist/tests/driver/toolchain.test.js.map +1 -1
  69. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  70. package/dist/tests/e2e/child-process.test.js +59 -0
  71. package/dist/tests/e2e/child-process.test.js.map +1 -0
  72. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  73. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  74. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  75. package/dist/tests/e2e/error-stack.test.js +94 -0
  76. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  77. package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
  78. package/dist/tests/e2e/gui-example-svg.test.js +105 -0
  79. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
  80. package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
  81. package/dist/tests/e2e/gui-transform.test.js +198 -0
  82. package/dist/tests/e2e/gui-transform.test.js.map +1 -0
  83. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  84. package/dist/tests/e2e/string-concat.test.js +102 -0
  85. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  86. package/dist/tests/helpers.d.ts +9 -0
  87. package/dist/tests/helpers.js +26 -3
  88. package/dist/tests/helpers.js.map +1 -1
  89. package/dist/tests/parser/expressions.test.js +7 -0
  90. package/dist/tests/parser/expressions.test.js.map +1 -1
  91. package/dist/tests/version.test.d.ts +1 -0
  92. package/dist/tests/version.test.js +24 -0
  93. package/dist/tests/version.test.js.map +1 -0
  94. package/doc/DESIGN.md +20 -12
  95. package/doc/ai/cli.md +7 -0
  96. package/doc/ai/contributing.md +7 -1
  97. package/doc/ai/extensions.md +5 -1
  98. package/doc/ai/language-support.md +4 -4
  99. package/doc/ai/troubleshooting.md +39 -2
  100. package/doc/ai/zh-CN/cli.md +2 -0
  101. package/doc/ai/zh-CN/contributing.md +6 -2
  102. package/doc/ai/zh-CN/extensions.md +5 -1
  103. package/doc/ai/zh-CN/language-support.md +4 -4
  104. package/doc/ai/zh-CN/troubleshooting.md +35 -2
  105. package/doc/gui/animation.md +53 -2
  106. package/doc/gui/api.md +1 -1
  107. package/doc/gui/dom.md +6 -1
  108. package/doc/gui/input.md +4 -1
  109. package/doc/gui/known-issues.md +16 -4
  110. package/doc/gui/layout.md +36 -0
  111. package/doc/gui/rendering.md +15 -0
  112. package/doc/gui.md +32 -3
  113. package/doc/implemented.md +4 -4
  114. package/doc/requirements.md +13 -4
  115. package/doc/unimplemented.md +11 -10
  116. package/doc/zh-CN/DESIGN.md +15 -9
  117. package/doc/zh-CN/gui/animation.md +49 -2
  118. package/doc/zh-CN/gui/api.md +1 -1
  119. package/doc/zh-CN/gui/dom.md +6 -1
  120. package/doc/zh-CN/gui/input.md +2 -0
  121. package/doc/zh-CN/gui/known-issues.md +12 -2
  122. package/doc/zh-CN/gui/layout.md +31 -0
  123. package/doc/zh-CN/gui/rendering.md +11 -0
  124. package/doc/zh-CN/gui.md +28 -3
  125. package/doc/zh-CN/implemented.md +4 -4
  126. package/doc/zh-CN/requirements.md +12 -3
  127. package/doc/zh-CN/unimplemented.md +10 -9
  128. package/package.json +3 -1
  129. package/runtime/ext_gui/document.cpp +21 -3
  130. package/runtime/ext_gui/document.h +4 -1
  131. package/runtime/ext_gui/dom_api_node.cpp +6 -4
  132. package/runtime/ext_gui/layout.cpp +78 -1
  133. package/runtime/ext_gui/layout.h +21 -0
  134. package/runtime/ext_gui/paint.cpp +48 -15
  135. package/runtime/ext_gui/paint.h +8 -2
  136. package/runtime/ext_gui/renderer.cpp +4 -2
  137. package/runtime/ext_gui/renderer_image.cpp +2 -1
  138. package/runtime/ext_gui/renderer_internal.h +6 -1
  139. package/runtime/ext_gui/renderer_text.cpp +37 -15
  140. package/runtime/ext_gui/style.h +10 -0
  141. package/runtime/ext_gui/style_print.cpp +6 -0
  142. package/runtime/ext_gui/style_values.cpp +43 -0
  143. package/runtime/ext_gui/transform.cpp +412 -0
  144. package/runtime/ext_gui/transform.h +112 -0
  145. package/runtime/ext_gui/window.cpp +6 -4
  146. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  147. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  148. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  149. package/runtime/ext_node/child_process/child_process.c +25 -0
  150. package/runtime/ext_node/fs/fd_ops.c +1 -1
  151. package/runtime/rt.h +17 -1
  152. package/runtime/rt_internal.h +47 -4
  153. package/runtime/xt_alloc.c +14 -2
  154. package/runtime/xt_bigint/conversions.inc +1 -1
  155. package/runtime/xt_builtins.c +136 -14
  156. package/runtime/xt_containers/access.inc +17 -16
  157. package/runtime/xt_containers/objects.inc +4 -3
  158. package/runtime/xt_io/exception.inc +171 -2
  159. package/runtime/xt_io/inspect.inc +15 -13
  160. package/runtime/xt_io.c +5 -0
  161. package/runtime/xt_stdlib/arrays.inc +2 -2
  162. package/runtime/xt_stdlib/dispatch.inc +2 -2
  163. package/runtime/xt_stdlib/strings.inc +33 -21
  164. package/runtime/xt_stdlib2/array-statics.inc +2 -1
  165. package/runtime/xt_stdlib2/arrays.inc +1 -1
  166. package/runtime/xt_stdlib2/error.inc +22 -6
  167. package/runtime/xt_stdlib2/json.inc +3 -2
  168. package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
  169. package/runtime/xt_stdlib2/regexp-match.inc +48 -33
  170. package/runtime/xt_stdlib2/strings.inc +23 -14
  171. package/runtime/xt_symbol.c +1 -1
  172. package/runtime/xt_values/comparison.inc +2 -2
  173. package/runtime/xt_values/constructors.inc +10 -5
  174. package/runtime/xt_values/conversions.inc +4 -3
  175. package/runtime/xt_values/strings.inc +78 -4
  176. package/scripts/bench.ts +172 -0
  177. package/scripts/build-runtime.ts +2 -1
  178. package/scripts/check-version.ts +53 -0
  179. package/src/ast/common.ts +23 -0
  180. package/src/cli/main.ts +68 -26
  181. package/src/codegen/generator/calls/invocation.ts +5 -0
  182. package/src/codegen/generator/context.ts +129 -18
  183. package/src/codegen/generator/debug.ts +132 -0
  184. package/src/codegen/generator/expressions/assignment.ts +9 -5
  185. package/src/codegen/generator/expressions/operators.ts +47 -8
  186. package/src/codegen/generator/expressions/primary.ts +5 -2
  187. package/src/codegen/generator/generator.ts +18 -2
  188. package/src/codegen/generator/module.ts +78 -6
  189. package/src/codegen/generator/numeric.ts +158 -0
  190. package/src/codegen/generator/state.ts +22 -0
  191. package/src/codegen/generator/statements/control-flow.ts +1 -1
  192. package/src/codegen/generator/statements/dispatch.ts +3 -0
  193. package/src/codegen/generator/statements/variables.ts +8 -2
  194. package/src/codegen/generator/tables.ts +26 -0
  195. package/src/driver/bundler/merge.ts +39 -5
  196. package/src/driver/compiler.ts +68 -56
  197. package/src/driver/fingerprint.ts +85 -0
  198. package/src/driver/toolchain-provider.ts +17 -16
  199. package/src/driver/toolchain.ts +210 -21
  200. package/src/extensions/gui/index.ts +10 -2
  201. package/src/parser/expressions.ts +20 -1
@@ -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,13 +191,13 @@ 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
  }
@@ -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,18 +334,18 @@ 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
  }
@@ -345,7 +355,7 @@ xt_value xt_regexp_replace(xt_value value, xt_value regexpValue, xt_value replac
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;
@@ -9,13 +9,16 @@ static xt_value xt_string_pad(xt_value value, xt_value lengthValue, xt_value pad
9
9
  if (!pad || pad->length == 0) return value;
10
10
  size_t total = (size_t)target;
11
11
  xt_string *out = xt_string_alloc(total);
12
+ char *mutable = xt_string_mutable(out);
13
+ const char *text = xt_string_bytes(s);
14
+ const char *padBytes = xt_string_bytes(pad);
12
15
  size_t needed = total - s->length;
13
16
  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);
17
+ for (size_t i = 0; i < needed; i++) mutable[i] = padBytes[i % pad->length];
18
+ memcpy(mutable + needed, text, s->length);
16
19
  } 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];
20
+ memcpy(mutable, text, s->length);
21
+ for (size_t i = 0; i < needed; i++) mutable[s->length + i] = padBytes[i % pad->length];
19
22
  }
20
23
  return XT_FROM_PTR(XT_TAG_STRING, out);
21
24
  }
@@ -23,11 +26,12 @@ static xt_value xt_string_pad(xt_value value, xt_value lengthValue, xt_value pad
23
26
  static xt_value xt_string_trim_side(xt_value value, int start) {
24
27
  xt_string *s = xt_as_string(value);
25
28
  if (!s) return value;
29
+ const char *bytes = xt_string_bytes(s);
26
30
  uint32_t begin = 0;
27
31
  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);
32
+ if (start) { while (begin < end && isspace((unsigned char)bytes[begin])) begin++; }
33
+ else { while (end > begin && isspace((unsigned char)bytes[end - 1])) end--; }
34
+ return xt_string_new(bytes + begin, end - begin);
31
35
  }
32
36
 
33
37
  static xt_value xt_string_at(xt_value value, xt_value indexValue) {
@@ -36,7 +40,7 @@ static xt_value xt_string_at(xt_value value, xt_value indexValue) {
36
40
  int32_t index = indexValue == XT_UNDEFINED ? 0 : xt_to_int32(indexValue);
37
41
  if (index < 0) index += (int32_t)s->length;
38
42
  if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
39
- return xt_string_new(s->data + index, 1);
43
+ return xt_string_new(xt_string_bytes(s) + index, 1);
40
44
  }
41
45
 
42
46
  static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_value replacementValue) {
@@ -46,20 +50,23 @@ static xt_value xt_string_replace_all(xt_value value, xt_value searchValue, xt_v
46
50
  xt_string *search = xt_as_string(xt_to_string(searchValue));
47
51
  xt_string *replacement = xt_as_string(xt_to_string(replacementValue));
48
52
  if (!search || search->length == 0) return value;
53
+ const char *text = xt_string_bytes(s);
54
+ const char *needle = xt_string_bytes(search);
55
+ const char *repl = xt_string_bytes(replacement);
49
56
  size_t capacity = s->length + replacement->length * 4 + 16;
50
57
  char *out = (char *)malloc(capacity);
51
58
  if (!out) abort();
52
59
  size_t length = 0;
53
60
  uint32_t i = 0;
54
61
  while (i < s->length) {
55
- if (i + search->length <= s->length && memcmp(s->data + i, search->data, search->length) == 0) {
62
+ if (i + search->length <= s->length && memcmp(text + i, needle, search->length) == 0) {
56
63
  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);
64
+ memcpy(out + length, repl, replacement->length);
58
65
  length += replacement->length;
59
66
  i += search->length;
60
67
  } else {
61
68
  if (length + 2 > capacity) { capacity *= 2; out = (char *)realloc(out, capacity); if (!out) abort(); }
62
- out[length++] = s->data[i++];
69
+ out[length++] = text[i++];
63
70
  }
64
71
  }
65
72
  xt_value result = xt_string_new(out, length);
@@ -106,17 +113,19 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
106
113
  if (limit > (int32_t)s->length) limit = (int32_t)s->length;
107
114
  int32_t nlen = (int32_t)needle->length;
108
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);
109
118
  int32_t i = limit;
110
119
  if (i > (int32_t)s->length - nlen) i = (int32_t)s->length - nlen;
111
120
  for (; i >= 0; i--) {
112
- if (memcmp(s->data + i, needle->data, (size_t)nlen) == 0) return xt_number((double)i);
121
+ if (memcmp(text + i, need, (size_t)nlen) == 0) return xt_number((double)i);
113
122
  }
114
123
  return xt_number(-1);
115
124
  }
116
125
  if (strcmp(method, "localeCompare") == 0) {
117
126
  xt_string *a = xt_as_string(target);
118
127
  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 : "");
128
+ int cmp = strcmp(a ? xt_string_bytes(a) : "", b ? xt_string_bytes(b) : "");
120
129
  return xt_number(cmp < 0 ? -1 : cmp > 0 ? 1 : 0);
121
130
  }
122
131
  if (strcmp(method, "codePointAt") == 0) {
@@ -124,7 +133,7 @@ xt_value xt_ext_string_method(xt_value target, const char *method, int32_t argc,
124
133
  if (!s) return XT_UNDEFINED;
125
134
  int32_t index = xt_to_int32(xt_arg_at(argc, argv, 0));
126
135
  if (index < 0 || (uint32_t)index >= s->length) return XT_UNDEFINED;
127
- return xt_number((double)(unsigned char)s->data[index]);
136
+ return xt_number((double)(unsigned char)xt_string_bytes(s)[index]);
128
137
  }
129
138
  if (strcmp(method, "valueOf") == 0 || strcmp(method, "toString") == 0) return target;
130
139
  *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);
@@ -33,7 +33,7 @@ xt_value xt_lt(xt_value a, xt_value b) {
33
33
  xt_string *x = xt_as_string(a);
34
34
  xt_string *y = xt_as_string(b);
35
35
  size_t n = x->length < y->length ? x->length : y->length;
36
- int cmp = memcmp(x->data, y->data, n);
36
+ int cmp = memcmp(xt_string_bytes(x), xt_string_bytes(y), n);
37
37
  if (cmp == 0) return xt_bool(x->length < y->length);
38
38
  return xt_bool(cmp < 0);
39
39
  }
@@ -48,7 +48,7 @@ xt_value xt_le(xt_value a, xt_value b) {
48
48
  xt_string *x = xt_as_string(a);
49
49
  xt_string *y = xt_as_string(b);
50
50
  size_t n = x->length < y->length ? x->length : y->length;
51
- int cmp = memcmp(x->data, y->data, n);
51
+ int cmp = memcmp(xt_string_bytes(x), xt_string_bytes(y), n);
52
52
  if (cmp == 0) return xt_bool(x->length <= y->length);
53
53
  return xt_bool(cmp < 0);
54
54
  }
@@ -10,19 +10,22 @@ xt_value xt_number(double d) { return xt_from_double(d); }
10
10
  xt_string *xt_string_alloc(size_t length) {
11
11
  xt_string *s = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
12
12
  s->length = (uint32_t)length;
13
+ /* Keep the terminator in the allocation: many consumers hand `flat` to libc. */
13
14
  s->capacity = (uint32_t)length + 1;
14
- s->data = (char *)malloc(s->capacity);
15
- if (!s->data) {
15
+ s->flat = (char *)malloc(s->capacity);
16
+ if (!s->flat) {
16
17
  fprintf(stderr, "xbintsc: out of memory allocating string\n");
17
18
  abort();
18
19
  }
19
- s->data[length] = '\0';
20
+ s->flat[length] = '\0';
21
+ s->left = NULL;
22
+ s->right = NULL;
20
23
  return s;
21
24
  }
22
25
 
23
26
  xt_value xt_string_new(const char *data, size_t len) {
24
27
  xt_string *s = xt_string_alloc(len);
25
- if (len > 0) memcpy(s->data, data, len);
28
+ if (len > 0) memcpy(s->flat, data, len);
26
29
  return XT_FROM_PTR(XT_TAG_STRING, s);
27
30
  }
28
31
 
@@ -30,9 +33,11 @@ xt_value xt_string_from_cstr(const char *data) { return xt_string_new(data, strl
30
33
 
31
34
  xt_string *xt_as_string(xt_value v);
32
35
 
36
+ char *xt_string_mutable(xt_string *s) { return s ? s->flat : NULL; }
37
+
33
38
  const char *xt_string_data(xt_value value) {
34
39
  xt_string *s = xt_as_string(value);
35
- return s ? s->data : NULL;
40
+ return s ? xt_string_bytes(s) : NULL;
36
41
  }
37
42
 
38
43
  int32_t xt_string_length_value(xt_value value) {
@@ -24,8 +24,9 @@ double xt_to_number(xt_value v) {
24
24
  if (XT_IS_BIGINT(v)) return xt_bigint_to_double_value(v);
25
25
  if (XT_IS_STRING(v)) {
26
26
  xt_string *s = xt_as_string(v);
27
- const char *begin = s->data;
28
- const char *end = s->data + s->length;
27
+ const char *bytes = xt_string_bytes(s);
28
+ const char *begin = bytes;
29
+ const char *end = bytes + s->length;
29
30
  while (begin < end && isspace((unsigned char)*begin)) begin++;
30
31
  while (end > begin && isspace((unsigned char)end[-1])) end--;
31
32
  size_t length = (size_t)(end - begin);
@@ -83,7 +84,7 @@ xt_value xt_to_string(xt_value v) {
83
84
  out = (char *)realloc(out, capacity);
84
85
  }
85
86
  if (i > 0) out[length++] = ',';
86
- memcpy(out + length, str->data, str->length);
87
+ memcpy(out + length, xt_string_bytes(str), str->length);
87
88
  length += str->length;
88
89
  out[length] = '\0';
89
90
  }
@@ -2,6 +2,12 @@
2
2
  /* Strings */
3
3
  /* ------------------------------------------------------------------------- */
4
4
 
5
+ /* Concatenations up to this many bytes are copied eagerly. The copy is cheaper
6
+ * than a cons node for the short strings that dominate everyday code, and a
7
+ * loop that keeps appending crosses the threshold quickly, after which every
8
+ * append is O(1). */
9
+ #define XT_STRING_CONS_THRESHOLD 32
10
+
5
11
  xt_string *xt_as_string(xt_value v) {
6
12
  if (XT_IS_STRING(v)) return (xt_string *)XT_GET_PTR(v);
7
13
  return NULL;
@@ -9,14 +15,82 @@ xt_string *xt_as_string(xt_value v) {
9
15
 
10
16
  int32_t xt_string_length(xt_string *s) { return (int32_t)s->length; }
11
17
 
18
+ const char *xt_string_bytes(xt_string *s) {
19
+ if (!s) return NULL;
20
+ if (s->flat) return s->flat;
21
+
22
+ /* The tree is traversed depth-first without recursion: a loop that appends
23
+ * 100k times produces a 100k-deep left spine, which would overflow the C
24
+ * stack. Right children are pushed so the leftmost leaf is emitted first. */
25
+ size_t total = (size_t)s->length;
26
+ char *out = (char *)malloc(total + 1);
27
+ if (!out) {
28
+ fprintf(stderr, "xbintsc: out of memory flattening string\n");
29
+ abort();
30
+ }
31
+
32
+ size_t capacity = 64;
33
+ size_t count = 0;
34
+ xt_string **stack = (xt_string **)malloc(sizeof(xt_string *) * capacity);
35
+ if (!stack) {
36
+ fprintf(stderr, "xbintsc: out of memory flattening string\n");
37
+ abort();
38
+ }
39
+ stack[count++] = s;
40
+
41
+ size_t offset = 0;
42
+ while (count > 0) {
43
+ xt_string *node = stack[--count];
44
+ if (node->flat) {
45
+ memcpy(out + offset, node->flat, node->length);
46
+ offset += node->length;
47
+ continue;
48
+ }
49
+ if (count + 2 > capacity) {
50
+ while (count + 2 > capacity) capacity *= 2;
51
+ xt_string **grown = (xt_string **)realloc(stack, sizeof(xt_string *) * capacity);
52
+ if (!grown) {
53
+ fprintf(stderr, "xbintsc: out of memory flattening string\n");
54
+ abort();
55
+ }
56
+ stack = grown;
57
+ }
58
+ if (node->right) stack[count++] = node->right;
59
+ if (node->left) stack[count++] = node->left;
60
+ }
61
+ free(stack);
62
+
63
+ out[offset] = '\0';
64
+ s->flat = out;
65
+ s->capacity = (uint32_t)(total + 1);
66
+ s->left = NULL;
67
+ s->right = NULL;
68
+ return s->flat;
69
+ }
70
+
12
71
  static xt_value xt_string_concat(xt_string *a, xt_string *b) {
72
+ if (a->length == 0) return XT_FROM_PTR(XT_TAG_STRING, b);
73
+ if (b->length == 0) return XT_FROM_PTR(XT_TAG_STRING, a);
13
74
  size_t total = (size_t)a->length + (size_t)b->length;
14
- xt_string *out = xt_string_alloc(total);
15
- memcpy(out->data, a->data, a->length);
16
- memcpy(out->data + a->length, b->data, b->length);
75
+ if (total <= XT_STRING_CONS_THRESHOLD) {
76
+ xt_string *out = xt_string_alloc(total);
77
+ memcpy(out->flat, xt_string_bytes(a), a->length);
78
+ memcpy(out->flat + a->length, xt_string_bytes(b), b->length);
79
+ return XT_FROM_PTR(XT_TAG_STRING, out);
80
+ }
81
+ xt_string *out = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
82
+ out->length = (uint32_t)total;
83
+ out->capacity = 0;
84
+ out->flat = NULL;
85
+ out->left = a;
86
+ out->right = b;
17
87
  return XT_FROM_PTR(XT_TAG_STRING, out);
18
88
  }
19
89
 
20
90
  int xt_string_equals(xt_string *a, xt_string *b) {
21
- return a->length == b->length && memcmp(a->data, b->data, a->length) == 0;
91
+ /* Interned literals (property names above all) are shared, so identity is the
92
+ * common case and skips the length, flattening and byte comparison entirely. */
93
+ if (a == b) return 1;
94
+ if (a->length != b->length) return 0;
95
+ return memcmp(xt_string_bytes(a), xt_string_bytes(b), a->length) == 0;
22
96
  }