xbintsc 0.3.87 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +4 -3
  3. package/README.zh-CN.md +2 -2
  4. package/dist/src/codegen/generator/calls/access.js +6 -2
  5. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/closures.js +4 -0
  7. package/dist/src/codegen/generator/calls/closures.js.map +1 -1
  8. package/dist/src/codegen/generator/calls/invocation.js +4 -3
  9. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  10. package/dist/src/codegen/generator/expressions/primary.js +6 -1
  11. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  12. package/dist/src/codegen/generator/module.d.ts +0 -1
  13. package/dist/src/codegen/generator/module.js +10 -10
  14. package/dist/src/codegen/generator/module.js.map +1 -1
  15. package/dist/src/codegen/generator/state.js +4 -0
  16. package/dist/src/codegen/generator/state.js.map +1 -1
  17. package/dist/src/codegen/generator/statements/control-flow.js +1 -3
  18. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  19. package/dist/src/codegen/generator/tables.d.ts +9 -0
  20. package/dist/src/codegen/generator/tables.js +74 -2
  21. package/dist/src/codegen/generator/tables.js.map +1 -1
  22. package/dist/src/driver/compiler.d.ts +2 -2
  23. package/dist/src/driver/compiler.js +4 -1
  24. package/dist/src/driver/compiler.js.map +1 -1
  25. package/dist/tests/codegen/closures.test.js +8 -0
  26. package/dist/tests/codegen/closures.test.js.map +1 -1
  27. package/dist/tests/e2e/async-await.test.d.ts +10 -0
  28. package/dist/tests/e2e/async-await.test.js +247 -0
  29. package/dist/tests/e2e/async-await.test.js.map +1 -0
  30. package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
  31. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
  32. package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
  33. package/dist/tests/e2e/string-utf16.test.js +133 -0
  34. package/dist/tests/e2e/string-utf16.test.js.map +1 -0
  35. package/doc/ai/contributing.md +9 -7
  36. package/doc/ai/extensions.md +3 -2
  37. package/doc/ai/language-support.md +11 -8
  38. package/doc/ai/troubleshooting.md +4 -3
  39. package/doc/ai/zh-CN/contributing.md +5 -5
  40. package/doc/ai/zh-CN/extensions.md +2 -1
  41. package/doc/ai/zh-CN/language-support.md +11 -8
  42. package/doc/ai/zh-CN/troubleshooting.md +3 -2
  43. package/doc/implemented.md +15 -12
  44. package/doc/unimplemented.md +11 -10
  45. package/doc/zh-CN/implemented.md +14 -11
  46. package/doc/zh-CN/unimplemented.md +11 -10
  47. package/package.json +1 -1
  48. package/runtime/rt.h +32 -0
  49. package/runtime/rt_internal.h +99 -10
  50. package/runtime/xt_alloc.c +6 -0
  51. package/runtime/xt_async.c +197 -0
  52. package/runtime/xt_builtins.c +40 -36
  53. package/runtime/xt_constructors.c +389 -0
  54. package/runtime/xt_containers/access.inc +47 -11
  55. package/runtime/xt_containers/arrays.inc +26 -2
  56. package/runtime/xt_containers/objects.inc +202 -5
  57. package/runtime/xt_coroutine.c +212 -0
  58. package/runtime/xt_generator.c +31 -162
  59. package/runtime/xt_io/inspect.inc +35 -2
  60. package/runtime/xt_promise.c +62 -17
  61. package/runtime/xt_stdlib/dispatch.inc +18 -2
  62. package/runtime/xt_stdlib/strings.inc +73 -16
  63. package/runtime/xt_stdlib2/array-statics.inc +3 -3
  64. package/runtime/xt_stdlib2/error.inc +11 -0
  65. package/runtime/xt_stdlib2/json.inc +44 -15
  66. package/runtime/xt_stdlib2/number-string-statics.inc +24 -15
  67. package/runtime/xt_stdlib2/objects.inc +1 -1
  68. package/runtime/xt_stdlib2/regexp-match.inc +2 -2
  69. package/runtime/xt_stdlib2/set.inc +6 -0
  70. package/runtime/xt_stdlib2/strings.inc +70 -40
  71. package/runtime/xt_typed_array/construction.inc +43 -2
  72. package/runtime/xt_typed_array.c +4 -0
  73. package/runtime/xt_values/constructors.inc +8 -0
  74. package/runtime/xt_values/strings.inc +19 -3
  75. package/runtime/xt_values/utf16.inc +340 -0
  76. package/runtime/xt_values.c +1 -0
  77. package/src/codegen/generator/calls/access.ts +7 -2
  78. package/src/codegen/generator/calls/closures.ts +3 -0
  79. package/src/codegen/generator/calls/invocation.ts +4 -3
  80. package/src/codegen/generator/expressions/primary.ts +6 -1
  81. package/src/codegen/generator/module.ts +10 -12
  82. package/src/codegen/generator/state.ts +4 -0
  83. package/src/codegen/generator/statements/control-flow.ts +1 -2
  84. package/src/codegen/generator/tables.ts +84 -2
  85. package/src/driver/compiler.ts +4 -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
 
