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
@@ -14,6 +14,7 @@
14
14
 
15
15
  #include <stdint.h>
16
16
  #include <stdio.h>
17
+ #include <stdlib.h>
17
18
  #include <string.h>
18
19
 
19
20
  /* -- SHA-256 -------------------------------------------------------------- */
@@ -128,6 +129,115 @@ static void sha256_hash(const uint8_t *data, size_t length, uint8_t *out) {
128
129
  sha256_final(&ctx, out);
129
130
  }
130
131
 
132
+ /* -- SHA-1 ---------------------------------------------------------------- */
133
+
134
+ typedef struct {
135
+ uint32_t state[5];
136
+ uint64_t bitlen;
137
+ uint8_t buffer[64];
138
+ size_t buffer_length;
139
+ } sha1_ctx;
140
+
141
+ #define SHA1_ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
142
+
143
+ static void sha1_init(sha1_ctx *ctx) {
144
+ ctx->state[0] = 0x67452301;
145
+ ctx->state[1] = 0xefcdab89;
146
+ ctx->state[2] = 0x98badcfe;
147
+ ctx->state[3] = 0x10325476;
148
+ ctx->state[4] = 0xc3d2e1f0;
149
+ ctx->bitlen = 0;
150
+ ctx->buffer_length = 0;
151
+ }
152
+
153
+ static void sha1_compress(sha1_ctx *ctx, const uint8_t *block) {
154
+ uint32_t w[80];
155
+ for (int i = 0; i < 16; i++) {
156
+ w[i] = ((uint32_t)block[i * 4] << 24) | ((uint32_t)block[i * 4 + 1] << 16) |
157
+ ((uint32_t)block[i * 4 + 2] << 8) | ((uint32_t)block[i * 4 + 3]);
158
+ }
159
+ for (int i = 16; i < 80; i++) {
160
+ w[i] = SHA1_ROTL(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
161
+ }
162
+ uint32_t a = ctx->state[0], b = ctx->state[1], c = ctx->state[2], d = ctx->state[3];
163
+ uint32_t e = ctx->state[4];
164
+ for (int i = 0; i < 80; i++) {
165
+ uint32_t f, k;
166
+ if (i < 20) {
167
+ f = (b & c) | ((~b) & d);
168
+ k = 0x5a827999;
169
+ } else if (i < 40) {
170
+ f = b ^ c ^ d;
171
+ k = 0x6ed9eba1;
172
+ } else if (i < 60) {
173
+ f = (b & c) | (b & d) | (c & d);
174
+ k = 0x8f1bbcdc;
175
+ } else {
176
+ f = b ^ c ^ d;
177
+ k = 0xca62c1d6;
178
+ }
179
+ uint32_t temp = SHA1_ROTL(a, 5) + f + e + k + w[i];
180
+ e = d;
181
+ d = c;
182
+ c = SHA1_ROTL(b, 30);
183
+ b = a;
184
+ a = temp;
185
+ }
186
+ ctx->state[0] += a;
187
+ ctx->state[1] += b;
188
+ ctx->state[2] += c;
189
+ ctx->state[3] += d;
190
+ ctx->state[4] += e;
191
+ }
192
+
193
+ static void sha1_update(sha1_ctx *ctx, const uint8_t *data, size_t length) {
194
+ for (size_t i = 0; i < length; i++) {
195
+ ctx->buffer[ctx->buffer_length++] = data[i];
196
+ if (ctx->buffer_length == 64) {
197
+ sha1_compress(ctx, ctx->buffer);
198
+ ctx->bitlen += 512;
199
+ ctx->buffer_length = 0;
200
+ }
201
+ }
202
+ }
203
+
204
+ static void sha1_final(sha1_ctx *ctx, uint8_t *out) {
205
+ size_t i = ctx->buffer_length;
206
+ ctx->buffer[i++] = 0x80;
207
+ if (i > 56) {
208
+ while (i < 64) ctx->buffer[i++] = 0;
209
+ sha1_compress(ctx, ctx->buffer);
210
+ i = 0;
211
+ }
212
+ while (i < 56) ctx->buffer[i++] = 0;
213
+ ctx->bitlen += (uint64_t)ctx->buffer_length * 8;
214
+ for (int j = 0; j < 8; j++) ctx->buffer[56 + j] = (uint8_t)(ctx->bitlen >> (56 - j * 8));
215
+ sha1_compress(ctx, ctx->buffer);
216
+ for (int j = 0; j < 5; j++) {
217
+ out[j * 4] = (uint8_t)(ctx->state[j] >> 24);
218
+ out[j * 4 + 1] = (uint8_t)(ctx->state[j] >> 16);
219
+ out[j * 4 + 2] = (uint8_t)(ctx->state[j] >> 8);
220
+ out[j * 4 + 3] = (uint8_t)(ctx->state[j]);
221
+ }
222
+ }
223
+
224
+ /* Compute `algorithm` over `data`; returns the digest length, or 0 when the
225
+ * algorithm is unsupported. */
226
+ static size_t xt_hash_compute(const char *algorithm, const uint8_t *data, size_t length, uint8_t *out) {
227
+ if (algorithm && (strcmp(algorithm, "sha1") == 0 || strcmp(algorithm, "sha-1") == 0)) {
228
+ sha1_ctx ctx;
229
+ sha1_init(&ctx);
230
+ sha1_update(&ctx, data, length);
231
+ sha1_final(&ctx, out);
232
+ return 20;
233
+ }
234
+ if (!algorithm || strcmp(algorithm, "sha256") == 0 || strcmp(algorithm, "sha-256") == 0) {
235
+ sha256_hash(data, length, out);
236
+ return 32;
237
+ }
238
+ return 0;
239
+ }
240
+
131
241
  /* -- node:crypto surface -------------------------------------------------- */
132
242
 
133
243
  static xt_value hash_data(xt_value hash) {
@@ -136,6 +246,82 @@ static xt_value hash_data(xt_value hash) {
136
246
  return data;
137
247
  }
138
248
 
249
+ static const char *hash_algorithm(xt_value hash) {
250
+ xt_value algorithm = xt_object_get(hash, xt_string_from_cstr("_algorithm"));
251
+ const char *name = algorithm ? xt_string_data(xt_to_string(algorithm)) : NULL;
252
+ return name ? name : "sha256";
253
+ }
254
+
255
+ static const char *hash_encoding(xt_value hash, int argc, xt_value *argv) {
256
+ if (argc > 0 && !xt_truthy(xt_is_nullish(argv[0]))) {
257
+ const char *explicitEncoding = xt_string_data(xt_to_string(argv[0]));
258
+ if (explicitEncoding) return explicitEncoding;
259
+ }
260
+ xt_value encoding = xt_object_get(hash, xt_string_from_cstr("_outEncoding"));
261
+ const char *name = encoding ? xt_string_data(xt_to_string(encoding)) : NULL;
262
+ return name ? name : "hex";
263
+ }
264
+
265
+ /* Encode `length` digest bytes into an xt string using `encoding`. */
266
+ static xt_value hash_encode(const uint8_t *digest, size_t length, const char *encoding) {
267
+ if (!encoding || strcmp(encoding, "hex") == 0) {
268
+ static const char digits[] = "0123456789abcdef";
269
+ char *hex = (char *)malloc(length * 2 + 1);
270
+ if (!hex) return xt_undefined();
271
+ for (size_t i = 0; i < length; i++) {
272
+ hex[i * 2] = digits[digest[i] >> 4];
273
+ hex[i * 2 + 1] = digits[digest[i] & 0xf];
274
+ }
275
+ hex[length * 2] = 0;
276
+ xt_value result = xt_string_from_cstr(hex);
277
+ free(hex);
278
+ return result;
279
+ }
280
+ if (strcmp(encoding, "base64") == 0) {
281
+ static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
282
+ size_t outLength = ((length + 2) / 3) * 4;
283
+ char *buffer = (char *)malloc(outLength + 1);
284
+ if (!buffer) return xt_undefined();
285
+ size_t i = 0, j = 0;
286
+ while (i + 2 < length) {
287
+ uint32_t n = ((uint32_t)digest[i] << 16) | ((uint32_t)digest[i + 1] << 8) | (uint32_t)digest[i + 2];
288
+ buffer[j++] = alphabet[(n >> 18) & 63];
289
+ buffer[j++] = alphabet[(n >> 12) & 63];
290
+ buffer[j++] = alphabet[(n >> 6) & 63];
291
+ buffer[j++] = alphabet[n & 63];
292
+ i += 3;
293
+ }
294
+ if (length - i == 1) {
295
+ uint32_t n = (uint32_t)digest[i] << 16;
296
+ buffer[j++] = alphabet[(n >> 18) & 63];
297
+ buffer[j++] = alphabet[(n >> 12) & 63];
298
+ buffer[j++] = '=';
299
+ buffer[j++] = '=';
300
+ } else if (length - i == 2) {
301
+ uint32_t n = ((uint32_t)digest[i] << 16) | ((uint32_t)digest[i + 1] << 8);
302
+ buffer[j++] = alphabet[(n >> 18) & 63];
303
+ buffer[j++] = alphabet[(n >> 12) & 63];
304
+ buffer[j++] = alphabet[(n >> 6) & 63];
305
+ buffer[j++] = '=';
306
+ }
307
+ xt_value result = xt_string_new(buffer, j);
308
+ free(buffer);
309
+ return result;
310
+ }
311
+ return xt_string_new((const char *)digest, length);
312
+ }
313
+
314
+ /* Compute the digest of everything accumulated so far. */
315
+ static xt_value hash_finish(xt_value hash, int argc, xt_value *argv) {
316
+ xt_value data = hash_data(hash);
317
+ const char *text = xt_string_data(xt_to_string(data));
318
+ if (!text) text = "";
319
+ uint8_t digest[64];
320
+ size_t length = xt_hash_compute(hash_algorithm(hash), (const uint8_t *)text, strlen(text), digest);
321
+ if (length == 0) return xt_string_from_cstr("");
322
+ return hash_encode(digest, length, hash_encoding(hash, argc, argv));
323
+ }
324
+
139
325
  static xt_value xt_hash_update(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
140
326
  (void)env;
141
327
  if (XT_IS_OBJECT(thisValue) && argc > 0) {
@@ -147,22 +333,48 @@ static xt_value xt_hash_update(xt_value thisValue, xt_value env, int32_t argc, x
147
333
 
148
334
  static xt_value xt_hash_digest(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
149
335
  (void)env;
150
- const char *text = xt_string_data(xt_to_string(hash_data(thisValue)));
151
- if (!text) text = "";
152
- uint8_t out[32];
153
- sha256_hash((const uint8_t *)text, strlen(text), out);
154
- const char *encoding = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : "hex";
155
- if (!encoding || strcmp(encoding, "hex") == 0) {
156
- static const char digits[] = "0123456789abcdef";
157
- char hex[65];
158
- for (int i = 0; i < 32; i++) {
159
- hex[i * 2] = digits[out[i] >> 4];
160
- hex[i * 2 + 1] = digits[out[i] & 0xf];
336
+ return hash_finish(thisValue, argc, argv);
337
+ }
338
+
339
+ static xt_value xt_hash_set_encoding(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
340
+ (void)env;
341
+ if (XT_IS_OBJECT(thisValue) && argc > 0) {
342
+ xt_set(thisValue, xt_string_from_cstr("_outEncoding"), xt_to_string(argv[0]));
343
+ }
344
+ return thisValue;
345
+ }
346
+
347
+ static xt_value xt_hash_write(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
348
+ (void)env;
349
+ if (XT_IS_OBJECT(thisValue) && argc > 0 && !xt_truthy(xt_is_nullish(argv[0]))) {
350
+ xt_value combined = xt_add(hash_data(thisValue), xt_to_string(argv[0]));
351
+ xt_set(thisValue, xt_string_from_cstr("_data"), combined);
352
+ }
353
+ return xt_bool(1);
354
+ }
355
+
356
+ static xt_value xt_hash_end(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
357
+ (void)env;
358
+ if (XT_IS_OBJECT(thisValue)) {
359
+ xt_value chunk = xt_arg(argc, argv, 0);
360
+ if (argc > 0 && !xt_truthy(xt_is_nullish(chunk))) {
361
+ xt_value combined = xt_add(hash_data(thisValue), xt_to_string(chunk));
362
+ xt_set(thisValue, xt_string_from_cstr("_data"), combined);
161
363
  }
162
- hex[64] = 0;
163
- return xt_string_from_cstr(hex);
364
+ xt_set(thisValue, xt_string_from_cstr("_result"), hash_finish(thisValue, 0, NULL));
365
+ xt_set(thisValue, xt_string_from_cstr("_ended"), xt_bool(1));
164
366
  }
165
- return xt_string_from_cstr("");
367
+ return thisValue;
368
+ }
369
+
370
+ static xt_value xt_hash_read(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
371
+ (void)env;
372
+ (void)argc;
373
+ (void)argv;
374
+ if (!XT_IS_OBJECT(thisValue)) return xt_undefined();
375
+ xt_value result = xt_object_get(thisValue, xt_string_from_cstr("_result"));
376
+ if (xt_truthy(xt_is_nullish(result))) return xt_undefined();
377
+ return result;
166
378
  }
167
379
 
168
380
  xt_value xt_crypto_create_hash(int32_t argc, xt_value *argv) {
@@ -173,5 +385,9 @@ xt_value xt_crypto_create_hash(int32_t argc, xt_value *argv) {
173
385
  xt_set(hash, xt_string_from_cstr("_data"), xt_string_from_cstr(""));
174
386
  xt_set(hash, xt_string_from_cstr("update"), xt_closure_new((void *)xt_hash_update, 0, NULL));
175
387
  xt_set(hash, xt_string_from_cstr("digest"), xt_closure_new((void *)xt_hash_digest, 0, NULL));
388
+ xt_set(hash, xt_string_from_cstr("setEncoding"), xt_closure_new((void *)xt_hash_set_encoding, 0, NULL));
389
+ xt_set(hash, xt_string_from_cstr("write"), xt_closure_new((void *)xt_hash_write, 0, NULL));
390
+ xt_set(hash, xt_string_from_cstr("end"), xt_closure_new((void *)xt_hash_end, 0, NULL));
391
+ xt_set(hash, xt_string_from_cstr("read"), xt_closure_new((void *)xt_hash_read, 0, NULL));
176
392
  return hash;
177
393
  }
@@ -0,0 +1,31 @@
1
+ /*
2
+ * `fs.createReadStream` / `fs.createWriteStream` for xbintsc.
3
+ *
4
+ * xbintsc has no asynchronous file streaming, so these factories return small
5
+ * descriptor objects understood by `stream/promises`'s `pipeline`: they carry
6
+ * the target `_path` (and a `_kind` tag) and the pipeline reads/writes the file
7
+ * in one shot. They also behave well enough as EventEmitters for simple
8
+ * synchronous `on("data")` usage (see `stream.c`).
9
+ */
10
+
11
+ #include "rt.h"
12
+ #include "../node_common.h"
13
+
14
+ static xt_value xt_node_stream_descriptor(const char *kind, const char *path) {
15
+ xt_value stream = xt_object_new();
16
+ xt_node_set(stream, "_kind", xt_string_from_cstr(kind));
17
+ xt_node_set(stream, "_path", xt_string_from_cstr(path));
18
+ return stream;
19
+ }
20
+
21
+ xt_value xt_node_create_read_stream(int32_t argc, xt_value *argv) {
22
+ const char *path = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : NULL;
23
+ if (!path) return xt_undefined();
24
+ return xt_node_stream_descriptor("readStream", path);
25
+ }
26
+
27
+ xt_value xt_node_create_write_stream(int32_t argc, xt_value *argv) {
28
+ const char *path = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : NULL;
29
+ if (!path) return xt_undefined();
30
+ return xt_node_stream_descriptor("writeStream", path);
31
+ }
@@ -0,0 +1,147 @@
1
+ /*
2
+ * `stream/promises` `pipeline` for xbintsc.
3
+ *
4
+ * Synchronous drain: read the source descriptor's file, apply each transform
5
+ * (currently gzip), then write the destination descriptor's file. Returns an
6
+ * already-resolved promise so `await pipeline(...)` works at the top level.
7
+ *
8
+ * Sources and destinations are the descriptor objects produced by
9
+ * `fs.createReadStream` / `fs.createWriteStream`; a transform is the descriptor
10
+ * produced by `zlib.createGzip`.
11
+ */
12
+
13
+ #include "../node_common.h"
14
+
15
+ #include <string.h>
16
+
17
+ /* -- gzip (stored blocks) ------------------------------------------------- */
18
+
19
+ static uint32_t xt_pipeline_crc32(const unsigned char *data, size_t length) {
20
+ uint32_t crc = 0xffffffffu;
21
+ for (size_t i = 0; i < length; i++) {
22
+ crc ^= data[i];
23
+ for (int bit = 0; bit < 8; bit++) {
24
+ uint32_t mask = (uint32_t)(-(int32_t)(crc & 1));
25
+ crc = (crc >> 1) ^ (0xedb88320u & mask);
26
+ }
27
+ }
28
+ return crc ^ 0xffffffffu;
29
+ }
30
+
31
+ static void xt_pipeline_le32(unsigned char *out, uint32_t value) {
32
+ out[0] = (unsigned char)(value & 0xff);
33
+ out[1] = (unsigned char)((value >> 8) & 0xff);
34
+ out[2] = (unsigned char)((value >> 16) & 0xff);
35
+ out[3] = (unsigned char)((value >> 24) & 0xff);
36
+ }
37
+
38
+ /* Wrap `data` in a gzip container using DEFLATE stored blocks. */
39
+ static int xt_pipeline_gzip(const unsigned char *data, size_t length, unsigned char **out, size_t *outLength) {
40
+ size_t blocks = length / 65535 + 1;
41
+ size_t capacity = 10 + length + blocks * 5 + 8;
42
+ unsigned char *buffer = (unsigned char *)malloc(capacity);
43
+ if (!buffer) return 0;
44
+ size_t pos = 0;
45
+ buffer[pos++] = 0x1f;
46
+ buffer[pos++] = 0x8b;
47
+ buffer[pos++] = 0x08; /* CM: deflate */
48
+ buffer[pos++] = 0x00; /* FLG */
49
+ buffer[pos++] = 0x00; /* MTIME */
50
+ buffer[pos++] = 0x00;
51
+ buffer[pos++] = 0x00;
52
+ buffer[pos++] = 0x00;
53
+ buffer[pos++] = 0x00; /* XFL */
54
+ buffer[pos++] = 0xff; /* OS: unknown */
55
+
56
+ size_t offset = 0;
57
+ do {
58
+ size_t chunk = length - offset;
59
+ if (chunk > 65535) chunk = 65535;
60
+ int final = offset + chunk >= length;
61
+ buffer[pos++] = (unsigned char)(final ? 0x01 : 0x00);
62
+ buffer[pos++] = (unsigned char)(chunk & 0xff);
63
+ buffer[pos++] = (unsigned char)((chunk >> 8) & 0xff);
64
+ uint16_t inverse = (uint16_t)~chunk;
65
+ buffer[pos++] = (unsigned char)(inverse & 0xff);
66
+ buffer[pos++] = (unsigned char)((inverse >> 8) & 0xff);
67
+ if (chunk > 0) memcpy(buffer + pos, data + offset, chunk);
68
+ pos += chunk;
69
+ offset += chunk;
70
+ } while (offset < length);
71
+
72
+ xt_pipeline_le32(buffer + pos, xt_pipeline_crc32(data, length));
73
+ pos += 4;
74
+ xt_pipeline_le32(buffer + pos, (uint32_t)length);
75
+ pos += 4;
76
+ *out = buffer;
77
+ *outLength = pos;
78
+ return 1;
79
+ }
80
+
81
+ /* -- descriptor helpers --------------------------------------------------- */
82
+
83
+ static const char *xt_pipeline_prop(xt_value object, const char *key) {
84
+ if (!XT_IS_OBJECT(object)) return NULL;
85
+ return xt_string_data(xt_to_string(xt_node_get(object, key)));
86
+ }
87
+
88
+ static int xt_pipeline_read_file(const char *path, unsigned char **data, size_t *length) {
89
+ FILE *file = fopen(path, "rb");
90
+ if (!file) return 0;
91
+ fseek(file, 0, SEEK_END);
92
+ long size = ftell(file);
93
+ fseek(file, 0, SEEK_SET);
94
+ if (size < 0) {
95
+ fclose(file);
96
+ return 0;
97
+ }
98
+ unsigned char *buffer = (unsigned char *)malloc((size_t)size + 1);
99
+ if (!buffer) {
100
+ fclose(file);
101
+ return 0;
102
+ }
103
+ size_t read = fread(buffer, 1, (size_t)size, file);
104
+ fclose(file);
105
+ buffer[read] = 0;
106
+ *data = buffer;
107
+ *length = read;
108
+ return 1;
109
+ }
110
+
111
+ static int xt_pipeline_write_file(const char *path, const unsigned char *data, size_t length) {
112
+ FILE *file = fopen(path, "wb");
113
+ if (!file) return 0;
114
+ size_t written = length > 0 ? fwrite(data, 1, length, file) : 0;
115
+ fclose(file);
116
+ return written == length;
117
+ }
118
+
119
+ /* -- pipeline ------------------------------------------------------------- */
120
+
121
+ xt_value xt_node_stream_pipeline(int32_t argc, xt_value *argv) {
122
+ if (argc < 2) return xt_promise_resolve(xt_undefined());
123
+ const char *sourcePath = xt_pipeline_prop(argv[0], "_path");
124
+ const char *destPath = xt_pipeline_prop(argv[argc - 1], "_path");
125
+ if (!sourcePath || !destPath) return xt_promise_resolve(xt_undefined());
126
+
127
+ unsigned char *data = NULL;
128
+ size_t length = 0;
129
+ if (!xt_pipeline_read_file(sourcePath, &data, &length)) return xt_promise_resolve(xt_undefined());
130
+
131
+ for (int32_t i = 1; i < argc - 1; i++) {
132
+ const char *kind = xt_pipeline_prop(argv[i], "_kind");
133
+ if (kind && strcmp(kind, "gzip") == 0) {
134
+ unsigned char *compressed = NULL;
135
+ size_t compressedLength = 0;
136
+ if (xt_pipeline_gzip(data, length, &compressed, &compressedLength)) {
137
+ free(data);
138
+ data = compressed;
139
+ length = compressedLength;
140
+ }
141
+ }
142
+ }
143
+
144
+ xt_pipeline_write_file(destPath, data, length);
145
+ free(data);
146
+ return xt_promise_resolve(xt_undefined());
147
+ }
@@ -56,6 +56,24 @@ static xt_value xt_stream_deliver(xt_value stream, xt_value chunk) {
56
56
  return xt_bool(1);
57
57
  }
58
58
 
59
+ /* Attaching a `data` listener drains anything `push`/`Readable.from` buffered
60
+ * before the listener existed, so no chunk is silently lost. */
61
+ static xt_value xt_stream_method_on(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
62
+ xt_value result = xt_node_on_impl(thisValue, env, argc, argv);
63
+ const char *event = xt_node_cstr(xt_arg(argc, argv, 0));
64
+ if (event && strcmp(event, "data") == 0 && xt_stream_has_data_listener(thisValue)) {
65
+ xt_value buffer = xt_stream_buffer(thisValue);
66
+ int32_t count = (int32_t)xt_to_number(xt_array_length(buffer));
67
+ if (count > 0) {
68
+ xt_node_set(thisValue, "__readable", xt_array_new(0, NULL));
69
+ for (int32_t i = 0; i < count; i++) {
70
+ xt_node_emit1(thisValue, "data", xt_array_get(buffer, xt_number((double)i)));
71
+ }
72
+ }
73
+ }
74
+ return result;
75
+ }
76
+
59
77
  static xt_value xt_stream_method_push(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
60
78
  (void)env;
61
79
  xt_value chunk = xt_arg(argc, argv, 0);
@@ -331,6 +349,8 @@ static xt_value xt_stream_proto(void) {
331
349
  if (proto) return proto;
332
350
  proto = xt_object_new();
333
351
  xt_node_install_emitter(proto);
352
+ xt_node_define_method(proto, "on", (void *)xt_stream_method_on);
353
+ xt_node_define_method(proto, "addListener", (void *)xt_stream_method_on);
334
354
  xt_node_define_method(proto, "push", (void *)xt_stream_method_push);
335
355
  xt_node_define_method(proto, "unshift", (void *)xt_stream_method_unshift);
336
356
  xt_node_define_method(proto, "read", (void *)xt_stream_method_read);
@@ -0,0 +1,102 @@
1
+ /*
2
+ * Node.js `node:test` module for xbintsc.
3
+ *
4
+ * A compact, synchronous TAP runner. `test(name, fn)` runs the callback
5
+ * immediately, catching a thrown `AssertionError` (or any error) and emitting
6
+ * `ok` / `not ok` lines. A summary is printed once, at process exit, via
7
+ * `atexit`, and a failed run exits with status 1 like Node's runner.
8
+ *
9
+ * `import test from "node:test"` binds the callable default export
10
+ * (`xt_node_test`); `test.skip`, `test.todo` and the hook helpers are provided
11
+ * as no-op/wrapper variants.
12
+ */
13
+
14
+ #include "../node_common.h"
15
+
16
+ #include <string.h>
17
+
18
+ static int xt_test_count = 0;
19
+ static int xt_test_pass = 0;
20
+ static int xt_test_fail = 0;
21
+ static int xt_test_started = 0;
22
+ static int xt_test_reported = 0;
23
+
24
+ static void xt_test_report(void) {
25
+ if (xt_test_reported) return;
26
+ xt_test_reported = 1;
27
+ if (!xt_test_started) return;
28
+ fflush(stdout);
29
+ printf("1..%d\n", xt_test_count);
30
+ printf("# tests %d\n", xt_test_count);
31
+ printf("# pass %d\n", xt_test_pass);
32
+ if (xt_test_fail > 0) printf("# fail %d\n", xt_test_fail);
33
+ fflush(stdout);
34
+ if (xt_test_fail > 0) exit(1);
35
+ }
36
+
37
+ static void xt_test_begin(void) {
38
+ if (xt_test_started) return;
39
+ xt_test_started = 1;
40
+ printf("TAP version 13\n");
41
+ atexit(xt_test_report);
42
+ }
43
+
44
+ static const char *xt_test_name(int32_t argc, xt_value *argv) {
45
+ xt_value name = xt_arg(argc, argv, 0);
46
+ const char *text = name == XT_UNDEFINED ? NULL : xt_string_data(xt_to_string(name));
47
+ return text ? text : "<anonymous>";
48
+ }
49
+
50
+ xt_value xt_node_test(int32_t argc, xt_value *argv) {
51
+ xt_test_begin();
52
+ const char *name = xt_test_name(argc, argv);
53
+ xt_value fn = xt_arg(argc, argv, 1);
54
+ int index = ++xt_test_count;
55
+ printf("# Subtest: %s\n", name);
56
+ if (!XT_IS_FUNCTION(fn)) {
57
+ xt_test_pass += 1;
58
+ printf("ok %d - %s\n", index, name);
59
+ return xt_undefined();
60
+ }
61
+ void *frame = xt_try_enter();
62
+ if (xt_try_setjmp(frame) == 0) {
63
+ xt_closure_call(fn, 0, NULL);
64
+ xt_try_leave(frame);
65
+ xt_test_pass += 1;
66
+ printf("ok %d - %s\n", index, name);
67
+ } else {
68
+ xt_value error = xt_try_exception(frame);
69
+ xt_try_leave(frame);
70
+ xt_test_fail += 1;
71
+ printf("not ok %d - %s\n", index, name);
72
+ xt_value text = XT_IS_OBJECT(error) ? xt_error_to_string(error) : xt_to_string(error);
73
+ const char *message = xt_string_data(text);
74
+ printf(" ---\n error: '%s'\n ...\n", message ? message : "unknown error");
75
+ }
76
+ return xt_undefined();
77
+ }
78
+
79
+ xt_value xt_node_test_skip(int32_t argc, xt_value *argv) {
80
+ xt_test_begin();
81
+ const char *name = xt_test_name(argc, argv);
82
+ int index = ++xt_test_count;
83
+ xt_test_pass += 1;
84
+ printf("ok %d - %s # SKIP\n", index, name);
85
+ return xt_undefined();
86
+ }
87
+
88
+ xt_value xt_node_test_todo(int32_t argc, xt_value *argv) {
89
+ xt_test_begin();
90
+ const char *name = xt_test_name(argc, argv);
91
+ int index = ++xt_test_count;
92
+ xt_test_pass += 1;
93
+ printf("ok %d - %s # TODO\n", index, name);
94
+ return xt_undefined();
95
+ }
96
+
97
+ /* `before`/`after`/`beforeEach`/`afterEach` are accepted and ignored. */
98
+ xt_value xt_node_test_hook(int32_t argc, xt_value *argv) {
99
+ (void)argc;
100
+ (void)argv;
101
+ return xt_undefined();
102
+ }