xbintsc 0.3.13 → 0.3.15

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 (122) hide show
  1. package/README.md +22 -22
  2. package/README.zh-CN.md +20 -19
  3. package/bin/xbintsc.js +1 -2
  4. package/dist/src/cli/main.js +4 -1
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/access.js +13 -0
  7. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  8. package/dist/src/codegen/generator/calls/invocation.js +8 -2
  9. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  10. package/dist/src/codegen/generator/context.d.ts +6 -0
  11. package/dist/src/codegen/generator/context.js +24 -2
  12. package/dist/src/codegen/generator/context.js.map +1 -1
  13. package/dist/src/codegen/generator/expressions/primary.js +5 -0
  14. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  15. package/dist/src/codegen/generator/tables.d.ts +5 -6
  16. package/dist/src/codegen/generator/tables.js +5 -6
  17. package/dist/src/codegen/generator/tables.js.map +1 -1
  18. package/dist/src/driver/compiler.js +15 -3
  19. package/dist/src/driver/compiler.js.map +1 -1
  20. package/dist/src/driver/index.d.ts +1 -2
  21. package/dist/src/driver/index.js +1 -2
  22. package/dist/src/driver/index.js.map +1 -1
  23. package/dist/src/driver/modules.d.ts +1 -1
  24. package/dist/src/driver/modules.js +235 -25
  25. package/dist/src/driver/modules.js.map +1 -1
  26. package/dist/src/driver/paths.d.ts +0 -9
  27. package/dist/src/driver/paths.js +0 -31
  28. package/dist/src/driver/paths.js.map +1 -1
  29. package/dist/src/driver/toolchain-provider.d.ts +10 -10
  30. package/dist/src/driver/toolchain-provider.js +9 -38
  31. package/dist/src/driver/toolchain-provider.js.map +1 -1
  32. package/dist/src/driver/toolchain.js +8 -2
  33. package/dist/src/driver/toolchain.js.map +1 -1
  34. package/dist/src/extensions/native.d.ts +2 -3
  35. package/dist/src/extensions/native.js +2 -3
  36. package/dist/src/extensions/native.js.map +1 -1
  37. package/dist/src/extensions/node/fs/fs-ops.d.ts +5 -2
  38. package/dist/src/extensions/node/fs/fs-ops.js +45 -6
  39. package/dist/src/extensions/node/fs/fs-ops.js.map +1 -1
  40. package/dist/src/extensions/node/fs/index.d.ts +6 -2
  41. package/dist/src/extensions/node/fs/index.js +22 -3
  42. package/dist/src/extensions/node/fs/index.js.map +1 -1
  43. package/dist/src/extensions/node/fs-promises/index.d.ts +4 -3
  44. package/dist/src/extensions/node/fs-promises/index.js +31 -3
  45. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  46. package/dist/tests/cli/main.test.js +8 -0
  47. package/dist/tests/cli/main.test.js.map +1 -1
  48. package/dist/tests/driver/compiler.test.js +28 -1
  49. package/dist/tests/driver/compiler.test.js.map +1 -1
  50. package/dist/tests/driver/modules.test.js +91 -4
  51. package/dist/tests/driver/modules.test.js.map +1 -1
  52. package/dist/tests/driver/paths.test.js +1 -9
  53. package/dist/tests/driver/paths.test.js.map +1 -1
  54. package/dist/tests/driver/toolchain-provider.test.js +12 -79
  55. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  56. package/dist/tests/e2e/compile-node.test.js +240 -0
  57. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  58. package/dist/tests/e2e/compile.test.js +12 -0
  59. package/dist/tests/e2e/compile.test.js.map +1 -1
  60. package/dist/tests/e2e/harness.js +11 -8
  61. package/dist/tests/e2e/harness.js.map +1 -1
  62. package/dist/tests/e2e/native-extensions.test.d.ts +4 -5
  63. package/dist/tests/e2e/native-extensions.test.js +6 -7
  64. package/dist/tests/e2e/native-extensions.test.js.map +1 -1
  65. package/dist/tests/e2e/node-modules.test.js +1 -1
  66. package/dist/tests/e2e/node-modules.test.js.map +1 -1
  67. package/dist/tests/e2e/npm-modules.test.d.ts +9 -0
  68. package/dist/tests/e2e/npm-modules.test.js +55 -0
  69. package/dist/tests/e2e/npm-modules.test.js.map +1 -0
  70. package/dist/tests/e2e/runtime-coverage.test.d.ts +11 -0
  71. package/dist/tests/e2e/runtime-coverage.test.js +646 -0
  72. package/dist/tests/e2e/runtime-coverage.test.js.map +1 -0
  73. package/package.json +1 -2
  74. package/runtime/ext_node/buffer/parts/prototype.inc +7 -0
  75. package/runtime/ext_node/buffer/parts/search.inc +8 -4
  76. package/runtime/ext_node/child_process/child_process.c +5 -0
  77. package/runtime/ext_node/fs/constants.c +113 -0
  78. package/runtime/ext_node/fs/copy_ops.c +145 -0
  79. package/runtime/ext_node/fs/dir.c +101 -0
  80. package/runtime/ext_node/fs/fd_ops.c +405 -0
  81. package/runtime/ext_node/fs/fs_common.h +388 -5
  82. package/runtime/ext_node/fs/fs_ops.c +100 -139
  83. package/runtime/ext_node/fs/glob.c +232 -0
  84. package/runtime/ext_node/fs/link_ops.c +91 -0
  85. package/runtime/ext_node/fs/meta_ops.c +403 -0
  86. package/runtime/ext_node/fs/promises.c +275 -60
  87. package/runtime/ext_node/fs/read_file.c +8 -4
  88. package/runtime/ext_node/fs/watch.c +52 -0
  89. package/runtime/ext_node/fs/write_file.c +20 -11
  90. package/runtime/ext_node/stream/stream.c +19 -2
  91. package/runtime/rt.h +8 -0
  92. package/runtime/rt_internal.h +10 -23
  93. package/runtime/xt_ext.h +0 -1
  94. package/runtime/xt_io.c +24 -3
  95. package/runtime/xt_stdlib2/helpers.inc +2 -4
  96. package/scripts/build-runtime.ts +7 -8
  97. package/scripts/coverage-runtime.ts +4 -4
  98. package/scripts/package-release.ts +12 -84
  99. package/src/cli/main.ts +4 -1
  100. package/src/codegen/generator/calls/access.ts +15 -0
  101. package/src/codegen/generator/calls/invocation.ts +8 -2
  102. package/src/codegen/generator/context.ts +30 -2
  103. package/src/codegen/generator/expressions/primary.ts +5 -0
  104. package/src/codegen/generator/tables.ts +5 -6
  105. package/src/driver/compiler.ts +16 -3
  106. package/src/driver/index.ts +1 -9
  107. package/src/driver/modules.ts +251 -26
  108. package/src/driver/paths.ts +0 -31
  109. package/src/driver/toolchain-provider.ts +12 -45
  110. package/src/driver/toolchain.ts +7 -2
  111. package/src/extensions/native.ts +2 -3
  112. package/src/extensions/node/fs/fs-ops.ts +48 -6
  113. package/src/extensions/node/fs/index.ts +25 -3
  114. package/src/extensions/node/fs-promises/index.ts +33 -4
  115. package/dist/src/driver/toolchain-download.d.ts +0 -50
  116. package/dist/src/driver/toolchain-download.js +0 -102
  117. package/dist/src/driver/toolchain-download.js.map +0 -1
  118. package/dist/tests/driver/toolchain-download.test.d.ts +0 -1
  119. package/dist/tests/driver/toolchain-download.test.js +0 -53
  120. package/dist/tests/driver/toolchain-download.test.js.map +0 -1
  121. package/scripts/fetch-toolchain.ts +0 -101
  122. package/src/driver/toolchain-download.ts +0 -135
