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
@@ -0,0 +1,197 @@
1
+ /*
2
+ * xbintsc runtime — real `async` / `await`.
3
+ *
4
+ * An `async` function runs its body on a stackful coroutine (the same
5
+ * machinery generators use, see `xt_coroutine.c`) and immediately returns a
6
+ * promise. The body runs synchronously until it reaches an `await` whose
7
+ * operand is still pending; at that point the coroutine suspends and control
8
+ * returns to the caller, so the program body or the event loop can keep going.
9
+ * When the awaited promise settles, the promise runtime queues a microtask that
10
+ * resumes the coroutine exactly where it stopped — because the coroutine keeps
11
+ * a real C stack, `try`/`finally`, loops and locals all survive the suspension
12
+ * without a state-machine transform.
13
+ *
14
+ * `await` on an already-settled value keeps the historical fast path: it
15
+ * returns (or throws) synchronously instead of forcing a microtask turn. This
16
+ * makes the common "await a synchronous builtin" case cheap and preserves the
17
+ * existing ordering guarantees.
18
+ */
19
+
20
+ #include "rt_internal.h"
21
+
22
+ #include <stdlib.h>
23
+ #include <string.h>
24
+
25
+ typedef struct xt_async {
26
+ xt_header header;
27
+ xt_value function;
28
+ xt_value this_value;
29
+ xt_value *args;
30
+ int32_t argc;
31
+ /* Promise returned to the caller and settled when the body finishes. */
32
+ xt_value promise;
33
+ /* Completion value, or the exception that escaped the body. */
34
+ xt_value value;
35
+ /* Values handed over by the waiter and consumed by `xt_await`. */
36
+ xt_value resume_value;
37
+ int await_threw;
38
+ int done;
39
+ int threw;
40
+ xt_coroutine *coroutine;
41
+ } xt_async;
42
+
43
+ /* The async coroutine currently running on its private stack, if any. It is
44
+ * only reachable through this pointer while it runs, so it is a GC root. */
45
+ static xt_async *g_active_async = NULL;
46
+
47
+ static void xt_async_gc_register(void);
48
+
49
+ /* Runs on the coroutine's private stack and never returns normally: it
50
+ * suspends back to the resumer once the body has completed. */
51
+ static void xt_async_run(void *arg) {
52
+ xt_async *async = (xt_async *)arg;
53
+ void *boundary = xt_try_enter();
54
+ if (xt_try_setjmp(boundary) == 0) {
55
+ xt_function *function = (xt_function *)XT_GET_PTR(async->function);
56
+ async->value = function->code(async->this_value, async->function, async->argc, async->args);
57
+ async->threw = 0;
58
+ } else {
59
+ /* `throw` inside the body (not caught by a `try`) rejects the promise. */
60
+ async->value = xt_try_exception(boundary);
61
+ async->threw = 1;
62
+ }
63
+ async->done = 1;
64
+ xt_coroutine_leave(async->coroutine);
65
+ }
66
+
67
+ /* Switch onto the coroutine. It either suspends at an `await` or completes. */
68
+ static void xt_async_resume(xt_async *async) {
69
+ xt_async *previous = g_active_async;
70
+ g_active_async = async;
71
+ xt_coroutine_enter(async->coroutine);
72
+ g_active_async = previous;
73
+ if (!async->done) return;
74
+ xt_coroutine_release(async->coroutine);
75
+ async->coroutine = NULL;
76
+ if (async->threw) xt_promise_reject_value(async->promise, async->value);
77
+ else xt_promise_fulfill(async->promise, async->value);
78
+ }
79
+
80
+ xt_value xt_async_call(xt_value function, xt_value thisValue, int32_t argc, xt_value *argv) {
81
+ xt_async *async = (xt_async *)xt_alloc(sizeof(xt_async), XT_OBJECT_KIND_ASYNC);
82
+ async->function = function;
83
+ async->this_value = thisValue;
84
+ async->argc = argc;
85
+ async->args = NULL;
86
+ if (argc > 0 && argv) {
87
+ async->args = (xt_value *)malloc(sizeof(xt_value) * (size_t)argc);
88
+ if (!async->args) abort();
89
+ memcpy(async->args, argv, sizeof(xt_value) * (size_t)argc);
90
+ }
91
+ async->promise = xt_promise_create();
92
+ async->value = XT_UNDEFINED;
93
+ async->resume_value = XT_UNDEFINED;
94
+ async->await_threw = 0;
95
+ async->done = 0;
96
+ async->threw = 0;
97
+ async->coroutine = xt_coroutine_create(xt_async_run, async);
98
+ xt_async_gc_register();
99
+ /* Start synchronously, matching JavaScript: the body runs up to its first
100
+ * pending `await` before the call returns. */
101
+ xt_async_resume(async);
102
+ return async->promise;
103
+ }
104
+
105
+ void xt_async_wake(void *asyncPointer, xt_value value, int rejected) {
106
+ xt_async *async = (xt_async *)asyncPointer;
107
+ if (!async || async->done || !async->coroutine) return;
108
+ async->resume_value = value;
109
+ async->await_threw = rejected;
110
+ xt_async_resume(async);
111
+ }
112
+
113
+ /* `await value`: unwrap promises, suspending the running async coroutine when
114
+ * the promise is still pending. */
115
+ xt_value xt_await(xt_value value) {
116
+ int state = xt_promise_state_of(value);
117
+ if (state < 0) return value;
118
+ if (state == XT_PROMISE_REJECTED) {
119
+ xt_throw(xt_promise_value_of(value));
120
+ return XT_UNDEFINED;
121
+ }
122
+ if (state == XT_PROMISE_FULFILLED) return xt_promise_value_of(value);
123
+
124
+ xt_async *async = g_active_async;
125
+ if (async && async->coroutine) {
126
+ async->await_threw = 0;
127
+ async->resume_value = XT_UNDEFINED;
128
+ xt_promise_add_async_waiter(value, async);
129
+ xt_coroutine_leave(async->coroutine);
130
+ /* The waiter resumed us. A rejection is rethrown inside the coroutine so
131
+ * its own `try`/`catch` and `finally` see it. */
132
+ if (async->await_threw) {
133
+ async->await_threw = 0;
134
+ xt_throw(async->resume_value);
135
+ return XT_UNDEFINED;
136
+ }
137
+ return async->resume_value;
138
+ }
139
+
140
+ /* No coroutine to suspend (top-level `await`, or `await` reached outside an
141
+ * async function): block on the reactor until the promise settles, exactly
142
+ * like a synchronous embedder waiting for the event loop. The loop returns
143
+ * immediately when no work remains, so a promise nothing can settle does not
144
+ * hang the process here. */
145
+ for (int guard = 0; guard < 100000; guard++) {
146
+ xt_drain_microtasks();
147
+ state = xt_promise_state_of(value);
148
+ if (state != XT_PROMISE_PENDING) break;
149
+ if (!xt_loop_has_work()) break;
150
+ if (xt_loop_poll(-1) < 0) break;
151
+ }
152
+ state = xt_promise_state_of(value);
153
+ if (state == XT_PROMISE_REJECTED) {
154
+ xt_throw(xt_promise_value_of(value));
155
+ return XT_UNDEFINED;
156
+ }
157
+ return state == XT_PROMISE_FULFILLED ? xt_promise_value_of(value) : XT_UNDEFINED;
158
+ }
159
+
160
+ /* While a coroutine runs, the async object is only reachable through this
161
+ * static pointer. */
162
+ static void xt_async_gc_scan_roots(void) {
163
+ if (!g_active_async) return;
164
+ xt_gc_mark_header((xt_header *)g_active_async);
165
+ }
166
+
167
+ static void xt_async_gc_register(void) {
168
+ static int registered = 0;
169
+ if (registered) return;
170
+ registered = 1;
171
+ xt_gc_register_root_provider(xt_async_gc_scan_roots);
172
+ }
173
+
174
+ void xt_async_gc_trace(xt_header *header) {
175
+ xt_async *async = (xt_async *)header;
176
+ xt_gc_mark_value(async->function);
177
+ xt_gc_mark_value(async->this_value);
178
+ xt_gc_mark_value(async->promise);
179
+ xt_gc_mark_value(async->value);
180
+ xt_gc_mark_value(async->resume_value);
181
+ if (async->args && async->argc > 0) {
182
+ xt_gc_scan_region(async->args, sizeof(xt_value) * (size_t)async->argc);
183
+ }
184
+ xt_coroutine_gc_trace(async->coroutine);
185
+ }
186
+
187
+ void xt_async_gc_release(xt_header *header) {
188
+ xt_async *async = (xt_async *)header;
189
+ free(async->args);
190
+ xt_coroutine_release(async->coroutine);
191
+ async->coroutine = NULL;
192
+ }
193
+
194
+ xt_value xt_function_set_async(xt_value value) {
195
+ if (XT_IS_FUNCTION(value)) ((xt_function *)XT_GET_PTR(value))->is_async = 1;
196
+ return value;
197
+ }
@@ -18,23 +18,22 @@
18
18
 
