xbintsc 0.5.20 → 0.5.21

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 (100) hide show
  1. package/AGENTS.md +5 -3
  2. package/dist/src/driver/compiler.js +1 -1
  3. package/dist/src/driver/compiler.js.map +1 -1
  4. package/dist/src/driver/runtime-objects.d.ts +1 -1
  5. package/dist/src/driver/runtime-objects.js +1 -0
  6. package/dist/src/driver/runtime-objects.js.map +1 -1
  7. package/dist/src/driver/version.d.ts +1 -1
  8. package/dist/src/driver/version.js +1 -1
  9. package/dist/src/extensions/node/fs/index.d.ts +4 -3
  10. package/dist/src/extensions/node/fs/index.js +12 -3
  11. package/dist/src/extensions/node/fs/index.js.map +1 -1
  12. package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
  13. package/dist/src/extensions/node/fs-promises/index.js +15 -6
  14. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  15. package/dist/tests/e2e/async-await.test.js +1 -1
  16. package/dist/tests/e2e/async-fs.test.d.ts +9 -0
  17. package/dist/tests/e2e/async-fs.test.js +119 -0
  18. package/dist/tests/e2e/async-fs.test.js.map +1 -0
  19. package/dist/tests/e2e/fetch.test.js +24 -0
  20. package/dist/tests/e2e/fetch.test.js.map +1 -1
  21. package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
  22. package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
  23. package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
  24. package/doc/ai/contributing.md +2 -1
  25. package/doc/ai/extensions.md +4 -3
  26. package/doc/ai/language-support.md +2 -1
  27. package/doc/ai/zh-CN/contributing.md +1 -1
  28. package/doc/ai/zh-CN/extensions.md +4 -3
  29. package/doc/ai/zh-CN/language-support.md +2 -1
  30. package/doc/gui/api.md +8 -0
  31. package/doc/gui/known-issues.md +10 -9
  32. package/doc/gui/media.md +32 -3
  33. package/doc/gui.md +24 -2
  34. package/doc/implemented/async.md +16 -3
  35. package/doc/implemented/fetch.md +38 -6
  36. package/doc/implemented/node/fs-promises.md +19 -8
  37. package/doc/implemented/node.md +1 -1
  38. package/doc/implemented.md +1 -1
  39. package/doc/unimplemented/deviations.md +1 -1
  40. package/doc/unimplemented/node/async.md +6 -3
  41. package/doc/unimplemented/node/fs-promises.md +10 -6
  42. package/doc/unimplemented/node/fs.md +7 -8
  43. package/doc/unimplemented/node.md +2 -2
  44. package/doc/unimplemented/toolchain.md +1 -1
  45. package/doc/zh-CN/gui/api.md +7 -0
  46. package/doc/zh-CN/gui/known-issues.md +8 -7
  47. package/doc/zh-CN/gui/media.md +26 -2
  48. package/doc/zh-CN/gui.md +18 -1
  49. package/doc/zh-CN/implemented/async.md +13 -2
  50. package/doc/zh-CN/implemented/fetch.md +23 -4
  51. package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
  52. package/doc/zh-CN/implemented/node.md +1 -1
  53. package/doc/zh-CN/implemented.md +1 -1
  54. package/doc/zh-CN/unimplemented/deviations.md +1 -1
  55. package/doc/zh-CN/unimplemented/node/async.md +6 -3
  56. package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
  57. package/doc/zh-CN/unimplemented/node/fs.md +2 -2
  58. package/doc/zh-CN/unimplemented/node.md +2 -2
  59. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  60. package/package.json +1 -1
  61. package/runtime/ext_gui/inline_svg.cpp +219 -0
  62. package/runtime/ext_gui/inline_svg.h +41 -0
  63. package/runtime/ext_gui/layout.cpp +18 -0
  64. package/runtime/ext_gui/layout.h +7 -0
  65. package/runtime/ext_gui/layout_flow.cpp +4 -3
  66. package/runtime/ext_gui/paint.cpp +14 -0
  67. package/runtime/ext_gui/paint.h +5 -1
  68. package/runtime/ext_gui/renderer_image.cpp +53 -25
  69. package/runtime/ext_gui/style_ua.cpp +3 -0
  70. package/runtime/ext_gui/svg.cpp +122 -39
  71. package/runtime/ext_gui/svg.h +14 -3
  72. package/runtime/ext_gui/window.cpp +28 -0
  73. package/runtime/ext_node/fs/async_api.c +314 -0
  74. package/runtime/ext_node/fs/async_complete.c +99 -0
  75. package/runtime/ext_node/fs/async_fh.c +206 -0
  76. package/runtime/ext_node/fs/async_job.c +81 -0
  77. package/runtime/ext_node/fs/async_job.h +132 -0
  78. package/runtime/ext_node/fs/async_worker.c +534 -0
  79. package/runtime/ext_node/fs/async_worker_dir.c +439 -0
  80. package/runtime/ext_node/fs/async_worker_dir.h +33 -0
  81. package/runtime/ext_node/fs/async_worker_fd.c +74 -0
  82. package/runtime/ext_node/fs/dir.c +60 -15
  83. package/runtime/ext_node/fs/fs_common.h +9 -0
  84. package/runtime/ext_node/fs/fs_ops.c +18 -29
  85. package/runtime/ext_node/fs/parts/builtins.inc +34 -0
  86. package/runtime/ext_node/fs/parts/platform.inc +66 -0
  87. package/runtime/ext_node/fs/promises.c +44 -234
  88. package/runtime/rt_internal.h +2 -0
  89. package/runtime/xt_async_io.c +333 -0
  90. package/runtime/xt_async_io.h +47 -0
  91. package/runtime/xt_fetch/async.inc +295 -0
  92. package/runtime/xt_fetch/request.inc +6 -183
  93. package/runtime/xt_fetch/socket.inc +5 -4
  94. package/runtime/xt_fetch.c +6 -4
  95. package/runtime/xt_loop.c +44 -3
  96. package/src/driver/compiler.ts +1 -1
  97. package/src/driver/runtime-objects.ts +1 -0
  98. package/src/driver/version.ts +1 -1
  99. package/src/extensions/node/fs/index.ts +12 -3
  100. package/src/extensions/node/fs-promises/index.ts +15 -6
