xbintsc 0.3.15 → 0.3.16

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 (156) hide show
  1. package/dist/src/cli/main.js +4 -4
  2. package/dist/src/cli/main.js.map +1 -1
  3. package/dist/src/codegen/generator/state.js +8 -0
  4. package/dist/src/codegen/generator/state.js.map +1 -1
  5. package/dist/src/codegen/generator/tables.js +8 -0
  6. package/dist/src/codegen/generator/tables.js.map +1 -1
  7. package/dist/src/driver/bundler/graph.d.ts +8 -0
  8. package/dist/src/driver/bundler/graph.js +85 -0
  9. package/dist/src/driver/bundler/graph.js.map +1 -0
  10. package/dist/src/driver/bundler/merge.d.ts +11 -0
  11. package/dist/src/driver/bundler/merge.js +288 -0
  12. package/dist/src/driver/bundler/merge.js.map +1 -0
  13. package/dist/src/driver/bundler/resolve.d.ts +12 -0
  14. package/dist/src/driver/bundler/resolve.js +246 -0
  15. package/dist/src/driver/bundler/resolve.js.map +1 -0
  16. package/dist/src/driver/bundler/symbols.d.ts +15 -0
  17. package/dist/src/driver/bundler/symbols.js +51 -0
  18. package/dist/src/driver/bundler/symbols.js.map +1 -0
  19. package/dist/src/driver/bundler/types.d.ts +36 -0
  20. package/dist/src/driver/bundler/types.js +5 -0
  21. package/dist/src/driver/bundler/types.js.map +1 -0
  22. package/dist/src/driver/modules.d.ts +9 -16
  23. package/dist/src/driver/modules.js +8 -644
  24. package/dist/src/driver/modules.js.map +1 -1
  25. package/dist/src/extensions/catalog.js +2 -1
  26. package/dist/src/extensions/catalog.js.map +1 -1
  27. package/dist/src/extensions/gui/index.d.ts +24 -0
  28. package/dist/src/extensions/gui/index.js +101 -0
  29. package/dist/src/extensions/gui/index.js.map +1 -0
  30. package/dist/tests/e2e/compile-basics.test.d.ts +4 -0
  31. package/dist/tests/e2e/compile-basics.test.js +134 -0
  32. package/dist/tests/e2e/compile-basics.test.js.map +1 -0
  33. package/dist/tests/e2e/compile-builtins.test.d.ts +4 -0
  34. package/dist/tests/e2e/compile-builtins.test.js +191 -0
  35. package/dist/tests/e2e/compile-builtins.test.js.map +1 -0
  36. package/dist/tests/e2e/compile-control-flow.test.d.ts +4 -0
  37. package/dist/tests/e2e/compile-control-flow.test.js +190 -0
  38. package/dist/tests/e2e/compile-control-flow.test.js.map +1 -0
  39. package/dist/tests/e2e/compile-modules.test.d.ts +4 -0
  40. package/dist/tests/e2e/compile-modules.test.js +48 -0
  41. package/dist/tests/e2e/compile-modules.test.js.map +1 -0
  42. package/dist/tests/e2e/compile-objects-classes.test.d.ts +4 -0
  43. package/dist/tests/e2e/compile-objects-classes.test.js +362 -0
  44. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -0
  45. package/dist/tests/e2e/differential-helpers.d.ts +12 -0
  46. package/dist/tests/e2e/differential-helpers.js +16 -0
  47. package/dist/tests/e2e/differential-helpers.js.map +1 -0
  48. package/dist/tests/e2e/differential-language.test.d.ts +4 -0
  49. package/dist/tests/e2e/{differential.test.js → differential-language.test.js} +5 -287
  50. package/dist/tests/e2e/differential-language.test.js.map +1 -0
  51. package/dist/tests/e2e/differential-symbols.test.d.ts +4 -0
  52. package/dist/tests/e2e/differential-symbols.test.js +92 -0
  53. package/dist/tests/e2e/differential-symbols.test.js.map +1 -0
  54. package/dist/tests/e2e/differential-values.test.d.ts +4 -0
  55. package/dist/tests/e2e/differential-values.test.js +197 -0
  56. package/dist/tests/e2e/differential-values.test.js.map +1 -0
  57. package/dist/tests/e2e/gui.test.d.ts +12 -0
  58. package/dist/tests/e2e/gui.test.js +493 -0
  59. package/dist/tests/e2e/gui.test.js.map +1 -0
  60. package/dist/tests/e2e/runtime-coverage-buffer.test.d.ts +4 -0
  61. package/dist/tests/e2e/runtime-coverage-buffer.test.js +97 -0
  62. package/dist/tests/e2e/runtime-coverage-buffer.test.js.map +1 -0
  63. package/dist/tests/e2e/runtime-coverage-globals.test.d.ts +4 -0
  64. package/dist/tests/e2e/runtime-coverage-globals.test.js +149 -0
  65. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -0
  66. package/dist/tests/e2e/runtime-coverage-node-modules.test.d.ts +4 -0
  67. package/dist/tests/e2e/runtime-coverage-node-modules.test.js +177 -0
  68. package/dist/tests/e2e/runtime-coverage-node-modules.test.js.map +1 -0
  69. package/dist/tests/e2e/runtime-coverage-streams.test.d.ts +4 -0
  70. package/dist/tests/e2e/runtime-coverage-streams.test.js +161 -0
  71. package/dist/tests/e2e/runtime-coverage-streams.test.js.map +1 -0
  72. package/dist/tests/e2e/runtime-coverage-system.test.d.ts +4 -0
  73. package/dist/tests/e2e/runtime-coverage-system.test.js +91 -0
  74. package/dist/tests/e2e/runtime-coverage-system.test.js.map +1 -0
  75. package/dist/tests/e2e/timers.test.d.ts +7 -0
  76. package/dist/tests/e2e/timers.test.js +63 -0
  77. package/dist/tests/e2e/timers.test.js.map +1 -0
  78. package/package.json +2 -1
  79. package/runtime/ext_gui/css.cpp +743 -0
  80. package/runtime/ext_gui/css.h +98 -0
  81. package/runtime/ext_gui/document.cpp +264 -0
  82. package/runtime/ext_gui/document.h +89 -0
  83. package/runtime/ext_gui/dom.cpp +472 -0
  84. package/runtime/ext_gui/dom.h +71 -0
  85. package/runtime/ext_gui/gui.cpp +457 -0
  86. package/runtime/ext_gui/gui_engine.h +81 -0
  87. package/runtime/ext_gui/image.cpp +125 -0
  88. package/runtime/ext_gui/image.h +42 -0
  89. package/runtime/ext_gui/layout.cpp +748 -0
  90. package/runtime/ext_gui/layout.h +93 -0
  91. package/runtime/ext_gui/paint.cpp +137 -0
  92. package/runtime/ext_gui/paint.h +75 -0
  93. package/runtime/ext_gui/renderer.cpp +959 -0
  94. package/runtime/ext_gui/renderer.h +68 -0
  95. package/runtime/ext_gui/stb_image.h +7988 -0
  96. package/runtime/ext_gui/style.cpp +771 -0
  97. package/runtime/ext_gui/style.h +117 -0
  98. package/runtime/ext_gui/text.cpp +378 -0
  99. package/runtime/ext_gui/text.h +78 -0
  100. package/runtime/ext_gui/window.cpp +405 -0
  101. package/runtime/ext_node/fs/fs_common.h +13 -527
  102. package/runtime/ext_node/fs/parts/buffer-bridge.inc +6 -0
  103. package/runtime/ext_node/fs/parts/builtins.inc +69 -0
  104. package/runtime/ext_node/fs/parts/dir.inc +47 -0
  105. package/runtime/ext_node/fs/parts/encoding.inc +186 -0
  106. package/runtime/ext_node/fs/parts/errors.inc +66 -0
  107. package/runtime/ext_node/fs/parts/flags.inc +39 -0
  108. package/runtime/ext_node/fs/parts/platform.inc +86 -0
  109. package/runtime/ext_node/fs/parts/promisify.inc +15 -0
  110. package/runtime/ext_node/fs/parts/time.inc +9 -0
  111. package/runtime/rt.h +33 -0
  112. package/runtime/xt_builtins.c +4 -0
  113. package/runtime/xt_containers/access.inc +168 -0
  114. package/runtime/xt_containers/arrays.inc +108 -0
  115. package/runtime/xt_containers/boxes.inc +11 -0
  116. package/runtime/xt_containers/objects.inc +326 -0
  117. package/runtime/xt_containers.c +8 -618
  118. package/runtime/xt_io/base64.inc +87 -0
  119. package/runtime/xt_io/console.inc +119 -0
  120. package/runtime/xt_io/exception.inc +142 -0
  121. package/runtime/xt_io/inspect.inc +212 -0
  122. package/runtime/xt_io.c +8 -553
  123. package/runtime/xt_loop.c +262 -24
  124. package/runtime/xt_regex/api.inc +58 -0
  125. package/runtime/xt_regex/helpers.inc +68 -0
  126. package/runtime/xt_regex/matcher.inc +248 -0
  127. package/runtime/xt_regex/parser.inc +236 -0
  128. package/runtime/xt_regex/types.inc +58 -0
  129. package/runtime/xt_regex.h +9 -672
  130. package/runtime/xt_values/arithmetic.inc +150 -0
  131. package/runtime/xt_values/comparison.inc +112 -0
  132. package/runtime/xt_values/constructors.inc +41 -0
  133. package/runtime/xt_values/conversions.inc +115 -0
  134. package/runtime/xt_values/number-format.inc +180 -0
  135. package/runtime/xt_values/strings.inc +22 -0
  136. package/runtime/xt_values.c +10 -626
  137. package/scripts/build-gui.ts +317 -0
  138. package/src/cli/main.ts +4 -3
  139. package/src/codegen/generator/state.ts +8 -0
  140. package/src/codegen/generator/tables.ts +8 -0
  141. package/src/driver/bundler/graph.ts +95 -0
  142. package/src/driver/bundler/merge.ts +312 -0
  143. package/src/driver/bundler/resolve.ts +240 -0
  144. package/src/driver/bundler/symbols.ts +55 -0
  145. package/src/driver/bundler/types.ts +37 -0
  146. package/src/driver/modules.ts +9 -693
  147. package/src/extensions/catalog.ts +2 -1
  148. package/src/extensions/gui/index.ts +107 -0
  149. package/dist/tests/e2e/compile.test.d.ts +0 -8
  150. package/dist/tests/e2e/compile.test.js +0 -893
  151. package/dist/tests/e2e/compile.test.js.map +0 -1
  152. package/dist/tests/e2e/differential.test.d.ts +0 -7
  153. package/dist/tests/e2e/differential.test.js.map +0 -1
  154. package/dist/tests/e2e/runtime-coverage.test.d.ts +0 -11
  155. package/dist/tests/e2e/runtime-coverage.test.js +0 -646
  156. package/dist/tests/e2e/runtime-coverage.test.js.map +0 -1