19
19
  xt_value xt_object_values(xt_value value) {
20
20
  xt_value result = xt_array_new(0, NULL);
21
- if (!XT_IS_OBJECT(value)) return result;
22
- xt_object *obj = (xt_object *)XT_GET_PTR(value);
23
- for (uint32_t i = 0; i < obj->count; i++) {
24
- if (XT_IS_STRING(obj->properties[i].key)) xt_array_push(result, obj->properties[i].value);
25
- }
21
+ xt_value keys = xt_object_keys(value);
22
+ xt_array *array = xt_as_array(keys);
23
+ if (!array) return result;
24
+ for (uint32_t i = 0; i < array->length; i++) xt_array_push(result, xt_get(value, array->items[i]));
26
25
  return result;
27
26
  }
28
27
 
29
28
  xt_value xt_object_entries(xt_value value) {
30
29
  xt_value result = xt_array_new(0, NULL);
31
- if (!XT_IS_OBJECT(value)) return result;
32
- xt_object *obj = (xt_object *)XT_GET_PTR(value);
33
- for (uint32_t i = 0; i < obj->count; i++) {
34
- if (!XT_IS_STRING(obj->properties[i].key)) continue;
30
+ xt_value keys = xt_object_keys(value);
31
+ xt_array *array = xt_as_array(keys);
32
+ if (!array) return result;
33
+ for (uint32_t i = 0; i < array->length; i++) {
35
34
  xt_value pair = xt_array_new(0, NULL);
36
- xt_array_push(pair, obj->properties[i].key);
37
- xt_array_push(pair, obj->properties[i].value);
35
+ xt_array_push(pair, array->items[i]);
36
+ xt_array_push(pair, xt_get(value, array->items[i]));
38
37
  xt_array_push(result, pair);
39
38
  }
40
39
  return result;
@@ -45,8 +44,11 @@ xt_value xt_object_assign(int32_t argc, xt_value *argv) {
45
44
  xt_value target = argv[0];
46
45
  if (!XT_IS_OBJECT(target)) return target;
47
46
  for (int32_t i = 1; i < argc; i++) {
48
- if (!XT_IS_OBJECT(argv[i])) continue;
49
- xt_object *source = (xt_object *)XT_GET_PTR(argv[i]);
47
+ if (!XT_IS_OBJECT(argv[i]) && !XT_IS_ARRAY(argv[i]) && !XT_IS_STRING(argv[i])) continue;
48
+ /* Arrays and strings contribute their index properties, exactly like
49
+ * `{ ..."ab" }` / `Object.assign({}, [1, 2])` in ECMAScript. */
50
+ xt_value properties = xt_object_rest(argv[i], 0, NULL);
51
+ xt_object *source = (xt_object *)XT_GET_PTR(properties);
50
52
  for (uint32_t j = 0; j < source->count; j++) {
51
53
  xt_object_set(target, source->properties[j].key, source->properties[j].value);
52
54
  }
@@ -102,11 +104,11 @@ xt_value xt_object_rest(xt_value source, int32_t argc, xt_value *argv) {
102
104
 
103
105
  if (XT_IS_STRING(source)) {
104
106
  xt_string *string = (xt_string *)XT_GET_PTR(source);
105
- const char *bytes = xt_string_bytes(string);
106
- for (uint32_t i = 0; i < string->length; i++) {
107
+ uint32_t units = xt_string_utf16_length(string);
108
+ for (uint32_t i = 0; i < units; i++) {
107
109
  char buffer[16];
108
110
  snprintf(buffer, sizeof(buffer), "%u", i);
109
- xt_object_rest_copy(result, xt_string_from_cstr(buffer), xt_string_new(bytes + i, 1), argc, argv);
111
+ xt_object_rest_copy(result, xt_string_from_cstr(buffer), xt_string_unit_value(string, (int32_t)i), argc, argv);
110
112
  }
111
113
  return result;
112
114
  }
@@ -395,7 +397,7 @@ xt_value xt_in(xt_value key, xt_value value) {
395
397
  xt_string *k = xt_as_string(xt_to_string(key));
396
398
  if (k->length == 6 && memcmp(xt_string_bytes(k), "length", 6) == 0) return XT_TRUE;
397
399
  int32_t index = xt_to_int32(key);
398
- return xt_bool(index >= 0 && (uint32_t)index < s->length);
400
+ return xt_bool(index >= 0 && (uint32_t)index < xt_string_utf16_length(s));
399
401
  }
400
402
  return XT_FALSE;
401
403
  }
@@ -446,6 +448,9 @@ xt_value xt_closure_new(void *fn, int32_t env_count, xt_value *env) {
446
448
  function->properties = NULL;
447
449
  function->environment = NULL;
448
450
  function->prototype = XT_UNDEFINED;
451
+ function->is_generator = 0;
452
+ function->is_async = 0;
453
+ function->builtin_name = NULL;
449
454
  if (env_count > 0) {
450
455
  function->environment = (xt_value *)malloc(sizeof(xt_value) * (size_t)env_count);
451
456
  for (int32_t i = 0; i < env_count; i++) function->environment[i] = env[i];
@@ -466,6 +471,9 @@ xt_value xt_call_with_this(xt_value value, xt_value thisValue, int32_t argc, xt_
466
471
  /* Calling a generator function returns a suspended generator instead of
467
472
  * running the body. */
468
473
  if (function->is_generator) return xt_generator_new(value, thisValue, argc, argv);
474
+ /* An `async` function runs its body on a coroutine and returns a promise,
475
+ * which may already be settled or may resolve after an event-loop turn. */
476
+ if (function->is_async) return xt_async_call(value, thisValue, argc, argv);
469
477
  /* The closure value itself is threaded through as `env` so the callee can
470
478
  * read its captures with xt_closure_env(env, i). */
471
479
  return function->code(thisValue, value, argc, argv);
@@ -478,11 +486,6 @@ xt_value xt_function_set_prototype(xt_value value, xt_value proto) {
478
486
  return value;
479
487
  }
480
488
 
481
- xt_value xt_function_set_generator(xt_value value) {
482
- if (XT_IS_FUNCTION(value)) ((xt_function *)XT_GET_PTR(value))->is_generator = 1;
483
- return value;
484
- }
485
-
486
489
  xt_value xt_function_get_prototype(xt_value value) {
487
490
  if (XT_IS_FUNCTION(value)) return ((xt_function *)XT_GET_PTR(value))->prototype;
488
491
  return XT_UNDEFINED;
@@ -497,22 +500,22 @@ xt_value xt_new(xt_value ctor, int32_t argc, xt_value *argv) {
497
500
  xt_value proto = function->prototype;
498
501
  xt_value instance = xt_object_new_with_proto(XT_IS_OBJECT(proto) ? proto : XT_UNDEFINED);
499
502
  xt_value result = function->code(instance, ctor, argc, argv);
500
- if (XT_IS_OBJECT(result) || XT_IS_ARRAY(result)) return result;
503
+ if (XT_IS_OBJECT(result) || XT_IS_ARRAY(result) || XT_IS_FUNCTION(result)) return result;
501
504
  return instance;
502
505
  }
503
506
 
504
507
  xt_value xt_instance_of(xt_value value, xt_value ctor) {
505
508
  if (!XT_IS_FUNCTION(ctor)) return XT_FALSE;
506
509
  xt_value proto = ((xt_function *)XT_GET_PTR(ctor))->prototype;
507
- if (!XT_IS_OBJECT(proto)) return XT_FALSE;
508
- if (!XT_IS_OBJECT(value)) return XT_FALSE;
509
- xt_object *cur = (xt_object *)XT_GET_PTR(value);
510
- xt_object *target = (xt_object *)XT_GET_PTR(proto);
511
- cur = (xt_object *)XT_GET_PTR(XT_IS_OBJECT(cur->prototype) ? cur->prototype : XT_UNDEFINED);
512
- while (cur) {
513
- if (cur == target) return XT_TRUE;
514
- if (!XT_IS_OBJECT(cur->prototype)) break;
515
- cur = (xt_object *)XT_GET_PTR(cur->prototype);
510
+ if (XT_IS_OBJECT(proto) || XT_IS_ARRAY(proto) || XT_IS_FUNCTION(proto)) {
511
+ /* Walk the value's [[Prototype]] chain. `xt_object_get_prototype` knows the
512
+ * layout of every kind (arrays carry a prototype field; Map/Set/Date/... use
513
+ * their built-in prototype), so this is safe for containers too. */
514
+ xt_value cursor = xt_object_get_prototype(value);
515
+ while (cursor != XT_UNDEFINED && cursor != XT_NULL) {
516
+ if (cursor == proto) return XT_TRUE;
517
+ cursor = xt_object_get_prototype(cursor);
518
+ }
516
519
  }
517
520
  return XT_FALSE;
518
521
  }
@@ -520,10 +523,11 @@ xt_value xt_instance_of(xt_value value, xt_value ctor) {
520
523
  /*
521
524
  * `value instanceof Array` (and the rest of the built-in constructors).
522
525
  *
523
- * Built-in constructors are not first-class values: the codegen recognises the
524
- * name and asks this helper instead. Ids match `BUILTIN_INSTANCEOF` in
525
- * `src/codegen/generator/tables.ts`; 16..24 map onto the runtime's `XT_TA_*`
526
- * order (Uint8Array first).
526
+ * The codegen recognises the direct spelling and asks this helper instead of
527
+ * materialising the constructor value; an alias (`const A = Array`) goes
528
+ * through the ordinary prototype chain in `xt_instance_of`. Ids match
529
+ * `BUILTIN_INSTANCEOF` in `src/codegen/generator/tables.ts`; 16..24 map onto
530
+ * the runtime's `XT_TA_*` order (Uint8Array first).
527
531
  */
528
532
  enum {
529
533
  XT_INSTANCE_ARRAY = 0,