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
@@ -8,28 +8,17 @@
8
8
  * `finally` and nested loops work across suspension points without a state
9
9
  * machine transform.
10
10
  *
11
- * POSIX uses `ucontext` (`swapcontext`); Windows uses fibers. The generator's
12
- * exception stack is swapped alongside it so a `throw` inside the body still
13
- * targets the body's own `try` frames.
11
+ * The private stack and the context switch come from `xt_coroutine.c`, which
12
+ * `async` functions use as well. The generator's exception stack is swapped
13
+ * alongside it so a `throw` inside the body still targets the body's own
14
+ * `try` frames.
14
15
  */
15
16
 
16
- #define _XOPEN_SOURCE 700
17
-
18
17
  #include "rt_internal.h"
19
18
 
20
19
  #include <stdlib.h>
21
20
  #include <string.h>
22
21
 
23
- #if defined(__APPLE__)
24
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
25
- #endif
26
-
27
- #ifdef _WIN32
28
- #include <windows.h>
29
- #else
30
- #include <ucontext.h>
31
- #endif
32
-
33
22
  typedef struct xt_generator {
34
23
  xt_header header;
35
24
  xt_value function;
@@ -44,35 +33,13 @@ typedef struct xt_generator {
44
33
  int started;
45
34
  int done;
46
35
  int threw;
47
- int booted;
48
- /* Private stack bounds, registered as an active stack region while the
49
- * generator runs and scanned conservatively while it is suspended. */
50
- char *stack;
51
- size_t stack_size;
52
- int stack_pushed;
53
- void *saved_try_top;
54
- #ifdef _WIN32
55
- void *fiber;
56
- void *caller_fiber;
57
- /* `SwitchToFiber` keeps the resumer's callee-saved registers in an opaque
58
- * fiber context (POSIX stores them in `caller`), so snapshot them for the
59
- * collector before switching away. */
60
- jmp_buf caller_regs;
61
- int has_caller_regs;
62
- #else
63
- ucontext_t ctx;
64
- ucontext_t caller;
65
- #endif
36
+ /* Private stack, context switch and GC region bookkeeping live in
37
+ * xt_coroutine.c; `NULL` once the body has finished. */
38
+ xt_coroutine *coroutine;
66
39
  } xt_generator;
67
40
 
68
41
  /* The generator whose coroutine is running, if any. */
69
42
  static xt_generator *g_active_generator = NULL;
70
- /* Filled in just before a coroutine is first entered (POSIX has no portable
71
- * way to pass a pointer to `makecontext`). */
72
- static xt_generator *g_boot_generator = NULL;
73
- #ifdef _WIN32
74
- static void *g_main_fiber = NULL;
75
- #endif
76
43
 
77
44
  int xt_is_generator(xt_value value) {
78
45
  if (!XT_IS_OBJECT(value)) return 0;
@@ -91,38 +58,9 @@ static xt_value xt_generator_result_object(xt_value value, int done) {
91
58
  return result;
92
59
  }
93
60
 
94
- static void xt_generator_suspend(xt_generator *gen) {
95
- gen->saved_try_top = xt_try_mark();
96
- #ifdef _WIN32
97
- SwitchToFiber(gen->caller_fiber);
98
- #else
99
- swapcontext(&gen->ctx, &gen->caller);
100
- #endif
101
- }
102
-
103
61
  /* Runs on the generator's private stack. */
104
- static void xt_generator_run(xt_generator *gen) {
105
- #ifdef _WIN32
106
- /* The fiber stack is not observable from the outside; capture its bounds
107
- * here (before the body allocates anything) so a collection during the body
108
- * has a valid scan region. */
109
- if (!gen->stack) {
110
- MEMORY_BASIC_INFORMATION info;
111
- uintptr_t here = (uintptr_t)&info;
112
- if (VirtualQuery((LPCVOID)here, &info, sizeof(info))) {
113
- /* Use the *committed* region containing the current frame: a Windows
114
- * stack reserves more than it commits, and `AllocationBase + RegionSize`
115
- * can fall in uncommitted memory. `BaseAddress + RegionSize` is the
116
- * stack top. */
117
- gen->stack = (char *)info.BaseAddress;
118
- gen->stack_size = info.RegionSize;
119
- }
120
- }
121
- #endif
122
- if (gen->stack && gen->stack_size && !gen->stack_pushed) {
123
- xt_gc_push_stack((uintptr_t)gen->stack, (uintptr_t)gen->stack + gen->stack_size);
124
- gen->stack_pushed = 1;
125
- }
62
+ static void xt_generator_run(void *arg) {
63
+ xt_generator *gen = (xt_generator *)arg;
126
64
  void *boundary = xt_try_enter();
127
65
  if (xt_try_setjmp(boundary) == 0) {
128
66
  xt_function *function = (xt_function *)XT_GET_PTR(gen->function);
@@ -133,90 +71,23 @@ static void xt_generator_run(xt_generator *gen) {
133
71
  gen->threw = 1;
134
72
  }
135
73
  gen->done = 1;
136
- xt_generator_suspend(gen);
137
- }
138
-
139
- #ifndef _WIN32
140
- static void xt_generator_entry(void) {
141
- xt_generator *gen = g_boot_generator;
142
- g_boot_generator = NULL;
143
- xt_generator_run(gen);
144
- }
145
- #else
146
- static void CALLBACK xt_generator_entry(void *param) {
147
- xt_generator_run((xt_generator *)param);
148
- }
149
- #endif
150
-
151
- static void xt_generator_boot(xt_generator *gen) {
152
- #ifdef _WIN32
153
- if (!g_main_fiber) g_main_fiber = ConvertThreadToFiber(NULL);
154
- gen->fiber = CreateFiber(1 << 20, xt_generator_entry, gen);
155
- if (!gen->fiber) abort();
156
- #else
157
- size_t size = 1 << 20;
158
- gen->stack = (char *)malloc(size);
159
- if (!gen->stack) abort();
160
- gen->stack_size = size;
161
- if (getcontext(&gen->ctx) != 0) abort();
162
- gen->ctx.uc_stack.ss_sp = gen->stack;
163
- gen->ctx.uc_stack.ss_size = size;
164
- gen->ctx.uc_link = NULL;
165
- g_boot_generator = gen;
166
- makecontext(&gen->ctx, xt_generator_entry, 0);
167
- #endif
168
- gen->booted = 1;
74
+ xt_coroutine_leave(gen->coroutine);
169
75
  }
170
76
 
171
77
  /* Release the private stack once the coroutine has finished. The generator
172
78
  * object itself stays valid; later `.next()` calls just report `done`. */
173
79
  static void xt_generator_dispose(xt_generator *gen) {
174
- #ifdef _WIN32
175
- if (gen->fiber) {
176
- DeleteFiber(gen->fiber);
177
- gen->fiber = NULL;
178
- }
179
- gen->caller_fiber = NULL;
180
- /* `DeleteFiber` releases the stack: never scan it again. */
181
- gen->stack = NULL;
182
- gen->stack_size = 0;
183
- #else
184
- if (gen->stack) {
185
- free(gen->stack);
186
- gen->stack = NULL;
187
- }
188
- #endif
80
+ xt_coroutine_release(gen->coroutine);
81
+ gen->coroutine = NULL;
189
82
  }
190
83
 
84
+ static void xt_generator_boot(xt_generator *gen);
85
+
191
86
  static void xt_generator_resume(xt_generator *gen) {
192
- if (!gen->booted) xt_generator_boot(gen);
87
+ if (!gen->coroutine) xt_generator_boot(gen);
193
88
  xt_generator *previous = g_active_generator;
194
89
  g_active_generator = gen;
195
- /* Record where the current (calling) stack suspends before switching away.
196
- * On Windows the first push happens inside the fiber once its stack bounds
197
- * are known, so the caller's region must be marked suspended here, on the
198
- * caller's own stack. */
199
- xt_gc_suspend((uintptr_t)&gen);
200
- if (gen->stack && gen->stack_size && !gen->stack_pushed) {
201
- xt_gc_push_stack((uintptr_t)gen->stack, (uintptr_t)gen->stack + gen->stack_size);
202
- gen->stack_pushed = 1;
203
- }
204
- void *caller_try = xt_try_mark();
205
- xt_try_restore(gen->saved_try_top);
206
- #ifdef _WIN32
207
- _setjmp(gen->caller_regs);
208
- gen->has_caller_regs = 1;
209
- gen->caller_fiber = GetCurrentFiber();
210
- SwitchToFiber(gen->fiber);
211
- #else
212
- swapcontext(&gen->caller, &gen->ctx);
213
- #endif
214
- gen->saved_try_top = xt_try_mark();
215
- xt_try_restore(caller_try);
216
- if (gen->stack_pushed) {
217
- xt_gc_pop_stack();
218
- gen->stack_pushed = 0;
219
- }
90
+ xt_coroutine_enter(gen->coroutine);
220
91
  if (gen->done) xt_generator_dispose(gen);
221
92
  g_active_generator = previous;
222
93
  }
@@ -242,23 +113,18 @@ void xt_generator_gc_trace(xt_header *header) {
242
113
  if (gen->args && gen->argc > 0) {
243
114
  xt_gc_scan_region(gen->args, sizeof(xt_value) * (size_t)gen->argc);
244
115
  }
245
- if (gen->stack && gen->stack_size) xt_gc_scan_region(gen->stack, gen->stack_size);
246
- #ifndef _WIN32
247
- /* Registers saved by `swapcontext` at the last yield are not on the stack. */
248
- xt_gc_scan_region(&gen->ctx, sizeof(gen->ctx));
249
- #else
250
- if (gen->has_caller_regs) xt_gc_scan_region(&gen->caller_regs, sizeof(gen->caller_regs));
251
- #endif
116
+ xt_coroutine_gc_trace(gen->coroutine);
252
117
  }
253
118
 
254
119
  void xt_generator_gc_release(xt_header *header) {
255
120
  xt_generator *gen = (xt_generator *)header;
256
121
  free(gen->args);
257
- #ifdef _WIN32
258
- if (gen->fiber) DeleteFiber(gen->fiber);
259
- #else
260
- free(gen->stack);
261
- #endif
122
+ xt_coroutine_release(gen->coroutine);
123
+ gen->coroutine = NULL;
124
+ }
125
+
126
+ static void xt_generator_boot(xt_generator *gen) {
127
+ gen->coroutine = xt_coroutine_create(xt_generator_run, gen);
262
128
  }
263
129
 
264
130
  xt_value xt_generator_new(xt_value function, xt_value thisValue, int32_t argc, xt_value *argv) {
@@ -267,9 +133,7 @@ xt_value xt_generator_new(xt_value function, xt_value thisValue, int32_t argc, x
267
133
  gen->this_value = thisValue;
268
134
  gen->argc = argc;
269
135
  gen->args = NULL;
270
- gen->stack = NULL;
271
- gen->stack_size = 0;
272
- gen->stack_pushed = 0;
136
+ gen->coroutine = NULL;
273
137
  gen->result = XT_UNDEFINED;
274
138
  gen->sent = XT_UNDEFINED;
275
139
  gen->iter_value = XT_UNDEFINED;
@@ -339,7 +203,7 @@ xt_value xt_yield(xt_value value) {
339
203
  xt_generator *gen = g_active_generator;
340
204
  if (!gen || gen->done) return XT_UNDEFINED;
341
205
  gen->result = value;
342
- xt_generator_suspend(gen);
206
+ xt_coroutine_leave(gen->coroutine);
343
207
  if (gen->resume_kind == 1) {
344
208
  xt_throw(gen->sent);
345
209
  return XT_UNDEFINED;
@@ -348,7 +212,7 @@ xt_value xt_yield(xt_value value) {
348
212
  gen->done = 1;
349
213
  gen->threw = 0;
350
214
  gen->result = gen->sent;
351
- xt_generator_suspend(gen);
215
+ xt_coroutine_leave(gen->coroutine);
352
216
  return XT_UNDEFINED;
353
217
  }
354
218
  return gen->sent;
@@ -396,3 +260,8 @@ xt_value xt_generator_iter_value(xt_value generator) {
396
260
  xt_generator *gen = xt_as_generator(generator);
397
261
  return gen ? gen->iter_value : XT_UNDEFINED;
398
262
  }
263
+
264
+ xt_value xt_function_set_generator(xt_value value) {
265
+ if (XT_IS_FUNCTION(value)) ((xt_function *)XT_GET_PTR(value))->is_generator = 1;
266
+ return value;
267
+ }
@@ -2,15 +2,36 @@
2
2
  /* Output and inspection */
3
3
  /* ------------------------------------------------------------------------- */
4
4
 
5
+ /* Write UTF-8 bytes, turning a lone surrogate half (WTF-8) into U+FFFD. Node's
6
+ * terminal encoding does the same, so `console.log("😀"[0])` prints one
7
+ * replacement character instead of emitting invalid UTF-8. */
8
+ static void xt_print_utf8(FILE *out, const char *bytes, size_t length) {
9
+ size_t offset = 0;
10
+ while (offset < length) {
11
+ unsigned char c = (unsigned char)bytes[offset];
12
+ if (c == 0xED && offset + 2 < length) {
13
+ unsigned char second = (unsigned char)bytes[offset + 1];
14
+ unsigned char third = (unsigned char)bytes[offset + 2];
15
+ if (second >= 0xA0 && second <= 0xBF && third >= 0x80 && third <= 0xBF) {
16
+ fwrite("\xEF\xBF\xBD", 1, 3, out);
17
+ offset += 3;
18
+ continue;
19
+ }
20
+ }
21
+ fputc((int)c, out);
22
+ offset++;
23
+ }
24
+ }
25
+
5
26
  static void xt_print_value(xt_value v, FILE *out) {
6
27
  if (XT_IS_STRING(v)) {
7
28
  xt_string *s = xt_as_string(v);
8
- fwrite(xt_string_bytes(s), 1, s->length, out);
29
+ xt_print_utf8(out, xt_string_bytes(s), s->length);
9
30
  return;
10
31
  }
11
32
  xt_value text = xt_to_string(v);
12
33
  xt_string *s = xt_as_string(text);
13
- fwrite(xt_string_bytes(s), 1, s->length, out);
34
+ xt_print_utf8(out, xt_string_bytes(s), s->length);
14
35
  }
15
36
 
16
37
  void xt_print(xt_value v) { xt_print_value(v, stdout); }
@@ -83,6 +104,18 @@ static void xt_inspect_string(xt_string *s, FILE *out) {
83
104
  default:
84
105
  if (c == (unsigned char)quote) { fputc('\\', out); fputc(c, out); }
85
106
  else if (c < 0x20) fprintf(out, "\\x%02x", c);
107
+ else if (c == 0xED && i + 2 < s->length) {
108
+ unsigned char second = (unsigned char)bytes[i + 1];
109
+ unsigned char third = (unsigned char)bytes[i + 2];
110
+ if (second >= 0xA0 && second <= 0xBF && third >= 0x80 && third <= 0xBF) {
111
+ unsigned code = ((unsigned)(c & 0x0F) << 12) | ((unsigned)(second & 0x3F) << 6) |
112
+ (unsigned)(third & 0x3F);
113
+ fprintf(out, "\\u%04x", code);
114
+ i += 2;
115
+ } else {
116
+ fputc(c, out);
117
+ }
118
+ }
86
119
  else fputc(c, out);
87
120
  }
88
121
  }
@@ -1,10 +1,12 @@
1
1
  /*
2
- * xbintsc runtime — promises and async support.
2
+ * xbintsc runtime — promises and the microtask queue.
3
3
  *
4
- * There is no event loop in the runtime, so promises use a microtask queue
5
- * that the compiler drains when the program body finishes (and that `await`
6
- * drains on demand). This gives synchronous-looking `async`/`await` semantics
7
- * for the common case where every awaited value is already settled.
4
+ * Promise reactions are queued as microtasks and run when the program body
5
+ * finishes, by the event loop between callbacks, and on demand. `await` lives
6
+ * in `xt_async.c`: an `async` function runs on a coroutine, so awaiting a
7
+ * promise that is still pending suspends it and registers a waiter here. When
8
+ * the promise settles, the waiter becomes a microtask that resumes the
9
+ * coroutine. Everything is single-threaded and non-blocking.
8
10
  */
9
11
 
10
12
  #include "rt_internal.h"
@@ -12,10 +14,6 @@
12
14
  #include <stdlib.h>
13
15
  #include <string.h>
14
16
 
15
- #define XT_PROMISE_PENDING 0
16
- #define XT_PROMISE_FULFILLED 1
17
- #define XT_PROMISE_REJECTED 2
18
-
19
17
  typedef struct xt_promise xt_promise;
20
18
 
21
19
  typedef struct {
@@ -23,6 +21,9 @@ typedef struct {
23
21
  xt_value on_fulfilled;
24
22
  xt_value on_rejected;
25
23
  xt_promise *result;
24
+ /* Non-NULL for an `async` coroutine waiting on this promise. Such a reaction
25
+ * has no handlers: running it resumes the coroutine instead. */
26
+ void *async;
26
27
  } xt_reaction;
27
28
 
28
29
  struct xt_promise {
@@ -87,6 +88,7 @@ static void xt_promise_gc_scan_roots(void) {
87
88
  xt_gc_mark_value(reaction->on_fulfilled);
88
89
  xt_gc_mark_value(reaction->on_rejected);
89
90
  xt_gc_mark_header((xt_header *)reaction->result);
91
+ if (reaction->async) xt_gc_mark_header((xt_header *)reaction->async);
90
92
  }
91
93
  }
92
94
 
@@ -107,6 +109,7 @@ void xt_promise_gc_trace(xt_header *header) {
107
109
  xt_gc_mark_value(reaction->on_fulfilled);
108
110
  xt_gc_mark_value(reaction->on_rejected);
109
111
  xt_gc_mark_header((xt_header *)reaction->result);
112
+ if (reaction->async) xt_gc_mark_header((xt_header *)reaction->async);
110
113
  }
111
114
  }
112
115
 
@@ -133,6 +136,7 @@ static void xt_promise_resolve_into(xt_promise *result, xt_value value) {
133
136
  reaction->on_fulfilled = XT_UNDEFINED;
134
137
  reaction->on_rejected = XT_UNDEFINED;
135
138
  reaction->result = result;
139
+ reaction->async = NULL;
136
140
  if (other->reaction_count >= other->reaction_capacity) {
137
141
  other->reaction_capacity = other->reaction_capacity == 0 ? 4 : other->reaction_capacity * 2;
138
142
  other->reactions = (xt_reaction **)realloc(other->reactions, sizeof(xt_reaction *) * other->reaction_capacity);
@@ -156,6 +160,16 @@ static xt_value xt_promise_run_reactions(void) {
156
160
  while (xt_microtask_head < xt_microtask_count) {
157
161
  xt_reaction *reaction = xt_microtasks[xt_microtask_head++];
158
162
  xt_promise *promise = reaction->promise;
163
+ if (reaction->async) {
164
+ /* A suspended `async` coroutine: resume it with the settled value. It
165
+ * runs until it completes or awaits another pending promise. */
166
+ void *async = reaction->async;
167
+ int rejected = promise->state == XT_PROMISE_REJECTED;
168
+ xt_value settled = promise->value;
169
+ free(reaction);
170
+ xt_async_wake(async, settled, rejected);
171
+ continue;
172
+ }
159
173
  xt_value handler = promise->state == XT_PROMISE_FULFILLED ? reaction->on_fulfilled : reaction->on_rejected;
160
174
  if (XT_IS_FUNCTION(handler)) {
161
175
  xt_value arg = promise->value;
@@ -181,16 +195,46 @@ void xt_drain_microtasks(void) {
181
195
  }
182
196
  }
183
197
 
184
- xt_value xt_await(xt_value value) {
198
+ /* -- helpers used by the async coroutine runtime -------------------------- */
199
+
200
+ xt_value xt_promise_create(void) { return xt_promise_value(xt_promise_new()); }
201
+
202
+ void xt_promise_fulfill(xt_value promise, xt_value value) {
203
+ xt_promise *target = xt_as_promise(promise);
204
+ if (target) xt_promise_resolve_into(target, value);
205
+ }
206
+
207
+ void xt_promise_reject_value(xt_value promise, xt_value value) {
208
+ xt_promise *target = xt_as_promise(promise);
209
+ if (target) xt_promise_settle(target, XT_PROMISE_REJECTED, value);
210
+ }
211
+
212
+ int xt_promise_state_of(xt_value value) {
185
213
  xt_promise *promise = xt_as_promise(value);
186
- if (!promise) return value;
187
- int guard = 0;
188
- while (promise->state == XT_PROMISE_PENDING && guard++ < 100000) xt_drain_microtasks();
189
- if (promise->state == XT_PROMISE_REJECTED) {
190
- xt_throw(promise->value);
191
- return XT_UNDEFINED;
214
+ return promise ? promise->state : -1;
215
+ }
216
+
217
+ xt_value xt_promise_value_of(xt_value value) {
218
+ xt_promise *promise = xt_as_promise(value);
219
+ return promise ? promise->value : value;
220
+ }
221
+
222
+ void xt_promise_add_async_waiter(xt_value promiseValue, void *async) {
223
+ xt_promise *promise = xt_as_promise(promiseValue);
224
+ if (!promise || promise->state != XT_PROMISE_PENDING) return;
225
+ xt_reaction *reaction = (xt_reaction *)malloc(sizeof(xt_reaction));
226
+ if (!reaction) abort();
227
+ reaction->promise = promise;
228
+ reaction->on_fulfilled = XT_UNDEFINED;
229
+ reaction->on_rejected = XT_UNDEFINED;
230
+ reaction->result = NULL;
231
+ reaction->async = async;
232
+ if (promise->reaction_count >= promise->reaction_capacity) {
233
+ promise->reaction_capacity = promise->reaction_capacity == 0 ? 4 : promise->reaction_capacity * 2;
234
+ promise->reactions = (xt_reaction **)realloc(promise->reactions, sizeof(xt_reaction *) * promise->reaction_capacity);
235
+ if (!promise->reactions) abort();
192
236
  }
193
- return promise->value;
237
+ promise->reactions[promise->reaction_count++] = reaction;
194
238
  }
195
239
 
196
240
  /* -- C-callable resolve / reject ------------------------------------------ */
@@ -316,6 +360,7 @@ xt_value xt_promise_method(xt_value target, const char *method, int32_t argc, xt
316
360
  reaction->on_fulfilled = xt_arg_at(argc, argv, 0);
317
361
  reaction->on_rejected = xt_arg_at(argc, argv, 1);
318
362
  reaction->result = result;
363
+ reaction->async = NULL;
319
364
  if (promise->state == XT_PROMISE_PENDING) {
320
365
  if (promise->reaction_count >= promise->reaction_capacity) {
321
366
  promise->reaction_capacity = promise->reaction_capacity == 0 ? 4 : promise->reaction_capacity * 2;
@@ -40,6 +40,7 @@ static xt_value xt_string_method(xt_value target, const char *method, int32_t ar
40
40
  *handled = 1;
41
41
  if (strcmp(method, "charAt") == 0) return xt_string_char_at(target, xt_arg_at(argc, argv, 0));
42
42
  if (strcmp(method, "charCodeAt") == 0) return xt_string_char_code_at(target, xt_arg_at(argc, argv, 0));
43
+ if (strcmp(method, "codePointAt") == 0) return xt_string_code_point_at_value(target, xt_arg_at(argc, argv, 0));
43
44
  if (strcmp(method, "indexOf") == 0) return xt_string_index_of(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
44
45
  if (strcmp(method, "includes") == 0) return xt_bool(xt_truthy(xt_ne(xt_string_index_of(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1)), xt_number(-1))));
45
46
  if (strcmp(method, "slice") == 0) return xt_string_slice(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
@@ -51,8 +52,8 @@ static xt_value xt_string_method(xt_value target, const char *method, int32_t ar
51
52
  if (strcmp(method, "trim") == 0) return xt_string_trim(target);
52
53
  if (strcmp(method, "replace") == 0) return xt_string_replace(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
53
54
  if (strcmp(method, "repeat") == 0) return xt_string_repeat(target, xt_arg_at(argc, argv, 0));
54
- if (strcmp(method, "startsWith") == 0) return xt_string_starts_with(target, xt_arg_at(argc, argv, 0));
55
- if (strcmp(method, "endsWith") == 0) return xt_string_ends_with(target, xt_arg_at(argc, argv, 0));
55
+ if (strcmp(method, "startsWith") == 0) return xt_string_starts_with(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
56
+ if (strcmp(method, "endsWith") == 0) return xt_string_ends_with(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
56
57
  if (strcmp(method, "concat") == 0) return xt_string_concat_args(target, argc, argv);
57
58
  *handled = 0;
58
59
  return XT_UNDEFINED;
@@ -286,11 +287,19 @@ xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *
286
287
  if (kind != XT_OBJECT_KIND_OBJECT && kind != XT_OBJECT_KIND_FUNCTION) {
287
288
  xt_value result = xt_ext_container_method(target, method, argc, argv, &handled);
288
289
  if (handled) return result;
290
+ /* Containers keep their methods in dedicated C dispatch tables, but
291
+ * still inherit `Object.prototype` (`new Map().toString()`). */
292
+ xt_value inherited = xt_get(target, name);
293
+ if (XT_IS_FUNCTION(inherited)) return xt_call_with_this(inherited, target, argc, argv);
289
294
  }
290
295
  xt_value fn = xt_object_get(target, name);
291
296
  if (XT_IS_FUNCTION(fn)) return xt_call_with_this(fn, target, argc, argv);
292
297
  xt_value result = xt_ext_object_method(target, method, argc, argv, &handled);
293
298
  if (handled) return result;
299
+ /* Inherited Object.prototype methods on containers without a property bag
300
+ * (`new Map().toString()`, ...). */
301
+ xt_value inherited = xt_get(target, name);
302
+ if (XT_IS_FUNCTION(inherited)) return xt_call_with_this(inherited, target, argc, argv);
294
303
  {
295
304
  char message[256];
296
305
  snprintf(message, sizeof(message), "TypeError: object has no callable method '%s'", method);
@@ -333,6 +342,13 @@ xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *
333
342
  }
334
343
  xt_value fn = xt_object_get(target, name);
335
344
  if (XT_IS_FUNCTION(fn)) return xt_call_with_this(fn, target, argc, argv);
345
+ /* Statics of a first-class built-in constructor (`const A = Array;
346
+ * A.isArray(x)`). */
347
+ xt_value builtinStatic = xt_builtin_static_value(target, name);
348
+ if (builtinStatic != XT_UNDEFINED) {
349
+ if (XT_IS_FUNCTION(builtinStatic)) return xt_call_with_this(builtinStatic, target, argc, argv);
350
+ return builtinStatic;
351
+ }
336
352
  if (strcmp(method, "toString") == 0) {
337
353
  /* Source text is not retained; report a native-style representation. */
338
354
  xt_function *self = (xt_function *)XT_GET_PTR(target);