package/runtime/xt_loop.c CHANGED
@@ -5,12 +5,14 @@
5
5
  * `dgram`, `http`, ...) deliver callbacks without baking an OS abstraction
6
6
  * into the compiler. Generated `main` calls `xt_run_event_loop()` once the
7
7
  * program body (and its microtasks) have finished; the loop returns as soon as
8
- * no file descriptors remain registered, so programs that never touch I/O are
9
- * unaffected.
8
+ * no file descriptors or timers remain registered, so programs that never touch
9
+ * I/O or timers are unaffected.
10
10
  *
11
11
  * Extensions register descriptors with `xt_loop_add` and receive readiness
12
- * callbacks. The loop drains the promise microtask queue after every batch of
13
- * callbacks so `await`/`.then` chains keep working inside handlers.
12
+ * callbacks. The timer globals (`setTimeout` / `setInterval`) share the same
13
+ * wait: the nearest deadline bounds `select(2)`, so a timer-only program sleeps
14
+ * instead of spinning. The loop drains the promise microtask queue after every
15
+ * batch of callbacks so `await`/`.then` chains keep working inside handlers.
14
16
  */
15
17
 
16
18
  #include "rt_internal.h"
@@ -18,9 +20,11 @@
18
20
  #include <stdio.h>
19
21
  #include <stdlib.h>