@@ -10,6 +10,8 @@ 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;
13
15
  /* Keep the terminator in the allocation: many consumers hand `flat` to libc. */
14
16
  s->capacity = (uint32_t)length + 1;
15
17
  s->flat = (char *)malloc(s->capacity);
@@ -26,6 +28,7 @@ xt_string *xt_string_alloc(size_t length) {
26
28
  xt_value xt_string_new(const char *data, size_t len) {
27
29
  xt_string *s = xt_string_alloc(len);
28
30
  if (len > 0) memcpy(s->flat, data, len);
31
+ xt_string_finish(s);
29
32
  return XT_FROM_PTR(XT_TAG_STRING, s);
30
33
  }
31
34
 
@@ -44,3 +47,8 @@ int32_t xt_string_length_value(xt_value value) {
44
47
  xt_string *s = xt_as_string(value);
45
48
  return s ? (int32_t)s->length : -1;
46
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
+ }
@@ -62,9 +62,13 @@ const char *xt_string_bytes(xt_string *s) {
62
62
 
63
63
  out[offset] = '\0';
64
64
  s->flat = out;
65
+ s->length = (uint32_t)offset;
65
66
  s->capacity = (uint32_t)(total + 1);
66
67
  s->left = NULL;
67
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);
68
72
  return s->flat;
69
73
  }
70
74
 
@@ -74,12 +78,22 @@ static xt_value xt_string_concat(xt_string *a, xt_string *b) {
74
78
  size_t total = (size_t)a->length + (size_t)b->length;
75
79
  if (total <= XT_STRING_CONS_THRESHOLD) {
76
80
  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);
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);
79
88
  return XT_FROM_PTR(XT_TAG_STRING, out);
80
89
  }
81
90
  xt_string *out = (xt_string *)xt_alloc(sizeof(xt_string), XT_OBJECT_KIND_STRING);
82
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;
83
97
  out->capacity = 0;
84
98
  out->flat = NULL;
85
99
  out->left = a;
