xbintsc 0.3.8 → 0.3.10

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 (97) hide show
  1. package/README.md +14 -11
  2. package/README.zh-CN.md +13 -11
  3. package/dist/src/codegen/generator/calls/invocation.js +3 -0
  4. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  5. package/dist/src/codegen/generator/context.d.ts +5 -0
  6. package/dist/src/codegen/generator/context.js +18 -2
  7. package/dist/src/codegen/generator/context.js.map +1 -1
  8. package/dist/src/codegen/generator/expressions/primary.js +17 -0
  9. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  10. package/dist/src/codegen/generator/state.js +3 -0
  11. package/dist/src/codegen/generator/state.js.map +1 -1
  12. package/dist/src/codegen/generator/tables.js +3 -0
  13. package/dist/src/codegen/generator/tables.js.map +1 -1
  14. package/dist/src/driver/index.d.ts +1 -1
  15. package/dist/src/driver/index.js +1 -1
  16. package/dist/src/driver/index.js.map +1 -1
  17. package/dist/src/driver/paths.d.ts +7 -0
  18. package/dist/src/driver/paths.js +9 -0
  19. package/dist/src/driver/paths.js.map +1 -1
  20. package/dist/src/extensions/node/assert/index.d.ts +10 -0
  21. package/dist/src/extensions/node/assert/index.js +37 -0
  22. package/dist/src/extensions/node/assert/index.js.map +1 -0
  23. package/dist/src/extensions/node/buffer/index.js +16 -0
  24. package/dist/src/extensions/node/buffer/index.js.map +1 -1
  25. package/dist/src/extensions/node/dgram/index.js +4 -0
  26. package/dist/src/extensions/node/dgram/index.js.map +1 -1
  27. package/dist/src/extensions/node/events/index.js +1 -1
  28. package/dist/src/extensions/node/events/index.js.map +1 -1
  29. package/dist/src/extensions/node/fs/index.js +3 -1
  30. package/dist/src/extensions/node/fs/index.js.map +1 -1
  31. package/dist/src/extensions/node/fs/streams.d.ts +6 -0
  32. package/dist/src/extensions/node/fs/streams.js +9 -0
  33. package/dist/src/extensions/node/fs/streams.js.map +1 -0
  34. package/dist/src/extensions/node/http/index.js +3 -0
  35. package/dist/src/extensions/node/http/index.js.map +1 -1
  36. package/dist/src/extensions/node/index.js +10 -0
  37. package/dist/src/extensions/node/index.js.map +1 -1
  38. package/dist/src/extensions/node/net/index.js +7 -0
  39. package/dist/src/extensions/node/net/index.js.map +1 -1
  40. package/dist/src/extensions/node/stream/index.js +8 -0
  41. package/dist/src/extensions/node/stream/index.js.map +1 -1
  42. package/dist/src/extensions/node/stream-promises/index.d.ts +9 -0
  43. package/dist/src/extensions/node/stream-promises/index.js +16 -0
  44. package/dist/src/extensions/node/stream-promises/index.js.map +1 -0
  45. package/dist/src/extensions/node/test/index.d.ts +10 -0
  46. package/dist/src/extensions/node/test/index.js +30 -0
  47. package/dist/src/extensions/node/test/index.js.map +1 -0
  48. package/dist/src/extensions/node/worker_threads/index.d.ts +10 -0
  49. package/dist/src/extensions/node/worker_threads/index.js +22 -0
  50. package/dist/src/extensions/node/worker_threads/index.js.map +1 -0
  51. package/dist/src/extensions/node/zlib/index.d.ts +8 -0
  52. package/dist/src/extensions/node/zlib/index.js +15 -0
  53. package/dist/src/extensions/node/zlib/index.js.map +1 -0
  54. package/dist/src/extensions/registry.d.ts +6 -0
  55. package/dist/src/extensions/registry.js.map +1 -1
  56. package/dist/tests/driver/paths.test.js +11 -1
  57. package/dist/tests/driver/paths.test.js.map +1 -1
  58. package/dist/tests/e2e/compile-node.test.js +67 -1
  59. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  60. package/dist/tests/e2e/compile.test.js +12 -0
  61. package/dist/tests/e2e/compile.test.js.map +1 -1
  62. package/dist/tests/e2e/node-modules.test.js +127 -3
  63. package/dist/tests/e2e/node-modules.test.js.map +1 -1
  64. package/package.json +1 -1
  65. package/runtime/ext_node/assert/assert.c +207 -0
  66. package/runtime/ext_node/crypto/crypto.c +230 -14
  67. package/runtime/ext_node/fs/streams.c +31 -0
  68. package/runtime/ext_node/stream/pipeline.c +147 -0
  69. package/runtime/ext_node/stream/stream.c +20 -0
  70. package/runtime/ext_node/test/test.c +102 -0
  71. package/runtime/ext_node/worker_threads/worker_threads.c +206 -0
  72. package/runtime/ext_node/zlib/zlib.c +19 -0
  73. package/runtime/xt_io.c +86 -0
  74. package/runtime/xt_stdlib/dispatch.inc +9 -1
  75. package/scripts/package-release.ts +11 -3
  76. package/src/codegen/generator/calls/invocation.ts +2 -0
  77. package/src/codegen/generator/context.ts +19 -2
  78. package/src/codegen/generator/expressions/primary.ts +17 -0
  79. package/src/codegen/generator/state.ts +3 -0
  80. package/src/codegen/generator/tables.ts +3 -0
  81. package/src/driver/index.ts +1 -1
  82. package/src/driver/paths.ts +14 -0
  83. package/src/extensions/node/assert/index.ts +44 -0
  84. package/src/extensions/node/buffer/index.ts +19 -0
  85. package/src/extensions/node/dgram/index.ts +6 -0
  86. package/src/extensions/node/events/index.ts +1 -1
  87. package/src/extensions/node/fs/index.ts +3 -1
  88. package/src/extensions/node/fs/streams.ts +11 -0
  89. package/src/extensions/node/http/index.ts +8 -0
  90. package/src/extensions/node/index.ts +10 -0
  91. package/src/extensions/node/net/index.ts +10 -0
  92. package/src/extensions/node/stream/index.ts +10 -0
  93. package/src/extensions/node/stream-promises/index.ts +18 -0
  94. package/src/extensions/node/test/index.ts +35 -0
  95. package/src/extensions/node/worker_threads/index.ts +26 -0
  96. package/src/extensions/node/zlib/index.ts +17 -0
  97. package/src/extensions/registry.ts +6 -0
