xbintsc 0.3.13 → 0.3.14

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 (72) hide show
  1. package/dist/src/codegen/generator/calls/access.js +13 -0
  2. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  3. package/dist/src/codegen/generator/calls/invocation.js +8 -2
  4. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  5. package/dist/src/codegen/generator/context.d.ts +6 -0
  6. package/dist/src/codegen/generator/context.js +24 -2
  7. package/dist/src/codegen/generator/context.js.map +1 -1
  8. package/dist/src/codegen/generator/expressions/primary.js +5 -0
  9. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  10. package/dist/src/driver/compiler.js +15 -3
  11. package/dist/src/driver/compiler.js.map +1 -1
  12. package/dist/src/driver/modules.d.ts +1 -1
  13. package/dist/src/driver/modules.js +235 -25
  14. package/dist/src/driver/modules.js.map +1 -1
  15. package/dist/src/extensions/node/fs/fs-ops.d.ts +5 -2
  16. package/dist/src/extensions/node/fs/fs-ops.js +45 -6
  17. package/dist/src/extensions/node/fs/fs-ops.js.map +1 -1
  18. package/dist/src/extensions/node/fs/index.d.ts +6 -2
  19. package/dist/src/extensions/node/fs/index.js +22 -3
  20. package/dist/src/extensions/node/fs/index.js.map +1 -1
  21. package/dist/src/extensions/node/fs-promises/index.d.ts +4 -3
  22. package/dist/src/extensions/node/fs-promises/index.js +31 -3
  23. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  24. package/dist/tests/cli/main.test.js +8 -0
  25. package/dist/tests/cli/main.test.js.map +1 -1
  26. package/dist/tests/driver/compiler.test.js +28 -1
  27. package/dist/tests/driver/compiler.test.js.map +1 -1
  28. package/dist/tests/driver/modules.test.js +91 -4
  29. package/dist/tests/driver/modules.test.js.map +1 -1
  30. package/dist/tests/e2e/compile-node.test.js +240 -0
  31. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  32. package/dist/tests/e2e/compile.test.js +12 -0
  33. package/dist/tests/e2e/compile.test.js.map +1 -1
  34. package/dist/tests/e2e/harness.js +11 -8
  35. package/dist/tests/e2e/harness.js.map +1 -1
  36. package/dist/tests/e2e/node-modules.test.js +1 -1
  37. package/dist/tests/e2e/node-modules.test.js.map +1 -1
  38. package/dist/tests/e2e/npm-modules.test.d.ts +9 -0
  39. package/dist/tests/e2e/npm-modules.test.js +55 -0
  40. package/dist/tests/e2e/npm-modules.test.js.map +1 -0
  41. package/dist/tests/e2e/runtime-coverage.test.d.ts +11 -0
  42. package/dist/tests/e2e/runtime-coverage.test.js +646 -0
  43. package/dist/tests/e2e/runtime-coverage.test.js.map +1 -0
  44. package/package.json +1 -1
  45. package/runtime/ext_node/buffer/parts/prototype.inc +7 -0
  46. package/runtime/ext_node/buffer/parts/search.inc +8 -4
  47. package/runtime/ext_node/child_process/child_process.c +5 -0
  48. package/runtime/ext_node/fs/constants.c +113 -0
  49. package/runtime/ext_node/fs/copy_ops.c +145 -0
  50. package/runtime/ext_node/fs/dir.c +101 -0
  51. package/runtime/ext_node/fs/fd_ops.c +405 -0
  52. package/runtime/ext_node/fs/fs_common.h +388 -5
  53. package/runtime/ext_node/fs/fs_ops.c +100 -139
  54. package/runtime/ext_node/fs/glob.c +232 -0
  55. package/runtime/ext_node/fs/link_ops.c +91 -0
  56. package/runtime/ext_node/fs/meta_ops.c +403 -0
  57. package/runtime/ext_node/fs/promises.c +275 -60
  58. package/runtime/ext_node/fs/read_file.c +8 -4
  59. package/runtime/ext_node/fs/watch.c +52 -0
  60. package/runtime/ext_node/fs/write_file.c +20 -11
  61. package/runtime/ext_node/stream/stream.c +19 -2
  62. package/runtime/rt.h +9 -0
  63. package/runtime/xt_io.c +21 -0
  64. package/src/codegen/generator/calls/access.ts +15 -0
  65. package/src/codegen/generator/calls/invocation.ts +8 -2
  66. package/src/codegen/generator/context.ts +30 -2
  67. package/src/codegen/generator/expressions/primary.ts +5 -0
  68. package/src/driver/compiler.ts +16 -3
  69. package/src/driver/modules.ts +251 -26
  70. package/src/extensions/node/fs/fs-ops.ts +48 -6
  71. package/src/extensions/node/fs/index.ts +25 -3
  72. package/src/extensions/node/fs-promises/index.ts +33 -4
@@ -2,68 +2,283 @@
2
2
  * Node.js `fs/promises` for xbintsc.
3
3
  *
4
4
  * xbintsc has no asynchronous I/O scheduler, so these builtins reuse the