@@ -5,10 +5,8 @@
5
5
  * existsSync, readdirSync, mkdirSync, rmSync, unlinkSync, rmdirSync,
6
6
  * renameSync, copyFileSync, realpathSync, statSync, lstatSync
7
7
  *
8
- * Error handling is intentionally Node-compatible in spirit but not in
9
- * mechanics: xbintsc has no try/catch that can catch native errors yet, so
10
- * failures print to stderr and return `undefined` (or `false` for
11
- * `existsSync`) instead of throwing `ENOENT`-style exceptions.
8
+ * Error handling follows Node: failures throw a shaped Error (see
9
+ * `xt_fs_error`), which `fs/promises` converts into a rejected Promise.
12
10
  */
13
11
 
14
12
  #include "rt.h"
@@ -16,106 +14,6 @@
16
14
 
17
15
  #include <errno.h>
18
16
 
19
- #if defined(_WIN32)
20
- #include <direct.h>
21
- #include <io.h>
22
- #include <sys/stat.h>
23
- #include <sys/types.h>
24
-
25
- typedef struct _stat xt_fs_stat_t;
26
- #define xt_fs_stat_fn _stat
27
- #define xt_fs_lstat_fn _stat
28
- #define xt_fs_access _access
29
-
30
- #ifndef F_OK
31
- #define F_OK 0
32
- #endif
33
- #ifndef S_ISREG
34
- #define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
35
- #endif
36
- #ifndef S_ISDIR
37
- #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
38
- #endif
39
- #ifndef S_ISLNK
40
- #define S_ISLNK(m) 0
41
- #endif
42
- #ifndef S_ISFIFO
43
- #define S_ISFIFO(m) 0
44
- #endif
45
- #ifndef S_ISSOCK
46
- #define S_ISSOCK(m) 0
47
- #endif
48
- #ifndef S_ISBLK
49
- #define S_ISBLK(m) 0
50
- #endif
51
- #ifndef S_ISCHR
52
- #define S_ISCHR(m) (((m) & _S_IFMT) == _S_IFCHR)
53
- #endif
54
-
55
- /* Directory iteration backed by the CRT's _findfirst/_findnext. */
56
- typedef struct {
57
- intptr_t handle;
58
- struct _finddata_t entry;
59
- } xt_fs_dir;
60
-
61
- static int xt_fs_dir_open(xt_fs_dir *dir, const char *path) {
62
- size_t length = strlen(path);
63
- char *pattern = (char *)malloc(length + 3);
64
- if (!pattern) return 0;
65
- memcpy(pattern, path, length);
66
- pattern[length] = '/';
67
- pattern[length + 1] = '*';
68
- pattern[length + 2] = '\0';
69
- dir->handle = _findfirst(pattern, &dir->entry);
70
- free(pattern);
71
- return dir->handle != -1;
72
- }
73
-
74
- static int xt_fs_dir_next(xt_fs_dir *dir) { return _findnext(dir->handle, &dir->entry) == 0; }
75
-
76
- static const char *xt_fs_dir_name(xt_fs_dir *dir) { return dir->entry.name; }
77
-
78
- static void xt_fs_dir_close(xt_fs_dir *dir) { _findclose(dir->handle); }
79
- #else
80
- #include <dirent.h>
81
- #include <sys/stat.h>
82
- #include <sys/types.h>
83
- #include <unistd.h>
84
-
85
- typedef struct stat xt_fs_stat_t;
86
- #define xt_fs_stat_fn stat
87
- #define xt_fs_lstat_fn lstat
88
- #define xt_fs_access access
89
-
90
- typedef struct {
91
- DIR *handle;
92
- struct dirent *entry;
93
- } xt_fs_dir;
94
-
95
- static int xt_fs_dir_open(xt_fs_dir *dir, const char *path) {
96
- dir->handle = opendir(path);
97
- dir->entry = NULL;
98
- return dir->handle != NULL;
99
- }
100
-
101
- static int xt_fs_dir_next(xt_fs_dir *dir) {
102
- dir->entry = readdir(dir->handle);
103
- return dir->entry != NULL;
104
- }
105
-
106
- static const char *xt_fs_dir_name(xt_fs_dir *dir) { return dir->entry->d_name; }
107
-
108
- static void xt_fs_dir_close(xt_fs_dir *dir) { closedir(dir->handle); }
109
- #endif
110
-
111
- #if defined(_WIN32)
112
- #define xt_fs_mkdir_one(path) _mkdir(path)
113
- #define xt_fs_rmdir_one(path) _rmdir(path)
114
- #else
115
- #define xt_fs_mkdir_one(path) mkdir(path, 0777)
116
- #define xt_fs_rmdir_one(path) rmdir(path)
117
- #endif
118
-
119
17
  /* ------------------------------------------------------------------------- */