@@ -0,0 +1,206 @@
1
+ /*
2
+ * Node.js `worker_threads` module for xbintsc.
3
+ *
4
+ * xbintsc has no threads, so a worker is emulated by re-running the current
5
+ * executable as a child process. `isMainThread`/`workerData` read the
6
+ * `XBINTSC_WORKER`/`XBINTSC_WORKER_DATA` environment variables the parent sets,
7
+ * and `parentPort.postMessage(x)` writes `x` to the child's stdout, which the
8
+ * parent captures and re-emits as a `"message"` event on the `Worker` object.
9
+ * Because the event emitter buffers events until a listener is attached,
10
+ * `new Worker(...)` can run the child synchronously and still deliver the
11
+ * message to a later `worker.on("message", ...)`.
12
+ */
13
+
14
+ #include "../node_common.h"
15
+
16
+ #include <string.h>
17
+
18
+ #if defined(_WIN32)
19
+ #include <direct.h>
20
+ #include <io.h>
21
+ #include <process.h>
22
+ #else
23
+ #include <sys/wait.h>
24
+ #include <unistd.h>
25
+ #endif
26
+
27
+ /* -- environment ---------------------------------------------------------- */
28
+
29
+ static int xt_worker_is_worker(void) {
30
+ return getenv("XBINTSC_WORKER") != NULL;
31
+ }
32
+
33
+ static void xt_worker_set_env(const char *key, const char *value) {
34
+ #if defined(_WIN32)
35
+ _putenv_s(key, value ? value : "");
36
+ #else
37
+ if (value) setenv(key, value, 1);
38
+ else unsetenv(key);
39
+ #endif
40
+ }
41
+
42
+ /* -- value bindings ------------------------------------------------------- */
43
+
44
+ xt_value xt_worker_is_main_thread(int32_t argc, xt_value *argv) {
45
+ (void)argc;
46
+ (void)argv;
47
+ return xt_bool(!xt_worker_is_worker());
48
+ }
49
+
50
+ xt_value xt_worker_data(int32_t argc, xt_value *argv) {
51
+ (void)argc;
52
+ (void)argv;
53
+ const char *json = getenv("XBINTSC_WORKER_DATA");
54
+ if (!json) return xt_undefined();
55
+ xt_value text = xt_string_from_cstr(json);
56
+ return xt_json_parse(1, &text);
57
+ }
58
+
59
+ static xt_value xt_worker_port_post(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
60
+ (void)thisValue;
61
+ (void)env;
62
+ xt_value message = xt_arg(argc, argv, 0);
63
+ xt_value text = xt_to_string(message);
64
+ const char *data = xt_string_data(text);
65
+ if (data) {
66
+ fwrite(data, 1, strlen(data), stdout);
67
+ }
68
+ fputc('\n', stdout);
69
+ fflush(stdout);
70
+ return xt_undefined();
71
+ }
72
+
73
+ xt_value xt_worker_parent_port(int32_t argc, xt_value *argv) {
74
+ (void)argc;
75
+ (void)argv;
76
+ xt_value port = xt_object_new();
77
+ xt_node_set(port, "_kind", xt_string_from_cstr("parentPort"));
78
+ xt_node_set(port, "postMessage", xt_closure_new((void *)xt_worker_port_post, 0, NULL));
79
+ return port;
80
+ }
81
+
82
+ /* -- child process -------------------------------------------------------- */
83
+
84
+ /* Run `path` as a worker and capture its stdout. Returns a heap string (caller
85
+ * frees) or NULL on failure. */
86
+ static char *xt_worker_spawn(const char *path, const char *workerDataJson, int *exitCode) {
87
+ *exitCode = -1;
88
+ #if defined(_WIN32)
89
+ char previous[4096];
90
+ int had_cwd = _getcwd(previous, sizeof(previous)) != NULL;
91
+ (void)had_cwd;
92
+ FILE *capture = tmpfile();
93
+ if (!capture) return NULL;
94
+ int saved = _dup(1);
95
+ if (saved < 0) {
96
+ fclose(capture);
97
+ return NULL;
98
+ }
99
+ fflush(stdout);
100
+ _dup2(_fileno(capture), 1);
101
+ xt_worker_set_env("XBINTSC_WORKER", "1");
102
+ if (workerDataJson) xt_worker_set_env("XBINTSC_WORKER_DATA", workerDataJson);
103
+ intptr_t rc = _spawnlp(_P_WAIT, path, path, (char *)NULL);
104
+ fflush(stdout);
105
+ _dup2(saved, 1);
106
+ _close(saved);
107
+ xt_worker_set_env("XBINTSC_WORKER", NULL);
108
+ xt_worker_set_env("XBINTSC_WORKER_DATA", NULL);
109
+ if (rc < 0) {
110
+ fclose(capture);
111
+ return NULL;
112
+ }
113
+ *exitCode = (int)rc;
114
+ if (_fseeki64(capture, 0, SEEK_END) != 0) {
115
+ fclose(capture);
116
+ return NULL;
117
+ }
118
+ long long size = _ftelli64(capture);
119
+ _fseeki64(capture, 0, SEEK_SET);
120
+ char *buffer = (char *)malloc((size_t)size + 1);
121
+ if (!buffer) {
122
+ fclose(capture);
123
+ return NULL;
124
+ }
125
+ size_t got = fread(buffer, 1, (size_t)size, capture);
126
+ buffer[got] = 0;
127
+ fclose(capture);
128
+ return buffer;
129
+ #else
130
+ if (workerDataJson) xt_worker_set_env("XBINTSC_WORKER_DATA", workerDataJson);
131
+ xt_worker_set_env("XBINTSC_WORKER", "1");
132
+ int fds[2];
133
+ if (pipe(fds) != 0) return NULL;
134
+ pid_t pid = fork();
135
+ if (pid == 0) {
136
+ dup2(fds[1], STDOUT_FILENO);
137
+ close(fds[0]);
138
+ close(fds[1]);
139
+ char *childArgv[2];
140
+ childArgv[0] = (char *)path;
141
+ childArgv[1] = NULL;
142
+ execv(path, childArgv);
143
+ _exit(127);
144
+ }
145
+ close(fds[1]);
146
+ char *buffer = NULL;
147
+ size_t length = 0;
148
+ size_t capacity = 0;
149
+ char chunk[4096];
150
+ ssize_t got;
151
+ while ((got = read(fds[0], chunk, sizeof(chunk))) > 0) {
152
+ char *grown = (char *)realloc(buffer, length + (size_t)got + 1);
153
+ if (!grown) break;
154
+ buffer = grown;
155
+ memcpy(buffer + length, chunk, (size_t)got);
156
+ length += (size_t)got;
157
+ buffer[length] = 0;
158
+ }
159
+ close(fds[0]);
160
+ int status = 0;
161
+ waitpid(pid, &status, 0);
162
+ *exitCode = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
163
+ xt_worker_set_env("XBINTSC_WORKER", NULL);
164
+ xt_worker_set_env("XBINTSC_WORKER_DATA", NULL);
165
+ if (!buffer) buffer = (char *)calloc(1, 1);
166
+ (void)capacity;
167
+ return buffer;
168
+ #endif
169
+ }
170
+
171
+ xt_value xt_worker_ctor(int32_t argc, xt_value *argv) {
172
+ xt_value options = argc > 1 ? argv[1] : XT_UNDEFINED;
173
+ xt_value data = XT_IS_OBJECT(options) ? xt_object_get_cstr(options, "workerData") : XT_UNDEFINED;
174
+
175
+ const char *path = (xt_program_argv && xt_program_argv[0]) ? xt_program_argv[0] : NULL;
176
+ char *workerDataJson = NULL;
177
+ if (data != XT_UNDEFINED) {
178
+ xt_value json = xt_json_stringify(1, &data);
179
+ const char *text = xt_string_data(json);
180
+ if (text) {
181
+ size_t size = strlen(text);
182
+ workerDataJson = (char *)malloc(size + 1);
183
+ if (workerDataJson) memcpy(workerDataJson, text, size + 1);
184
+ }
185
+ }
186
+
187
+ xt_value worker = xt_object_new();
188
+ xt_node_set(worker, "_kind", xt_string_from_cstr("worker"));
189
+ xt_node_install_emitter(worker);
190
+ xt_node_set(worker, "postMessage", xt_closure_new((void *)xt_worker_port_post, 0, NULL));
191
+
192
+ int exitCode = -1;
193
+ char *output = path ? xt_worker_spawn(path, workerDataJson, &exitCode) : NULL;
194
+ free(workerDataJson);
195
+
196
+ if (output) {
197
+ size_t length = strlen(output);
198
+ while (length > 0 && (output[length - 1] == '\n' || output[length - 1] == '\r')) output[--length] = 0;
199
+ xt_node_emit1(worker, "message", xt_string_new(output, length));
200
+ free(output);
201
+ } else {
202
+ xt_node_emit1(worker, "error", xt_string_from_cstr("failed to start worker"));
203
+ }
204
+ xt_node_emit1(worker, "exit", xt_number((double)exitCode));
205
+ return worker;
206
+ }
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Node.js `zlib` module for xbintsc.
3
+ *
4
+ * Only `createGzip` is implemented, and the "compression" uses DEFLATE stored
5
+ * (uncompressed) blocks: the output is a fully valid gzip stream
6
+ * (`zlib.gunzipSync`/`gunzip`/HTTP clients all accept it) even though it does
7
+ * not shrink the payload. The descriptor is consumed by the `stream/promises`
8
+ * `pipeline` implementation.
9
+ */
10
+
11
+ #include "../node_common.h"
12
+
13
+ xt_value xt_node_create_gzip(int32_t argc, xt_value *argv) {
14
+ (void)argc;
15
+ (void)argv;
16
+ xt_value stream = xt_object_new();
17
+ xt_node_set(stream, "_kind", xt_string_from_cstr("gzip"));
18
+ return stream;
19
+ }
package/runtime/xt_io.c CHANGED
@@ -482,3 +482,89 @@ xt_value xt_console_time_end(int32_t argc, xt_value *argv) {
482
482
  if (index >= 0) xt_console_timers[index] = 0;
483
483
  return XT_UNDEFINED;
484
484
  }