@@ -0,0 +1,439 @@
1
+ /*
2
+ * Directory-walking worker jobs: `readdir`, `opendir`, recursive `rm`/`cp` and
3
+ * `glob`. All of it runs off the main thread; only the entry table built here
4
+ * crosses back (via async_complete.c).
5
+ */
6
+
7
+ #include "async_worker_dir.h"
8
+
9
+ #include <errno.h>
10
+
11
+ #if !defined(_WIN32)
12
+ #include <sys/time.h>
13
+ #include <unistd.h>
14
+ #endif
15
+
16
+ /* -- readdir / opendir ----------------------------------------------------- */
17
+
18
+ static int xt_fs_worker_scan(xt_fs_job *job, const char *base, const char *prefix, int recursive, int with_types) {
19
+ size_t baseLength = strlen(base);
20
+ size_t prefixLength = strlen(prefix);
21
+ char *dirPath = (char *)malloc(baseLength + prefixLength + 2);
22
+ if (!dirPath) return -1;
23
+ if (prefixLength == 0) snprintf(dirPath, baseLength + prefixLength + 2, "%s", base);
24
+ else snprintf(dirPath, baseLength + prefixLength + 2, "%s/%s", base, prefix);
25
+ size_t dirLength = strlen(dirPath);
26
+ while (dirLength > 1 && (dirPath[dirLength - 1] == '/' || dirPath[dirLength - 1] == '\\')) {
27
+ dirPath[--dirLength] = '\0';
28
+ }
29
+
30
+ xt_fs_dir dir;
31
+ if (!xt_fs_dir_open(&dir, dirPath)) {
32
+ int saved = errno;
33
+ free(dirPath);
34
+ errno = saved;
35
+ return -1;
36
+ }
37
+ while (xt_fs_dir_next(&dir)) {
38
+ const char *name = xt_fs_dir_name(&dir);
39
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
40
+ size_t nameLength = strlen(name);
41
+ char *relative = (char *)malloc(prefixLength + nameLength + 1);
42
+ if (!relative) continue;
43
+ snprintf(relative, prefixLength + nameLength + 1, "%s%s", prefix, name);
44
+ unsigned int mode = 0;
45
+ if (recursive || with_types) {
46
+ char *full = xt_fs_worker_join(dirPath, name);
47
+ if (full) {
48
+ xt_fs_stat_t info;
49
+ if (xt_fs_lstat_fn(full, &info) == 0) mode = (unsigned int)info.st_mode;
50
+ free(full);
51
+ }
52
+ }
53
+ if (xt_fs_entry_push(job, relative, name, mode) != 0) {
54
+ free(relative);
55
+ break;
56
+ }
57
+ if (recursive && mode && S_ISDIR(mode)) {
58
+ char *child = (char *)malloc(prefixLength + nameLength + 2);
59
+ if (child) {
60
+ snprintf(child, prefixLength + nameLength + 2, "%s%s/", prefix, name);
61
+ xt_fs_worker_scan(job, base, child, recursive, with_types);
62
+ free(child);
63
+ }
64
+ }
65
+ free(relative);
66
+ }
67
+ xt_fs_dir_close(&dir);
68
+ free(dirPath);
69
+ return 0;
70
+ }
71
+
72
+ void xt_fs_worker_readdir(xt_fs_job *job) {
73
+ if (!job->path) {
74
+ xt_fs_job_fail(job, EINVAL, "readdir", NULL);
75
+ return;
76
+ }
77
+ if (xt_fs_worker_scan(job, job->path, "", job->recursive, job->with_types) != 0) {
78
+ xt_fs_job_fail(job, errno, "open directory", job->path);
79
+ }
80
+ }
81
+
82
+ void xt_fs_worker_opendir(xt_fs_job *job) {
83
+ if (!job->path) {
84
+ xt_fs_job_fail(job, EINVAL, "opendir", NULL);
85
+ return;
86
+ }
87
+ if (xt_fs_worker_scan(job, job->path, "", 0, 1) != 0) {
88
+ xt_fs_job_fail(job, errno, "open directory", job->path);
89
+ }
90
+ }
91
+
92
+ /* -- recursive rm ---------------------------------------------------------- */
93
+
94
+ static int xt_fs_worker_remove(const char *path) {
95
+ xt_fs_stat_t info;
96
+ if (xt_fs_lstat_fn(path, &info) != 0) return -1;
97
+ if (!S_ISDIR(info.st_mode)) {
98
+ #if defined(_WIN32)
99
+ return _unlink(path);
100
+ #else
101
+ return unlink(path);
102
+ #endif
103
+ }
104
+ xt_fs_dir dir;
105
+ if (!xt_fs_dir_open(&dir, path)) return -1;
106
+ while (xt_fs_dir_next(&dir)) {
107
+ const char *name = xt_fs_dir_name(&dir);
108
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
109
+ char *child = xt_fs_worker_join(path, name);
110
+ if (!child) continue;
111
+ xt_fs_worker_remove(child);
112
+ free(child);
113
+ }
114
+ xt_fs_dir_close(&dir);
115
+ return xt_fs_rmdir_one(path);
116
+ }
117
+
118
+ void xt_fs_worker_remove_tree(xt_fs_job *job) {
119
+ if (!job->path) {
120
+ xt_fs_job_fail(job, EINVAL, "remove", NULL);
121
+ return;
122
+ }
123
+ int status = job->recursive ? xt_fs_worker_remove(job->path) : remove(job->path);
124
+ if (status != 0) {
125
+ if (job->force && errno == ENOENT) return;
126
+ xt_fs_job_fail(job, errno, "remove", job->path);
127
+ }
128
+ }
129
+
130
+ /* Stream `from` into `to` without materialising the whole file. */
131
+ int xt_fs_worker_copy_bytes(const char *from, const char *to) {
132
+ int source = XT_FS_OPEN(from, XT_FS_RDONLY | XT_FS_BINARY_FLAG, 0);
133
+ if (source < 0) return -1;
134
+ int destination = XT_FS_OPEN(to, XT_FS_WRONLY | XT_FS_CREAT | XT_FS_TRUNC | XT_FS_BINARY_FLAG, 0666);
135
+ if (destination < 0) {
136
+ int saved = errno;
137
+ XT_FS_CLOSE(source);
138
+ errno = saved;
139
+ return -1;
140
+ }
141
+ char buffer[65536];
142
+ int status = 0;
143
+ for (;;) {
144
+ ssize_t count = XT_FS_READ(source, buffer, sizeof(buffer));
145
+ if (count < 0) {
146
+ status = -1;
147
+ break;
148
+ }
149
+ if (count == 0) break;
150
+ size_t written = 0;
151
+ while (written < (size_t)count) {
152
+ ssize_t chunk = XT_FS_WRITE(destination, buffer + written, (size_t)count - written);
153
+ if (chunk <= 0) {
154
+ status = -1;
155
+ break;
156
+ }
157
+ written += (size_t)chunk;
158
+ }
159
+ if (status != 0) break;
160
+ }
161
+ int saved = errno;
162
+ if (XT_FS_CLOSE(source) != 0 && status == 0) {
163
+ status = -1;
164
+ saved = errno;
165
+ }
166
+ if (XT_FS_CLOSE(destination) != 0 && status == 0) {
167
+ status = -1;
168
+ saved = errno;
169
+ }
170
+ errno = saved;
171
+ return status;
172
+ }
173
+
174
+ void xt_fs_worker_copy_file(xt_fs_job *job) {
175
+ if (!job->path || !job->path2) {
176
+ xt_fs_job_fail(job, EINVAL, "copyfile", NULL);
177
+ return;
178
+ }
179
+ if ((job->flags & 1) && xt_fs_access(job->path2, F_OK) == 0) {
180
+ xt_fs_job_fail(job, EEXIST, "copyfile", job->path2);
181
+ return;
182
+ }
183
+ if (xt_fs_worker_copy_bytes(job->path, job->path2) != 0) {
184
+ xt_fs_job_fail(job, errno, "open", job->path);
185
+ }
186
+ }
187
+
188
+ /* -- recursive cp ---------------------------------------------------------- */
189
+
190
+ static int xt_fs_worker_copy_any(const char *src, const char *dest, xt_fs_job *job) {
191
+ xt_fs_stat_t info;
192
+ if (xt_fs_lstat_fn(src, &info) != 0) return -1;
193
+ if (S_ISDIR(info.st_mode)) {
194
+ if (!job->recursive) {
195
+ errno = EISDIR;
196
+ return -1;
197
+ }
198
+ if (xt_fs_mkdir_mode(dest, (int)(info.st_mode & 0777)) != 0 && errno != EEXIST) return -1;
199
+ xt_fs_dir dir;
200
+ if (!xt_fs_dir_open(&dir, src)) return -1;
201
+ int status = 0;
202
+ while (xt_fs_dir_next(&dir)) {
203
+ const char *name = xt_fs_dir_name(&dir);
204
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
205
+ char *childSrc = xt_fs_worker_join(src, name);
206
+ char *childDest = xt_fs_worker_join(dest, name);
207
+ if (childSrc && childDest && xt_fs_worker_copy_any(childSrc, childDest, job) != 0) status = -1;
208
+ free(childSrc);
209
+ free(childDest);
210
+ }
211
+ xt_fs_dir_close(&dir);
212
+ return status;
213
+ }
214
+
215
+ if (xt_fs_access(dest, F_OK) == 0) {
216
+ if (!job->force) {
217
+ if (job->error_on_exist) {
218
+ errno = EEXIST;
219
+ return -1;
220
+ }
221
+ return 0;
222
+ }
223
+ }
224
+
225
+ #if !defined(_WIN32)
226
+ if (S_ISLNK(info.st_mode) && !job->dereference) {
227
+ size_t capacity = 256;
228
+ for (;;) {
229
+ char *target = (char *)malloc(capacity);
230
+ if (!target) return -1;
231
+ ssize_t length = readlink(src, target, capacity);
232
+ if (length < 0) {
233
+ free(target);
234
+ return -1;
235
+ }
236
+ if ((size_t)length < capacity) {
237
+ target[length] = '\0';
238
+ int status = symlink(target, dest);
239
+ free(target);
240
+ return status;
241
+ }
242
+ free(target);
243
+ capacity *= 2;
244
+ }
245
+ }
246
+ #endif
247
+
248
+ int status = xt_fs_worker_copy_bytes(src, dest);
249
+ if (status != 0) return -1;
250
+
251
+ if (job->preserve) {
252
+ #if !defined(_WIN32)
253
+ struct timeval times[2];
254
+ times[0].tv_sec = info.st_atime;
255
+ times[0].tv_usec = 0;
256
+ times[1].tv_sec = info.st_mtime;
257
+ times[1].tv_usec = 0;
258
+ utimes(dest, times);
259
+ #endif
260
+ }
261
+ return 0;
262
+ }
263
+
264
+ void xt_fs_worker_copy_tree(xt_fs_job *job) {
265
+ if (!job->path || !job->path2) {
266
+ xt_fs_job_fail(job, EINVAL, "cp", NULL);
267
+ return;
268
+ }
269
+ if (xt_fs_worker_copy_any(job->path, job->path2, job) != 0) {
270
+ xt_fs_job_fail(job, errno, "cp", job->path);
271
+ }
272
+ }
273
+
274
+ /* -- glob ------------------------------------------------------------------ */
275
+
276
+ /* Single path-segment match (`*`, `?`, `[...]`; no separator). */
277
+ static int xt_fs_worker_glob_match(const char *pattern, const char *text) {
278
+ while (*pattern) {
279
+ if (*pattern == '*') {
280
+ pattern++;
281
+ if (!*pattern) return 1;
282
+ while (*text) {
283
+ if (xt_fs_worker_glob_match(pattern, text)) return 1;
284
+ text++;
285
+ }
286
+ return 0;
287
+ }
288
+ if (*pattern == '?') {
289
+ if (!*text) return 0;
290
+ pattern++;
291
+ text++;
292
+ continue;
293
+ }
294
+ if (*pattern == '[') {
295
+ const char *cursor = pattern + 1;
296
+ int negate = 0;
297
+ int matched = 0;
298
+ char value = *text;
299
+ if (*cursor == '!' || *cursor == '^') {
300
+ negate = 1;
301
+ cursor++;
302
+ }
303
+ while (*cursor && *cursor != ']') {
304
+ char low = *cursor++;
305
+ char high = low;
306
+ if (*cursor == '-' && cursor[1] && cursor[1] != ']') {
307
+ cursor++;
308
+ high = *cursor++;
309
+ }
310
+ if (value >= low && value <= high) matched = 1;
311
+ }
312
+ if (*cursor == ']') cursor++;
313
+ if (!*text || matched == negate) return 0;
314
+ pattern = cursor;
315
+ text++;
316
+ continue;
317
+ }
318
+ if (*pattern != *text) return 0;
319
+ pattern++;
320
+ text++;
321
+ }
322
+ return *text == '\0';
323
+ }
324
+
325
+ static int xt_fs_worker_glob_is_dir(const char *path, int follow) {
326
+ xt_fs_stat_t info;
327
+ int failed = follow ? xt_fs_stat_fn(path, &info) : xt_fs_lstat_fn(path, &info);
328
+ if (failed != 0) return 0;
329
+ return S_ISDIR(info.st_mode);
330
+ }
331
+
332
+ static void xt_fs_worker_glob_walk(xt_fs_job *job, const char *dirPath, const char *pattern,
333
+ const char *resultPrefix);
334
+
335
+ static void xt_fs_worker_glob_descend(xt_fs_job *job, const char *dirPath, const char *pattern,
336
+ const char *resultPrefix) {
337
+ xt_fs_dir dir;
338
+ if (!xt_fs_dir_open(&dir, dirPath)) return;
339
+ while (xt_fs_dir_next(&dir)) {
340
+ const char *name = xt_fs_dir_name(&dir);
341
+ if (name[0] == '.') continue;
342
+ char *child = xt_fs_worker_join(dirPath, name);
343
+ if (!child) continue;
344
+ if (xt_fs_worker_glob_is_dir(child, 0)) {
345
+ size_t size = strlen(resultPrefix) + strlen(name) + 2;
346
+ char *childPrefix = (char *)malloc(size);
347
+ if (childPrefix) {
348
+ snprintf(childPrefix, size, "%s%s/", resultPrefix, name);
349
+ xt_fs_worker_glob_walk(job, child, pattern, childPrefix);
350
+ free(childPrefix);
351
+ }
352
+ }
353
+ free(child);
354
+ }
355
+ xt_fs_dir_close(&dir);
356
+ }
357
+
358
+ static void xt_fs_worker_glob_walk(xt_fs_job *job, const char *dirPath, const char *pattern,
359
+ const char *resultPrefix) {
360
+ if (pattern[0] == '\0') {
361
+ if (resultPrefix[0] != '\0') {
362
+ size_t length = strlen(resultPrefix);
363
+ char *trimmed = (char *)malloc(length + 1);
364
+ if (!trimmed) return;
365
+ memcpy(trimmed, resultPrefix, length + 1);
366
+ while (length > 1 && trimmed[length - 1] == '/') trimmed[--length] = '\0';
367
+ xt_fs_entry_push(job, trimmed, trimmed, 0);
368
+ free(trimmed);
369
+ }
370
+ return;
371
+ }
372
+
373
+ const char *slash = strchr(pattern, '/');
374
+ size_t segmentLength = slash ? (size_t)(slash - pattern) : strlen(pattern);
375
+ const char *rest = slash ? slash + 1 : "";
376
+
377
+ if (segmentLength == 2 && pattern[0] == '*' && pattern[1] == '*') {
378
+ xt_fs_worker_glob_walk(job, dirPath, rest, resultPrefix);
379
+ xt_fs_worker_glob_descend(job, dirPath, pattern, resultPrefix);
380
+ return;
381
+ }
382
+
383
+ char segment[256];
384
+ if (segmentLength >= sizeof(segment)) return;
385
+ memcpy(segment, pattern, segmentLength);
386
+ segment[segmentLength] = '\0';
387
+
388
+ xt_fs_dir dir;
389
+ if (!xt_fs_dir_open(&dir, dirPath)) return;
390
+ while (xt_fs_dir_next(&dir)) {
391
+ const char *name = xt_fs_dir_name(&dir);
392
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
393
+ if (name[0] == '.' && segment[0] != '.') continue;
394
+ if (!xt_fs_worker_glob_match(segment, name)) continue;
395
+ char *child = xt_fs_worker_join(dirPath, name);
396
+ if (!child) continue;
397
+ if (rest[0] == '\0') {
398
+ size_t size = strlen(resultPrefix) + strlen(name) + 1;
399
+ char *result = (char *)malloc(size);
400
+ if (result) {
401
+ snprintf(result, size, "%s%s", resultPrefix, name);
402
+ xt_fs_entry_push(job, result, result, 0);
403
+ free(result);
404
+ }
405
+ } else if (xt_fs_worker_glob_is_dir(child, 1)) {
406
+ size_t size = strlen(resultPrefix) + strlen(name) + 2;
407
+ char *childPrefix = (char *)malloc(size);
408
+ if (childPrefix) {
409
+ snprintf(childPrefix, size, "%s%s/", resultPrefix, name);
410
+ xt_fs_worker_glob_walk(job, child, rest, childPrefix);
411
+ free(childPrefix);
412
+ }
413
+ }
414
+ free(child);
415
+ }
416
+ xt_fs_dir_close(&dir);
417
+ }
418
+
419
+ static void xt_fs_worker_glob_pattern(xt_fs_job *job, const char *pattern, const char *cwd) {
420
+ if (!pattern) return;
421
+ const char *base = cwd && cwd[0] ? cwd : ".";
422
+ const char *prefix = "";
423
+ if (pattern[0] == '/') {
424
+ base = "/";
425
+ prefix = "/";
426
+ pattern++;
427
+ }
428
+ xt_fs_worker_glob_walk(job, base, pattern, prefix);
429
+ }
430
+
431
+ void xt_fs_worker_glob(xt_fs_job *job) {
432
+ if (job->patterns) {
433
+ for (size_t i = 0; i < job->pattern_count; i++) {
434
+ xt_fs_worker_glob_pattern(job, job->patterns[i], job->path2);
435
+ }
436
+ return;
437
+ }
438
+ xt_fs_worker_glob_pattern(job, job->text, job->path2);
439
+ }
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Directory-walking jobs shared by the worker translation units.
3
+ *
4
+ * These run on an async worker thread and only touch plain C memory. Their
5
+ * results are `xt_fs_entry` arrays on the job, turned into strings, `Dirent`s or
6
+ * `Dir` objects by async_complete.c.
7
+ */
8
+ #ifndef XT_NODE_FS_ASYNC_WORKER_DIR_H
9
+ #define XT_NODE_FS_ASYNC_WORKER_DIR_H
10
+
11
+ #include "async_job.h"
12
+
13
+ void xt_fs_worker_readdir(xt_fs_job *job);
14
+ void xt_fs_worker_opendir(xt_fs_job *job);
15
+ void xt_fs_worker_remove_tree(xt_fs_job *job);
16
+ void xt_fs_worker_copy_tree(xt_fs_job *job);
17
+ void xt_fs_worker_glob(xt_fs_job *job);
18
+
19
+ /* Shared byte helpers (async_worker.c). */
20
+ int xt_fs_worker_read_fd(int fd, unsigned char **out, size_t *out_length);
21
+ int xt_fs_worker_read_file(const char *path, unsigned char **out, size_t *out_length);
22
+ int xt_fs_worker_write_fd(int fd, const unsigned char *data, size_t length);
23
+ int xt_fs_worker_write_file(const char *path, const unsigned char *data, size_t length, int append);
24
+ int xt_fs_entry_push(xt_fs_job *job, const char *name, const char *bare, unsigned int mode);
25
+ char *xt_fs_worker_join(const char *dir, const char *name);
26
+ int xt_fs_worker_copy_bytes(const char *from, const char *to);
27
+ void xt_fs_worker_copy_file(xt_fs_job *job);
28
+
29
+ /* Descriptor read/write jobs (async_worker_fd.c). */
30
+ void xt_fs_worker_fd_read(xt_fs_job *job);
31
+ void xt_fs_worker_fd_write(xt_fs_job *job);
32
+
33
+ #endif /* XT_NODE_FS_ASYNC_WORKER_DIR_H */
@@ -0,0 +1,74 @@
1
+ /*
2
+ * Descriptor read/write worker jobs.
3
+ *
4
+ * Split out of async_worker.c to keep each translation unit small. These only
5
+ * touch the descriptor and a plain byte buffer; the completion handler copies
6
+ * the bytes into the caller's `Buffer` on the main thread.
7
+ */
8
+
9
+ #include "async_worker_dir.h"
10
+
11
+ #include <errno.h>
12
+
13
+ #if defined(_WIN32)
14
+ #include <io.h>
15
+ #else
16
+ #include <unistd.h>
17
+ #endif
18
+
19
+ void xt_fs_worker_fd_read(xt_fs_job *job) {
20
+ size_t length = (size_t)job->length;
21
+ if (length == 0) {
22
+ job->bytes = NULL;
23
+ job->bytes_length = 0;
24
+ return;
25
+ }
26
+ unsigned char *bytes = (unsigned char *)malloc(length);
27
+ if (!bytes) {
28
+ xt_fs_job_fail(job, ENOMEM, "read", NULL);
29
+ return;
30
+ }
31
+ ssize_t count;
32
+ if (job->position >= 0) {
33
+ #if defined(_WIN32)
34
+ _lseeki64(job->fd, job->position, SEEK_SET);
35
+ count = _read(job->fd, bytes, (unsigned int)length);
36
+ #else
37
+ count = pread(job->fd, bytes, length, (off_t)job->position);
38
+ #endif
39
+ } else {
40
+ count = XT_FS_READ(job->fd, bytes, length);
41
+ }
42
+ if (count < 0) {
43
+ int saved = errno;
44
+ free(bytes);
45
+ xt_fs_job_fail(job, saved, "read", NULL);
46
+ return;
47
+ }
48
+ job->bytes = bytes;
49
+ job->bytes_length = (size_t)count;
50
+ }
51
+
52
+ void xt_fs_worker_fd_write(xt_fs_job *job) {
53
+ if (job->data_length == 0) {
54
+ job->written = 0;
55
+ return;
56
+ }
57
+ ssize_t count;
58
+ if (job->position >= 0) {
59
+ #if defined(_WIN32)
60
+ _lseeki64(job->fd, job->position, SEEK_SET);
61
+ count = _write(job->fd, job->data, (unsigned int)job->data_length);
62
+ #else
63
+ count = pwrite(job->fd, job->data, job->data_length, (off_t)job->position);
64
+ #endif
65
+ } else {
66
+ count = XT_FS_WRITE(job->fd, job->data, job->data_length);
67
+ }
68
+ if (count < 0) {
69
+ xt_fs_job_fail(job, errno, "write", NULL);
70
+ return;
71
+ }
72
+ job->written = (long long)count;
73
+ }
74
+
@@ -1,21 +1,40 @@
1
1
  /*
2
- * `fs.opendirSync` / the `Dir` object for xbintsc.
2
+ * `fs.opendirSync` / `fs.promises.opendir` and the `Dir` object for xbintsc.
3
3
  *
4
- * The runtime has no asynchronous thread pool, so `read`/`close` invoke their
5
- * callbacks immediately (Node would defer them). `readSync`/`closeSync` are the
6
- * fully synchronous equivalents. `Dir` is a plain object (there is no shared
7
- * `Dir` prototype yet), so `dir instanceof Dir` is not supported.
4
+ * A `Dir` either owns a live directory handle (the synchronous `opendirSync`
5
+ * path) or an entry table collected by an async worker (`opendir` from
6
+ * `fs/promises`). `readSync`/`closeSync` serve both; `read`/`close` invoke their
7
+ * callbacks immediately. `Dir` is a plain object, so `dir instanceof Dir` is not
8
+ * supported yet.
8
9
  */