@@ -91,6 +105,8 @@ int xt_string_equals(xt_string *a, xt_string *b) {
91
105
  /* Interned literals (property names above all) are shared, so identity is the
92
106
  * common case and skips the length, flattening and byte comparison entirely. */
93
107
  if (a == b) return 1;
108
+ const char *aBytes = xt_string_bytes(a);
109
+ const char *bBytes = xt_string_bytes(b);
94
110
  if (a->length != b->length) return 0;
95
- return memcmp(xt_string_bytes(a), xt_string_bytes(b), a->length) == 0;
111
+ return memcmp(aBytes, bBytes, a->length) == 0;
96
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"
@@ -13,7 +13,7 @@ import {
13
13
  type PropertyAccessExpression,
14
14
  } from "../../../ast/nodes.js";
15
15
  import { i64, numberLiteral, XT_TRUE, XT_UNDEFINED } from "../../values.js";
16
- import { MATH_CONSTANTS, NAMESPACE_PROPERTIES, NUMBER_CONSTANTS, TYPED_ARRAYS } from "../tables.js";
16
+ import { MATH_CONSTANTS, NAMESPACE_PROPERTIES, NUMBER_CONSTANTS, SYMBOL_WELL_KNOWN, TYPED_ARRAYS } from "../tables.js";
17
17
  import type { Generator } from "../generator.js";
18
18
 
19
19
  export interface AccessCallMethods {
@@ -85,7 +85,12 @@ export const accessCallMethods: AccessCallMethods = {
85
85
  !this.binding.symbolOfIdentifier.get(node.expression as Identifier)
86
86
  ) {
87
87
  const getter = NAMESPACE_PROPERTIES[(node.expression as Identifier).text];
88
- if (getter) {
88
+ /* `Symbol.iterator` & friends are well-known symbol values, but
89
+ * `Symbol.prototype` / `.name` / `.length` are ordinary properties of
90
+ * the first-class constructor value. */
91
+ const symbolGetter =
92
+ (node.expression as Identifier).text === "Symbol" && SYMBOL_WELL_KNOWN.has(node.name.text);
93
+ if (getter && (getter !== "xt_symbol_get" || symbolGetter)) {
89
94
  const key = this.stringValue(node.name.text);
90
95
  const result = this.reg();
91
96
  this.emit(` ${result} = call i64 @${getter}(i64 ${key})`);
@@ -80,5 +80,8 @@ export const closureCallMethods: ClosureCallMethods = {
80
80
  const nameValue = this.stringValue(name);
81
81
  this.emit(` call i64 @xt_function_set_metadata(i64 ${closure}, i64 ${nameValue}, i32 ${arity})`);
82
82
  if (fn.isGenerator) this.emit(` call i64 @xt_function_set_generator(i64 ${closure})`);
83
+ /* `async` functions run on a coroutine: the runtime must know at call time
84
+ * so it can return a promise and suspend at `await`. */
85
+ if (fn.isAsync) this.emit(` call i64 @xt_function_set_async(i64 ${closure})`);
83
86
  },
84
87
  };
@@ -109,9 +109,10 @@ export const invocationCallMethods: InvocationCallMethods = {
109
109
  if (symbol && symbol.kind === SymbolKind.Function) {
110
110
  const declaration = symbol.declarations[0];
111
111
  const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
112
- /* Generator functions are not called directly: the call must create a
113
- * suspended generator, so fall through to the closure-call path. */
114
- if (fn && !fn.isGenerator) {
112
+ /* Generator and `async` functions are not called directly: the call
113
+ * must create a suspended generator / start a coroutine, so fall
114
+ * through to the closure-call path. */
115
+ if (fn && !fn.isGenerator && !fn.isAsync) {
115
116
  const args = this.emitArguments(node.arguments);
116
117
  const result = this.reg();
117
118
  this.emit(
@@ -28,7 +28,7 @@ import {
28
28
  import { SymbolKind, type SymbolInfo } from "../../../binder/binder.js";
29
29
  import { DiagnosticCode } from "../../../diagnostics/diagnostic.js";
30
30
  import { i64, numberLiteral, XT_FALSE, XT_NULL, XT_TRUE, XT_UNDEFINED } from "../../values.js";
31
- import { BUILTIN_FUNCTION_VALUES, CTOR_FUNCTIONS, ERROR_CONSTRUCTORS } from "../tables.js";
31
+ import { BUILTIN_FUNCTION_VALUES, CTOR_FUNCTIONS, ERROR_CONSTRUCTORS, FIRST_CLASS_CONSTRUCTORS } from "../tables.js";
32
32
  import type { Generator } from "../generator.js";
33
33
 
34
34
  export interface PrimaryExpressionMethods {
@@ -218,6 +218,11 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
218
218
  return rest;
219
219
  }
220
220
  default: {
221
+ if (FIRST_CLASS_CONSTRUCTORS.has(identifier.text)) {
222
+ // `const A = Array`, `arr.filter(Boolean)`, `new (Map)()`: the
223
+ // constructor is an ordinary function value backed by the runtime.
224
+ return this.runtimeCall("xt_builtin_constructor", [this.stringValue(identifier.text)]);
225
+ }
221
226
  const builtinValue = BUILTIN_FUNCTION_VALUES[identifier.text];
222
227
  if (builtinValue) {
223
228
  // A global like `Boolean` used as a value (`arr.filter(Boolean)`):
@@ -41,7 +41,6 @@ export interface ModuleMethods {
41
41
  emitFunctionReturn(this: Generator, value: string): void;
42
42
  emitMain(this: Generator): void;
43
43
  functionName(this: Generator, fn: FunctionInfo): string;
44
- wrapAsync(this: Generator, value: string): string;
45
44
  classGlobal(this: Generator, classInfo: ClassInfo): string;
46
45
  emitClassReference(this: Generator, classInfo: ClassInfo): string;
47
46
  emitClassSetup(this: Generator, classInfo: ClassInfo): void;
@@ -124,7 +123,11 @@ export const moduleMethods: ModuleMethods = {
124
123
  finallyStack: [],
125
124
  escapePointers: [],
126
125
  usesTry: false,
127
- trackFrames: !fn.isGenerator,
126
+ /* Generator and async bodies run on a private coroutine stack that can be
127
+ * suspended in the middle of a call. Their shadow frame would then sit on
128
+ * the shadow stack while the caller unwinds its own, so leave them out of
129
+ * `Error.stack` frames (nesting calls inside them are still recorded). */
130
+ trackFrames: !fn.isGenerator && !fn.isAsync,
128
131
  ...(debugSubprogram ? { debugSubprogram } : {}),
129
132
  };
130
133
  this.current = state;
@@ -254,15 +257,17 @@ export const moduleMethods: ModuleMethods = {
254
257
  this.emitStatements(this.sourceFile.statements);
255
258
  } else if (fn.node.kind === SyntaxKind.ArrowFunction && (fn.node as ArrowFunction).body.kind !== SyntaxKind.Block) {
256
259
  const value = this.emitExpression((fn.node as ArrowFunction).body as Expression);
257
- this.emitFunctionReturn(fn.isAsync ? this.wrapAsync(value) : value);
260
+ this.emitFunctionReturn(value);
258
261
  } else {
259
262
  const body = (fn.node as { body: Block }).body;
260
263
  if (body) this.emitStatements(body.statements);
261
264
  }
262
265
 
263
266
  if (!this.current.terminated) {
264
- const fallback = fn.isAsync ? this.wrapAsync(i64(XT_UNDEFINED)) : i64(XT_UNDEFINED);
265
- this.emitFunctionReturn(fallback);
267
+ /* An `async` body returns its value plainly: the runtime's async
268
+ * coroutine settles the returned promise (and adopts a returned
269
+ * promise), so wrapping here would add a redundant promise. */
270
+ this.emitFunctionReturn(i64(XT_UNDEFINED));
266
271
  }
267
272
 
268
273
  const escapes = state.usesTry
@@ -327,13 +332,6 @@ export const moduleMethods: ModuleMethods = {
327
332
  this.terminate(`ret i64 ${value}`);
328
333
  },
329
334
 
330
- /** Wrap a returned value in a resolved promise (for `async` functions). */
331
- wrapAsync(value: string): string {
332
- const result = this.reg();
333
- this.emit(` ${result} = call i64 @xt_promise_resolve(i64 ${value})`);
334
- return result;
335
- },
336
-
337
335
  classGlobal(classInfo: ClassInfo): string {
338
336
  const existing = this.classGlobals.get(classInfo.id);
339
337
  if (existing) return existing;
@@ -104,6 +104,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
104
104
  "declare i64 @xt_closure_new(i8*, i32, i64*)",
105
105
  "declare i64 @xt_function_set_metadata(i64, i64, i32)",
106
106
  "declare i64 @xt_function_set_generator(i64)",
107
+ "declare i64 @xt_function_set_async(i64)",
107
108
  "declare i64 @xt_closure_call(i64, i32, i64*)",
108
109
  "declare i64 @xt_closure_env(i64, i32)",
109
110
  "declare i64 @xt_call_with_this(i64, i64, i32, i64*)",
@@ -111,6 +112,9 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
111
112
  "declare i64 @xt_new(i64, i32, i64*)",
112
113
  "declare i64 @xt_instance_of(i64, i64)",
113
114
  "declare i64 @xt_instance_of_builtin(i64, i32)",
115
+ "declare i64 @xt_builtin_constructor(i64)",
116
+ "declare i64 @xt_array_ctor(i32, i64*)",
117
+ "declare i64 @xt_object_ctor(i32, i64*)",
114
118
  "declare i64 @xt_function_set_prototype(i64, i64)",
115
119
  "declare i64 @xt_function_get_prototype(i64)",
116
120
  "declare i64 @xt_object_new_with_proto(i64)",
@@ -250,8 +250,7 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
250
250
  },
251
251
 
252
252
  emitReturn(statement: ReturnStatement): void {
253
- let value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
254
- if (this.current.fn.isAsync) value = this.wrapAsync(value);
253
+ const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
255
254
  this.runFinallysBeforeExit(0);
256
255
  if (this.current.terminated) return;
257
256
  this.popTryFramesTo(0);