485
+
486
+ /* -- base64 globals (btoa/atob) ------------------------------------------- */
487
+
488
+ static const char xt_base64_alphabet[] =
489
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
490
+
491
+ xt_value xt_btoa(int32_t argc, xt_value *argv) {
492
+ const char *input = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : "";
493
+ if (!input) input = "";
494
+ size_t length = strlen(input);
495
+ size_t outLength = ((length + 2) / 3) * 4;
496
+ char *buffer = (char *)malloc(outLength + 1);
497
+ if (!buffer) return xt_string_from_cstr("");
498
+ size_t i = 0, j = 0;
499
+ while (i + 2 < length) {
500
+ unsigned int n = ((unsigned char)input[i] << 16) | ((unsigned char)input[i + 1] << 8) |
501
+ (unsigned char)input[i + 2];
502
+ buffer[j++] = xt_base64_alphabet[(n >> 18) & 63];
503
+ buffer[j++] = xt_base64_alphabet[(n >> 12) & 63];
504
+ buffer[j++] = xt_base64_alphabet[(n >> 6) & 63];
505
+ buffer[j++] = xt_base64_alphabet[n & 63];
506
+ i += 3;
507
+ }
508
+ if (length - i == 1) {
509
+ unsigned int n = (unsigned char)input[i] << 16;
510
+ buffer[j++] = xt_base64_alphabet[(n >> 18) & 63];
511
+ buffer[j++] = xt_base64_alphabet[(n >> 12) & 63];
512
+ buffer[j++] = '=';
513
+ buffer[j++] = '=';
514
+ } else if (length - i == 2) {
515
+ unsigned int n = ((unsigned char)input[i] << 16) | ((unsigned char)input[i + 1] << 8);
516
+ buffer[j++] = xt_base64_alphabet[(n >> 18) & 63];
517
+ buffer[j++] = xt_base64_alphabet[(n >> 12) & 63];
518
+ buffer[j++] = xt_base64_alphabet[(n >> 6) & 63];
519
+ buffer[j++] = '=';
520
+ }
521
+ buffer[j] = 0;
522
+ xt_value result = xt_string_new(buffer, j);
523
+ free(buffer);
524
+ return result;
525
+ }
526
+
527
+ static int xt_base64_value(char c) {
528
+ if (c >= 'A' && c <= 'Z') return c - 'A';
529
+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
530
+ if (c >= '0' && c <= '9') return c - '0' + 52;
531
+ if (c == '+') return 62;
532
+ if (c == '/') return 63;
533
+ return -1;
534
+ }
535
+
536
+ xt_value xt_atob(int32_t argc, xt_value *argv) {
537
+ const char *input = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : "";
538
+ if (!input) input = "";
539
+ size_t length = strlen(input);
540
+ char *buffer = (char *)malloc(length / 4 * 3 + 1);
541
+ if (!buffer) return xt_string_from_cstr("");
542
+ size_t out = 0;
543
+ int values[4];
544
+ int count = 0;
545
+ for (size_t i = 0; i < length; i++) {
546
+ char c = input[i];
547
+ if (c == '=' || c == '\n' || c == '\r' || c == ' ' || c == '\t') {
548
+ if (c == '=') break;
549
+ continue;
550
+ }
551
+ int value = xt_base64_value(c);
552
+ if (value < 0) continue;
553
+ values[count++] = value;
554
+ if (count == 4) {
555
+ buffer[out++] = (char)((values[0] << 2) | (values[1] >> 4));
556
+ buffer[out++] = (char)(((values[1] & 15) << 4) | (values[2] >> 2));
557
+ buffer[out++] = (char)(((values[2] & 3) << 6) | values[3]);
558
+ count = 0;
559
+ }
560
+ }
561
+ if (count == 2) {
562
+ buffer[out++] = (char)((values[0] << 2) | (values[1] >> 4));
563
+ } else if (count == 3) {
564
+ buffer[out++] = (char)((values[0] << 2) | (values[1] >> 4));
565
+ buffer[out++] = (char)(((values[1] & 15) << 4) | (values[2] >> 2));
566
+ }
567
+ xt_value result = xt_string_new(buffer, out);
568
+ free(buffer);
569
+ return result;
570
+ }
@@ -246,7 +246,15 @@ xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *
246
246
  return XT_UNDEFINED;