9
10
 
10
11
  #include "../node_common.h"
11
12
  #include "fs_common.h"
12
13
 
13
- static xt_fs_dir *xt_fs_dir_handle(xt_value thisValue) {
14
- xt_value handle = xt_object_get_cstr(thisValue, "__xt_dir");
15
- return (xt_fs_dir *)(uintptr_t)xt_to_number(handle);
14
+ /* A pointer is stored as a number; `undefined`/`NaN` must read as "none" (a
15
+ * bare cast would turn NaN into a bogus non-zero handle on some targets). */
16
+ static void *xt_fs_dir_pointer(xt_value thisValue, const char *key) {
17
+ double value = xt_to_number(xt_object_get_cstr(thisValue, key));
18
+ if (!(value > 0)) return NULL;
19
+ return (void *)(uintptr_t)value;
20
+ }
21
+
22
+ static xt_fs_dir *xt_fs_dir_handle(xt_value thisValue) { return (xt_fs_dir *)xt_fs_dir_pointer(thisValue, "__xt_dir"); }
23
+
24
+ static xt_fs_entry *xt_fs_dir_entries(xt_value thisValue) {
25
+ return (xt_fs_entry *)xt_fs_dir_pointer(thisValue, "__xt_entries");
16
26
  }
17
27
 
18
28
  static xt_value xt_fs_dir_read_entry(xt_value thisValue) {
29
+ xt_fs_entry *entries = xt_fs_dir_entries(thisValue);
30
+ if (entries) {
31
+ size_t index = (size_t)xt_to_number(xt_object_get_cstr(thisValue, "__xt_index"));
32
+ size_t count = (size_t)xt_to_number(xt_object_get_cstr(thisValue, "__xt_count"));
33
+ if (index >= count) return xt_null();
34
+ xt_fs_entry *entry = &entries[index];
35
+ xt_object_set(thisValue, xt_string_from_cstr("__xt_index"), xt_number((double)(index + 1)));
36
+ return xt_node_dirent_named(entry->bare ? entry->bare : entry->name, entry->mode);
37
+ }
19
38
  xt_fs_dir *dir = xt_fs_dir_handle(thisValue);
20
39
  if (!dir) return xt_null();
21
40
  const char *path = xt_node_cstr(xt_object_get_cstr(thisValue, "path"));
@@ -38,6 +57,16 @@ static xt_value xt_fs_dir_close_sync(xt_value thisValue, xt_value env, int32_t a
38
57
  (void)env;
39
58
  (void)argc;
40
59
  (void)argv;
60
+ xt_fs_entry *entries = xt_fs_dir_entries(thisValue);
61
+ if (entries) {
62
+ size_t count = (size_t)xt_to_number(xt_object_get_cstr(thisValue, "__xt_count"));
63
+ for (size_t i = 0; i < count; i++) {
64
+ free(entries[i].name);
65
+ if (entries[i].bare != entries[i].name) free(entries[i].bare);
66
+ }
67
+ free(entries);
68
+ xt_object_set(thisValue, xt_string_from_cstr("__xt_entries"), xt_number(0));
69
+ }
41
70
  xt_fs_dir *dir = xt_fs_dir_handle(thisValue);
42
71
  if (dir) {
43
72
  xt_fs_dir_close(dir);
@@ -72,6 +101,28 @@ static xt_value xt_fs_dir_close_async(xt_value thisValue, xt_value env, int32_t
72
101
  return xt_undefined();
73
102
  }
74
103
 
104
+ static xt_value xt_node_dir_object(const char *path) {
105
+ xt_value object = xt_object_new();
106
+ xt_node_set(object, "path", xt_string_from_cstr(path ? path : ""));
107
+ xt_node_set(object, "__xt_dir", xt_number(0));
108
+ xt_node_set(object, "__xt_entries", xt_number(0));
109
+ xt_node_define_method(object, "readSync", (void *)xt_fs_dir_read_sync);
110
+ xt_node_define_method(object, "closeSync", (void *)xt_fs_dir_close_sync);
111
+ xt_node_define_method(object, "read", (void *)xt_fs_dir_read_async);
112
+ xt_node_define_method(object, "close", (void *)xt_fs_dir_close_async);
113
+ return object;
114
+ }
115
+
116
+ /* Adopt an entry table collected on a worker. The `Dir` owns the table and
117
+ * frees it on close; the caller must not free it afterwards. */
118
+ xt_value xt_node_dir_from_entries(const char *path, xt_fs_entry *entries, size_t count) {
119
+ xt_value object = xt_node_dir_object(path);
120
+ xt_node_set(object, "__xt_entries", xt_number((double)(uintptr_t)entries));
121
+ xt_node_set(object, "__xt_count", xt_number((double)count));
122
+ xt_node_set(object, "__xt_index", xt_number(0));
123
+ return object;
124
+ }
125
+
75
126
  xt_value xt_node_opendir(int32_t argc, xt_value *argv) {
76
127
  if (argc < 1) return xt_undefined();
77
128
  const char *path = xt_node_cstr(argv[0]);
@@ -89,13 +140,7 @@ xt_value xt_node_opendir(int32_t argc, xt_value *argv) {
89
140
  xt_fs_error("open directory", path);
90
141
  return xt_undefined();
91
142
  }
92
-
93
- xt_value object = xt_object_new();
94
- xt_node_set(object, "path", xt_string_from_cstr(path));
143
+ xt_value object = xt_node_dir_object(path);
95
144
  xt_node_set(object, "__xt_dir", xt_number((double)(uintptr_t)dir));
96
- xt_node_define_method(object, "readSync", (void *)xt_fs_dir_read_sync);
97
- xt_node_define_method(object, "closeSync", (void *)xt_fs_dir_close_sync);
98
- xt_node_define_method(object, "read", (void *)xt_fs_dir_read_async);
99
- xt_node_define_method(object, "close", (void *)xt_fs_dir_close_async);
100
145
  return object;
101
146
  }
@@ -32,4 +32,13 @@
32
32
  #include "parts/buffer-bridge.inc"
33
33
  #include "parts/encoding.inc"
34
34
 
35
+ /* One directory entry collected by the asynchronous worker (see async_job.h).
36
+ * `name` is relative to the scanned directory, `bare` is the entry name alone.
37
+ * Defined here because both the sync `Dir` and the async promise layer use it. */
38
+ typedef struct {
39
+ char *name;
40
+ char *bare;
41
+ unsigned int mode;
42
+ } xt_fs_entry;
43
+
35
44
  #endif /* XT_NODE_FS_COMMON_H */