20
22
  #include <string.h>
23
+ #include <time.h>
21
24
 
22
25
  #if defined(_WIN32)
23
26
  #include <winsock2.h>
27
+ #include <windows.h>
24
28
  #else
25
29
  #include <sys/select.h>
26
30
  #include <sys/time.h>
@@ -48,6 +52,184 @@ static xt_loop_watcher *xt_loop_find(int fd) {
48
52
  return NULL;
49
53
  }
50
54
 
55
+ /* -- timers --------------------------------------------------------------- */
56
+ /*
57
+ * `setTimeout` / `setInterval` and their `clear*` counterparts are implemented
58
+ * here, next to the reactor they piggyback on. The pending timers live in a
59
+ * flat array (the same shape as the watcher table above); before every wait the
60
+ * reactor looks at the nearest deadline and uses it to bound `select(2)`, so a
61
+ * program whose only pending work is a timer still sleeps instead of spinning.
62
+ *
63
+ * Callbacks are ordinary closure values invoked through `xt_closure_call`, so
64
+ * they capture their lexical environment like any other JS function.
65
+ */
66
+ typedef struct {
67
+ uint64_t id;
68
+ xt_value callback;
69
+ xt_value *args;
70
+ int32_t argc;
71
+ int64_t due_ms;
72
+ int64_t interval_ms;
73
+ int active;
74
+ } xt_timer;
75
+
76
+ static xt_timer *xt_timers = NULL;
77
+ static uint32_t xt_timer_count = 0;
78
+ static uint32_t xt_timer_capacity = 0;
79
+ static uint64_t xt_timer_next_id = 1;
80
+
81
+ static int64_t xt_loop_now_ms(void) {
82
+ #if defined(_WIN32)
83
+ return (int64_t)GetTickCount64();
84
+ #else
85
+ struct timespec ts;
86
+ clock_gettime(CLOCK_MONOTONIC, &ts);
87
+ return (int64_t)ts.tv_sec * 1000 + (int64_t)(ts.tv_nsec / 1000000);
88
+ #endif
89
+ }
90
+
91
+ static void xt_loop_sleep_ms(int ms) {
92
+ if (ms <= 0) return;
93
+ #if defined(_WIN32)
94
+ Sleep((DWORD)ms);
95
+ #else
96
+ struct timespec ts;
97
+ ts.tv_sec = ms / 1000;
98
+ ts.tv_nsec = (long)(ms % 1000) * 1000000L;
99
+ nanosleep(&ts, NULL);
100
+ #endif
101
+ }
102
+
103
+ /* Coerce a delay argument to a non-negative whole number of milliseconds. */
104
+ static int64_t xt_timer_delay_arg(xt_value value) {
105
+ double d = xt_to_number(value);
106
+ if (d != d || d < 0) return 0;
107
+ if (d > 2147483647.0) return 2147483647;
108
+ return (int64_t)d;
109
+ }
110
+
111
+ static uint32_t xt_timer_active_count(void) {
112
+ uint32_t count = 0;
113
+ for (uint32_t i = 0; i < xt_timer_count; i++) {
114
+ if (xt_timers[i].active) count++;
115
+ }
116
+ return count;
117
+ }
118
+
119
+ /* Earliest deadline among the live timers, or -1 when none is pending. */
120
+ static int64_t xt_timer_next_due(void) {
121
+ int64_t next = -1;
122
+ for (uint32_t i = 0; i < xt_timer_count; i++) {
123
+ if (!xt_timers[i].active) continue;
124
+ if (next < 0 || xt_timers[i].due_ms < next) next = xt_timers[i].due_ms;
125
+ }
126
+ return next;
127
+ }
128
+
129
+ /* Reclaim slots left by cleared/fired one-shot timers. */
130
+ static void xt_timer_compact(void) {
131
+ uint32_t write = 0;
132
+ for (uint32_t read = 0; read < xt_timer_count; read++) {
133
+ if (!xt_timers[read].active) continue;
134
+ if (write != read) xt_timers[write] = xt_timers[read];
135
+ write++;
136
+ }
137
+ xt_timer_count = write;
138
+ }
139
+
140
+ static uint64_t xt_timer_add(xt_value callback, int64_t delay_ms, int64_t interval_ms, int32_t argc, xt_value *argv) {
141
+ if (xt_timer_count >= xt_timer_capacity) {
142
+ xt_timer_capacity = xt_timer_capacity == 0 ? 16 : xt_timer_capacity * 2;
143
+ xt_timer *grown = (xt_timer *)realloc(xt_timers, sizeof(xt_timer) * xt_timer_capacity);
144
+ if (!grown) abort();
145
+ xt_timers = grown;
146
+ }
147
+ xt_timer *timer = &xt_timers[xt_timer_count++];
148
+ timer->id = xt_timer_next_id++;
149
+ timer->callback = callback;
150
+ timer->argc = argc;
151
+ timer->args = NULL;
152
+ if (argc > 0) {
153
+ timer->args = (xt_value *)malloc(sizeof(xt_value) * (size_t)argc);
154
+ if (!timer->args) abort();
155
+ for (int32_t i = 0; i < argc; i++) timer->args[i] = argv[i];
156
+ }
157
+ timer->due_ms = xt_loop_now_ms() + delay_ms;
158
+ timer->interval_ms = interval_ms;
159
+ timer->active = 1;
160
+ return timer->id;
161
+ }
162
+
163
+ static int xt_timer_cancel(uint64_t id) {
164
+ for (uint32_t i = 0; i < xt_timer_count; i++) {
165
+ if (xt_timers[i].active && xt_timers[i].id == id) {
166
+ xt_timers[i].active = 0;
167
+ return 1;
168
+ }
169
+ }
170
+ return 0;
171
+ }
172
+
173
+ /* Invoke every timer whose deadline has passed. Timers scheduled by a callback
174
+ * (or by a microtask) are left for the next reactor iteration, so a recursive
175
+ * `setTimeout(fn, 0)` cannot spin forever inside a single call. */
176
+ static void xt_timer_run_due(void) {
177
+ xt_timer_compact();
178
+ int64_t now = xt_loop_now_ms();
179
+ uint32_t limit = xt_timer_count;
180
+ for (uint32_t i = 0; i < limit; i++) {
181
+ xt_timer *timer = &xt_timers[i];
182
+ if (!timer->active || timer->due_ms > now) continue;
183
+ xt_value callback = timer->callback;
184
+ int32_t argc = timer->argc;
185
+ xt_value *args = timer->args;
186
+ if (timer->interval_ms > 0) {
187
+ timer->due_ms = now + timer->interval_ms;
188
+ } else {
189
+ timer->active = 0;
190
+ }
191
+ if (XT_IS_FUNCTION(callback)) xt_closure_call(callback, argc, args);
192
+ }
193
+ }
194
+
195
+ xt_value xt_set_timeout(int32_t argc, xt_value *argv) {
196
+ xt_value callback = xt_arg_at(argc, argv, 0);
197
+ if (!XT_IS_FUNCTION(callback)) {
198
+ xt_throw(xt_string_from_cstr("TypeError: The \"callback\" argument must be of type function"));
199
+ return XT_UNDEFINED;
200
+ }
201
+ int64_t delay = xt_timer_delay_arg(xt_arg_at(argc, argv, 1));
202
+ int32_t extra = argc > 2 ? argc - 2 : 0;
203
+ uint64_t id = xt_timer_add(callback, delay, 0, extra, extra > 0 ? argv + 2 : NULL);
204
+ return xt_number((double)id);
205
+ }
206
+
207
+ xt_value xt_set_interval(int32_t argc, xt_value *argv) {
208
+ xt_value callback = xt_arg_at(argc, argv, 0);
209
+ if (!XT_IS_FUNCTION(callback)) {
210
+ xt_throw(xt_string_from_cstr("TypeError: The \"callback\" argument must be of type function"));
211
+ return XT_UNDEFINED;
212
+ }
213
+ int64_t delay = xt_timer_delay_arg(xt_arg_at(argc, argv, 1));
214
+ /* Intervals are clamped to at least 1ms so an interval can never busy-spin. */
215
+ int64_t interval = delay > 0 ? delay : 1;
216
+ int32_t extra = argc > 2 ? argc - 2 : 0;
217
+ uint64_t id = xt_timer_add(callback, delay, interval, extra, extra > 0 ? argv + 2 : NULL);
218
+ return xt_number((double)id);
219
+ }
220
+
221
+ xt_value xt_clear_timeout(int32_t argc, xt_value *argv) {
222
+ double id = xt_to_number(xt_arg_at(argc, argv, 0));
223
+ if (id >= 1) xt_timer_cancel((uint64_t)id);
224
+ return XT_UNDEFINED;
225
+ }
226
+
227
+ xt_value xt_clear_interval(int32_t argc, xt_value *argv) {
228
+ double id = xt_to_number(xt_arg_at(argc, argv, 0));
229
+ if (id >= 1) xt_timer_cancel((uint64_t)id);
230
+ return XT_UNDEFINED;
231
+ }
232
+
51
233
  int xt_loop_add(int fd, int events, xt_io_handler handler, void *userdata) {
52
234
  if (fd < 0 || !handler) return -1;
53
235
  xt_loop_watcher *existing = xt_loop_find(fd);
@@ -81,26 +263,67 @@ void xt_loop_remove(int fd) {
81
263
  xt_loop_active_count--;
82
264
  }
83
265
 
84
- void xt_run_event_loop(void) {
85
- while (xt_loop_active_count > 0) {
86
- fd_set readSet;
87
- fd_set writeSet;
88
- FD_ZERO(&readSet);
89
- FD_ZERO(&writeSet);
90
- int maxFd = -1;
91
- for (int i = 0; i < xt_loop_watcher_count; i++) {
92
- xt_loop_watcher *watcher = &xt_loop_watchers[i];
93
- if (!watcher->active) continue;
94
- if (watcher->events & XT_IO_READ) FD_SET(watcher->fd, &readSet);
95
- if (watcher->events & XT_IO_WRITE) FD_SET(watcher->fd, &writeSet);
96
- if (watcher->fd > maxFd) maxFd = watcher->fd;
97
- }
98
- if (maxFd < 0) break;
266
+ /* A host (GUI toolkit, embedder) can take over the main loop; see
267
+ * `xt_loop_set_main`. `NULL` keeps the default blocking behaviour. */
268
+ static xt_main_loop_fn xt_loop_main = NULL;
269
+
270
+ void xt_loop_set_main(xt_main_loop_fn fn) { xt_loop_main = fn; }
271
+
272
+ int xt_loop_poll(int timeout_ms) {
273
+ int has_fds = xt_loop_active_count > 0;
274
+ int64_t next_due = xt_timer_next_due();
275
+ if (!has_fds && next_due < 0) {
276
+ xt_drain_microtasks();
277
+ return 0;
278
+ }
279
+
280
+ /* Bound the wait by the nearest timer deadline when one is pending. */
281
+ int wait_ms = timeout_ms;
282
+ if (next_due >= 0) {
283
+ int64_t delta = next_due - xt_loop_now_ms();
284
+ if (delta < 0) delta = 0;
285
+ if (delta > 100000000) delta = 100000000;
286
+ if (wait_ms < 0 || delta < (int64_t)wait_ms) wait_ms = (int)delta;
287
+ }
288
+
289
+ /* Timers only: sleep, then run whatever is due. */
290
+ if (!has_fds) {
291
+ xt_loop_sleep_ms(wait_ms);
292
+ xt_timer_run_due();
293
+ xt_drain_microtasks();
294
+ return (int)(xt_loop_active_count + xt_timer_active_count());
295
+ }
296
+
297
+ fd_set readSet;
298
+ fd_set writeSet;
299
+ FD_ZERO(&readSet);
300
+ FD_ZERO(&writeSet);
301
+ int maxFd = -1;
302
+ for (int i = 0; i < xt_loop_watcher_count; i++) {
303
+ xt_loop_watcher *watcher = &xt_loop_watchers[i];
304
+ if (!watcher->active) continue;
305
+ if (watcher->events & XT_IO_READ) FD_SET(watcher->fd, &readSet);
306
+ if (watcher->events & XT_IO_WRITE) FD_SET(watcher->fd, &writeSet);
307
+ if (watcher->fd > maxFd) maxFd = watcher->fd;
308
+ }
309
+ if (maxFd < 0) {
310
+ xt_timer_run_due();
311
+ xt_drain_microtasks();
312
+ return (int)(xt_loop_active_count + xt_timer_active_count());
313
+ }
314
+
315
+ struct timeval timeout;
316
+ struct timeval *timeoutPtr = NULL;
317
+ if (wait_ms >= 0) {
318
+ timeout.tv_sec = wait_ms / 1000;
319
+ timeout.tv_usec = (wait_ms % 1000) * 1000;
320
+ timeoutPtr = &timeout;
321
+ }
99
322
 
100
- int ready = select(maxFd + 1, &readSet, &writeSet, NULL, NULL);
101
- if (ready < 0) break;
102
- if (ready == 0) continue;
323
+ int ready = select(maxFd + 1, &readSet, &writeSet, NULL, timeoutPtr);
324
+ if (ready < 0) return -1;
103
325
 
326
+ if (ready > 0) {
104
327
  /* Collect the ready watchers first: handlers may add/remove descriptors. */
105
328
  for (int i = 0; i < xt_loop_watcher_count; i++) {
106
329
  xt_loop_watcher *watcher = &xt_loop_watchers[i];
@@ -110,8 +333,23 @@ void xt_run_event_loop(void) {
110
333
  if ((watcher->events & XT_IO_WRITE) && FD_ISSET(watcher->fd, &writeSet)) events |= XT_IO_WRITE;
111
334
  if (events) watcher->handler(watcher->userdata, events);
112
335
  }
113
- xt_drain_microtasks();
336
+ }
337
+
338
+ xt_timer_run_due();
339
+ xt_drain_microtasks();
340
+ return (int)(xt_loop_active_count + xt_timer_active_count());
341
+ }
342
+
343
+ void xt_run_event_loop(void) {
344
+ /* A host that owns the main loop (GUI toolkit) takes precedence: it is
345
+ responsible for calling `xt_loop_poll` while it runs. */
346
+ if (xt_loop_main != NULL) {
347
+ xt_loop_main();
348
+ return;
349
+ }
350
+ while (xt_loop_has_work()) {
351
+ if (xt_loop_poll(-1) < 0) break;
114
352
  }
115
353
  }
116
354
 
117
- int xt_loop_has_work(void) { return xt_loop_active_count > 0; }
355
+ int xt_loop_has_work(void) { return xt_loop_active_count > 0 || xt_timer_active_count() > 0; }
@@ -0,0 +1,58 @@
1
+ /* -- public API ----------------------------------------------------------- */
2
+
3
+ static int regcomp(regex_t *preg, const char *pattern, int cflags) {
4
+ xt_re_parser p;
5
+ xt_re_alt *root;
6
+ if (!preg || !pattern) return 1;
7
+ memset(preg, 0, sizeof(*preg));
8
+ p.s = pattern;
9
+ p.i = 0;
10
+ p.error = 0;
11
+ p.groupCount = 0;
12
+ root = xt_re_parse_alt(&p);
13
+ if (!root || p.error || p.s[p.i] != '\0') {
14
+ if (root) xt_re_free_alt(root);
15
+ return 1;
16
+ }
17
+ preg->root = root;
18
+ preg->icase = (cflags & REG_ICASE) ? 1 : 0;
19
+ return 0;
20
+ }
21
+
22
+ static int regexec(const regex_t *preg, const char *string, size_t nmatch,
23
+ regmatch_t pmatch[], int eflags) {
24
+ xt_re_input in;
25
+ size_t start;
26
+ size_t i;
27
+ (void)eflags;
28
+ if (!preg || !preg->root || !string) return 1;
29
+ if (pmatch) {
30
+ for (i = 0; i < nmatch; i++) {
31
+ pmatch[i].rm_so = -1;
32
+ pmatch[i].rm_eo = -1;
33
+ }
34
+ }
35
+ in.text = string;
36
+ in.len = strlen(string);
37
+ in.icase = preg->icase;
38
+ for (start = 0; start <= in.len; start++) {
39
+ xt_re_match_result result;
40
+ result.end = 0;
41
+ result.pmatch = pmatch;
42
+ result.nmatch = nmatch;
43
+ if (xt_re_match_alt(preg->root, &in, start, xt_re_record_match, &result, NULL)) {
44
+ if (nmatch > 0 && pmatch) {
45
+ pmatch[0].rm_so = (int)start;
46
+ pmatch[0].rm_eo = (int)result.end;
47
+ }
48
+ return 0;
49
+ }
50
+ }
51
+ return 1;
52
+ }
53
+
54
+ static void regfree(regex_t *preg) {
55
+ if (!preg) return;
56
+ xt_re_free_alt(preg->root);
57
+ preg->root = NULL;
58
+ }
@@ -0,0 +1,68 @@
1
+ /* -- small helpers -------------------------------------------------------- */
2
+
3
+ static void xt_re_set_bit(unsigned char *bits, unsigned char c) {
4
+ bits[c >> 3] |= (unsigned char)(1u << (c & 7));
5
+ }
6
+
7
+ static int xt_re_bit_test(const unsigned char *bits, unsigned char c) {
8
+ return (bits[c >> 3] >> (c & 7)) & 1;
9
+ }
10
+
11
+ static unsigned char xt_re_lower(unsigned char c) {
12
+ if (c >= 'A' && c <= 'Z') return (unsigned char)(c - 'A' + 'a');
13
+ return c;
14
+ }
15
+
16
+ static void xt_re_add_range(unsigned char *bits, unsigned char lo, unsigned char hi) {
17
+ unsigned int c;
18
+ if (hi < lo) {
19
+ unsigned char t = lo;
20
+ lo = hi;
21
+ hi = t;
22
+ }
23
+ for (c = lo; c <= hi; c++) xt_re_set_bit(bits, (unsigned char)c);
24
+ }
25
+
26
+ /* Adds a predefined class such as \d or \w. Returns 1 when handled. */
27
+ static int xt_re_add_predefined(unsigned char *bits, char e) {
28
+ int c;
29
+ switch (e) {
30
+ case 'd':
31
+ xt_re_add_range(bits, '0', '9');
32
+ return 1;
33
+ case 'D':
34
+ for (c = 0; c < 256; c++)
35
+ if (!(c >= '0' && c <= '9')) xt_re_set_bit(bits, (unsigned char)c);
36
+ return 1;
37
+ case 'w':
38
+ xt_re_add_range(bits, 'a', 'z');
39
+ xt_re_add_range(bits, 'A', 'Z');
40
+ xt_re_add_range(bits, '0', '9');
41
+ xt_re_set_bit(bits, '_');
42
+ return 1;
43
+ case 'W':
44
+ for (c = 0; c < 256; c++) {
45
+ int is_word = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
46
+ (c >= '0' && c <= '9') || c == '_';
47
+ if (!is_word) xt_re_set_bit(bits, (unsigned char)c);
48
+ }
49
+ return 1;
50
+ case 's':
51
+ xt_re_set_bit(bits, ' ');
52
+ xt_re_set_bit(bits, '\t');
53
+ xt_re_set_bit(bits, '\n');
54
+ xt_re_set_bit(bits, '\r');
55
+ xt_re_set_bit(bits, '\f');
56
+ xt_re_set_bit(bits, '\v');
57
+ return 1;
58
+ case 'S':
59
+ for (c = 0; c < 256; c++) {
60
+ int is_space = c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
61
+ c == '\f' || c == '\v';
62
+ if (!is_space) xt_re_set_bit(bits, (unsigned char)c);
63
+ }
64
+ return 1;
65
+ default:
66
+ return 0;
67
+ }
68
+ }
@@ -0,0 +1,248 @@
1
+ /* -- matching ------------------------------------------------------------- */
2
+
3
+ typedef struct {
4
+ const char *text;
5
+ size_t len;
6
+ int icase;
7
+ } xt_re_input;
8
+
9
+ /* One capture on the immutable chain of the current search path. `parent`
10
+ * points to the captures that were live when the group was entered, so an
11
+ * abandoned backtrack simply drops the frame instead of restoring it. */
12
+ typedef struct xt_re_cap {
13
+ const struct xt_re_cap *parent;
14
+ int index; /* 1-based capture group number */
15
+ int start;
16
+ int end;
17
+ } xt_re_cap;
18
+
19
+ typedef int (*xt_re_cont)(void *ctx, size_t pos, const xt_re_cap *caps);
20
+
21
+ static void xt_re_free_alt(xt_re_alt *alt);
22
+
23
+ static void xt_re_free_node(xt_re_node *n) {
24
+ if (!n) return;
25
+ if (n->type == XT_RE_GROUP) xt_re_free_alt(n->group);
26
+ free(n->classbits);
27
+ free(n);
28
+ }
29
+
30
+ static void xt_re_free_alt(xt_re_alt *alt) {
31
+ int i;
32
+ if (!alt) return;
33
+ for (i = 0; i < alt->count; i++) {
34
+ int j;
35
+ for (j = 0; j < alt->branches[i].count; j++)
36
+ xt_re_free_node(alt->branches[i].pieces[j].node);
37
+ free(alt->branches[i].pieces);
38
+ }
39
+ free(alt->branches);
40
+ free(alt);
41
+ }
42
+
43
+ static int xt_re_char_eq(const xt_re_input *in, unsigned char a, unsigned char b) {
44
+ if (a == b) return 1;
45
+ if (in->icase && xt_re_lower(a) == xt_re_lower(b)) return 1;
46
+ return 0;
47
+ }
48
+
49
+ static int xt_re_class_match(const unsigned char *bits, unsigned char c, int icase) {
50
+ if (xt_re_bit_test(bits, c)) return 1;
51
+ if (icase) {
52
+ unsigned char l = xt_re_lower(c);
53
+ if (xt_re_bit_test(bits, l)) return 1;
54
+ if (l >= 'a' && l <= 'z' && xt_re_bit_test(bits, (unsigned char)(l - 'a' + 'A'))) return 1;
55
+ }
56
+ return 0;
57
+ }
58
+
59
+ static int xt_re_match_alt(xt_re_alt *alt, const xt_re_input *in, size_t pos,
60
+ xt_re_cont cont, void *ctx, const xt_re_cap *caps);
61
+
62
+ /* Resumes the outer continuation once a group's body has matched, recording
63
+ * where the group ended on the frame it pushed. */
64
+ typedef struct {
65
+ xt_re_cap *frame;
66
+ xt_re_cont cont;
67
+ void *ctx;
68
+ } xt_re_group_state;
69
+
70
+ static int xt_re_group_next(void *v, size_t pos, const xt_re_cap *caps) {
71
+ xt_re_group_state *s = (xt_re_group_state *)v;
72
+ /* `caps` already heads the chain that includes this group's own frame (and
73
+ * any groups nested inside it), so forward it as-is. */
74
+ s->frame->end = (int)pos;
75
+ return s->cont(s->ctx, pos, caps);
76
+ }
77
+
78
+ static int xt_re_match_atom(xt_re_node *node, const xt_re_input *in, size_t pos,
79
+ xt_re_cont cont, void *ctx, const xt_re_cap *caps) {
80
+ switch (node->type) {
81
+ case XT_RE_CHAR:
82
+ if (pos < in->len && xt_re_char_eq(in, (unsigned char)in->text[pos], node->ch))
83
+ return cont(ctx, pos + 1, caps);
84
+ return 0;
85
+ case XT_RE_ANY:
86
+ if (pos < in->len && in->text[pos] != '\n') return cont(ctx, pos + 1, caps);
87
+ return 0;
88
+ case XT_RE_CLASS:
89
+ if (pos < in->len && xt_re_class_match(node->classbits, (unsigned char)in->text[pos], in->icase))
90
+ return cont(ctx, pos + 1, caps);
91
+ return 0;
92
+ case XT_RE_BOL:
93
+ return pos == 0 ? cont(ctx, pos, caps) : 0;
94
+ case XT_RE_EOL:
95
+ return pos == in->len ? cont(ctx, pos, caps) : 0;
96
+ case XT_RE_GROUP: {
97
+ xt_re_cap frame;
98
+ xt_re_group_state state;
99
+ frame.parent = caps;
100
+ frame.index = node->groupIndex;
101
+ frame.start = (int)pos;
102
+ frame.end = -1;
103
+ state.frame = &frame;
104
+ state.cont = cont;
105
+ state.ctx = ctx;
106
+ return xt_re_match_alt(node->group, in, pos, xt_re_group_next, &state, &frame);
107
+ }
108
+ default:
109
+ return 0;
110
+ }
111
+ }
112
+
113
+ typedef struct {
114
+ xt_re_node *atom;
115
+ const xt_re_input *in;
116
+ int min;
117
+ int max; /* -1 == unbounded */
118
+ xt_re_cont cont;
119
+ void *ctx;
120
+ } xt_re_repeat;
121
+
122
+ typedef struct {
123
+ xt_re_repeat *rep;
124
+ size_t pos;
125
+ int count;
126
+ } xt_re_repeat_step;
127
+
128
+ static int xt_re_repeat_try(xt_re_repeat *rep, size_t pos, int count, const xt_re_cap *caps);
129
+
130
+ static int xt_re_repeat_next(void *v, size_t newpos, const xt_re_cap *caps) {
131
+ xt_re_repeat_step *s = (xt_re_repeat_step *)v;
132
+ if (newpos == s->pos) return 0; /* no progress -> stop to avoid loops */
133
+ return xt_re_repeat_try(s->rep, newpos, s->count + 1, caps);
134
+ }
135
+
136
+ static int xt_re_repeat_try(xt_re_repeat *rep, size_t pos, int count, const xt_re_cap *caps) {
137
+ if (rep->max < 0 || count < rep->max) {
138
+ xt_re_repeat_step step;
139
+ step.rep = rep;
140
+ step.pos = pos;
141
+ step.count = count;
142
+ if (xt_re_match_atom(rep->atom, rep->in, pos, xt_re_repeat_next, &step, caps)) return 1;
143
+ }
144
+ if (count >= rep->min) return rep->cont(rep->ctx, pos, caps);
145
+ return 0;
146
+ }
147
+
148
+ static int xt_re_match_piece(xt_re_piece *piece, const xt_re_input *in, size_t pos,
149
+ xt_re_cont cont, void *ctx, const xt_re_cap *caps) {
150
+ xt_re_repeat rep;
151
+ switch (piece->quant) {
152
+ case XT_RE_Q_STAR:
153
+ rep.atom = piece->node;
154
+ rep.in = in;
155
+ rep.min = 0;
156
+ rep.max = -1;
157
+ rep.cont = cont;
158
+ rep.ctx = ctx;
159
+ return xt_re_repeat_try(&rep, pos, 0, caps);
160
+ case XT_RE_Q_PLUS:
161
+ rep.atom = piece->node;
162
+ rep.in = in;
163
+ rep.min = 1;
164
+ rep.max = -1;
165
+ rep.cont = cont;
166
+ rep.ctx = ctx;
167
+ return xt_re_repeat_try(&rep, pos, 0, caps);
168
+ case XT_RE_Q_QUEST:
169
+ rep.atom = piece->node;
170
+ rep.in = in;
171
+ rep.min = 0;
172
+ rep.max = 1;
173
+ rep.cont = cont;
174
+ rep.ctx = ctx;
175
+ return xt_re_repeat_try(&rep, pos, 0, caps);
176
+ default:
177
+ return xt_re_match_atom(piece->node, in, pos, cont, ctx, caps);
178
+ }
179
+ }
180
+
181
+ typedef struct {
182
+ xt_re_concat *concat;
183
+ const xt_re_input *in;
184
+ xt_re_cont cont;
185
+ void *ctx;
186
+ } xt_re_concat_state;
187
+
188
+ typedef struct {
189
+ xt_re_concat_state *state;
190
+ int index;
191
+ } xt_re_concat_step;
192
+
193
+ static int xt_re_match_concat(xt_re_concat_state *state, int index, size_t pos, const xt_re_cap *caps);
194
+
195
+ /* The continuation for a concat piece resumes matching at the next piece. */
196
+ static int xt_re_concat_next(void *v, size_t pos, const xt_re_cap *caps) {
197
+ xt_re_concat_step *s = (xt_re_concat_step *)v;
198
+ return xt_re_match_concat(s->state, s->index + 1, pos, caps);
199
+ }
200
+
201
+ static int xt_re_match_concat(xt_re_concat_state *state, int index, size_t pos, const xt_re_cap *caps) {
202
+ xt_re_concat_step step;
203
+ if (index >= state->concat->count) return state->cont(state->ctx, pos, caps);
204
+ step.state = state;
205
+ step.index = index;
206
+ return xt_re_match_piece(&state->concat->pieces[index], state->in, pos,
207
+ xt_re_concat_next, &step, caps);
208
+ }
209
+
210
+ static int xt_re_match_alt(xt_re_alt *alt, const xt_re_input *in, size_t pos,
211
+ xt_re_cont cont, void *ctx, const xt_re_cap *caps) {
212
+ int i;
213
+ for (i = 0; i < alt->count; i++) {
214
+ xt_re_concat_state state;
215
+ state.concat = &alt->branches[i];
216
+ state.in = in;
217
+ state.cont = cont;
218
+ state.ctx = ctx;
219
+ if (xt_re_match_concat(&state, 0, pos, caps)) return 1;
220
+ }
221
+ return 0;
222
+ }
223
+
224
+ typedef struct {
225
+ size_t end;
226
+ regmatch_t *pmatch;
227
+ size_t nmatch;
228
+ } xt_re_match_result;
229
+
230
+ /* Final continuation: records the whole-match end offset and copies every
231
+ * capture on the successful path into `pmatch`. Walking from the head means
232
+ * the most recent frame wins, which is what a quantified group such as
233
+ * `(a)+` should report (its last iteration). */
234
+ static int xt_re_record_match(void *ctx, size_t pos, const xt_re_cap *caps) {
235
+ xt_re_match_result *result = (xt_re_match_result *)ctx;
236
+ const xt_re_cap *cap;
237
+ result->end = pos;
238
+ for (cap = caps; cap; cap = cap->parent) {
239
+ if (cap->index >= 1 && (size_t)cap->index < result->nmatch) {
240
+ regmatch_t *slot = &result->pmatch[cap->index];
241
+ if (slot->rm_so < 0) {
242
+ slot->rm_so = cap->start;
243
+ slot->rm_eo = cap->end;
244
+ }
245
+ }
246
+ }
247
+ return 1;
248
+ }