@scriptc/runtime 0.0.10 → 0.0.11
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/package.json +1 -1
- package/src/scr_async.c +24 -4
- package/src/scr_bytes.c +40 -3
- package/src/scr_events_emitter.c +58 -5
- package/src/scr_library.c +15 -0
- package/src/scr_regex.c +39 -6
- package/src/scr_runtime.h +60 -13
- package/src/scr_stream.c +214 -7
package/package.json
CHANGED
package/src/scr_async.c
CHANGED
|
@@ -591,7 +591,8 @@ ScrArr *scr_active_resources(void) {
|
|
|
591
591
|
* left queued on the uncaught paths) never run — the teardown releases
|
|
592
592
|
* them, like Node dropping the queue at exit. */
|
|
593
593
|
typedef struct ScrNtick {
|
|
594
|
-
ScrClosure *cb;
|
|
594
|
+
ScrClosure *cb; /* owned; NULL for a raw C-hook entry */
|
|
595
|
+
void (*raw)(void); /* the hook when cb == NULL (stream tick markers) */
|
|
595
596
|
struct ScrNtick *next;
|
|
596
597
|
} ScrNtick;
|
|
597
598
|
|
|
@@ -612,11 +613,25 @@ void scr_next_tick(ScrClosure *cb /*moves*/) {
|
|
|
612
613
|
scr_nt_tail = t;
|
|
613
614
|
}
|
|
614
615
|
|
|
616
|
+
/* A RAW C-hook tick: the stream unit enqueues one marker per deferred
|
|
617
|
+
* stream emission so those emissions interleave with user nextTicks in
|
|
618
|
+
* true FIFO order — in Node they ARE nextTicks (resume_, emitReadable_,
|
|
619
|
+
* endReadableNT, ...). The hook dispatches exactly one stream tick;
|
|
620
|
+
* teardown just drops markers (the stream queue owns its entries). */
|
|
621
|
+
void scr_next_tick_raw(void (*fn)(void)) {
|
|
622
|
+
ScrNtick *t = calloc(1, sizeof *t);
|
|
623
|
+
if (!t) scr_oom();
|
|
624
|
+
t->raw = fn;
|
|
625
|
+
if (scr_nt_tail) scr_nt_tail->next = t;
|
|
626
|
+
else scr_nt_head = t;
|
|
627
|
+
scr_nt_tail = t;
|
|
628
|
+
}
|
|
629
|
+
|
|
615
630
|
void scr_nticks_teardown(void) {
|
|
616
631
|
while (scr_nt_head != NULL) {
|
|
617
632
|
ScrNtick *t = scr_nt_head;
|
|
618
633
|
scr_nt_head = t->next;
|
|
619
|
-
scr_closure_release(t->cb);
|
|
634
|
+
if (t->cb) scr_closure_release(t->cb);
|
|
620
635
|
free(t);
|
|
621
636
|
}
|
|
622
637
|
scr_nt_tail = NULL;
|
|
@@ -1677,9 +1692,14 @@ void scr_loop_run(void) {
|
|
|
1677
1692
|
scr_nt_head = t->next;
|
|
1678
1693
|
if (scr_nt_head == NULL) scr_nt_tail = NULL;
|
|
1679
1694
|
ScrClosure *cb = t->cb;
|
|
1695
|
+
void (*raw)(void) = t->raw;
|
|
1680
1696
|
free(t);
|
|
1681
|
-
|
|
1682
|
-
|
|
1697
|
+
if (cb) {
|
|
1698
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
1699
|
+
scr_closure_release(cb);
|
|
1700
|
+
} else {
|
|
1701
|
+
raw(); /* one stream tick, FIFO with the user ticks around it */
|
|
1702
|
+
}
|
|
1683
1703
|
if (scr_exc_pending()) return;
|
|
1684
1704
|
}
|
|
1685
1705
|
}
|
package/src/scr_bytes.c
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* Typed arrays / Buffer: ONE runtime representation (ScrBytes — see the
|
|
2
|
-
* header contract).
|
|
3
|
-
*
|
|
2
|
+
* header contract). An ScrBytes either OWNS its storage or is a VIEW into
|
|
3
|
+
* an owner's (backing set, chain depth exactly 1): DataView, subarray(),
|
|
4
|
+
* and Buffer's slice() all alias JS-exactly; only the plain typed arrays'
|
|
5
|
+
* slice() copies. Coercions are JS-exact
|
|
4
6
|
* (ToUint8/ToUint32 modular truncation, double→float rounding); the
|
|
5
7
|
* encoding conversions (utf8 with WHATWG replacement, hex, base64) match
|
|
6
8
|
* Node byte-for-byte — the differential corpus holds them to it. */
|
|
@@ -164,7 +166,7 @@ void scr_bytes_set(ScrBytes *b, double i, double v) {
|
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
/* ── slice / subarray (
|
|
169
|
+
/* ── slice (copy) / subarray (view) ────────────────────────────────────── */
|
|
168
170
|
|
|
169
171
|
/* Relative index: ToIntegerOrInfinity, negatives from the end, clamped. */
|
|
170
172
|
static size_t scr_bytes_rel_index(double i, size_t len) {
|
|
@@ -186,6 +188,41 @@ ScrBytes *scr_bytes_slice(const ScrBytes *b, double start, double end) {
|
|
|
186
188
|
return out;
|
|
187
189
|
}
|
|
188
190
|
|
|
191
|
+
/* TypedArray.prototype.fill on non-u8 receivers: per-ELEMENT fill with
|
|
192
|
+
* the element write's JS-exact coercion (ToUint32/ToInt32 wrap, f32
|
|
193
|
+
* rounding), slice-clamped relative indices; answers the receiver +1
|
|
194
|
+
* (chaining). Never throws — Buffer's throwing fill family is separate. */
|
|
195
|
+
ScrBytes *scr_bytes_fill_elem(ScrBytes *b, double v, double start, double end) {
|
|
196
|
+
size_t s = scr_bytes_rel_index(start, b->len);
|
|
197
|
+
size_t e = scr_bytes_rel_index(end, b->len);
|
|
198
|
+
for (size_t i = s; i < e; i++) scr_bytes_set(b, (double)i, v);
|
|
199
|
+
return scr_bytes_retain(b);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* subarray(start, end): a same-elem VIEW over the receiver's storage —
|
|
203
|
+
* TypedArray.prototype.subarray and Buffer's slice()/subarray() all alias
|
|
204
|
+
* in JS (mutations are visible both ways; buffer-swap through a slice is
|
|
205
|
+
* the canonical Node use). Chain depth stays exactly 1: a subarray of a
|
|
206
|
+
* view retains the OWNER, offsets composed here (the DataView rule).
|
|
207
|
+
* Relative indices clamp like slice; never throws. */
|
|
208
|
+
ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end) {
|
|
209
|
+
size_t s = scr_bytes_rel_index(start, b->len);
|
|
210
|
+
size_t e = scr_bytes_rel_index(end, b->len);
|
|
211
|
+
size_t count = e > s ? e - s : 0;
|
|
212
|
+
ScrBytes *owner = b->backing ? b->backing : b;
|
|
213
|
+
ScrBytes *v = malloc(sizeof(ScrBytes));
|
|
214
|
+
if (!v) scr_bytes_oom();
|
|
215
|
+
v->rc = 1;
|
|
216
|
+
v->len = count;
|
|
217
|
+
v->elem = b->elem;
|
|
218
|
+
v->data = b->data + s * scr_bytes_elem_size(b->elem);
|
|
219
|
+
v->backing = scr_bytes_retain(owner);
|
|
220
|
+
#ifdef SCR_RC_AUDIT
|
|
221
|
+
scr_live_bytes++;
|
|
222
|
+
#endif
|
|
223
|
+
return v;
|
|
224
|
+
}
|
|
225
|
+
|
|
189
226
|
void scr_bytes_set_from(ScrBytes *dst, const ScrBytes *src, double offset) {
|
|
190
227
|
double t = (offset != offset) ? 0 : trunc(offset);
|
|
191
228
|
if (!(t >= 0) || (double)src->len + t > (double)dst->len) {
|
package/src/scr_events_emitter.c
CHANGED
|
@@ -85,6 +85,12 @@ typedef struct ScrEeBucket {
|
|
|
85
85
|
struct ScrEeReg {
|
|
86
86
|
ScrEeBucket *head; /* singly linked, first-registration order */
|
|
87
87
|
double max; /* -1 = unset (follows the default), 0 = unlimited */
|
|
88
|
+
/* Node's kShapeMode: set when names were PRE-CREATED (the stream
|
|
89
|
+
* constructors). In shape mode removeListener KEEPS an emptied name's
|
|
90
|
+
* rank (Node writes events[type] = undefined instead of deleting) —
|
|
91
|
+
* only removeAllListeners deletes: the named form drops the one key,
|
|
92
|
+
* the no-argument wipe resets everything and leaves shape mode. */
|
|
93
|
+
bool shape;
|
|
88
94
|
};
|
|
89
95
|
|
|
90
96
|
/* The vtable of BARE `new EventEmitter()` instances; the emitted main()
|
|
@@ -145,6 +151,21 @@ static ScrEeBucket *scr_ee_bucket_ensure(ScrEeReg *reg, ScrStr *name) {
|
|
|
145
151
|
return b;
|
|
146
152
|
}
|
|
147
153
|
|
|
154
|
+
/* Reserve a name's first-registration RANK without any listener: Node's
|
|
155
|
+
* stream classes pre-create their known _events keys (a V8 shape
|
|
156
|
+
* optimization eventNames() order observes — 'error'/'data'/... list
|
|
157
|
+
* before user events added earlier). An empty bucket is ABSENT for every
|
|
158
|
+
* read (n == 0 everywhere); the first add fills it in place, and a bucket
|
|
159
|
+
* emptied by removal still drops — Node deleting the key. */
|
|
160
|
+
void scr_emitter_reserve(ScrEmitter *em, const char *name) {
|
|
161
|
+
ScrEeReg *reg = scr_ee_reg_ensure(em);
|
|
162
|
+
reg->shape = true;
|
|
163
|
+
if (scr_ee_bucket_find(reg, name, strlen(name))) return;
|
|
164
|
+
ScrStr *n = scr_str_new(name, strlen(name));
|
|
165
|
+
scr_ee_bucket_ensure(reg, n);
|
|
166
|
+
scr_str_release(n);
|
|
167
|
+
}
|
|
168
|
+
|
|
148
169
|
/* Drops an emptied bucket from the list (Node deletes the _events key; a
|
|
149
170
|
* later add re-appends at the end). */
|
|
150
171
|
static void scr_ee_bucket_drop(ScrEeReg *reg, ScrEeBucket *b) {
|
|
@@ -402,13 +423,14 @@ void scr_ee_inv_fixed4(ScrClosure *cb, va_list ap) {
|
|
|
402
423
|
|
|
403
424
|
/* Removes one entry (by index) from a bucket's live list and fires the
|
|
404
425
|
* 'removeListener' meta event AFTER the removal, Node's order. Drops the
|
|
405
|
-
* bucket when emptied
|
|
426
|
+
* bucket when emptied — except in shape mode, where the emptied name
|
|
427
|
+
* keeps its rank (Node's events[type] = undefined). */
|
|
406
428
|
static void scr_ee_remove_at(ScrEmitter *em, ScrEeBucket *b, size_t i) {
|
|
407
429
|
ScrEeEntry *e = b->ls[i];
|
|
408
430
|
memmove(b->ls + i, b->ls + i + 1, (b->n - i - 1) * sizeof *b->ls);
|
|
409
431
|
b->n--;
|
|
410
432
|
ScrStr *name = scr_str_retain(b->name);
|
|
411
|
-
if (b->n == 0) scr_ee_bucket_drop(em->reg, b);
|
|
433
|
+
if (b->n == 0 && !em->reg->shape) scr_ee_bucket_drop(em->reg, b);
|
|
412
434
|
scr_ee_entry_unref(e);
|
|
413
435
|
scr_ee_emit_meta(em, "removeListener", name);
|
|
414
436
|
scr_str_release(name);
|
|
@@ -456,6 +478,13 @@ void scr_emitter_check_listener(const ScrDyn *cb) {
|
|
|
456
478
|
* firing the meta event — and the whole-emitter form does every other
|
|
457
479
|
* event first (bucket order), 'removeListener' itself LAST. Returns em +1. */
|
|
458
480
|
static void scr_ee_remove_all_named(ScrEmitter *em, ScrEeBucket *b, bool meta) {
|
|
481
|
+
if (b->n == 0) {
|
|
482
|
+
/* An empty rank-holding bucket reaches here only from the WIPE scan
|
|
483
|
+
* (the named form skips undefined keys — Node no-ops and the rank
|
|
484
|
+
* stays): drop it, the wipe resets _events wholesale. */
|
|
485
|
+
scr_ee_bucket_drop(em->reg, b);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
459
488
|
if (!meta) {
|
|
460
489
|
for (size_t i = 0; i < b->n; i++) scr_ee_entry_unref(b->ls[i]);
|
|
461
490
|
b->n = 0;
|
|
@@ -497,7 +526,26 @@ ScrEmitter *scr_emitter_remove_all(ScrEmitter *em, ScrStr *name, bool all) {
|
|
|
497
526
|
bool meta = scr_ee_has(reg, "removeListener");
|
|
498
527
|
if (!all) {
|
|
499
528
|
ScrEeBucket *b = scr_ee_bucket_find(reg, name->data, name->len);
|
|
500
|
-
|
|
529
|
+
/* A rank-holding empty bucket is Node's `events[type] === undefined`:
|
|
530
|
+
* the named form no-ops there (the rank survives). A POPULATED name
|
|
531
|
+
* deletes its key even in shape mode — but through the meta path each
|
|
532
|
+
* listener leaves via removeListener, whose shape rule KEEPS the
|
|
533
|
+
* emptied rank (Node's exact split). */
|
|
534
|
+
if (b && b->n > 0) {
|
|
535
|
+
scr_ee_remove_all_named(em, b, meta);
|
|
536
|
+
if (!meta) {
|
|
537
|
+
/* Node's non-meta named branch: the count hitting zero replaces
|
|
538
|
+
* _events wholesale — every rank (pre-created included) goes,
|
|
539
|
+
* though kShapeMode itself stays set (Node's exact quirk). */
|
|
540
|
+
bool any_live = false;
|
|
541
|
+
for (ScrEeBucket *w = reg->head; w; w = w->next) {
|
|
542
|
+
if (w->n > 0) { any_live = true; break; }
|
|
543
|
+
}
|
|
544
|
+
if (!any_live) {
|
|
545
|
+
while (reg->head) scr_ee_bucket_drop(reg, reg->head);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
501
549
|
return scr_emitter_retain(em);
|
|
502
550
|
}
|
|
503
551
|
/* Every event except 'removeListener' first, in bucket order; then
|
|
@@ -515,6 +563,10 @@ ScrEmitter *scr_emitter_remove_all(ScrEmitter *em, ScrStr *name, bool all) {
|
|
|
515
563
|
}
|
|
516
564
|
ScrEeBucket *rl = scr_ee_bucket_find(reg, "removeListener", 14);
|
|
517
565
|
if (rl) scr_ee_remove_all_named(em, rl, meta);
|
|
566
|
+
/* The wipe replaces _events wholesale in Node — pre-created ranks are
|
|
567
|
+
* gone and the emitter leaves shape mode. */
|
|
568
|
+
while (reg->head) scr_ee_bucket_drop(reg, reg->head);
|
|
569
|
+
reg->shape = false;
|
|
518
570
|
return scr_emitter_retain(em);
|
|
519
571
|
}
|
|
520
572
|
|
|
@@ -633,12 +685,13 @@ double scr_emitter_listener_count_fn(ScrEmitter *em, ScrStr *name, ScrClosure *f
|
|
|
633
685
|
}
|
|
634
686
|
|
|
635
687
|
/* eventNames(): +1 string[] of the bucket names in first-registration
|
|
636
|
-
* order (exactly Node's _events key order).
|
|
688
|
+
* order (exactly Node's _events key order). Reserved-but-empty buckets
|
|
689
|
+
* (the stream pre-created keys) are invisible until a listener lands. */
|
|
637
690
|
ScrArr *scr_emitter_event_names(ScrEmitter *em) {
|
|
638
691
|
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0);
|
|
639
692
|
if (em->reg) {
|
|
640
693
|
for (ScrEeBucket *b = em->reg->head; b; b = b->next) {
|
|
641
|
-
scr_arr_push_ref(out, scr_str_retain(b->name));
|
|
694
|
+
if (b->n > 0) scr_arr_push_ref(out, scr_str_retain(b->name));
|
|
642
695
|
}
|
|
643
696
|
}
|
|
644
697
|
return out;
|
package/src/scr_library.c
CHANGED
|
@@ -251,6 +251,21 @@ ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len, const char *trap_ms
|
|
|
251
251
|
return b;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
double scr_library_i64_in(int64_t v, const char *trap_msg) {
|
|
255
|
+
/* The inbound declared-integer edge (ask 4): only |v| <= 2^53-1 rides
|
|
256
|
+
* f64 exactly; past it, (double)v silently rounds — a coercion the
|
|
257
|
+
* author never wrote, so the wrapper funnels the host-contract
|
|
258
|
+
* violation instead (same story as an impossible bytes length; the
|
|
259
|
+
* message is the compiler-assembled structured trap-teaching form). */
|
|
260
|
+
if (v > 9007199254740991LL || v < -9007199254740991LL) scr_trap(trap_msg);
|
|
261
|
+
return (double)v;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
double scr_library_u64_in(uint64_t v, const char *trap_msg) {
|
|
265
|
+
if (v > 9007199254740991ULL) scr_trap(trap_msg);
|
|
266
|
+
return (double)v;
|
|
267
|
+
}
|
|
268
|
+
|
|
254
269
|
void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len) {
|
|
255
270
|
scr_library_arena_keep(s, true);
|
|
256
271
|
*out = (const uint8_t *)s->data; /* NUL-terminated after len (ScrStr layout) */
|
package/src/scr_regex.c
CHANGED
|
@@ -484,13 +484,20 @@ ScrArr *scr_regex_match_all_into(ScrStr *s, ScrRegex *re, ScrArr *indices) {
|
|
|
484
484
|
* replacements are frontend-fenced): $$, $&, $` and $', $1..$99 (two digits
|
|
485
485
|
* win when in range — the refined ES2019 rule quickjs implements), with
|
|
486
486
|
* out-of-range references left literal and unmatched groups substituting
|
|
487
|
-
* empty. $<name>
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
487
|
+
* empty. $<name> resolves against the pattern's named capture groups
|
|
488
|
+
* (lre_get_groupnames — one NUL-terminated name per capture, "" for
|
|
489
|
+
* unnamed): the first PARTICIPATING capture with that name substitutes
|
|
490
|
+
* (ES2025 duplicates live in distinct alternatives, so at most one
|
|
491
|
+
* participates); a nonexistent or nonparticipating name substitutes
|
|
492
|
+
* empty (the spec's Get→undefined→"" path, Node-exact). When the pattern
|
|
493
|
+
* has NO named groups, namedCaptures is undefined and '$<' stays literal
|
|
494
|
+
* — also Node's unterminated-'$<name' answer either way. The template is
|
|
495
|
+
* scanned byte-wise: every '$' directive is ASCII, and UTF-8 continuation
|
|
496
|
+
* bytes can never alias it. */
|
|
491
497
|
static void scr_put_substitution(ScrJsonBuf *b, const uint16_t *u, int len,
|
|
492
498
|
int start, int end, uint8_t **capture,
|
|
493
|
-
int capture_count, const
|
|
499
|
+
int capture_count, const char *groupnames,
|
|
500
|
+
const ScrStr *rep) {
|
|
494
501
|
const uint8_t *ubase = (const uint8_t *)u;
|
|
495
502
|
size_t i = 0;
|
|
496
503
|
while (i < rep->len) {
|
|
@@ -534,6 +541,31 @@ static void scr_put_substitution(ScrJsonBuf *b, const uint16_t *u, int len,
|
|
|
534
541
|
scr_jb_putc(b, '$');
|
|
535
542
|
i++;
|
|
536
543
|
}
|
|
544
|
+
} else if (c1 == '<' && groupnames != NULL) {
|
|
545
|
+
/* $<name>: scan for '>'; absent, the '$' is literal (the rest
|
|
546
|
+
* re-scans verbatim — GetSubstitution's not-found answer). */
|
|
547
|
+
size_t gt = i + 2;
|
|
548
|
+
while (gt < rep->len && rep->data[gt] != '>') gt++;
|
|
549
|
+
if (gt >= rep->len) {
|
|
550
|
+
scr_jb_putc(b, '$');
|
|
551
|
+
i++;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const char *name = rep->data + i + 2;
|
|
555
|
+
size_t name_len = gt - (i + 2);
|
|
556
|
+
const char *p = groupnames; /* capture_count-1 entries: name NUL scope */
|
|
557
|
+
for (int k = 1; k < capture_count; k++) {
|
|
558
|
+
size_t glen = strlen(p);
|
|
559
|
+
if (glen == name_len && memcmp(p, name, name_len) == 0) {
|
|
560
|
+
const uint8_t *cs = capture[2 * k], *ce = capture[2 * k + 1];
|
|
561
|
+
if (cs && ce) {
|
|
562
|
+
scr_jb_put_utf16(b, u, (int)((cs - ubase) >> 1), (int)((ce - ubase) >> 1));
|
|
563
|
+
break; /* at most one duplicate participates */
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
p += glen + LRE_GROUP_NAME_TRAILER_LEN;
|
|
567
|
+
}
|
|
568
|
+
i = gt + 1;
|
|
537
569
|
} else {
|
|
538
570
|
scr_jb_putc(b, '$');
|
|
539
571
|
i++;
|
|
@@ -550,6 +582,7 @@ static ScrStr *scr_replace_impl(ScrStr *s, ScrRegex *re, ScrStr *rep) {
|
|
|
550
582
|
bool global = (re_flags & LRE_FLAG_GLOBAL) != 0;
|
|
551
583
|
bool unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0;
|
|
552
584
|
int capture_count = lre_get_capture_count(bc);
|
|
585
|
+
const char *groupnames = lre_get_groupnames(bc);
|
|
553
586
|
int len;
|
|
554
587
|
uint16_t *u = scr_to_utf16(s, &len);
|
|
555
588
|
uint8_t **capture = scr_capture_alloc(bc);
|
|
@@ -562,7 +595,7 @@ static ScrStr *scr_replace_impl(ScrStr *s, ScrRegex *re, ScrStr *rep) {
|
|
|
562
595
|
int start = (int)((capture[0] - ubase) >> 1);
|
|
563
596
|
int end = (int)((capture[1] - ubase) >> 1);
|
|
564
597
|
scr_jb_put_utf16(&b, u, next, start);
|
|
565
|
-
scr_put_substitution(&b, u, len, start, end, capture, capture_count, rep);
|
|
598
|
+
scr_put_substitution(&b, u, len, start, end, capture, capture_count, groupnames, rep);
|
|
566
599
|
next = end;
|
|
567
600
|
if (!global) break;
|
|
568
601
|
pos = end == start ? scr_advance(u, len, end, unicode) : end;
|
package/src/scr_runtime.h
CHANGED
|
@@ -163,6 +163,12 @@ ScrStr *scr_library_str_in(const uint8_t *p, size_t len); /* +1 */
|
|
|
163
163
|
* (structured trap-teaching bytes naming this entry's symbol), delivered
|
|
164
164
|
* through the funnel when len falls outside the marshalling class. */
|
|
165
165
|
ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len, const char *trap_msg); /* +1, u8 */
|
|
166
|
+
/* The inbound declared-integer edge (ask 4's i64/u64 parameter classes):
|
|
167
|
+
* exact conversion for |v| <= 2^53-1, the host-contract trap (same
|
|
168
|
+
* assembled SC4012 message shape as the bytes trap) past it — silent
|
|
169
|
+
* rounding is a coercion the author never wrote. */
|
|
170
|
+
double scr_library_i64_in(int64_t v, const char *trap_msg);
|
|
171
|
+
double scr_library_u64_in(uint64_t v, const char *trap_msg);
|
|
166
172
|
void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len);
|
|
167
173
|
void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
|
|
168
174
|
|
|
@@ -1362,6 +1368,10 @@ bool scr_emitter_emit_error(ScrEmitter *em, ScrStr *name, ScrError *err);
|
|
|
1362
1368
|
double scr_emitter_listener_count(ScrEmitter *em, ScrStr *name);
|
|
1363
1369
|
double scr_emitter_listener_count_fn(ScrEmitter *em, ScrStr *name, ScrClosure *fn);
|
|
1364
1370
|
ScrArr *scr_emitter_event_names(ScrEmitter *em); /* +1 string[] */
|
|
1371
|
+
/* Pre-create a name's eventNames() rank with no listener (Node's stream
|
|
1372
|
+
* classes pre-create their known _events keys; empty = absent for every
|
|
1373
|
+
* other read). The stream constructors call this. */
|
|
1374
|
+
void scr_emitter_reserve(ScrEmitter *em, const char *name);
|
|
1365
1375
|
ScrArr *scr_emitter_listeners(ScrEmitter *em, ScrStr *name); /* +1 closures */
|
|
1366
1376
|
ScrEmitter *scr_emitter_set_max(ScrEmitter *em, double n); /* returns em +1 */
|
|
1367
1377
|
double scr_emitter_get_max(ScrEmitter *em);
|
|
@@ -1562,6 +1572,13 @@ ScrDyn *scr_stream_done_dyn_l(ScrClosure *clo, ScrDyn *const *args, size_t argc)
|
|
|
1562
1572
|
* utf8, Node's decodeStrings default); push answers the below-hwm bool. */
|
|
1563
1573
|
bool scr_stream_push(ScrStream *s, ScrBytes *chunk);
|
|
1564
1574
|
bool scr_stream_push_str(ScrStream *s, ScrStr *str);
|
|
1575
|
+
/* push(chunk, enc): the per-call literal encoding (canonical); overrides
|
|
1576
|
+
* the stream's defaultEncoding. Borrows both. */
|
|
1577
|
+
bool scr_stream_push_str_enc(ScrStream *s, ScrStr *str, ScrStr *enc);
|
|
1578
|
+
/* The defaultEncoding option's push side: how push(string) decodes chunks
|
|
1579
|
+
* (Buffer.from(chunk, enc)). Canonical literal, never "utf8". Receiver
|
|
1580
|
+
* answers +1 (the setEncoding chaining shape). */
|
|
1581
|
+
ScrStream *scr_stream_set_push_encoding(ScrStream *s, ScrStr *enc);
|
|
1565
1582
|
bool scr_stream_push_null(ScrStream *s);
|
|
1566
1583
|
void scr_stream_unshift(ScrStream *s, ScrBytes *chunk);
|
|
1567
1584
|
void scr_stream_unshift_str(ScrStream *s, ScrStr *str);
|
|
@@ -1591,6 +1608,18 @@ ScrPromise *scr_stream_next_chunk_dyn(ScrStream *s);
|
|
|
1591
1608
|
* Streams borrowed; +1 promises. */
|
|
1592
1609
|
ScrPromise *scr_sp_finished(ScrStream *s);
|
|
1593
1610
|
ScrPromise *scr_sp_pipeline(double n, ScrStream **streams);
|
|
1611
|
+
/* node:stream/consumers — the promise consumers over the readable
|
|
1612
|
+
* machinery: accumulate every chunk (string chunks as their utf8 bytes)
|
|
1613
|
+
* and settle at the terminal point (right after 'close', the eos timing
|
|
1614
|
+
* Node's consumers share) — text answers the utf8 decode, json parses
|
|
1615
|
+
* the text (malformed input rejects with the parse's SyntaxError; the
|
|
1616
|
+
* result is a +1 DOM tree), buffer the concatenated bytes. Stream
|
|
1617
|
+
* errors reject; an early close rejects ERR_STREAM_PREMATURE_CLOSE; a
|
|
1618
|
+
* stream with no readable side rejects Node's async-iterable TypeError.
|
|
1619
|
+
* Streams borrowed; +1 promises. */
|
|
1620
|
+
ScrPromise *scr_sc_text(ScrStream *s);
|
|
1621
|
+
ScrPromise *scr_sc_json(ScrStream *s);
|
|
1622
|
+
ScrPromise *scr_sc_buffer(ScrStream *s);
|
|
1594
1623
|
/* Readable.from(array): +1 fully-seeded object-entry stream (one WHOLE
|
|
1595
1624
|
* chunk per element — strings or Buffers per the flag; hwm 1, already
|
|
1596
1625
|
* EOF'd). Borrows arr. */
|
|
@@ -3487,6 +3516,11 @@ bool scr_immediate_has_ref(double handle);
|
|
|
3487
3516
|
* teardown (they must never run yet must not leak). cb ownership moves
|
|
3488
3517
|
* in. */
|
|
3489
3518
|
void scr_next_tick(ScrClosure *cb);
|
|
3519
|
+
/* A raw C-hook entry on the SAME queue: the stream unit enqueues one
|
|
3520
|
+
* marker per deferred stream emission, so stream ticks and user
|
|
3521
|
+
* nextTicks run in true FIFO order (in Node they are the same queue).
|
|
3522
|
+
* Teardown drops markers without running them. */
|
|
3523
|
+
void scr_next_tick_raw(void (*fn)(void));
|
|
3490
3524
|
void scr_nticks_teardown(void);
|
|
3491
3525
|
/* ── the events unit (scr_events.c — OPTIONAL, link-gated) ────────────
|
|
3492
3526
|
* Process signal/exit events and the piped-stdin surface. The unit links
|
|
@@ -4207,13 +4241,15 @@ double scr_bit_not(double a);
|
|
|
4207
4241
|
/* ── typed arrays / Buffer (scr_bytes.c) ──────────────────────────────
|
|
4208
4242
|
* ONE runtime representation for Uint8Array/Uint32Array/Float32Array,
|
|
4209
4243
|
* Node's Buffer (a Uint8Array subclass), and DataView: a refcounted,
|
|
4210
|
-
* MUTABLE, fixed-length element buffer.
|
|
4211
|
-
* (backing == NULL
|
|
4212
|
-
*
|
|
4213
|
-
*
|
|
4214
|
-
*
|
|
4215
|
-
*
|
|
4216
|
-
*
|
|
4244
|
+
* MUTABLE, fixed-length element buffer. An ScrBytes either OWNS its
|
|
4245
|
+
* storage (backing == NULL, byteOffset 0) or is a VIEW: its `data` points
|
|
4246
|
+
* INTO an owner's storage and its `backing` retains that owner (chain
|
|
4247
|
+
* depth is always exactly 1 — views over views resolve to the owner at
|
|
4248
|
+
* construction), so reads/writes through the view alias the source
|
|
4249
|
+
* JS-exactly. Views come from DataView (scr_dataview_new) and from
|
|
4250
|
+
* subarray()/Buffer-slice() (scr_bytes_subarray — Buffer's slice is
|
|
4251
|
+
* subarray's deprecated alias in Node); only the plain typed arrays'
|
|
4252
|
+
* slice() copies (scr_bytes_slice, matching JS).
|
|
4217
4253
|
* Elements are scalars only and the backing edge is acyclic by
|
|
4218
4254
|
* construction (owners point at nothing): never part of a cycle, no
|
|
4219
4255
|
* trace. Element reads widen to double; writes coerce JS-exactly (ToUint8
|
|
@@ -4236,10 +4272,10 @@ typedef struct ScrBytes {
|
|
|
4236
4272
|
size_t len; /* ELEMENT count, fixed at construction */
|
|
4237
4273
|
ScrBytesElem elem;
|
|
4238
4274
|
uint8_t *data; /* len * elem_size bytes; owned unless backing is set */
|
|
4239
|
-
/* NULL for owners (
|
|
4240
|
-
* the retained OWNER it aliases (chain depth is always exactly 1:
|
|
4241
|
-
* over
|
|
4242
|
-
*
|
|
4275
|
+
/* NULL for owners. A view (DataView, subarray, Buffer-slice) sets this
|
|
4276
|
+
* to the retained OWNER it aliases (chain depth is always exactly 1:
|
|
4277
|
+
* views over views resolve to the owner at construction) and its `data`
|
|
4278
|
+
* points into backing->data — released, never freed. */
|
|
4243
4279
|
struct ScrBytes *backing;
|
|
4244
4280
|
} ScrBytes;
|
|
4245
4281
|
|
|
@@ -4344,10 +4380,21 @@ void scr_bytes_set(ScrBytes *b, double i, double v);
|
|
|
4344
4380
|
|
|
4345
4381
|
/* TypedArray.prototype.slice(start, end): relative indices clamp like
|
|
4346
4382
|
* string/array slice (ToIntegerOrInfinity, negatives from the end); the
|
|
4347
|
-
* result is a fresh same-kind copy.
|
|
4348
|
-
* the documented divergence. Never throws. */
|
|
4383
|
+
* result is a fresh same-kind copy. Never throws. */
|
|
4349
4384
|
ScrBytes *scr_bytes_slice(const ScrBytes *b, double start, double end); /* +1 */
|
|
4350
4385
|
|
|
4386
|
+
/* TypedArray.prototype.fill on non-u8 receivers: per-element fill with
|
|
4387
|
+
* the element write's coercion, slice-clamped relative indices; answers
|
|
4388
|
+
* the receiver +1 (chaining). Never throws. */
|
|
4389
|
+
ScrBytes *scr_bytes_fill_elem(ScrBytes *b, double v, double start, double end); /* +1 */
|
|
4390
|
+
|
|
4391
|
+
/* TypedArray.prototype.subarray(start, end) — and Buffer's slice(), its
|
|
4392
|
+
* deprecated alias: a same-elem VIEW aliasing the receiver's storage
|
|
4393
|
+
* (mutations visible both ways, JS-exactly). The view retains the OWNER
|
|
4394
|
+
* (chain depth exactly 1, the DataView rule) and its byteOffset composes.
|
|
4395
|
+
* Same index clamping as slice; never throws. */
|
|
4396
|
+
ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end); /* +1 */
|
|
4397
|
+
|
|
4351
4398
|
/* dst.set(src, offset): same-kind bulk copy (memmove — dst may be src).
|
|
4352
4399
|
* offset goes through ToIntegerOrInfinity; a negative offset or
|
|
4353
4400
|
* src.len + offset > dst.len THROWS Node's "offset is out of bounds"
|
package/src/scr_stream.c
CHANGED
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
* nothing.
|
|
14
14
|
*
|
|
15
15
|
* EVENT TIMING. Node schedules most stream emissions on process.nextTick;
|
|
16
|
-
* here
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* here each deferral enqueues on the stream tick FIFO AND posts a raw
|
|
17
|
+
* marker on the USER nextTick queue (scr_next_tick_raw), so stream
|
|
18
|
+
* emissions and user nextTicks run in true FIFO enqueue order — Node's,
|
|
19
|
+
* where they are the same queue. The scr_loop_set_stream station remains
|
|
20
|
+
* as the drain of anything a marker never reached (and the uncaught-throw
|
|
21
|
+
* cleanup). The implemented orderings follow lib/internal/streams:
|
|
21
22
|
* - on('data') starts flowing on a TICK (resume_): synchronous code
|
|
22
23
|
* after the registration runs before the first 'data'.
|
|
23
24
|
* - push() while flowing with an empty buffer emits 'data'
|
|
@@ -103,6 +104,10 @@ struct ScrStreamState {
|
|
|
103
104
|
bool encoded; /* string-chunk mode (decoder active) */
|
|
104
105
|
ScrStr *enc; /* owned canonical encoding name, or NULL */
|
|
105
106
|
double dec_pending; /* scr_strdec packed pending state */
|
|
107
|
+
/* The defaultEncoding option's push-side effect: how push(string)
|
|
108
|
+
* DECODES string chunks into bytes (Node's Buffer.from(chunk,
|
|
109
|
+
* state.defaultEncoding)). Owned canonical name; NULL = utf8. */
|
|
110
|
+
ScrStr *push_enc;
|
|
106
111
|
/* Readable.from mode: entries are whole OBJECTS (each counts 1
|
|
107
112
|
* toward length/hwm and delivers undivided — Node's objectMode
|
|
108
113
|
* accounting for the from() surface; hwm is 1). */
|
|
@@ -223,6 +228,7 @@ static void scr_stream_state_drop(ScrStreamState *st, bool gc) {
|
|
|
223
228
|
for (size_t i = 0; i < st->r.n; i++) scr_stream_entry_release(st, st->r.buf[i]);
|
|
224
229
|
free(st->r.buf);
|
|
225
230
|
if (st->r.enc) scr_str_release(st->r.enc);
|
|
231
|
+
if (st->r.push_enc) scr_str_release(st->r.push_enc);
|
|
226
232
|
if (!gc && st->r.next_waiter) scr_promise_release(st->r.next_waiter);
|
|
227
233
|
if (!gc) {
|
|
228
234
|
if (st->r.read_cb) scr_closure_release(st->r.read_cb);
|
|
@@ -350,6 +356,8 @@ typedef struct ScrStreamTick {
|
|
|
350
356
|
static ScrStreamTick *scr_st_head = NULL;
|
|
351
357
|
static ScrStreamTick *scr_st_tail = NULL;
|
|
352
358
|
|
|
359
|
+
static void scr_stream_dispatch_one(void);
|
|
360
|
+
|
|
353
361
|
static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*/,
|
|
354
362
|
ScrClosure *cb /*moves*/) {
|
|
355
363
|
ScrStreamTick *t = calloc(1, sizeof *t);
|
|
@@ -361,6 +369,13 @@ static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*
|
|
|
361
369
|
if (scr_st_tail) scr_st_tail->next = t;
|
|
362
370
|
else scr_st_head = t;
|
|
363
371
|
scr_st_tail = t;
|
|
372
|
+
/* One marker per tick on the USER nextTick queue: stream emissions are
|
|
373
|
+
* process.nextTicks in Node (resume_, emitReadable_, endReadableNT,
|
|
374
|
+
* afterWrite, ...), so they must interleave with user nextTicks in
|
|
375
|
+
* enqueue order — a `push(); on('data'); process.nextTick(assert)`
|
|
376
|
+
* sequence sees its data before the assert runs. The station dispatch
|
|
377
|
+
* below stays as the drain of anything a marker never reached. */
|
|
378
|
+
scr_next_tick_raw(&scr_stream_dispatch_one);
|
|
364
379
|
}
|
|
365
380
|
|
|
366
381
|
static bool scr_stream_ticks_pending(void) { return scr_st_head != NULL; }
|
|
@@ -1222,6 +1237,24 @@ static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r,
|
|
|
1222
1237
|
s->reg = NULL;
|
|
1223
1238
|
s->cls = cls;
|
|
1224
1239
|
s->st = scr_stream_state_new(has_r, has_w, rhwm, whwm, auto_destroy, emit_close, allow_half_open);
|
|
1240
|
+
/* Node's stream constructors pre-create their known _events keys (a V8
|
|
1241
|
+
* shape optimization) — eventNames() lists these BEFORE user events
|
|
1242
|
+
* added earlier. Same names, same order: Readable close/error/data/end/
|
|
1243
|
+
* readable, Writable close/error/prefinish/finish/drain, Duplex the
|
|
1244
|
+
* union (writable trio first — Node's observed key order). */
|
|
1245
|
+
ScrEmitter *em = (ScrEmitter *)s;
|
|
1246
|
+
scr_emitter_reserve(em, "close");
|
|
1247
|
+
scr_emitter_reserve(em, "error");
|
|
1248
|
+
if (has_w) {
|
|
1249
|
+
scr_emitter_reserve(em, "prefinish");
|
|
1250
|
+
scr_emitter_reserve(em, "finish");
|
|
1251
|
+
scr_emitter_reserve(em, "drain");
|
|
1252
|
+
}
|
|
1253
|
+
if (has_r) {
|
|
1254
|
+
scr_emitter_reserve(em, "data");
|
|
1255
|
+
scr_emitter_reserve(em, "end");
|
|
1256
|
+
scr_emitter_reserve(em, "readable");
|
|
1257
|
+
}
|
|
1225
1258
|
scr_obj_alloc_note();
|
|
1226
1259
|
return s;
|
|
1227
1260
|
}
|
|
@@ -2193,6 +2226,133 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams) {
|
|
|
2193
2226
|
return p;
|
|
2194
2227
|
}
|
|
2195
2228
|
|
|
2229
|
+
/* ── node:stream/consumers ────────────────────────────────────────────
|
|
2230
|
+
* text/json/buffer over the readable machinery: a native 'data' listener
|
|
2231
|
+
* accumulates every chunk (Buffer chunks as-is; string chunks — an
|
|
2232
|
+
* encoded stream or Readable.from strings — as their utf8 bytes, the
|
|
2233
|
+
* Blob rule Node's consumers share for whole-stream accumulation), and
|
|
2234
|
+
* the finished watcher settles at the terminal point — the accumulated
|
|
2235
|
+
* result on a clean end (right after 'close', Node's own timing: the
|
|
2236
|
+
* consumer's async iterator completes through eos), the stream's error,
|
|
2237
|
+
* or ERR_STREAM_PREMATURE_CLOSE on an early close — and marks lifecycle
|
|
2238
|
+
* errors handled, exactly like the iterator's eos registration. Both
|
|
2239
|
+
* closures share the cap layout: caps[0] the pending promise, caps[1]
|
|
2240
|
+
* the chunk list (Buffer entries). */
|
|
2241
|
+
|
|
2242
|
+
enum { SCR_SC_TEXT = 0, SCR_SC_JSON = 1, SCR_SC_BUFFER = 2 };
|
|
2243
|
+
|
|
2244
|
+
static void scr_sc_settle_ok(ScrClosure *cb, int kind) {
|
|
2245
|
+
ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
|
|
2246
|
+
ScrArr *chunks = scr_box_get_ref(cb->caps[1]); /* +1 */
|
|
2247
|
+
ScrBytes *all = scr_bytes_concat(chunks);
|
|
2248
|
+
scr_arr_release(chunks);
|
|
2249
|
+
if (kind == SCR_SC_BUFFER) {
|
|
2250
|
+
scr_promise_fulfill_ref(p, all, scr_bytes_retain_v, scr_bytes_release_v, NULL);
|
|
2251
|
+
scr_promise_release(p);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
ScrStr *enc = scr_str_new("utf8", 4);
|
|
2255
|
+
ScrStr *text = scr_bytes_to_str(all, enc); /* U+FFFD per invalid subpart */
|
|
2256
|
+
scr_str_release(enc);
|
|
2257
|
+
scr_bytes_release(all);
|
|
2258
|
+
if (kind == SCR_SC_TEXT) {
|
|
2259
|
+
scr_promise_fulfill_str(p, text); /* moves */
|
|
2260
|
+
} else {
|
|
2261
|
+
ScrDyn *doc = scr_json_parse(text);
|
|
2262
|
+
scr_str_release(text);
|
|
2263
|
+
if (doc == NULL) {
|
|
2264
|
+
/* the parse's SyntaxError rides the cell — the rejection, like
|
|
2265
|
+
* Node's json() rejecting with JSON.parse's throw */
|
|
2266
|
+
scr_promise_reject_pending(p);
|
|
2267
|
+
} else {
|
|
2268
|
+
scr_promise_fulfill_ref(p, doc, scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
scr_promise_release(p);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
/* The 'data' accumulation (the emit ABI's two payload slots — exactly
|
|
2275
|
+
* one non-NULL). */
|
|
2276
|
+
static void scr_sc_data(ScrClosure *cb, void *b, void *str) {
|
|
2277
|
+
ScrArr *chunks = scr_box_get_ref(cb->caps[1]); /* +1 */
|
|
2278
|
+
if (b != NULL) {
|
|
2279
|
+
scr_arr_push_ref(chunks, scr_bytes_retain((ScrBytes *)b));
|
|
2280
|
+
} else if (str != NULL) {
|
|
2281
|
+
ScrStr *enc = scr_str_new("utf8", 4);
|
|
2282
|
+
scr_arr_push_ref(chunks, scr_bytes_from_str((ScrStr *)str, enc));
|
|
2283
|
+
scr_str_release(enc);
|
|
2284
|
+
}
|
|
2285
|
+
scr_arr_release(chunks);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
static void scr_sc_fin(ScrClosure *cb, ScrError *err, int kind) {
|
|
2289
|
+
if (err != NULL) {
|
|
2290
|
+
ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
|
|
2291
|
+
scr_throw_obj(scr_error_retain(err), &scr_error_retain_v, &scr_error_release_v,
|
|
2292
|
+
scr_error_trace_arg());
|
|
2293
|
+
scr_promise_reject_pending(p);
|
|
2294
|
+
scr_promise_release(p);
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
scr_sc_settle_ok(cb, kind);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
static void scr_sc_fin_text(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2301
|
+
(void)s;
|
|
2302
|
+
scr_sc_fin(cb, err, SCR_SC_TEXT);
|
|
2303
|
+
}
|
|
2304
|
+
static void scr_sc_fin_json(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2305
|
+
(void)s;
|
|
2306
|
+
scr_sc_fin(cb, err, SCR_SC_JSON);
|
|
2307
|
+
}
|
|
2308
|
+
static void scr_sc_fin_buffer(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2309
|
+
(void)s;
|
|
2310
|
+
scr_sc_fin(cb, err, SCR_SC_BUFFER);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
static ScrClosure *scr_sc_closure(void *fn, ScrPromise *p, ScrArr *chunks) {
|
|
2314
|
+
ScrClosure *c = scr_closure_new(fn, 2);
|
|
2315
|
+
c->caps[0] = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v, scr_promise_trace_v);
|
|
2316
|
+
scr_box_set_ref(c->caps[0], scr_promise_retain(p));
|
|
2317
|
+
c->caps[1] = scr_box_new_obj(scr_arr_retain_v, scr_arr_release_v, NULL);
|
|
2318
|
+
scr_box_set_ref(c->caps[1], scr_arr_retain(chunks));
|
|
2319
|
+
return c;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
static ScrPromise *scr_sc_consume(ScrStream *s, int kind) {
|
|
2323
|
+
ScrPromise *p = scr_promise_new();
|
|
2324
|
+
if (s->st == NULL || !s->st->has_r) {
|
|
2325
|
+
/* Node's consumers for-await the argument; a stream with no readable
|
|
2326
|
+
* side has no async iterator — the TypeError rejects. */
|
|
2327
|
+
static const char msg[] = "stream is not async iterable";
|
|
2328
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
|
2329
|
+
scr_promise_reject_pending(p);
|
|
2330
|
+
return p;
|
|
2331
|
+
}
|
|
2332
|
+
ScrArr *chunks = scr_arr_new_ref(scr_bytes_retain_v, scr_bytes_release_v, NULL, 4);
|
|
2333
|
+
ScrStreamErrInv fin_inv = kind == SCR_SC_TEXT ? &scr_sc_fin_text
|
|
2334
|
+
: kind == SCR_SC_JSON ? &scr_sc_fin_json
|
|
2335
|
+
: &scr_sc_fin_buffer;
|
|
2336
|
+
/* The terminal watcher first (it marks lifecycle errors handled); the
|
|
2337
|
+
* promise form exposes no unhook — the cleanup closure drops. */
|
|
2338
|
+
ScrClosure *cleanup = scr_stream_finished(s, scr_sc_closure((void *)fin_inv, p, chunks), fin_inv);
|
|
2339
|
+
scr_closure_release(cleanup);
|
|
2340
|
+
ScrStr *dn = scr_str_new("data", 4);
|
|
2341
|
+
scr_emitter_release(scr_emitter_on((ScrEmitter *)s, dn,
|
|
2342
|
+
scr_sc_closure((void *)&scr_sc_data, p, chunks),
|
|
2343
|
+
scr_ee_inv_fixed2, false, false));
|
|
2344
|
+
scr_str_release(dn);
|
|
2345
|
+
/* resume() rather than the 'data' hook alone: Node's consumer pulls
|
|
2346
|
+
* through the iterator, which drains a PAUSED stream too. */
|
|
2347
|
+
scr_stream_release(scr_stream_resume(s));
|
|
2348
|
+
scr_arr_release(chunks);
|
|
2349
|
+
return p;
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
ScrPromise *scr_sc_text(ScrStream *s) { return scr_sc_consume(s, SCR_SC_TEXT); }
|
|
2353
|
+
ScrPromise *scr_sc_json(ScrStream *s) { return scr_sc_consume(s, SCR_SC_JSON); }
|
|
2354
|
+
ScrPromise *scr_sc_buffer(ScrStream *s) { return scr_sc_consume(s, SCR_SC_BUFFER); }
|
|
2355
|
+
|
|
2196
2356
|
/* ── the readable surface ─────────────────────────────────────────────── */
|
|
2197
2357
|
|
|
2198
2358
|
bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
|
|
@@ -2200,13 +2360,41 @@ bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
|
|
|
2200
2360
|
}
|
|
2201
2361
|
|
|
2202
2362
|
bool scr_stream_push_str(ScrStream *s, ScrStr *str) {
|
|
2203
|
-
|
|
2204
|
-
|
|
2363
|
+
ScrStr *enc = s->st->has_r ? s->st->r.push_enc : NULL;
|
|
2364
|
+
ScrBytes *b;
|
|
2365
|
+
if (enc != NULL) {
|
|
2366
|
+
/* Node: Buffer.from(chunk, state.defaultEncoding). */
|
|
2367
|
+
b = scr_bytes_from_str(str, enc);
|
|
2368
|
+
} else {
|
|
2369
|
+
b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
|
|
2370
|
+
memcpy(b->data, str->data, str->len);
|
|
2371
|
+
}
|
|
2372
|
+
bool ret = scr_stream_add_chunk(s, b, false);
|
|
2373
|
+
scr_bytes_release(b);
|
|
2374
|
+
return ret;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
/* push(chunk, encoding) with an explicit non-utf8 literal: the per-call
|
|
2378
|
+
* encoding overrides the stream's default. Borrows both strings. */
|
|
2379
|
+
bool scr_stream_push_str_enc(ScrStream *s, ScrStr *str, ScrStr *enc) {
|
|
2380
|
+
ScrBytes *b = scr_bytes_from_str(str, enc);
|
|
2205
2381
|
bool ret = scr_stream_add_chunk(s, b, false);
|
|
2206
2382
|
scr_bytes_release(b);
|
|
2207
2383
|
return ret;
|
|
2208
2384
|
}
|
|
2209
2385
|
|
|
2386
|
+
/* The defaultEncoding option's push side (canonical literal, frontend-
|
|
2387
|
+
* folded; never "utf8" — that stays the NULL fast path). Answers the
|
|
2388
|
+
* receiver +1, the setEncoding chaining shape. */
|
|
2389
|
+
ScrStream *scr_stream_set_push_encoding(ScrStream *s, ScrStr *enc) {
|
|
2390
|
+
ScrStreamState *st = s->st;
|
|
2391
|
+
if (st->has_r) {
|
|
2392
|
+
if (st->r.push_enc) scr_str_release(st->r.push_enc);
|
|
2393
|
+
st->r.push_enc = scr_str_retain(enc);
|
|
2394
|
+
}
|
|
2395
|
+
return scr_stream_retain(s);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2210
2398
|
bool scr_stream_push_null(ScrStream *s) {
|
|
2211
2399
|
ScrStreamState *st = s->st;
|
|
2212
2400
|
if (!st->has_r || st->destroyed) return false;
|
|
@@ -2891,6 +3079,25 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
|
|
|
2891
3079
|
}
|
|
2892
3080
|
}
|
|
2893
3081
|
|
|
3082
|
+
static void scr_stream_dispatch(void);
|
|
3083
|
+
|
|
3084
|
+
/* One tick-marker's dispatch: the queue's FIFO head (markers and entries
|
|
3085
|
+
* are enqueued 1:1 in the same order). After an uncaught throw the
|
|
3086
|
+
* remaining entries drop through the station's exc branch below, keeping
|
|
3087
|
+
* the RC audit clean. */
|
|
3088
|
+
static void scr_stream_dispatch_one(void) {
|
|
3089
|
+
ScrStreamTick *t = scr_st_head;
|
|
3090
|
+
if (t == NULL || scr_exc_pending()) return;
|
|
3091
|
+
scr_st_head = t->next;
|
|
3092
|
+
if (scr_st_head == NULL) scr_st_tail = NULL;
|
|
3093
|
+
scr_stream_run_tick(t);
|
|
3094
|
+
scr_stream_release(t->s);
|
|
3095
|
+
if (t->err) scr_error_release(t->err);
|
|
3096
|
+
if (t->cb) scr_closure_release(t->cb);
|
|
3097
|
+
free(t);
|
|
3098
|
+
if (scr_exc_pending()) scr_stream_dispatch(); /* its exc branch drops the rest */
|
|
3099
|
+
}
|
|
3100
|
+
|
|
2894
3101
|
static void scr_stream_dispatch(void) {
|
|
2895
3102
|
while (scr_st_head != NULL && !scr_exc_pending() && !scr_loop_has_ready()) {
|
|
2896
3103
|
ScrStreamTick *t = scr_st_head;
|