5
- * synchronous `fs` implementations and wrap the result in an immediately
6
- * settled promise. `await`/`.then` therefore work exactly as expected while
7
- * the actual work happens synchronously.
5
+ * synchronous `fs` implementations and wrap the result in a promise that is
6
+ * already settled. `await`/`.then` therefore work as expected while the actual
7
+ * work happens synchronously. A synchronous failure is turned into a rejected
8
+ * promise instead of a thrown exception (matching Node's contract).
9
+ *
10
+ * `open()` resolves to a `FileHandle`-shaped object (a plain object with an
11
+ * `fd` property and the usual methods); there is no shared prototype yet, so
12
+ * `handle instanceof FileHandle` is not supported.
8
13
  */
9
14
 
10
15
  #include "../node_common.h"
16
+ #include "fs_common.h"
17
+
18
+ /* ------------------------------------------------------------------------- */
19
+ /* FileHandle */
20
+ /* ------------------------------------------------------------------------- */
21
+
22
+ static xt_value xt_fh_method(xt_value thisValue, xt_value (*fn)(int32_t, xt_value *), int32_t argc, xt_value *argv,
23
+ int prefix) {
24
+ xt_value args[10];
25
+ int32_t count = 0;
26
+ if (prefix >= 1) args[count++] = xt_object_get_cstr(thisValue, "fd");
27
+ if (prefix >= 2) args[count++] = xt_object_get_cstr(thisValue, "fd2");
28
+ for (int32_t i = 0; i < argc && count < 10; i++) args[count++] = argv[i];
29
+ return xt_fs_promisify(fn, count, args);
30
+ }
31
+
32
+ static xt_value xt_fh_read(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
33
+ (void)env;
34
+ return xt_fh_method(thisValue, xt_node_read, argc, argv, 1);
35
+ }
36
+
37
+ static xt_value xt_fh_write(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
38
+ (void)env;
39
+ return xt_fh_method(thisValue, xt_node_write, argc, argv, 1);
40
+ }
41
+
42
+ static xt_value xt_fh_close(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
43
+ (void)env;
44
+ return xt_fh_method(thisValue, xt_node_close, argc, argv, 1);
45
+ }
46
+
47
+ static xt_value xt_fh_stat(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
48
+ (void)env;
49
+ return xt_fh_method(thisValue, xt_node_fstat, argc, argv, 1);
50
+ }
51
+
52
+ static xt_value xt_fh_truncate(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
53
+ (void)env;
54
+ return xt_fh_method(thisValue, xt_node_ftruncate, argc, argv, 1);
55
+ }
56
+
57
+ static xt_value xt_fh_chmod(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
58
+ (void)env;
59
+ return xt_fh_method(thisValue, xt_node_fchmod, argc, argv, 1);
60
+ }
61
+
62
+ static xt_value xt_fh_chown(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
63
+ (void)env;
64
+ return xt_fh_method(thisValue, xt_node_fchown, argc, argv, 1);
65
+ }
66
+
67
+ static xt_value xt_fh_utimes(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
68
+ (void)env;
69
+ return xt_fh_method(thisValue, xt_node_futimes, argc, argv, 1);
70
+ }
71
+
72
+ static xt_value xt_fh_sync(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
73
+ (void)env;
74
+ return xt_fh_method(thisValue, xt_node_fsync, argc, argv, 1);
75
+ }
76
+
77
+ static xt_value xt_fh_datasync(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
78
+ (void)env;
79
+ return xt_fh_method(thisValue, xt_node_fdatasync, argc, argv, 1);
80
+ }
81
+
82
+ /* Read from the handle's current position to EOF; caller frees. */
83
+ static unsigned char *xt_fh_slurp(int fd, size_t *outLength) {
84
+ size_t capacity = 8192;
85
+ size_t length = 0;
86
+ unsigned char *bytes = (unsigned char *)malloc(capacity);
87
+ if (!bytes) return NULL;
88
+ for (;;) {
89
+ if (length == capacity) {
90
+ capacity *= 2;
91
+ unsigned char *grown = (unsigned char *)realloc(bytes, capacity);
92
+ if (!grown) {
93
+ free(bytes);
94
+ return NULL;
95
+ }
96
+ bytes = grown;
97
+ }
98
+ ssize_t bytesRead = XT_FS_READ(fd, bytes + length, capacity - length);
99
+ if (bytesRead < 0) {
100
+ free(bytes);
101
+ return NULL;
102
+ }
103
+ if (bytesRead == 0) break;
104
+ length += (size_t)bytesRead;
105
+ }
106
+ *outLength = length;
107
+ return bytes;
108
+ }
109
+
110
+ static xt_value xt_fh_read_all(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
111
+ (void)env;
112
+ int fd = (int)xt_to_number(xt_object_get_cstr(thisValue, "fd"));
113
+ const char *encoding = argc > 0 ? xt_fs_encoding(argv[0]) : NULL;
114
+ size_t length = 0;
115
+ unsigned char *bytes = xt_fh_slurp(fd, &length);
116
+ if (!bytes) return xt_promise_reject(xt_fs_error_object(errno, "read", NULL));
117
+ xt_value result = xt_fs_encode_bytes(bytes, length, encoding);
118
+ free(bytes);
119
+ return xt_promise_resolve(result);
120
+ }
121
+
122
+ static xt_value xt_fh_write_all(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
123
+ (void)env;
124
+ xt_value args[2];
125
+ args[0] = xt_object_get_cstr(thisValue, "fd");
126
+ args[1] = argc > 0 ? argv[0] : XT_UNDEFINED;
127
+ return xt_fs_promisify(xt_node_write, 2, args);
128
+ }
129
+
130
+ static xt_value xt_fh_new(xt_value fdValue) {
131
+ xt_value handle = xt_object_new();
132
+ xt_node_set(handle, "fd", fdValue);
133
+ xt_node_define_method(handle, "read", (void *)xt_fh_read);
134
+ xt_node_define_method(handle, "write", (void *)xt_fh_write);
135
+ xt_node_define_method(handle, "readFile", (void *)xt_fh_read_all);
136
+ xt_node_define_method(handle, "writeFile", (void *)xt_fh_write_all);
137
+ xt_node_define_method(handle, "appendFile", (void *)xt_fh_write_all);
138
+ xt_node_define_method(handle, "close", (void *)xt_fh_close);
139
+ xt_node_define_method(handle, "stat", (void *)xt_fh_stat);
140
+ xt_node_define_method(handle, "truncate", (void *)xt_fh_truncate);
141
+ xt_node_define_method(handle, "chmod", (void *)xt_fh_chmod);
142
+ xt_node_define_method(handle, "chown", (void *)xt_fh_chown);
143
+ xt_node_define_method(handle, "utimes", (void *)xt_fh_utimes);
144
+ xt_node_define_method(handle, "sync", (void *)xt_fh_sync);
145
+ xt_node_define_method(handle, "datasync", (void *)xt_fh_datasync);
146
+ return handle;
147
+ }
148
+
149
+ /* ------------------------------------------------------------------------- */
150
+ /* Promise wrappers */
151
+ /* ------------------------------------------------------------------------- */
152
+
153
+ #define XT_FS_PROMISE(name, fn) \
154
+ xt_value name(int32_t argc, xt_value *argv) { return xt_fs_promisify((fn), argc, argv); }
155
+
156
+ XT_FS_PROMISE(xt_node_p_read_file, xt_node_read_text_file)
157
+ XT_FS_PROMISE(xt_node_p_write_file, xt_node_write_file)
158
+ XT_FS_PROMISE(xt_node_p_append_file, xt_node_append_file)
159
+ XT_FS_PROMISE(xt_node_p_mkdir, xt_node_mkdir)
160
+ XT_FS_PROMISE(xt_node_p_readdir, xt_node_read_dir)
161
+ XT_FS_PROMISE(xt_node_p_rm, xt_node_rm)
162
+ XT_FS_PROMISE(xt_node_p_unlink, xt_node_unlink)
163
+ XT_FS_PROMISE(xt_node_p_rmdir, xt_node_rmdir)
164
+ XT_FS_PROMISE(xt_node_p_rename, xt_node_rename)
165
+ XT_FS_PROMISE(xt_node_p_copy_file, xt_node_copy_file)
166
+ XT_FS_PROMISE(xt_node_p_realpath, xt_node_realpath)
167
+ XT_FS_PROMISE(xt_node_p_stat, xt_node_stat)
168
+ XT_FS_PROMISE(xt_node_p_lstat, xt_node_lstat)
169
+ XT_FS_PROMISE(xt_node_p_access, xt_node_access)
170
+ XT_FS_PROMISE(xt_node_p_cp, xt_node_cp)
171
+ XT_FS_PROMISE(xt_node_p_chmod, xt_node_chmod)
172
+ XT_FS_PROMISE(xt_node_p_chown, xt_node_chown)
173
+ XT_FS_PROMISE(xt_node_p_lchmod, xt_node_lchmod)
174
+ XT_FS_PROMISE(xt_node_p_lchown, xt_node_lchown)
175
+ XT_FS_PROMISE(xt_node_p_truncate, xt_node_truncate)
176
+ XT_FS_PROMISE(xt_node_p_utimes, xt_node_utimes)
177
+ XT_FS_PROMISE(xt_node_p_lutimes, xt_node_lutimes)
178
+ XT_FS_PROMISE(xt_node_p_link, xt_node_link)
179
+ XT_FS_PROMISE(xt_node_p_symlink, xt_node_symlink)
180
+ XT_FS_PROMISE(xt_node_p_readlink, xt_node_readlink)
181
+ XT_FS_PROMISE(xt_node_p_mkdtemp, xt_node_mkdtemp)
182
+ XT_FS_PROMISE(xt_node_p_statfs, xt_node_statfs)
183
+ XT_FS_PROMISE(xt_node_p_opendir, xt_node_opendir)
184
+ XT_FS_PROMISE(xt_node_p_glob, xt_node_glob)
185
+ XT_FS_PROMISE(xt_node_p_watch, xt_node_watch)
186
+
187
+ xt_value xt_node_p_open(int32_t argc, xt_value *argv) {
188
+ void *frame = xt_try_enter();
189
+ if (xt_try_setjmp(frame) == 0) {
190
+ xt_value fdValue = xt_node_open(argc, argv);
191
+ xt_try_leave(frame);
192
+ return xt_promise_resolve(xt_fh_new(fdValue));
193
+ }
194
+ xt_value exception = xt_try_exception(frame);
195
+ xt_try_leave(frame);
196
+ return xt_promise_reject(exception);
197
+ }
198
+
199
+ /* ------------------------------------------------------------------------- */
200
+ /* `fs.promises` facade */
201
+ /* ------------------------------------------------------------------------- */
202
+
203
+ #define XT_FS_PROMISES_FACADE(name, object) \
204
+ static xt_value xt_fs_promises_facade_##name(xt_value thisValue, xt_value env, int32_t argc, \
205
+ xt_value *argv) { \
206
+ (void)thisValue; \
207
+ (void)env; \
208
+ return name(argc, argv); \
209
+ }
210
+
211
+ XT_FS_PROMISES_FACADE(xt_node_p_read_file, _)
212
+ XT_FS_PROMISES_FACADE(xt_node_p_write_file, _)
213
+ XT_FS_PROMISES_FACADE(xt_node_p_append_file, _)
214
+ XT_FS_PROMISES_FACADE(xt_node_p_mkdir, _)
215
+ XT_FS_PROMISES_FACADE(xt_node_p_readdir, _)
216
+ XT_FS_PROMISES_FACADE(xt_node_p_rm, _)
217
+ XT_FS_PROMISES_FACADE(xt_node_p_unlink, _)
218
+ XT_FS_PROMISES_FACADE(xt_node_p_rmdir, _)
219
+ XT_FS_PROMISES_FACADE(xt_node_p_rename, _)
220
+ XT_FS_PROMISES_FACADE(xt_node_p_copy_file, _)
221
+ XT_FS_PROMISES_FACADE(xt_node_p_realpath, _)
222
+ XT_FS_PROMISES_FACADE(xt_node_p_stat, _)
223
+ XT_FS_PROMISES_FACADE(xt_node_p_lstat, _)
224
+ XT_FS_PROMISES_FACADE(xt_node_p_access, _)
225
+ XT_FS_PROMISES_FACADE(xt_node_p_cp, _)
226
+ XT_FS_PROMISES_FACADE(xt_node_p_chmod, _)
227
+ XT_FS_PROMISES_FACADE(xt_node_p_chown, _)
228
+ XT_FS_PROMISES_FACADE(xt_node_p_lchmod, _)
229
+ XT_FS_PROMISES_FACADE(xt_node_p_lchown, _)
230
+ XT_FS_PROMISES_FACADE(xt_node_p_truncate, _)
231
+ XT_FS_PROMISES_FACADE(xt_node_p_utimes, _)
232
+ XT_FS_PROMISES_FACADE(xt_node_p_lutimes, _)
233
+ XT_FS_PROMISES_FACADE(xt_node_p_link, _)
234
+ XT_FS_PROMISES_FACADE(xt_node_p_symlink, _)
235
+ XT_FS_PROMISES_FACADE(xt_node_p_readlink, _)
236
+ XT_FS_PROMISES_FACADE(xt_node_p_mkdtemp, _)
237
+ XT_FS_PROMISES_FACADE(xt_node_p_statfs, _)
238
+ XT_FS_PROMISES_FACADE(xt_node_p_opendir, _)
239
+ XT_FS_PROMISES_FACADE(xt_node_p_glob, _)
240
+ XT_FS_PROMISES_FACADE(xt_node_p_watch, _)
241
+ XT_FS_PROMISES_FACADE(xt_node_p_open, _)
11
242
 
