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.
- package/README.md +14 -11
- package/README.zh-CN.md +13 -11
- package/dist/src/codegen/generator/calls/invocation.js +3 -0
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +5 -0
- package/dist/src/codegen/generator/context.js +18 -2
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +17 -0
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/state.js +3 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +3 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/index.d.ts +1 -1
- package/dist/src/driver/index.js +1 -1
- package/dist/src/driver/index.js.map +1 -1
- package/dist/src/driver/paths.d.ts +7 -0
- package/dist/src/driver/paths.js +9 -0
- package/dist/src/driver/paths.js.map +1 -1
- package/dist/src/extensions/node/assert/index.d.ts +10 -0
- package/dist/src/extensions/node/assert/index.js +37 -0
- package/dist/src/extensions/node/assert/index.js.map +1 -0
- package/dist/src/extensions/node/buffer/index.js +16 -0
- package/dist/src/extensions/node/buffer/index.js.map +1 -1
- package/dist/src/extensions/node/dgram/index.js +4 -0
- package/dist/src/extensions/node/dgram/index.js.map +1 -1
- package/dist/src/extensions/node/events/index.js +1 -1
- package/dist/src/extensions/node/events/index.js.map +1 -1
- package/dist/src/extensions/node/fs/index.js +3 -1
- package/dist/src/extensions/node/fs/index.js.map +1 -1
- package/dist/src/extensions/node/fs/streams.d.ts +6 -0
- package/dist/src/extensions/node/fs/streams.js +9 -0
- package/dist/src/extensions/node/fs/streams.js.map +1 -0
- package/dist/src/extensions/node/http/index.js +3 -0
- package/dist/src/extensions/node/http/index.js.map +1 -1
- package/dist/src/extensions/node/index.js +10 -0
- package/dist/src/extensions/node/index.js.map +1 -1
- package/dist/src/extensions/node/net/index.js +7 -0
- package/dist/src/extensions/node/net/index.js.map +1 -1
- package/dist/src/extensions/node/stream/index.js +8 -0
- package/dist/src/extensions/node/stream/index.js.map +1 -1
- package/dist/src/extensions/node/stream-promises/index.d.ts +9 -0
- package/dist/src/extensions/node/stream-promises/index.js +16 -0
- package/dist/src/extensions/node/stream-promises/index.js.map +1 -0
- package/dist/src/extensions/node/test/index.d.ts +10 -0
- package/dist/src/extensions/node/test/index.js +30 -0
- package/dist/src/extensions/node/test/index.js.map +1 -0
- package/dist/src/extensions/node/worker_threads/index.d.ts +10 -0
- package/dist/src/extensions/node/worker_threads/index.js +22 -0
- package/dist/src/extensions/node/worker_threads/index.js.map +1 -0
- package/dist/src/extensions/node/zlib/index.d.ts +8 -0
- package/dist/src/extensions/node/zlib/index.js +15 -0
- package/dist/src/extensions/node/zlib/index.js.map +1 -0
- package/dist/src/extensions/registry.d.ts +6 -0
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/tests/driver/paths.test.js +11 -1
- package/dist/tests/driver/paths.test.js.map +1 -1
- package/dist/tests/e2e/compile-node.test.js +67 -1
- package/dist/tests/e2e/compile-node.test.js.map +1 -1
- package/dist/tests/e2e/compile.test.js +12 -0
- package/dist/tests/e2e/compile.test.js.map +1 -1
- package/dist/tests/e2e/node-modules.test.js +127 -3
- package/dist/tests/e2e/node-modules.test.js.map +1 -1
- package/package.json +1 -1
- package/runtime/ext_node/assert/assert.c +207 -0
- package/runtime/ext_node/crypto/crypto.c +230 -14
- package/runtime/ext_node/fs/streams.c +31 -0
- package/runtime/ext_node/stream/pipeline.c +147 -0
- package/runtime/ext_node/stream/stream.c +20 -0
- package/runtime/ext_node/test/test.c +102 -0
- package/runtime/ext_node/worker_threads/worker_threads.c +206 -0
- package/runtime/ext_node/zlib/zlib.c +19 -0
- package/runtime/xt_io.c +86 -0
- package/runtime/xt_stdlib/dispatch.inc +9 -1
- package/scripts/package-release.ts +11 -3
- package/src/codegen/generator/calls/invocation.ts +2 -0
- package/src/codegen/generator/context.ts +19 -2
- package/src/codegen/generator/expressions/primary.ts +17 -0
- package/src/codegen/generator/state.ts +3 -0
- package/src/codegen/generator/tables.ts +3 -0
- package/src/driver/index.ts +1 -1
- package/src/driver/paths.ts +14 -0
- package/src/extensions/node/assert/index.ts +44 -0
- package/src/extensions/node/buffer/index.ts +19 -0
- package/src/extensions/node/dgram/index.ts +6 -0
- package/src/extensions/node/events/index.ts +1 -1
- package/src/extensions/node/fs/index.ts +3 -1
- package/src/extensions/node/fs/streams.ts +11 -0
- package/src/extensions/node/http/index.ts +8 -0
- package/src/extensions/node/index.ts +10 -0
- package/src/extensions/node/net/index.ts +10 -0
- package/src/extensions/node/stream/index.ts +10 -0
- package/src/extensions/node/stream-promises/index.ts +18 -0
- package/src/extensions/node/test/index.ts +35 -0
- package/src/extensions/node/worker_threads/index.ts +26 -0
- package/src/extensions/node/zlib/index.ts +17 -0
- 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)
|
|
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, `${
|
|
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)
|
|
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)
|
|
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. */
|
package/src/driver/index.ts
CHANGED
|
@@ -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,
|
package/src/driver/paths.ts
CHANGED
|
@@ -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
|
};
|