120
18
  /* existsSync */
121
19
  /* ------------------------------------------------------------------------- */
@@ -138,7 +36,7 @@ static void xt_fs_define_stat_method(xt_value object, const char *name, unsigned
138
36
  /* Build the Node `Dirent`-shaped object (`name` plus stat predicates) for a
139
37
  single directory entry. Types come from `lstat`, matching Node's `d_type`
140
38
  based `Dirent` (a symlink reports `isSymbolicLink()` true). */
141
- static xt_value xt_node_dirent_result(const char *dir, const char *name) {
39
+ xt_value xt_node_dirent_result(const char *dir, const char *name) {
142
40
  size_t size = strlen(dir) + strlen(name) + 2;
143
41
  char *full = (char *)malloc(size);
144
42
  unsigned int mode = 0;
@@ -161,45 +59,96 @@ static xt_value xt_node_dirent_result(const char *dir, const char *name) {
161
59
  return object;
162
60
  }
163
61
 
164
- xt_value xt_node_read_dir(int32_t argc, xt_value *argv) {
165
- if (argc < 1) return xt_undefined();
166
- const char *path = xt_string_data(xt_to_string(argv[0]));
167
- if (!path) return xt_undefined();
62
+ typedef struct {
63
+ xt_value *items;
64
+ size_t count;
65
+ size_t capacity;
66
+ } xt_fs_list;
67
+
68
+ static void xt_fs_list_push(xt_fs_list *list, xt_value value) {
69
+ if (list->count == list->capacity) {
70
+ list->capacity = list->capacity ? list->capacity * 2 : 16;
71
+ xt_value *grown = (xt_value *)realloc(list->items, sizeof(xt_value) * list->capacity);
72
+ if (!grown) return;
73
+ list->items = grown;
74
+ }
75
+ list->items[list->count++] = value;
76
+ }
168
77
 
169
- /* `readdirSync(path, { withFileTypes: true })` yields `Dirent` objects;
170
- without the option the result is a plain array of entry names. */
171
- int withFileTypes =
172
- argc > 1 && XT_IS_OBJECT(argv[1]) && xt_truthy(xt_object_get_cstr(argv[1], "withFileTypes"));
78
+ /* Depth-first walk used by `readdirSync(path, { recursive: true })`. `prefix`
79
+ is the directory path relative to `base` (empty for the root, otherwise
80
+ ending in `/`). */
81
+ static void xt_fs_readdir_into(const char *base, const char *prefix, int withFileTypes, int recursive,
82
+ xt_fs_list *list) {
83
+ size_t baseLength = strlen(base);
84
+ size_t prefixLength = strlen(prefix);
85
+ size_t dirSize = baseLength + prefixLength + 2;
86
+ char *dirPath = (char *)malloc(dirSize);
87
+ if (!dirPath) return;
88
+ if (prefixLength == 0) snprintf(dirPath, dirSize, "%s", base);
89
+ else snprintf(dirPath, dirSize, "%s/%s", base, prefix);
90
+ size_t dirLength = strlen(dirPath);
91
+ while (dirLength > 1 && dirPath[dirLength - 1] == '/') dirPath[--dirLength] = '\0';
173
92
 
174
93
  xt_fs_dir dir;
175
- if (!xt_fs_dir_open(&dir, path)) {
176
- xt_fs_error("open directory", path);
177
- return xt_undefined();
178
- }
179
-
180
- size_t count = 0;
181
- size_t capacity = 16;
182
- xt_value *items = (xt_value *)malloc(sizeof(xt_value) * capacity);
183
- if (!items) {
184
- xt_fs_dir_close(&dir);
185
- return xt_undefined();
94
+ if (!xt_fs_dir_open(&dir, dirPath)) {
95
+ xt_fs_error("open directory", dirPath);
96
+ free(dirPath);
97
+ return;
186
98
  }
187
-
188
99
  while (xt_fs_dir_next(&dir)) {
189
100
  const char *name = xt_fs_dir_name(&dir);
190
101
  if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
191
- if (count == capacity) {
192
- capacity *= 2;
193
- xt_value *grown = (xt_value *)realloc(items, sizeof(xt_value) * capacity);
194
- if (!grown) break;
195
- items = grown;
102
+ size_t nameLength = strlen(name);
103
+ size_t relSize = prefixLength + nameLength + 1;
104
+ char *relative = (char *)malloc(relSize);
105
+ if (!relative) continue;
106
+ snprintf(relative, relSize, "%s%s", prefix, name);
107
+ xt_fs_list_push(list, withFileTypes ? xt_node_dirent_result(dirPath, name) : xt_string_from_cstr(relative));
108
+
109
+ if (recursive) {
110
+ size_t fullSize = dirLength + nameLength + 2;
111
+ char *full = (char *)malloc(fullSize);
112
+ if (full) {
113
+ snprintf(full, fullSize, "%s/%s", dirPath, name);
114
+ xt_fs_stat_t info;
115
+ if (xt_fs_lstat_fn(full, &info) == 0 && S_ISDIR(info.st_mode)) {
116
+ size_t childSize = relSize + 1;
117
+ char *child = (char *)malloc(childSize);
118
+ if (child) {
119
+ snprintf(child, childSize, "%s/", relative);
120
+ xt_fs_readdir_into(base, child, withFileTypes, recursive, list);
121
+ free(child);
122
+ }
123
+ }
124
+ free(full);
125
+ }
196
126
  }
197
- items[count++] = withFileTypes ? xt_node_dirent_result(path, name) : xt_string_from_cstr(name);
127
+ free(relative);
198
128
  }
199
129
  xt_fs_dir_close(&dir);
130
+ free(dirPath);
131
+ }
132
+
133
+ xt_value xt_node_read_dir(int32_t argc, xt_value *argv) {
134
+ if (argc < 1) return xt_undefined();
135
+ const char *path = xt_string_data(xt_to_string(argv[0]));
136
+ if (!path) return xt_undefined();
200
137
 
201
- xt_value result = xt_array_new((int32_t)count, items);
202
- free(items);
138
+ /* `readdirSync(path, { withFileTypes: true })` yields `Dirent` objects;
139
+ without the option the result is a plain array of entry names. */
140
+ xt_value options = argc > 1 ? argv[1] : XT_UNDEFINED;
141
+ int withFileTypes = XT_IS_OBJECT(options) && xt_truthy(xt_object_get_cstr(options, "withFileTypes"));
142
+ int recursive = XT_IS_OBJECT(options) && xt_truthy(xt_object_get_cstr(options, "recursive"));
143
+
144
+ xt_fs_list list;
145
+ list.items = NULL;
146
+ list.count = 0;
147
+ list.capacity = 0;
148
+ xt_fs_readdir_into(path, "", withFileTypes, recursive, &list);
149
+
150
+ xt_value result = xt_array_new((int32_t)list.count, list.items);
151
+ free(list.items);
203
152
  return result;
204
153
  }
205
154
 
@@ -214,7 +163,7 @@ static int xt_fs_recursive(xt_value options) {
214
163
  return 1;
215
164
  }
216
165
 
217
- static int xt_fs_mkdir_recursive(const char *path) {
166
+ static int xt_fs_mkdir_recursive(const char *path, int mode) {
218
167
  size_t length = strlen(path);
219
168
  char *copy = (char *)malloc(length + 1);
220
169
  if (!copy) return -1;
@@ -225,7 +174,7 @@ static int xt_fs_mkdir_recursive(const char *path) {
225
174
  if (copy[i] != '/' && copy[i] != '\0') continue;
226
175
  char saved = copy[i];
227
176
  copy[i] = '\0';
228
- if (xt_fs_mkdir_one(copy) != 0 && errno != EEXIST) {
177
+ if (xt_fs_mkdir_mode(copy, mode) != 0 && errno != EEXIST) {
229
178
  free(copy);
230
179
  return -1;
231
180
  }
@@ -240,11 +189,17 @@ xt_value xt_node_mkdir(int32_t argc, xt_value *argv) {
240
189
  const char *path = xt_string_data(xt_to_string(argv[0]));
241
190
  if (!path) return xt_undefined();
242
191
 
243
- if (argc > 1 && xt_fs_recursive(argv[1])) {
244
- if (xt_fs_mkdir_recursive(path) != 0) xt_fs_error("create directory", path);
192
+ xt_value options = argc > 1 ? argv[1] : XT_UNDEFINED;
193
+ int mode = 0777;
194
+ if (XT_IS_OBJECT(options) && !XT_IS_UNDEFINED(xt_object_get_cstr(options, "mode"))) {
195
+ mode = (int)xt_to_number(xt_object_get_cstr(options, "mode"));
196
+ }
197
+
198
+ if (argc > 1 && xt_fs_recursive(options)) {
199
+ if (xt_fs_mkdir_recursive(path, mode) != 0) xt_fs_error("create directory", path);
245
200
  return xt_undefined();
246
201
  }
247
- if (xt_fs_mkdir_one(path) != 0 && errno != EEXIST) xt_fs_error("create directory", path);
202
+ if (xt_fs_mkdir_mode(path, mode) != 0 && errno != EEXIST) xt_fs_error("create directory", path);
248
203
  return xt_undefined();
249
204
  }
250
205
 
@@ -319,6 +274,12 @@ xt_value xt_node_copy_file(int32_t argc, xt_value *argv) {
319
274
  const char *to = xt_string_data(xt_to_string(argv[1]));
320
275
  if (!from || !to) return xt_undefined();
321
276
 
277
+ int flags = argc > 2 ? (int)xt_to_number(argv[2]) : 0;
278
+ if ((flags & 1) && xt_fs_access(to, F_OK) == 0) {
279
+ xt_fs_raise_errno(EEXIST, "copyfile", to);
280
+ return xt_undefined();
281
+ }
282
+
322
283
  FILE *source = fopen(from, "rb");
323
284
  if (!source) {
324
285
  xt_fs_error("open", from);
@@ -400,7 +361,7 @@ static double xt_fs_seconds_ms(long seconds, long nanoseconds) {
400
361
  return (double)seconds * 1000.0 + (double)nanoseconds / 1000000.0;
401
362
  }
402
363
 
403
- static xt_value xt_node_stat_result(const xt_fs_stat_t *info) {
364
+ xt_value xt_node_stat_result(const xt_fs_stat_t *info) {
404
365
  xt_value object = xt_object_new();
405
366
  xt_object_set(object, xt_string_from_cstr("size"), xt_number((double)info->st_size));
406
367
  xt_object_set(object, xt_string_from_cstr("mode"), xt_number((double)info->st_mode));
@@ -0,0 +1,232 @@
1
+ /*
2
+ * `fs.globSync` for xbintsc.
3
+ *
4
+ * A small, dependency-free globber supporting `*`, `?`, `[...]` character
5
+ * classes and the recursive `**` segment. Supported options: `cwd` and
6
+ * `withFileTypes`. `exclude` (a callback) and `follow` (symlinked directories)
7
+ * are not implemented (documented deviation).
8
+ */
9
+
10
+ #include "../node_common.h"
11
+ #include "fs_common.h"
12
+
13
+ typedef struct {
14
+ xt_value *items;
15
+ size_t count;
16
+ size_t capacity;
17
+ } xt_glob_list;
18
+
19
+ static void xt_glob_push(xt_glob_list *list, xt_value value) {
20
+ if (list->count == list->capacity) {
21
+ list->capacity = list->capacity ? list->capacity * 2 : 16;
22
+ xt_value *grown = (xt_value *)realloc(list->items, sizeof(xt_value) * list->capacity);
23
+ if (!grown) return;
24
+ list->items = grown;
25
+ }
26
+ list->items[list->count++] = value;
27
+ }
28
+
29
+ static char *xt_glob_join(const char *dir, const char *name) {
30
+ size_t dirLength = strlen(dir);
31
+ int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/';
32
+ size_t size = dirLength + (needsSlash ? 1 : 0) + strlen(name) + 1;
33
+ char *path = (char *)malloc(size);
34
+ if (!path) return NULL;
35
+ snprintf(path, size, "%s%s%s", dir, needsSlash ? "/" : "", name);
36
+ return path;
37
+ }
38
+
39
+ /* Single path-segment glob match (`*`, `?`, `[...]`; no `/`). */
40
+ static int xt_glob_match_segment(const char *pattern, const char *text) {
41
+ while (*pattern) {
42
+ if (*pattern == '*') {
43
+ pattern++;
44
+ if (!*pattern) return 1;
45
+ while (*text) {
46
+ if (xt_glob_match_segment(pattern, text)) return 1;
47
+ text++;
48
+ }
49
+ return 0;
50
+ }
51
+ if (*pattern == '?') {
52
+ if (!*text) return 0;
53
+ pattern++;
54
+ text++;
55
+ continue;
56
+ }
57
+ if (*pattern == '[') {
58
+ const char *cursor = pattern + 1;
59
+ int negate = 0;
60
+ int matched = 0;
61
+ char value = *text;
62
+ if (*cursor == '!' || *cursor == '^') {
63
+ negate = 1;
64
+ cursor++;
65
+ }
66
+ while (*cursor && *cursor != ']') {
67
+ char low = *cursor++;
68
+ char high = low;
69
+ if (*cursor == '-' && cursor[1] && cursor[1] != ']') {
70
+ cursor++;
71
+ high = *cursor++;
72
+ }
73
+ if (value >= low && value <= high) matched = 1;
74
+ }
75
+ if (*cursor == ']') cursor++;
76
+ if (!*text || matched == negate) return 0;
77
+ pattern = cursor;
78
+ text++;
79
+ continue;
80
+ }
81
+ if (*pattern != *text) return 0;
82
+ pattern++;
83
+ text++;
84
+ }
85
+ return *text == '\0';
86
+ }
87
+
88
+ /* `follow` uses `stat` so a symlinked directory is traversed (needed for
89
+ * literal path segments such as macOS's `/tmp`); `**` uses `lstat` to match
90
+ * Node's default `follow: false` and avoid symlink cycles. */
91
+ static int xt_glob_is_dir(const char *path, int follow) {
92
+ xt_fs_stat_t info;
93
+ int failed = follow ? xt_fs_stat_fn(path, &info) : xt_fs_lstat_fn(path, &info);
94
+ if (failed != 0) return 0;
95
+ return S_ISDIR(info.st_mode);
96
+ }
97
+
98
+ static void xt_glob_walk(const char *dirPath, const char *pattern, const char *resultPrefix, int withFileTypes,
99
+ xt_glob_list *list);
100
+
101
+ /* Recurse one directory level for the `**` segment. */
102
+ static void xt_glob_descend(const char *dirPath, const char *pattern, const char *resultPrefix, int withFileTypes,
103
+ xt_glob_list *list) {
104
+ xt_fs_dir dir;
105
+ if (!xt_fs_dir_open(&dir, dirPath)) return;
106
+ while (xt_fs_dir_next(&dir)) {
107
+ const char *name = xt_fs_dir_name(&dir);
108
+ if (name[0] == '.') continue; /* dotfiles are not matched by default */
109
+ char *child = xt_glob_join(dirPath, name);
110
+ if (!child) continue;
111
+ if (xt_glob_is_dir(child, 0)) {
112
+ size_t size = strlen(resultPrefix) + strlen(name) + 2;
113
+ char *childPrefix = (char *)malloc(size);
114
+ if (childPrefix) {
115
+ snprintf(childPrefix, size, "%s%s/", resultPrefix, name);
116
+ xt_glob_walk(child, pattern, childPrefix, withFileTypes, list);
117
+ free(childPrefix);
118
+ }
119
+ }
120
+ free(child);
121
+ }
122
+ xt_fs_dir_close(&dir);
123
+ }
124
+
125
+ static void xt_glob_walk(const char *dirPath, const char *pattern, const char *resultPrefix, int withFileTypes,
126
+ xt_glob_list *list) {
127
+ if (pattern[0] == '\0') {
128
+ if (resultPrefix[0] != '\0') {
129
+ size_t length = strlen(resultPrefix);
130
+ char *trimmed = (char *)malloc(length + 1);
131
+ if (!trimmed) return;
132
+ memcpy(trimmed, resultPrefix, length + 1);
133
+ while (length > 1 && trimmed[length - 1] == '/') trimmed[--length] = '\0';
134
+ xt_glob_push(list, xt_string_from_cstr(trimmed));
135
+ free(trimmed);
136
+ }
137
+ return;
138
+ }
139
+
140
+ const char *slash = strchr(pattern, '/');
141
+ size_t segmentLength = slash ? (size_t)(slash - pattern) : strlen(pattern);
142
+ const char *rest = slash ? slash + 1 : "";
143
+
144
+ if (segmentLength == 2 && pattern[0] == '*' && pattern[1] == '*') {
145
+ /* Zero directories: match the remainder here. */
146
+ xt_glob_walk(dirPath, rest, resultPrefix, withFileTypes, list);
147
+ /* One or more: descend and retry `**`. */
148
+ xt_glob_descend(dirPath, pattern, resultPrefix, withFileTypes, list);
149
+ return;
150
+ }
151
+
152
+ char segment[256];
153
+ if (segmentLength >= sizeof(segment)) return;
154
+ memcpy(segment, pattern, segmentLength);
155
+ segment[segmentLength] = '\0';
156
+
157
+ xt_fs_dir dir;
158
+ if (!xt_fs_dir_open(&dir, dirPath)) return;
159
+ while (xt_fs_dir_next(&dir)) {
160
+ const char *name = xt_fs_dir_name(&dir);
161
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
162
+ if (name[0] == '.' && segment[0] != '.') continue;
163
+ if (!xt_glob_match_segment(segment, name)) continue;
164
+
165
+ char *child = xt_glob_join(dirPath, name);
166
+ if (!child) continue;
167
+ if (rest[0] == '\0') {
168
+ if (withFileTypes) xt_glob_push(list, xt_node_dirent_result(dirPath, name));
169
+ else {
170
+ size_t size = strlen(resultPrefix) + strlen(name) + 1;
171
+ char *result = (char *)malloc(size);
172
+ if (result) {
173
+ snprintf(result, size, "%s%s", resultPrefix, name);
174
+ xt_glob_push(list, xt_string_from_cstr(result));
175
+ free(result);
176
+ }
177
+ }
178
+ } else if (xt_glob_is_dir(child, 1)) {
179
+ size_t size = strlen(resultPrefix) + strlen(name) + 2;
180
+ char *childPrefix = (char *)malloc(size);
181
+ if (childPrefix) {
182
+ snprintf(childPrefix, size, "%s%s/", resultPrefix, name);
183
+ xt_glob_walk(child, rest, childPrefix, withFileTypes, list);
184
+ free(childPrefix);
185
+ }
186
+ }
187
+ free(child);
188
+ }
189
+ xt_fs_dir_close(&dir);
190
+ }
191
+
192
+ static void xt_glob_run(const char *pattern, const char *cwd, int withFileTypes, xt_glob_list *list) {
193
+ if (!pattern) return;
194
+ const char *base = cwd && cwd[0] ? cwd : ".";
195
+ const char *prefix = "";
196
+ if (pattern[0] == '/') {
197
+ base = "/";
198
+ prefix = "/";
199
+ pattern++;
200
+ }
201
+ xt_glob_walk(base, pattern, prefix, withFileTypes, list);
202
+ }
203
+
204
+ xt_value xt_node_glob(int32_t argc, xt_value *argv) {
205
+ if (argc < 1) return xt_array_new(0, NULL);
206
+ xt_value options = argc > 1 ? argv[1] : XT_UNDEFINED;
207
+ int withFileTypes = XT_IS_OBJECT(options) && xt_truthy(xt_object_get_cstr(options, "withFileTypes"));
208
+ const char *cwd = NULL;
209
+ xt_value cwdValue = XT_IS_OBJECT(options) ? xt_object_get_cstr(options, "cwd") : XT_UNDEFINED;
210
+ if (XT_IS_STRING(cwdValue)) cwd = xt_string_data(cwdValue);
211
+
212
+ xt_glob_list list;
213
+ list.items = NULL;
214
+ list.count = 0;
215
+ list.capacity = 0;
216
+
217
+ xt_value patternValue = argv[0];
218
+ if (XT_IS_ARRAY(patternValue)) {
219
+ int32_t count = (int32_t)xt_to_number(xt_array_length(patternValue));
220
+ for (int32_t i = 0; i < count; i++) {
221
+ xt_value item = xt_array_get(patternValue, xt_number((double)i));
222
+ if (XT_IS_STRING(item)) xt_glob_run(xt_string_data(item), cwd, withFileTypes, &list);
223
+ }
224
+ } else {
225
+ const char *pattern = xt_node_cstr(patternValue);
226
+ xt_glob_run(pattern, cwd, withFileTypes, &list);
227
+ }
228
+
229
+ xt_value result = xt_array_new((int32_t)list.count, list.items);
230
+ free(list.items);
231
+ return result;
232
+ }
@@ -0,0 +1,91 @@
1
+ /*
2
+ * Node.js `fs` symbolic/hard link operations for xbintsc.
3
+ *
4
+ * linkSync, symlinkSync, readlinkSync
5
+ *
6
+ * Windows uses `CreateHardLink`/`CreateSymbolicLink`; `readlinkSync` is not
7
+ * available there (documented deviation).
8
+ */
9
+
10
+ #include "../node_common.h"
11
+ #include "fs_common.h"
12
+
13
+ #if defined(_WIN32)
14
+ #include <windows.h>
15
+ #else
16
+ #include <unistd.h>
17
+ #endif
18
+
19
+ xt_value xt_node_link(int32_t argc, xt_value *argv) {
20
+ if (argc < 2) return xt_undefined();
21
+ const char *existing = xt_node_cstr(argv[0]);
22
+ const char *newPath = xt_node_cstr(argv[1]);
23
+ #if defined(_WIN32)
24
+ if (!existing || !newPath || !CreateHardLinkA(newPath, existing, NULL)) {
25
+ xt_fs_raise_errno(errno ? errno : EPERM, "link", newPath);
26
+ return xt_undefined();
27
+ }
28
+ #else
29
+ if (!existing || !newPath || link(existing, newPath) != 0) {
30
+ xt_fs_raise_path("link", newPath);
31
+ return xt_undefined();
32
+ }
33
+ #endif
34
+ return xt_undefined();
35
+ }
36
+
37
+ xt_value xt_node_symlink(int32_t argc, xt_value *argv) {
38
+ if (argc < 2) return xt_undefined();
39
+ const char *target = xt_node_cstr(argv[0]);
40
+ const char *path = xt_node_cstr(argv[1]);
41
+ const char *type = argc > 2 ? xt_node_cstr(argv[2]) : NULL;
42
+ #if defined(_WIN32)
43
+ DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
44
+ if (type && (strcmp(type, "dir") == 0 || strcmp(type, "junction") == 0)) flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
45
+ if (!target || !path || !CreateSymbolicLinkA(path, target, flags)) {
46
+ xt_fs_raise_errno(errno ? errno : EPERM, "symlink", path);
47
+ return xt_undefined();
48
+ }
49
+ #else
50
+ (void)type;
51
+ if (!target || !path || symlink(target, path) != 0) {
52
+ xt_fs_raise_path("symlink", path);
53
+ return xt_undefined();
54
+ }
55
+ #endif
56
+ return xt_undefined();
57
+ }
58
+
59
+ xt_value xt_node_readlink(int32_t argc, xt_value *argv) {
60
+ if (argc < 1) return xt_undefined();
61
+ const char *path = xt_node_cstr(argv[0]);
62
+ #if defined(_WIN32)
63
+ (void)path;
64
+ xt_fs_raise_errno(ENOSYS, "readlink", path);
65
+ return xt_undefined();
66
+ #else
67
+ if (!path) {
68
+ xt_fs_raise_errno(ENOENT, "readlink", NULL);
69
+ return xt_undefined();
70
+ }
71
+ size_t capacity = 256;
72
+ for (;;) {
73
+ char *buffer = (char *)malloc(capacity);
74
+ if (!buffer) return xt_undefined();
75
+ ssize_t length = readlink(path, buffer, capacity);
76
+ if (length < 0) {
77
+ free(buffer);
78
+ xt_fs_raise_path("readlink", path);
79
+ return xt_undefined();
80
+ }
81
+ if ((size_t)length < capacity) {
82
+ buffer[length] = '\0';
83
+ xt_value result = xt_string_from_cstr(buffer);
84
+ free(buffer);
85
+ return result;
86
+ }
87
+ free(buffer);
88
+ capacity *= 2;
89
+ }
90
+ #endif
91
+ }