12
- #include <errno.h>
13
- #include <string.h>
14
- #if defined(_WIN32)
15
- #include <io.h>
16
- #ifndef F_OK
17
- #define F_OK 0
18
- #endif
19
- #define xt_node_access _access
20
- #else
21
- #include <unistd.h>
22
- #define xt_node_access access
23
- #endif
24
-
25
- /* Synchronous implementations provided by the sibling `fs` translation units. */
26
- extern xt_value xt_node_read_text_file(int32_t argc, xt_value *argv);
27
- extern xt_value xt_node_write_file(int32_t argc, xt_value *argv);
28
- extern xt_value xt_node_append_file(int32_t argc, xt_value *argv);
29
- extern xt_value xt_node_mkdir(int32_t argc, xt_value *argv);
30
- extern xt_value xt_node_read_dir(int32_t argc, xt_value *argv);
31
- extern xt_value xt_node_rm(int32_t argc, xt_value *argv);
32
- extern xt_value xt_node_unlink(int32_t argc, xt_value *argv);
33
- extern xt_value xt_node_rmdir(int32_t argc, xt_value *argv);
34
- extern xt_value xt_node_rename(int32_t argc, xt_value *argv);
35
- extern xt_value xt_node_copy_file(int32_t argc, xt_value *argv);
36
- extern xt_value xt_node_realpath(int32_t argc, xt_value *argv);
37
- extern xt_value xt_node_stat(int32_t argc, xt_value *argv);
38
- extern xt_value xt_node_lstat(int32_t argc, xt_value *argv);
39
-
40
- static xt_value xt_node_promises_wrap(int32_t argc, xt_value *argv, xt_value (*fn)(int32_t, xt_value *)) {
41
- return xt_promise_resolve(fn(argc, argv));
42
- }
43
-
44
- xt_value xt_node_p_read_file(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_read_text_file); }
45
- xt_value xt_node_p_write_file(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_write_file); }
46
- xt_value xt_node_p_append_file(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_append_file); }
47
- xt_value xt_node_p_mkdir(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_mkdir); }
48
- xt_value xt_node_p_readdir(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_read_dir); }
49
- xt_value xt_node_p_rm(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_rm); }
50
- xt_value xt_node_p_unlink(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_unlink); }
51
- xt_value xt_node_p_rmdir(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_rmdir); }
52
- xt_value xt_node_p_rename(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_rename); }
53
- xt_value xt_node_p_copy_file(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_copy_file); }
54
- xt_value xt_node_p_realpath(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_realpath); }
55
- xt_value xt_node_p_stat(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_stat); }
56
- xt_value xt_node_p_lstat(int32_t argc, xt_value *argv) { return xt_node_promises_wrap(argc, argv, xt_node_lstat); }
57
-
58
- xt_value xt_node_p_access(int32_t argc, xt_value *argv) {
59
- if (argc < 1) return xt_promise_resolve(xt_undefined());
60
- const char *path = xt_string_data(xt_to_string(argv[0]));
61
- if (!path) return xt_promise_resolve(xt_undefined());
62
- if (xt_node_access(path, F_OK) == 0) return xt_promise_resolve(xt_undefined());
63
- xt_value error = xt_object_new();
64
- xt_node_set(error, "code", xt_string_from_cstr("ENOENT"));
65
- xt_node_set(error, "errno", xt_number(-2));
66
- xt_node_set(error, "path", xt_string_from_cstr(path));
67
- xt_node_set(error, "message", xt_string_from_cstr("ENOENT: no such file or directory"));
68
- return xt_promise_reject(error);
243
+ xt_value xt_fs_promises(void) {
244
+ static xt_value cached = 0;
245
+ if (XT_IS_OBJECT(cached)) return cached;
246
+ xt_value object = xt_object_new();
247
+ #define XT_FS_PROMISE_METHOD(jsName, name) \
248
+ xt_node_define_method(object, jsName, (void *)xt_fs_promises_facade_##name)
249
+ XT_FS_PROMISE_METHOD("readFile", xt_node_p_read_file);
250
+ XT_FS_PROMISE_METHOD("writeFile", xt_node_p_write_file);
251
+ XT_FS_PROMISE_METHOD("appendFile", xt_node_p_append_file);
252
+ XT_FS_PROMISE_METHOD("mkdir", xt_node_p_mkdir);
253
+ XT_FS_PROMISE_METHOD("readdir", xt_node_p_readdir);
254
+ XT_FS_PROMISE_METHOD("rm", xt_node_p_rm);
255
+ XT_FS_PROMISE_METHOD("unlink", xt_node_p_unlink);
256
+ XT_FS_PROMISE_METHOD("rmdir", xt_node_p_rmdir);
257
+ XT_FS_PROMISE_METHOD("rename", xt_node_p_rename);
258
+ XT_FS_PROMISE_METHOD("copyFile", xt_node_p_copy_file);
259
+ XT_FS_PROMISE_METHOD("realpath", xt_node_p_realpath);
260
+ XT_FS_PROMISE_METHOD("stat", xt_node_p_stat);
261
+ XT_FS_PROMISE_METHOD("lstat", xt_node_p_lstat);
262
+ XT_FS_PROMISE_METHOD("access", xt_node_p_access);
263
+ XT_FS_PROMISE_METHOD("cp", xt_node_p_cp);
264
+ XT_FS_PROMISE_METHOD("chmod", xt_node_p_chmod);
265
+ XT_FS_PROMISE_METHOD("chown", xt_node_p_chown);
266
+ XT_FS_PROMISE_METHOD("lchmod", xt_node_p_lchmod);
267
+ XT_FS_PROMISE_METHOD("lchown", xt_node_p_lchown);
268
+ XT_FS_PROMISE_METHOD("truncate", xt_node_p_truncate);
269
+ XT_FS_PROMISE_METHOD("utimes", xt_node_p_utimes);
270
+ XT_FS_PROMISE_METHOD("lutimes", xt_node_p_lutimes);
271
+ XT_FS_PROMISE_METHOD("link", xt_node_p_link);
272
+ XT_FS_PROMISE_METHOD("symlink", xt_node_p_symlink);
273
+ XT_FS_PROMISE_METHOD("readlink", xt_node_p_readlink);
274
+ XT_FS_PROMISE_METHOD("mkdtemp", xt_node_p_mkdtemp);
275
+ XT_FS_PROMISE_METHOD("statfs", xt_node_p_statfs);
276
+ XT_FS_PROMISE_METHOD("opendir", xt_node_p_opendir);
277
+ XT_FS_PROMISE_METHOD("glob", xt_node_p_glob);
278
+ XT_FS_PROMISE_METHOD("watch", xt_node_p_watch);
279
+ XT_FS_PROMISE_METHOD("open", xt_node_p_open);
280
+ #undef XT_FS_PROMISE_METHOD
281
+ xt_object_set(object, xt_string_from_cstr("constants"), xt_fs_constants());
282
+ cached = object;
283
+ return cached;
69
284
  }
@@ -7,9 +7,13 @@
7
7
  *
8
8
  * `readFileSync(path[, options])` reads a file and returns its contents as a
9
9
  * string. `options` may be an encoding string or an object with an `encoding`
10
- * property; `"utf8"` (the default), `"ascii"`, `"latin1"` and `"binary"` return
11
- * the raw bytes, while `"hex"` and `"base64"` return encoded strings. There is
12
- * no `Buffer` type yet, so binary reads always come back as text.
10
+ * property; `"utf8"` (the default), `"ascii"`, `"latin1"` and `"binary"`
11
+ * return the raw bytes, while `"hex"`, `"base64"` and `"base64url"` return
12
+ * encoded strings.
13
+ *
14
+ * Deviation: Node returns a `Buffer` when no encoding is given. xbintsc has no
15
+ * dedicated binary value type, so the default result is a UTF-8 string (raw
16
+ * byte values are preserved for ASCII/latin1 content).
13
17
  */
14
18
 
15
19
  #include "rt.h"
@@ -23,7 +27,7 @@ xt_value xt_node_read_text_file(int32_t argc, xt_value *argv) {
23
27
 
24
28
  FILE *file = fopen(path, "rb");
25
29
  if (!file) {
26
- fprintf(stderr, "xbintsc: cannot open '%s'\n", path);
30
+ xt_fs_error("open", path);
27
31
  return xt_undefined();
28
32
  }
29
33
  fseek(file, 0, SEEK_END);
@@ -0,0 +1,52 @@
1
+ /*
2
+ * `fs.watch` / `fs.watchFile` / `fs.unwatchFile` for xbintsc.
3
+ *
4
+ * The runtime has no event loop or timers, so file-change notifications can
5
+ * never fire. These builtins still return Node-shaped watcher objects (an
6
+ * emitter with a `close()` method) so that feature-detection and cleanup code
7
+ * keeps working; no `change`/`rename` events are delivered. This is a
8
+ * documented deviation.
9
+ */
10
+
11
+ #include "../node_common.h"
12
+ #include "fs_common.h"
13
+
14
+ static xt_value xt_fs_watch_close(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
15
+ (void)thisValue;
16
+ (void)env;
17
+ (void)argc;
18
+ (void)argv;
19
+ return xt_undefined();
20
+ }
21
+
22
+ static xt_value xt_fs_watch_make(xt_value filename, const char *listenerName) {
23
+ xt_value watcher = xt_object_new();
24
+ xt_node_install_emitter(watcher);
25
+ xt_node_define_method(watcher, "close", (void *)xt_fs_watch_close);
26
+ if (XT_IS_STRING(filename)) xt_node_set(watcher, "path", filename);
27
+ if (listenerName) xt_node_set(watcher, "kind", xt_string_from_cstr(listenerName));
28
+ return watcher;
29
+ }
30
+
31
+ xt_value xt_node_watch(int32_t argc, xt_value *argv) {
32
+ xt_value filename = argc > 0 ? argv[0] : XT_UNDEFINED;
33
+ for (int32_t i = 1; i < argc; i++) {
34
+ if (XT_IS_FUNCTION(argv[i])) {
35
+ xt_value watcher = xt_fs_watch_make(filename, NULL);
36
+ xt_node_listeners(watcher, "change", 1);
37
+ return watcher;
38
+ }
39
+ }
40
+ return xt_fs_watch_make(filename, NULL);
41
+ }
42
+
43
+ xt_value xt_node_watch_file(int32_t argc, xt_value *argv) {
44
+ xt_value filename = argc > 0 ? argv[0] : XT_UNDEFINED;
45
+ return xt_fs_watch_make(filename, "file");
46
+ }
47
+
48
+ xt_value xt_node_unwatch_file(int32_t argc, xt_value *argv) {
49
+ (void)argc;
50
+ (void)argv;
51
+ return xt_undefined();
52
+ }
@@ -2,9 +2,14 @@
2
2
  * Node.js `fs.writeFileSync` / `fs.appendFileSync` for xbintsc.
3
3
  *
4
4
  * `writeFileSync(path, data[, options])` truncates the file before writing;
5
- * `appendFileSync(path, data[, options])` appends. `data` is converted to a
6
- * string with the usual JS rules and `options` selects an encoding:
7
- * `"hex"`/`"base64"` decode the string, everything else writes its UTF-8 bytes.
5
+ * `appendFileSync(path, data[, options])` appends. `data` may be a string or a
6
+ * `Buffer`; strings are converted with `options.encoding` (`"hex"`/`"base64"`/
7
+ * `"base64url"` decode, everything else writes its UTF-8 bytes).
8
+ *
9
+ * Deviation: `options.mode` and `options.flag` are accepted but not applied
10
+ * (the file is always created with the process default mode and opened in the
11
+ * `"w"`/`"a"` binary mode). `TypedArray`/`DataView` inputs are not supported.
12
+ * Failures throw a Node-shaped `Error`.
8
13
  */
9
14
 
10
15
  #include "rt.h"
@@ -16,19 +21,23 @@ static xt_value xt_node_write_common(int32_t argc, xt_value *argv, const char *m
16
21
  const char *path = xt_string_data(pathValue);
17
22
  if (!path) return xt_undefined();
18
23
 
19
- xt_value dataValue = xt_to_string(argv[1]);
20
- const char *text = xt_string_data(dataValue);
21
- int32_t length = xt_string_length_value(dataValue);
22
- if (!text || length < 0) return xt_undefined();
23
-
24
- const char *encoding = argc > 2 ? xt_fs_encoding(argv[2]) : NULL;
25
24
  size_t byteLength = 0;
26
- unsigned char *bytes = xt_fs_decode_text(text, (size_t)length, encoding, &byteLength);
25
+ unsigned char *bytes;
26
+ if (xt_node_is_buffer(argv[1])) {
27
+ bytes = xt_node_buffer_bytes(argv[1], &byteLength);
28
+ } else {
29
+ xt_value dataValue = xt_to_string(argv[1]);
30
+ const char *text = xt_string_data(dataValue);
31
+ int32_t length = xt_string_length_value(dataValue);
32
+ if (!text || length < 0) return xt_undefined();
33
+ const char *encoding = argc > 2 ? xt_fs_encoding(argv[2]) : NULL;
34
+ bytes = xt_fs_decode_text(text, (size_t)length, encoding, &byteLength);
35
+ }
27
36
  if (!bytes) return xt_undefined();
28
37
 
29
38
  FILE *file = fopen(path, mode);
30
39
  if (!file) {
31
- fprintf(stderr, "xbintsc: cannot open '%s'\n", path);
40
+ xt_fs_error("open", path);
32
41
  free(bytes);
33
42
  return xt_undefined();
34
43
  }
@@ -145,6 +145,17 @@ static xt_value xt_stream_method_is_paused(xt_value thisValue, xt_value env, int
145
145
 
146
146
  static xt_value xt_stream_transform_flush(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv);
147
147
 
148
+ /* No-op write callback used when `end(chunk)` forwards the chunk to `write`:
149
+ * the caller's `write` hook may invoke its completion callback, which must not
150
+ * be the user's `end` callback and must not be missing. */
151
+ static xt_value xt_stream_noop(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
152
+ (void)thisValue;
153
+ (void)env;
154
+ (void)argc;
155
+ (void)argv;
156
+ return xt_undefined();
157
+ }
158
+
148
159
  static xt_value xt_stream_method_write(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
149
160
  (void)env;
150
161
  xt_value chunk = xt_arg(argc, argv, 0);
@@ -168,10 +179,12 @@ static xt_value xt_stream_method_write(xt_value thisValue, xt_value env, int32_t
168
179
 
169
180
  xt_value writeFn = xt_object_get_cstr(thisValue, "__write");
170
181
  if (XT_IS_FUNCTION(writeFn)) {
182
+ xt_value writeCallback =
183
+ XT_IS_FUNCTION(callback) ? callback : xt_closure_new((void *)xt_stream_noop, 0, NULL);
171
184
  xt_value args[3];
172
185
  args[0] = chunk;
173
186
  args[1] = encoding;
174
- args[2] = callback;
187
+ args[2] = writeCallback;
175
188
  xt_call_with_this(writeFn, thisValue, 3, args);
176
189
  }
177
190
 
@@ -190,7 +203,11 @@ static xt_value xt_stream_method_end(xt_value thisValue, xt_value env, int32_t a
190
203
  xt_value callback = XT_UNDEFINED;
191
204
  if (argc >= 1 && !xt_truthy(xt_is_nullish(chunk))) {
192
205
  xt_value writeFn = xt_object_get_cstr(thisValue, "write");
193
- if (XT_IS_FUNCTION(writeFn)) xt_call_with_this(writeFn, thisValue, 1, &chunk);
206
+ if (XT_IS_FUNCTION(writeFn)) {
207
+ xt_value noop = xt_closure_new((void *)xt_stream_noop, 0, NULL);
208
+ xt_value writeArgs[3] = {chunk, xt_undefined(), noop};
209
+ xt_call_with_this(writeFn, thisValue, 3, writeArgs);
210
+ }
194
211
  }
195
212
  for (int32_t i = 0; i < argc; i++) {
196
213
  if (XT_IS_FUNCTION(argv[i])) callback = argv[i];
package/runtime/rt.h CHANGED
@@ -17,6 +17,15 @@
17
17
  #include <stddef.h>
18
18
  #include <stdint.h>
19
19
 
20
+ /* POSIX `ssize_t` is not exposed by the MSVC/UCRT headers clang uses on
21
+ * Windows, but MinGW-w64 (the vendored toolchain) and every Unix libc define
22
+ * it. Alias the Windows pointer-sized signed integer so the runtime's POSIX
23
+ * shims compile unchanged across toolchains. */
24
+ #if defined(_WIN32) && !defined(__MINGW32__) && !defined(_SSIZE_T_DEFINED)
25
+ typedef intptr_t ssize_t;
26
+ #define _SSIZE_T_DEFINED
27
+ #endif
28
+
20
29
  #ifdef __cplusplus
21
30
  extern "C" {
22
31
  #endif
package/runtime/xt_io.c CHANGED
@@ -52,6 +52,26 @@ static xt_try_frame *g_try_top = NULL;
52
52
  int32_t xt_program_argc = 0;
53
53
  char **xt_program_argv = NULL;
54
54
 
55
+ #if defined(_WIN32)
56
+ /*
57
+ * The UCRT validates its low-level I/O arguments and, when one is out of
58
+ * range, calls the invalid-parameter handler. The default handler terminates
59
+ * the process through `_invoke_watson`, which fast-fails with exit status
60
+ * 0xC0000409. That makes POSIX-style calls such as `close(9999)` abort instead
61
+ * of reporting `EBADF`. Install a no-op handler so the CRT returns its normal
62
+ * error code (and sets `errno`) instead of crashing.
63
+ */
64
+ static void __cdecl xt_noop_invalid_parameter_handler(const wchar_t *expression, const wchar_t *function_name,
65
+ const wchar_t *file_name, unsigned int line_number,
66
+ uintptr_t reserved) {
67
+ (void)expression;
68
+ (void)function_name;
69
+ (void)file_name;
70
+ (void)line_number;
71
+ (void)reserved;
72
+ }
73
+ #endif
74
+
55
75
  void xt_set_program_args(int32_t argc, char **argv) {
56
76
  #if defined(_WIN32)
57
77
  /* Keep stdout/stderr in binary mode so `\n` is not translated to `\r\n`,
@@ -59,6 +79,7 @@ void xt_set_program_args(int32_t argc, char **argv) {
59
79
  sockets (the generated `main` calls this before running the program). */
60
80
  _setmode(_fileno(stdout), _O_BINARY);
61
81
  _setmode(_fileno(stderr), _O_BINARY);
82
+ _set_invalid_parameter_handler(xt_noop_invalid_parameter_handler);
62
83
  WSADATA wsaData;
63
84
  WSAStartup(MAKEWORD(2, 2), &wsaData);
64
85
  #endif
@@ -106,6 +106,21 @@ export const accessCallMethods: AccessCallMethods = {
106
106
  }
107
107
  }
108
108
  }
109
+ // Modules with no namespace dispatcher (`fs`, ...) can still expose value
110
+ // getters (`fs.constants`, `fs.promises`). Lower `fs.constants` the same
111
+ // way a named `import { constants } from "fs"` is lowered.
112
+ if (node.expression.kind === SyntaxKind.Identifier) {
113
+ const moduleExports = this.moduleExportsOfSymbol(
114
+ this.binding.symbolOfIdentifier.get(node.expression as Identifier),
115
+ );
116
+ const exported = moduleExports?.[node.name.text];
117
+ if (exported?.valueSymbol) {
118
+ this.extraDeclarations.add(`declare i64 @${exported.valueSymbol}(i32, i64*)`);
119
+ const result = this.reg();
120
+ this.emit(` ${result} = call i64 @${exported.valueSymbol}(i32 0, i64* null)`);
121
+ return result;
122
+ }
123
+ }
109
124
  const object = this.emitExpression(node.expression);
110
125
  return this.emitPropertyGet(object, node.name.text);
111
126
  },
@@ -91,8 +91,14 @@ export const invocationCallMethods: InvocationCallMethods = {
91
91
  return result;
92
92
  }
93
93
  if (callee.kind === SyntaxKind.Identifier) {
94
- if ((callee as Identifier).text === "super") return this.emitSuperConstructor(node);
95
- const symbol = this.binding.symbolOfIdentifier.get(callee as Identifier);
94
+ const identifier = callee as Identifier;
95
+ if (identifier.text === "super") return this.emitSuperConstructor(node);
96
+ const symbol = this.binding.symbolOfIdentifier.get(identifier);
97
+ // `require("x")` is CommonJS; the ESM form is the only supported import.
98
+ if (!symbol && identifier.text === "require") {
99
+ this.reportRequireUse(node);
100
+ return i64(XT_UNDEFINED);
101
+ }
96
102
  if (symbol && symbol.kind === SymbolKind.Import) {
97
103
  const exported = this.importExports.get(symbol.id);
98
104
  if (exported) return this.emitModuleExport(node, exported);
@@ -162,8 +162,8 @@ export class GeneratorContext {
162
162
  if (!symbols.some((symbol) => this.usedAsValue(symbol))) return;
163
163
  this.diagnostics.error(
164
164
  DiagnosticCode.ModuleNotFound,
165
- `module '${specifier}' is not supported: xbintsc can only import built-in platform modules and ` +
166
- "relative '.ts' files; third-party npm packages (node_modules) are not implemented yet",
165
+ `module '${specifier}' is not supported: xbintsc can import built-in platform modules, ` +
166
+ "relative '.ts' files and ESM packages under node_modules; CommonJS packages are not supported",
167
167
  declaration.moduleSpecifier,
168
168
  this.sourceFile.fileName,
169
169
  );
@@ -414,6 +414,34 @@ export class GeneratorContext {
414
414
  this.sourceFile.fileName,
415
415
  );
416
416
  }
417
+
418
+ /**
419
+ * Report a CommonJS `require(...)` use. xbintsc has no CommonJS module
420
+ * runtime, so point the user at the ESM `import` form (with the requested
421
+ * module in the hint when it is a string literal).
422
+ */
423
+ reportRequireUse(node: Node): void {
424
+ const specifier = requireSpecifier(node);
425
+ const replacement = specifier
426
+ ? `use an ESM import instead, e.g. \`import value from ${JSON.stringify(specifier)}\``
427
+ : "use an ESM `import` statement instead";
428
+ this.diagnostics.error(
429
+ DiagnosticCode.UnsupportedFeature,
430
+ `CommonJS \`require()\` is not supported; ${replacement}`,
431
+ node,
432
+ this.sourceFile.fileName,
433
+ );
434
+ }
435
+ }
436
+
437
+ /** The string literal passed to `require("...")`, when there is one. */
438
+ function requireSpecifier(node: Node): string | undefined {
439
+ if (node.kind !== SyntaxKind.CallExpression) return undefined;
440
+ const first = (node as { arguments?: readonly Node[] }).arguments?.[0];
441
+ if (first && first.kind === SyntaxKind.StringLiteral) {
442
+ return (first as unknown as { value: string }).value;
443
+ }
444
+ return undefined;
417
445
  }
418
446
 
419
447
  /** A bare module specifier (`fs`, `node:fs`, `@scope/pkg`), not a file path. */