247
247
  }
248
248
  const char *method = xt_string_data(name);
249
- if (!method) return XT_UNDEFINED;
249
+ if (!method) {
250
+ /* A computed non-string key (`arr[0]()`, `table[someKey]()` where the key
251
+ * is not a string) resolves like a property read followed by a call with
252
+ * `target` as `this`, instead of silently returning undefined. */
253
+ xt_value fn = xt_get(target, name);
254
+ if (XT_IS_FUNCTION(fn)) return xt_call_with_this(fn, target, argc, argv);
255
+ xt_throw(xt_string_from_cstr("TypeError: value at computed index is not a function"));
256
+ return XT_UNDEFINED;
257
+ }
250
258
  if (target == XT_UNDEFINED || target == XT_NULL) {
251
259
  const char *base = target == XT_NULL ? "null" : "undefined";
252
260
  char message[256];
@@ -2,8 +2,11 @@
2
2
  /**
3
3
  * Assemble a self-contained release archive for the current platform:
4
4
  *
5
- * dist/release/xbintsc-<os>-<arch>.tar.gz (or .tar.zst)
6
- * dist/release/xbintsc-<os>-<arch>.tar.gz.sha256
5
+ * dist/release/xbintsc-<version>-<os>-<arch>.tar.gz (or .tar.zst)
6
+ * dist/release/xbintsc-<version>-<os>-<arch>.tar.gz.sha256
7
+ *
8
+ * The version comes from package.json, so release assets from different
9
+ * versions do not collide in a download folder.
7
10
  *
8
11
  * The unpacked staging tree is built under `build/release-stage/`, never under
9
12
  * `dist/release/`, so an upload glob over `dist/release/*` cannot accidentally
@@ -49,11 +52,16 @@ import {
49
52
  platformSlug,
50
53
  releaseArchiveBase,
51
54
  releaseArchiveExtension,
55
+ releaseArchiveFileName,
52
56
  } from "../src/driver/paths.js";
53
57
 
54
58
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
55
59
  const slug = platformSlug();
56
60
  const base = releaseArchiveBase();
61
+ const version = (
62
+ JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { version: string }
63
+ ).version;
64
+ const archiveBase = releaseArchiveFileName(version);
57
65
  const exeSuffix = process.platform === "win32" ? ".exe" : "";
58
66
 
59
67
  function fail(message: string): never {
@@ -177,7 +185,7 @@ for (const name of ["README.md", "LICENSE"]) {
177
185
  // 5. Archive it. Prefer zstd when available, otherwise gzip.
178
186
  const hasZstd = !spawnSync("zstd", ["--version"], { stdio: "ignore" }).error;
179
187
  const extension = releaseArchiveExtension(hasZstd);
180
- const archive = join(releaseRoot, `${base}${extension}`);
188
+ const archive = join(releaseRoot, `${archiveBase}${extension}`);
181
189
  rmSync(archive, { force: true });
182
190
  const tarArgs = ["-c", hasZstd ? "--zstd" : "-z", "-f", archive, "-C", stageRoot, base];
183
191
  const tar = spawnSync("tar", tarArgs, { stdio: "inherit" });
@@ -96,6 +96,8 @@ export const invocationCallMethods: InvocationCallMethods = {
96
96
  if (symbol && symbol.kind === SymbolKind.Import) {
97
97
  const exported = this.importExports.get(symbol.id);
98
98
  if (exported) return this.emitModuleExport(node, exported);
99
+ const defaultExport = this.importDefaults.get(symbol.id);
100
+ if (defaultExport) return this.emitModuleExport(node, defaultExport);
99
101
  }
100
102
  if (symbol && symbol.kind === SymbolKind.Function) {
101
103
  const declaration = symbol.declarations[0];
@@ -39,6 +39,11 @@ export class GeneratorContext {
39
39
  * `fs.readFileSync(...)` straight to the module's runtime symbol.
40
40
  */
41
41
  readonly importModuleExports = new Map<number, ModuleExports>();
42
+ /**
43
+ * Imported symbol id -> the module's `default` export, for callable default
44
+ * imports such as `import test from "node:test"`.
45
+ */
46
+ readonly importDefaults = new Map<number, ModuleExport>();
42
47
  readonly globals: string[] = [];
43
48
  readonly functions: string[] = [];
44
49
  readonly strings = new Map<string, { label: string; length: number }>();
@@ -74,7 +79,11 @@ export class GeneratorContext {
74
79
  if (!clause) continue;
75
80
  if (clause.name) {
76
81
  const symbol = this.binding.symbolOfDeclaration.get(clause.name);
77
- if (symbol) this.bindModuleAlias(symbol, module);
82
+ if (symbol) {
83
+ this.bindModuleAlias(symbol, module);
84
+ const defaultExport = module.exports?.["default"];
85
+ if (defaultExport) this.importDefaults.set(symbol.id, defaultExport);
86
+ }
78
87
  }
79
88
  const bindings = clause.namedBindings;
80
89
  if (bindings && bindings.kind === SyntaxKind.NamedImports) {
@@ -82,7 +91,15 @@ export class GeneratorContext {
82
91
  const importedName = specifier.propertyName?.text ?? specifier.name.text;
83
92
  const exported = module.exports?.[importedName];
84
93
  const symbol = this.binding.symbolOfDeclaration.get(specifier.name);
85
- if (symbol && exported) this.importExports.set(symbol.id, exported);
94
+ if (symbol && exported) {
95
+ this.importExports.set(symbol.id, exported);
96
+ // A named constructor (`import { Buffer } from "buffer"`) also
97
+ // inherits its module's static dispatcher, so `Buffer.from(...)`
98
+ // lowers like `buffer.from(...)`.
99
+ if (exported.isConstructor && module.namespace) {
100
+ this.importNamespaces.set(symbol.id, module.namespace);
101
+ }
102
+ }
86
103
  }
87
104
  } else if (bindings && bindings.kind === SyntaxKind.NamespaceImport) {
88
105
  const symbol = this.binding.symbolOfDeclaration.get(bindings.name);
@@ -166,6 +166,23 @@ export const primaryExpressionMethods: PrimaryExpressionMethods = {
166
166
  return this.emitFunctionValue(symbol);
167
167
  }
168
168
  if (symbol.kind === SymbolKind.Import) {
169
+ // Some module bindings are plain values rather than functions
170
+ // (`isMainThread`, `workerData`, ...). They resolve through a nullary
171
+ // runtime getter instead of being called or namespaced.
172
+ const exported = this.importExports.get(symbol.id);
173
+ if (exported?.valueSymbol) {
174
+ this.extraDeclarations.add(`declare i64 @${exported.valueSymbol}(i32, i64*)`);
175
+ const result = this.reg();
176
+ this.emit(` ${result} = call i64 @${exported.valueSymbol}(i32 0, i64* null)`);
177
+ return result;
178
+ }
179
+ const defaultExport = this.importDefaults.get(symbol.id);
180
+ if (defaultExport?.valueSymbol) {
181
+ this.extraDeclarations.add(`declare i64 @${defaultExport.valueSymbol}(i32, i64*)`);
182
+ const result = this.reg();
183
+ this.emit(` ${result} = call i64 @${defaultExport.valueSymbol}(i32 0, i64* null)`);
184
+ return result;
185
+ }
169
186
  // Imported runtime bindings have no first-class value: they are meant
170
187
  // to be called (`readFileSync(...)`) or used as a namespace
171
188
  // (`path.join(...)`), both handled before identifier lowering.
@@ -124,6 +124,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
124
124
  "declare i64 @xt_events_static(i64, i32, i64*)",
125
125
  "declare i64 @xt_util_static(i64, i32, i64*)",
126
126
  "declare i64 @xt_querystring_static(i64, i32, i64*)",
127
+ "declare i64 @xt_assert_static(i64, i32, i64*)",
127
128
  "declare i64 @xt_process_call(i64, i32, i64*)",
128
129
  "declare i64 @xt_process_get(i64)",
129
130
  "declare i64 @xt_map_ctor(i32, i64*)",
@@ -205,6 +206,8 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
205
206
  "declare i64 @xt_encode_uri(i32, i64*)",
206
207
  "declare i64 @xt_decode_uri_component(i32, i64*)",
207
208
  "declare i64 @xt_decode_uri(i32, i64*)",
209
+ "declare i64 @xt_btoa(i32, i64*)",
210
+ "declare i64 @xt_atob(i32, i64*)",
208
211
  "declare i64 @xt_number_ctor(i32, i64*)",
209
212
  "declare i64 @xt_bigint_ctor(i32, i64*)",
210
213
  "declare i64 @xt_bigint_static(i64, i32, i64*)",
@@ -122,6 +122,8 @@ export const GLOBAL_FUNCTIONS: Record<string, string> = {
122
122
  encodeURI: "xt_encode_uri",
123
123
  decodeURIComponent: "xt_decode_uri_component",
124
124
  decodeURI: "xt_decode_uri",
125
+ btoa: "xt_btoa",
126
+ atob: "xt_atob",
125
127
  Number: "xt_number_ctor",
126
128
  String: "xt_string_ctor",
127
129
  Boolean: "xt_boolean_ctor",
@@ -176,6 +178,7 @@ export const NAMESPACE_STATICS: Record<string, string> = {
176
178
  events: "xt_events_static",
177
179
  util: "xt_util_static",
178
180
  querystring: "xt_querystring_static",
181
+ assert: "xt_assert_static",
179
182
  };
180
183
 
181
184
  /** Namespace identifiers whose property access maps to a runtime getter. */
@@ -1,6 +1,6 @@
1
1
  export { build, compileString, COMPILER_VERSION, type BuildOptions, type BuildResult, type EmitKind } from "./compiler.js";
2
2
  export { BuildCache, hashParts, hashString } from "./cache.js";
3
- export { findRuntimeDir, findPackageRoot, findVendorDir, vendorRootDir, platformSlug, releaseArchiveBase, releaseArchiveExtension } from "./paths.js";
3
+ export { findRuntimeDir, findPackageRoot, findVendorDir, vendorRootDir, platformSlug, releaseArchiveBase, releaseArchiveFileName, releaseArchiveExtension } from "./paths.js";
4
4
  export {
5
5
  toolchainDownload,
6
6
  toolchainSupportLibraries,
@@ -47,6 +47,20 @@ export function releaseArchiveBase(
47
47
  return `xbintsc-${platform}-${arch}`;
48
48
  }
49
49
 
50
+ /**
51
+ * Base name (no extension) of the published release archive, e.g.
52
+ * `xbintsc-0.3.8-linux-x64`. The version is embedded so downloads from
53
+ * different releases can coexist and users can tell them apart. The top-level
54
+ * directory inside the archive still uses {@link releaseArchiveBase}.
55
+ */
56
+ export function releaseArchiveFileName(
57
+ version: string,
58
+ platform: string = process.platform,
59
+ arch: string = process.arch,
60
+ ): string {
61
+ return `xbintsc-${version}-${platform}-${arch}`;
62
+ }
63
+
50
64
  /**
51
65
  * Extension for a release archive. `.tar.zst` when a `zstd` binary is available
52
66
  * (smaller, faster), otherwise the universally supported `.tar.gz`.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Node's `assert` module.
3
+ *
4
+ * All assertions dispatch through `runtime/ext_node/assert` via the `assert`
5
+ * namespace (`xt_assert_static`). `import assert from "node:assert"` binds the
6
+ * namespace, so `assert.strictEqual(...)` works, and named imports such as
7
+ * `import { strictEqual } from "assert"` lower to the same dispatcher.
8
+ */
9
+
10
+ import type { NodeModule } from "../module.js";
11
+ import { resolveFrom } from "../module.js";
12
+ import type { ModuleExports } from "../../registry.js";
13
+
14
+ const methods = [
15
+ "ok",
16
+ "fail",
17
+ "equal",
18
+ "notEqual",
19
+ "strictEqual",
20
+ "notStrictEqual",
21
+ "deepEqual",
22
+ "notDeepEqual",
23
+ "deepStrictEqual",
24
+ "notDeepStrictEqual",
25
+ "throws",
26
+ "doesNotThrow",
27
+ "rejects",
28
+ "doesNotReject",
29
+ "match",
30
+ "doesNotMatch",
31
+ "ifError",
32
+ ] as const;
33
+
34
+ const exports: ModuleExports = Object.fromEntries(
35
+ methods.map((method) => [method, { namespace: "assert", method }]),
36
+ );
37
+
38
+ export const assertModule: NodeModule = {
39
+ name: "assert",
40
+ namespace: "assert",
41
+ runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/assert/assert.c")],
42
+ builtins: () => ({}),
43
+ exports: () => exports,
44
+ };
@@ -9,10 +9,29 @@
9
9
 
10
10
  import type { NodeModule } from "../module.js";
11
11
  import { resolveFrom } from "../module.js";
12
+ import type { ModuleExports } from "../../registry.js";
13
+
14
+ const statics = [
15
+ "from",
16
+ "of",
17
+ "alloc",
18
+ "allocUnsafe",
19
+ "allocUnsafeSlow",
20
+ "isBuffer",
21
+ "byteLength",
22
+ "concat",
23
+ "compare",
24
+ ] as const;
25
+
26
+ const exports: ModuleExports = {
27
+ Buffer: { symbol: "xt_buffer_ctor", isConstructor: true },
28
+ ...Object.fromEntries(statics.map((method) => [method, { namespace: "Buffer", method }])),
29
+ };
12
30
 
13
31
  export const bufferModule: NodeModule = {
14
32
  name: "buffer",
15
33
  namespace: "Buffer",
16
34
  runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/buffer/buffer.c")],
17
35
  builtins: () => ({}),
36
+ exports: () => exports,
18
37
  };
@@ -7,10 +7,16 @@
7
7
 
8
8
  import type { NodeModule } from "../module.js";
9
9
  import { resolveFrom } from "../module.js";
10
+ import type { ModuleExports } from "../../registry.js";
11
+
12
+ const exports: ModuleExports = {
13
+ createSocket: { namespace: "dgram", method: "createSocket" },
14
+ };
10
15
 
11
16
  export const dgramModule: NodeModule = {
12
17
  name: "dgram",
13
18
  namespace: "dgram",
14
19
  runtimeSources: () => [resolveFrom(import.meta.url, "../../../../runtime/ext_node/dgram/dgram.c")],
15
20
  builtins: () => ({}),
21
+ exports: () => exports,
16
22
  };
@@ -11,7 +11,7 @@ import type { NodeModule } from "../module.js";
11
11
  import { resolveFrom } from "../module.js";
12
12
  import type { ModuleExports } from "../../registry.js";
13
13
 
14
- const methods = ["listenerCount", "getEventListeners", "getMaxListeners", "setMaxListeners", "once"] as const;
14
+ const methods = ["listenerCount", "getEventListeners", "getMaxListeners", "setMaxListeners", "once", "addAbortListener"] as const;
15
15
 
16
16
  const exports: ModuleExports = {
17
17
  EventEmitter: { symbol: "xt_event_emitter_ctor", isConstructor: true },
@@ -10,6 +10,7 @@ import { resolveFrom } from "../module.js";
10
10
  import { readFileBuiltins } from "./read-file.js";
11
11
  import { writeFileBuiltins } from "./write-file.js";
12
12
  import { fsOpsBuiltins } from "./fs-ops.js";
13
+ import { fsStreamBuiltins } from "./streams.js";
13
14
 
14
15
  export const fsModule: NodeModule = {
15
16
  name: "fs",
@@ -17,6 +18,7 @@ export const fsModule: NodeModule = {
17
18
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/read_file.c"),
18
19
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/write_file.c"),
19
20
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/fs_ops.c"),
21
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/streams.c"),
20
22
  ],
21
- builtins: () => ({ ...readFileBuiltins, ...writeFileBuiltins, ...fsOpsBuiltins }),
23
+ builtins: () => ({ ...readFileBuiltins, ...writeFileBuiltins, ...fsOpsBuiltins, ...fsStreamBuiltins }),
22
24
  };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Builtins for Node's `fs` streaming factories. xbintsc has no asynchronous
3
+ * streaming, so these return descriptor objects consumed by `stream/promises`.
4
+ */
5
+
6
+ import type { BuiltinFunction } from "../../registry.js";
7
+
8
+ export const fsStreamBuiltins: Readonly<Record<string, BuiltinFunction>> = {
9
+ createReadStream: { symbol: "xt_node_create_read_stream" },
10
+ createWriteStream: { symbol: "xt_node_create_write_stream" },
11
+ };
@@ -9,6 +9,13 @@
9
9
 
10
10
  import type { NodeModule } from "../module.js";
11
11
  import { resolveFrom } from "../module.js";
12
+ import type { ModuleExports } from "../../registry.js";
13
+
14
+ const methods = ["createServer", "request", "get"] as const;
15
+
16
+ const exports: ModuleExports = Object.fromEntries(
17
+ methods.map((method) => [method, { namespace: "http", method }]),
18
+ );
12
19
 
13
20
  export const httpModule: NodeModule = {
14
21
  name: "http",
@@ -18,4 +25,5 @@ export const httpModule: NodeModule = {
18
25
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/http/http.c"),
19
26
  ],
20
27
  builtins: () => ({}),
28
+ exports: () => exports,
21
29
  };