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
@@ -43,6 +43,10 @@ static xt_value xt_ta_proto(int kind) {
43
43
  xt_ta_define(proto, "keys", (void *)xt_ta_method_keys);
44
44
  xt_ta_define(proto, "values", (void *)xt_ta_method_values);
45
45
  xt_ta_define(proto, "entries", (void *)xt_ta_method_entries);
46
+ xt_value constructor = xt_builtin_constructor(xt_string_from_cstr(xt_ta_names[kind]));
47
+ if (constructor != XT_UNDEFINED) {
48
+ xt_object_set(proto, xt_string_from_cstr("constructor"), constructor);
49
+ }
46
50
  return proto;
47
51
  }
48
52
 
@@ -64,7 +68,7 @@ static int xt_ta_length_of(xt_value value, uint32_t *out) {
64
68
  return 1;
65
69
  }
66
70
  if (XT_IS_STRING(value)) {
67
- *out = xt_as_string(value)->length;
71
+ *out = xt_string_code_point_count(xt_as_string(value));
68
72
  return 1;
69
73
  }
70
74
  if (xt_typed_array_kind_of(value) >= 0) {
@@ -86,8 +90,10 @@ static xt_value xt_ta_from_value(int kind, xt_value value, xt_value mapFn, xt_va
86
90
  uint32_t length = 0;
87
91
  if (!xt_ta_length_of(value, &length)) length = 0;
88
92
  xt_value array = xt_ta_new(kind, length);
93
+ xt_string *sourceString = XT_IS_STRING(value) ? xt_as_string(value) : NULL;
89
94
  for (uint32_t i = 0; i < length; i++) {
90
- xt_value item = xt_get(value, xt_number((double)i));
95
+ xt_value item = sourceString ? xt_string_code_point_value(sourceString, (int32_t)i)
96
+ : xt_get(value, xt_number((double)i));
91
97
  if (haveMap) {
92
98
  xt_value args[3] = {item, xt_number((double)i), array};
93
99
  item = xt_call_with_this(mapFn, thisArg, 3, args);
@@ -140,3 +146,38 @@ XT_TA_CTOR(i32, XT_TA_I32)
140
146
  XT_TA_CTOR(f32, XT_TA_F32)
141
147
  XT_TA_CTOR(f64, XT_TA_F64)
142
148
 
149
+ /* Dispatch by constructor name, used when a typed-array constructor is a
150
+ * first-class value (`const T = Uint8Array; new T(4)`). */
151
+ static int xt_ta_kind_from_name(const char *name) {
152
+ if (!name) return -1;
153
+ if (strcmp(name, "Uint8Array") == 0) return XT_TA_U8;
154
+ if (strcmp(name, "Int8Array") == 0) return XT_TA_I8;
155
+ if (strcmp(name, "Uint8ClampedArray") == 0) return XT_TA_U8C;
156
+ if (strcmp(name, "Uint16Array") == 0) return XT_TA_U16;
157
+ if (strcmp(name, "Int16Array") == 0) return XT_TA_I16;
158
+ if (strcmp(name, "Uint32Array") == 0) return XT_TA_U32;
159
+ if (strcmp(name, "Int32Array") == 0) return XT_TA_I32;
160
+ if (strcmp(name, "Float32Array") == 0) return XT_TA_F32;
161
+ if (strcmp(name, "Float64Array") == 0) return XT_TA_F64;
162
+ return -1;
163
+ }
164
+
165
+ xt_value xt_typed_array_ctor_by_name(xt_value nameValue, int32_t argc, xt_value *argv) {
166
+ int kind = xt_ta_kind_from_name(XT_IS_STRING(nameValue) ? xt_string_data(nameValue) : NULL);
167
+ if (kind < 0) return XT_UNDEFINED;
168
+ return xt_ta_ctor_impl(kind, argc, argv);
169
+ }
170
+
171
+ xt_value xt_typed_array_static_by_name(xt_value nameValue, xt_value method, int32_t argc,
172
+ xt_value *argv) {
173
+ int kind = xt_ta_kind_from_name(XT_IS_STRING(nameValue) ? xt_string_data(nameValue) : NULL);
174
+ if (kind < 0) return XT_UNDEFINED;
175
+ return xt_ta_static_impl(kind, method, argc, argv);
176
+ }
177
+
178
+ xt_value xt_typed_array_prototype(xt_value nameValue) {
179
+ int kind = xt_ta_kind_from_name(XT_IS_STRING(nameValue) ? xt_string_data(nameValue) : NULL);
180
+ if (kind < 0) return XT_UNDEFINED;
181
+ return xt_ta_proto(kind);
182
+ }
183
+
@@ -34,6 +34,10 @@ enum {
34
34
  };
35
35
 
36
36
  static const int xt_ta_bytes[XT_TA_KIND_COUNT] = {1, 1, 1, 2, 2, 4, 4, 4, 8};
37
+ static const char *const xt_ta_names[XT_TA_KIND_COUNT] = {
38
+ "Uint8Array", "Int8Array", "Uint8ClampedArray", "Uint16Array", "Int16Array",
39
+ "Uint32Array", "Int32Array", "Float32Array", "Float64Array",
40
+ };
37
41
 
38
42
  static xt_value xt_ta_protos[XT_TA_KIND_COUNT];
39
43
 
@@ -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,25 @@ 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
+ s->utf16_length = 0;
14
+ s->code_point_count = 0;
15
+ /* Keep the terminator in the allocation: many consumers hand `flat` to libc. */
13
16
  s->capacity = (uint32_t)length + 1;
14
- s->data = (char *)malloc(s->capacity);
15
- if (!s->data) {
17
+ s->flat = (char *)malloc(s->capacity);
18
+ if (!s->flat) {
16
19
  fprintf(stderr, "xbintsc: out of memory allocating string\n");
17
20
  abort();
18
21
  }
19
- s->data[length] = '\0';
22
+ s->flat[length] = '\0';
23
+ s->left = NULL;
24
+ s->right = NULL;
20
25
  return s;
21
26
  }
22
27
 
23
28
  xt_value xt_string_new(const char *data, size_t len) {
24
29
  xt_string *s = xt_string_alloc(len);
25
- if (len > 0) memcpy(s->data, data, len);
30
+ if (len > 0) memcpy(s->flat, data, len);
31
+ xt_string_finish(s);
26
32
  return XT_FROM_PTR(XT_TAG_STRING, s);
27
33
  }
28
34
 
@@ -30,12 +36,19 @@ xt_value xt_string_from_cstr(const char *data) { return xt_string_new(data, strl
30
36
 
31
37
  xt_string *xt_as_string(xt_value v);
32
38
 
39
+ char *xt_string_mutable(xt_string *s) { return s ? s->flat : NULL; }
40
+
33
41
  const char *xt_string_data(xt_value value) {
34
42
  xt_string *s = xt_as_string(value);
35
- return s ? s->data : NULL;
43
+ return s ? xt_string_bytes(s) : NULL;
36
44
  }
37
45
 
38
46
  int32_t xt_string_length_value(xt_value value) {
39
47
  xt_string *s = xt_as_string(value);
40
48
  return s ? (int32_t)s->length : -1;
41
49
  }
50
+
51
+ int32_t xt_string_utf16_length_value(xt_value value) {
52
+ xt_string *s = xt_as_string(value);
53
+ return s ? (int32_t)xt_string_utf16_length(s) : -1;
54
+ }
@@ -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,98 @@ 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->length = (uint32_t)offset;
66
+ s->capacity = (uint32_t)(total + 1);
67
+ s->left = NULL;
68
+ s->right = NULL;
69
+ /* A concat boundary can split a surrogate pair; flattening is where the two
70
+ * halves meet, so normalise and record the UTF-16/code-point view now. */
71
+ xt_string_finish(s);
72
+ return s->flat;
73
+ }
74
+
12
75
  static xt_value xt_string_concat(xt_string *a, xt_string *b) {
76
+ if (a->length == 0) return XT_FROM_PTR(XT_TAG_STRING, b);
77
+ if (b->length == 0) return XT_FROM_PTR(XT_TAG_STRING, a);
13
78
  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);
79
+ if (total <= XT_STRING_CONS_THRESHOLD) {
80
+ xt_string *out = xt_string_alloc(total);
81
+ const char *aBytes = xt_string_bytes(a);
82
+ uint32_t aLength = a->length;
83
+ const char *bBytes = xt_string_bytes(b);
84
+ uint32_t bLength = b->length;
85
+ memcpy(out->flat, aBytes, aLength);
86
+ memcpy(out->flat + aLength, bBytes, bLength);
87
+ xt_string_finish(out);
88
+ return XT_FROM_PTR(XT_TAG_STRING, out);
89
+ }
90
+ xt_string *out = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
91
+ out->length = (uint32_t)total;
92
+ /* UTF-16 length is additive even when the boundary joins a surrogate pair;
93
+ * the code-point count is not, so leave it to be recomputed after
94
+ * flattening. */
95
+ out->utf16_length = xt_string_utf16_length(a) + xt_string_utf16_length(b);
96
+ out->code_point_count = 0;
97
+ out->capacity = 0;
98
+ out->flat = NULL;
99
+ out->left = a;
100
+ out->right = b;
17
101
  return XT_FROM_PTR(XT_TAG_STRING, out);
18
102
  }
19
103
 
20
104
  int xt_string_equals(xt_string *a, xt_string *b) {
21
- return a->length == b->length && memcmp(a->data, b->data, a->length) == 0;
105
+ /* Interned literals (property names above all) are shared, so identity is the
106
+ * common case and skips the length, flattening and byte comparison entirely. */
107
+ if (a == b) return 1;
108
+ const char *aBytes = xt_string_bytes(a);
109
+ const char *bBytes = xt_string_bytes(b);
110
+ if (a->length != b->length) return 0;
111
+ return memcmp(aBytes, bBytes, a->length) == 0;
22
112
  }
@@ -0,0 +1,340 @@
1
+ /* ------------------------------------------------------------------------- */
2
+ /* UTF-8 storage, UTF-16 view */
3
+ /* ------------------------------------------------------------------------- */
4
+ /*
5
+ * Strings are stored as UTF-8, but JavaScript observes them as sequences of
6
+ * UTF-16 code units. The helpers here bridge the two views:
7
+ *
8
+ * - a decoded code point above 0xFFFF occupies two UTF-16 units;
9
+ * - a surrogate half that is not part of a pair (produced by indexing or
10
+ * slicing through a pair) is stored as its own three-byte WTF-8 sequence;
11
+ * - `xt_utf8_normalize_surrogates` recombines adjacent WTF-8 halves so that
12
+ * concatenation and flattening keep the canonical four-byte form.
13
+ *
14
+ * Invalid UTF-8 bytes decode as U+FFFD with width 1, so a C extension that
15
+ * hands over arbitrary bytes can never make the walkers run off the end.
16
+ */
17
+
18
+ uint32_t xt_utf8_decode(const char *data, size_t length, size_t *width) {
19
+ const unsigned char *bytes = (const unsigned char *)data;
20
+ if (length == 0) {
21
+ if (width) *width = 0;
22
+ return 0xFFFD;
23
+ }
24
+ unsigned char first = bytes[0];
25
+ if (first < 0x80) {
26
+ if (width) *width = 1;
27
+ return first;
28
+ }
29
+ if ((first & 0xE0) == 0xC0 && length >= 2 && (bytes[1] & 0xC0) == 0x80) {
30
+ if (width) *width = 2;
31
+ return ((uint32_t)(first & 0x1F) << 6) | (uint32_t)(bytes[1] & 0x3F);
32
+ }
33
+ if ((first & 0xF0) == 0xE0 && length >= 3 && (bytes[1] & 0xC0) == 0x80 &&
34
+ (bytes[2] & 0xC0) == 0x80) {
35
+ if (width) *width = 3;
36
+ return ((uint32_t)(first & 0x0F) << 12) | ((uint32_t)(bytes[1] & 0x3F) << 6) |
37
+ (uint32_t)(bytes[2] & 0x3F);
38
+ }
39
+ if ((first & 0xF8) == 0xF0 && length >= 4 && (bytes[1] & 0xC0) == 0x80 &&
40
+ (bytes[2] & 0xC0) == 0x80 && (bytes[3] & 0xC0) == 0x80) {
41
+ uint32_t code = ((uint32_t)(first & 0x07) << 18) | ((uint32_t)(bytes[1] & 0x3F) << 12) |
42
+ ((uint32_t)(bytes[2] & 0x3F) << 6) | (uint32_t)(bytes[3] & 0x3F);
43
+ if (code >= 0x10000 && code <= 0x10FFFF) {
44
+ if (width) *width = 4;
45
+ return code;
46
+ }
47
+ }
48
+ /* Invalid or overlong sequence: consume one byte, report the replacement. */
49
+ if (width) *width = 1;
50
+ return 0xFFFD;
51
+ }
52
+
53
+ size_t xt_utf8_encode(uint32_t code, char *out) {
54
+ if (code < 0x80) {
55
+ out[0] = (char)code;
56
+ return 1;
57
+ }
58
+ if (code < 0x800) {
59
+ out[0] = (char)(0xC0 | (code >> 6));
60
+ out[1] = (char)(0x80 | (code & 0x3F));
61
+ return 2;
62
+ }
63
+ if (code < 0x10000) {
64
+ out[0] = (char)(0xE0 | (code >> 12));
65
+ out[1] = (char)(0x80 | ((code >> 6) & 0x3F));
66
+ out[2] = (char)(0x80 | (code & 0x3F));
67
+ return 3;
68
+ }
69
+ out[0] = (char)(0xF0 | ((code >> 18) & 0x07));
70
+ out[1] = (char)(0x80 | ((code >> 12) & 0x3F));
71
+ out[2] = (char)(0x80 | ((code >> 6) & 0x3F));
72
+ out[3] = (char)(0x80 | (code & 0x3F));
73
+ return 4;
74
+ }
75
+
76
+ void xt_utf8_measure(const char *data, size_t length, uint32_t *units, uint32_t *points) {
77
+ size_t offset = 0;
78
+ uint32_t unitCount = 0;
79
+ uint32_t pointCount = 0;
80
+ while (offset < length) {
81
+ size_t width = 0;
82
+ uint32_t code = xt_utf8_decode(data + offset, length - offset, &width);
83
+ if (width == 0) break;
84
+ unitCount += code >= 0x10000 ? 2 : 1;
85
+ pointCount++;
86
+ offset += width;
87
+ }
88
+ if (units) *units = unitCount;
89
+ if (points) *points = pointCount;
90
+ }
91
+
92
+ uint32_t xt_utf8_utf16_length(const char *data, size_t length) {
93
+ uint32_t units = 0;
94
+ xt_utf8_measure(data, length, &units, NULL);
95
+ return units;
96
+ }
97
+
98
+ /* Both halves of a surrogate pair are encoded as three-byte WTF-8 sequences:
99
+ * `ED A0..AF xx` for D800-DBFF and `ED B0..BF xx` for DC00-DFFF. */
100
+ static int xt_wtf8_surrogate(const unsigned char *bytes, size_t length, uint32_t *code, int *high) {
101
+ if (length < 3 || bytes[0] != 0xED || bytes[1] < 0xA0 || bytes[1] > 0xBF ||
102
+ bytes[2] < 0x80 || bytes[2] > 0xBF)
103
+ return 0;
104
+ *code = ((uint32_t)(bytes[0] & 0x0F) << 12) | ((uint32_t)(bytes[1] & 0x3F) << 6) |
105
+ (uint32_t)(bytes[2] & 0x3F);
106
+ *high = bytes[1] <= 0xAF;
107
+ return 1;
108
+ }
109
+
110
+ size_t xt_utf8_normalize_surrogates(char *data, size_t length) {
111
+ const unsigned char *source = (const unsigned char *)data;
112
+ unsigned char *write = (unsigned char *)data;
113
+ size_t offset = 0;
114
+ while (offset < length) {
115
+ uint32_t highCode = 0;
116
+ int high = 0;
117
+ if (xt_wtf8_surrogate(source + offset, length - offset, &highCode, &high) && high) {
118
+ uint32_t lowCode = 0;
119
+ int low = 0;
120
+ if (offset + 3 < length &&
121
+ xt_wtf8_surrogate(source + offset + 3, length - offset - 3, &lowCode, &low) && !low) {
122
+ uint32_t code = 0x10000 + ((highCode - 0xD800) << 10) + (lowCode - 0xDC00);
123
+ write += xt_utf8_encode(code, (char *)write);
124
+ offset += 6;
125
+ continue;
126
+ }
127
+ }
128
+ *write++ = source[offset++];
129
+ }
130
+ return (size_t)(write - (unsigned char *)data);
131
+ }
132
+
133
+ void xt_string_finish(xt_string *s) {
134
+ if (!s || !s->flat) return;
135
+ size_t length = xt_utf8_normalize_surrogates(s->flat, s->length);
136
+ s->length = (uint32_t)length;
137
+ s->flat[length] = '\0';
138
+ xt_utf8_measure(s->flat, length, &s->utf16_length, &s->code_point_count);
139
+ }
140
+
141
+ uint32_t xt_string_utf16_length(xt_string *s) {
142
+ if (!s || s->length == 0) return 0;
143
+ if (s->utf16_length) return s->utf16_length;
144
+ if (!s->flat) xt_string_bytes(s);
145
+ else xt_utf8_measure(s->flat, s->length, &s->utf16_length, &s->code_point_count);
146
+ return s->utf16_length;
147
+ }
148
+
149
+ uint32_t xt_string_code_point_count(xt_string *s) {
150
+ if (!s || s->length == 0) return 0;
151
+ if (s->code_point_count) return s->code_point_count;
152
+ if (!s->flat) xt_string_bytes(s);
153
+ else xt_utf8_measure(s->flat, s->length, &s->utf16_length, &s->code_point_count);
154
+ return s->code_point_count;
155
+ }
156
+
157
+ /* A string whose byte and UTF-16 lengths agree contains only ASCII, so the two
158
+ * index spaces coincide and every walker below can skip decoding. */
159
+ static int xt_string_is_ascii(xt_string *s) {
160
+ return s->length == xt_string_utf16_length(s);
161
+ }
162
+
163
+ int32_t xt_string_code_unit(xt_string *s, int32_t index) {
164
+ if (!s || index < 0 || (uint32_t)index >= s->length) return -1;
165
+ if (xt_string_is_ascii(s)) return (int32_t)(unsigned char)xt_string_bytes(s)[index];
166
+ const char *bytes = xt_string_bytes(s);
167
+ size_t offset = 0;
168
+ int32_t unit = 0;
169
+ while (offset < s->length) {
170
+ size_t width = 0;
171
+ uint32_t code = xt_utf8_decode(bytes + offset, s->length - offset, &width);
172
+ if (code >= 0x10000) {
173
+ uint32_t value = code - 0x10000;
174
+ if (unit == index) return (int32_t)(0xD800 + (value >> 10));
175
+ if (unit + 1 == index) return (int32_t)(0xDC00 + (value & 0x3FF));
176
+ unit += 2;
177
+ } else {
178
+ if (unit == index) return (int32_t)code;
179
+ unit += 1;
180
+ }
181
+ offset += width;
182
+ }
183
+ return -1;
184
+ }
185
+
186
+ int32_t xt_string_code_point_at(xt_string *s, int32_t index) {
187
+ if (!s || index < 0) return -1;
188
+ if (xt_string_is_ascii(s)) return index < (int32_t)s->length ? (int32_t)(unsigned char)xt_string_bytes(s)[index] : -1;
189
+ const char *bytes = xt_string_bytes(s);
190
+ size_t offset = 0;
191
+ int32_t unit = 0;
192
+ while (offset < s->length) {
193
+ size_t width = 0;
194
+ uint32_t code = xt_utf8_decode(bytes + offset, s->length - offset, &width);
195
+ int widthUnits = code >= 0x10000 ? 2 : 1;
196
+ if (unit == index) return (int32_t)code;
197
+ if (widthUnits == 2 && unit + 1 == index)
198
+ return (int32_t)(0xDC00 + ((code - 0x10000) & 0x3FF));
199
+ unit += widthUnits;
200
+ offset += width;
201
+ }
202
+ return -1;
203
+ }
204
+
205
+ xt_value xt_string_from_code_point(uint32_t code) {
206
+ char buffer[4];
207
+ size_t length = xt_utf8_encode(code, buffer);
208
+ return xt_string_new(buffer, length);
209
+ }
210
+
211
+ xt_value xt_string_from_units(const uint16_t *units, size_t count) {
212
+ size_t capacity = count * 4 + 1;
213
+ char *out = (char *)malloc(capacity);
214
+ if (!out) abort();
215
+ size_t length = 0;
216
+ for (size_t i = 0; i < count; i++) {
217
+ uint32_t code = units[i];
218
+ if (code >= 0xD800 && code <= 0xDBFF && i + 1 < count && units[i + 1] >= 0xDC00 &&
219
+ units[i + 1] <= 0xDFFF) {
220
+ code = 0x10000 + ((code - 0xD800) << 10) + ((uint32_t)units[i + 1] - 0xDC00);
221
+ i++;
222
+ }
223
+ length += xt_utf8_encode(code, out + length);
224
+ }
225
+ xt_value result = xt_string_new(out, length);
226
+ free(out);
227
+ return result;
228
+ }
229
+
230
+ xt_value xt_string_unit_value(xt_string *s, int32_t index) {
231
+ int32_t unit = xt_string_code_unit(s, index);
232
+ if (unit < 0) return xt_string_from_cstr("");
233
+ return xt_string_from_code_point((uint32_t)unit);
234
+ }
235
+
236
+ xt_value xt_string_code_point_value(xt_string *s, int32_t index) {
237
+ if (!s || index < 0) return XT_UNDEFINED;
238
+ if (xt_string_is_ascii(s))
239
+ return index < (int32_t)s->length
240
+ ? xt_string_from_code_point((uint32_t)(unsigned char)xt_string_bytes(s)[index])
241
+ : XT_UNDEFINED;
242
+ const char *bytes = xt_string_bytes(s);
243
+ size_t offset = 0;
244
+ int32_t point = 0;
245
+ while (offset < s->length) {
246
+ size_t width = 0;
247
+ uint32_t code = xt_utf8_decode(bytes + offset, s->length - offset, &width);
248
+ if (point == index) return xt_string_from_code_point(code);
249
+ point++;
250
+ offset += width;
251
+ }
252
+ return XT_UNDEFINED;
253
+ }
254
+
255
+ xt_value xt_string_slice_units(xt_string *s, int32_t start, int32_t end) {
256
+ if (!s) return xt_string_from_cstr("");
257
+ int32_t units = (int32_t)xt_string_utf16_length(s);
258
+ if (start < 0) start = 0;
259
+ if (start > units) start = units;
260
+ if (end < 0) end = 0;
261
+ if (end > units) end = units;
262
+ if (start >= end) return xt_string_from_cstr("");
263
+ if (xt_string_is_ascii(s)) {
264
+ const char *ascii = xt_string_bytes(s);
265
+ return xt_string_new(ascii + start, (size_t)(end - start));
266
+ }
267
+
268
+ const char *bytes = xt_string_bytes(s);
269
+ /* Splitting a surrogate pair expands four bytes into two three-byte WTF-8
270
+ * halves, so leave two bytes of headroom. */
271
+ char *out = (char *)malloc((size_t)s->length + 8);
272
+ if (!out) abort();
273
+ size_t length = 0;
274
+ size_t offset = 0;
275
+ int32_t unit = 0;
276
+ while (offset < s->length) {
277
+ size_t width = 0;
278
+ uint32_t code = xt_utf8_decode(bytes + offset, s->length - offset, &width);
279
+ if (code >= 0x10000) {
280
+ uint32_t value = code - 0x10000;
281
+ if (start <= unit && end >= unit + 2) {
282
+ length += xt_utf8_encode(code, out + length);
283
+ } else if (start <= unit && end == unit + 1) {
284
+ length += xt_utf8_encode(0xD800 + (value >> 10), out + length);
285
+ } else if (start == unit + 1 && end >= unit + 2) {
286
+ length += xt_utf8_encode(0xDC00 + (value & 0x3FF), out + length);
287
+ }
288
+ unit += 2;
289
+ } else {
290
+ if (unit >= start && unit < end) length += xt_utf8_encode(code, out + length);
291
+ unit += 1;
292
+ }
293
+ offset += width;
294
+ }
295
+ xt_value result = xt_string_new(out, length);
296
+ free(out);
297
+ return result;
298
+ }
299
+
300
+ int32_t xt_string_byte_offset(xt_string *s, int32_t unit_index) {
301
+ if (!s || unit_index <= 0) return 0;
302
+ if (xt_string_is_ascii(s)) return unit_index < (int32_t)s->length ? unit_index : (int32_t)s->length;
303
+ const char *bytes = xt_string_bytes(s);
304
+ size_t offset = 0;
305
+ int32_t unit = 0;
306
+ while (offset < s->length) {
307
+ if (unit >= unit_index) return (int32_t)offset;
308
+ size_t width = 0;
309
+ uint32_t code = xt_utf8_decode(bytes + offset, s->length - offset, &width);
310
+ int widthUnits = code >= 0x10000 ? 2 : 1;
311
+ if (widthUnits == 2 && unit + 1 == unit_index) return (int32_t)offset;
312
+ unit += widthUnits;
313
+ offset += width;
314
+ }
315
+ return (int32_t)s->length;
316
+ }
317
+
318
+ int32_t xt_string_unit_offset(xt_string *s, int32_t byte_offset) {
319
+ if (!s || byte_offset <= 0) return 0;
320
+ if ((uint32_t)byte_offset >= s->length) return (int32_t)xt_string_utf16_length(s);
321
+ if (xt_string_is_ascii(s)) return byte_offset;
322
+ const char *bytes = xt_string_bytes(s);
323
+ size_t offset = 0;
324
+ int32_t unit = 0;
325
+ while (offset < (size_t)byte_offset && offset < s->length) {
326
+ size_t width = 0;
327
+ uint32_t code = xt_utf8_decode(bytes + offset, s->length - offset, &width);
328
+ unit += code >= 0x10000 ? 2 : 1;
329
+ offset += width;
330
+ }
331
+ return unit;
332
+ }
333
+
334
+ int xt_string_is_boundary(xt_string *s, int32_t unit_index) {
335
+ int32_t units = (int32_t)xt_string_utf16_length(s);
336
+ if (unit_index <= 0 || unit_index >= units) return 1;
337
+ if (xt_string_is_ascii(s)) return 1;
338
+ int32_t byte = xt_string_byte_offset(s, unit_index);
339
+ return xt_string_unit_offset(s, byte) == unit_index;
340
+ }
@@ -19,6 +19,7 @@
19
19
 
20
20
  #include "xt_values/constructors.inc"
21
21
  #include "xt_values/strings.inc"
22
+ #include "xt_values/utf16.inc"
22
23
  #include "xt_values/number-format.inc"
23
24
  #include "xt_values/conversions.inc"
24
25
  #include "xt_values/arithmetic.inc"