mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Search TypedArrays, read stdin through 'readable', and assign constructor-function globals (#1974)
This commit is contained in:
@@ -2380,7 +2380,6 @@ export function collectGlobals(
|
||||
// References from separately-declared functions cascade to
|
||||
// their own per-site runtime fences.
|
||||
const factoryType =
|
||||
!isLet &&
|
||||
isJsSourceFile(sf) &&
|
||||
!decl.type &&
|
||||
!hasJsTypeAnnotation(decl) &&
|
||||
|
||||
@@ -6,10 +6,14 @@ import { bindingNeverReassigned } from "./binding-analysis.js";
|
||||
import { implicitDefaultInstance } from "./generic-functions.js";
|
||||
import { type FnSig } from "./call-signatures.js";
|
||||
|
||||
/** An ordinary constructor's explicit object return replaces its receiver.
|
||||
* Without observing this or new.target, it can use the function's call ABI. */
|
||||
function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean {
|
||||
if (!ts.isIdentifier(expr.expression) || lowerer.peekLocal(expr.expression)) return false;
|
||||
/** The JavaScript function declaration (or never-reassigned function
|
||||
* expression binding) `new` calls, when it is an ordinary constructor: not
|
||||
* a generator or async function. */
|
||||
function jsConstructorFunctionOf(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.NewExpression,
|
||||
): ts.FunctionDeclaration | ts.FunctionExpression | null {
|
||||
if (!ts.isIdentifier(expr.expression) || lowerer.peekLocal(expr.expression)) return null;
|
||||
// This probe also sees ordinary classes during global collection. Leave
|
||||
// their deferred diagnostics for the class declaration's lowering.
|
||||
const wasCollecting = lowerer.collecting;
|
||||
@@ -20,7 +24,7 @@ function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean {
|
||||
} finally {
|
||||
lowerer.collecting = wasCollecting;
|
||||
}
|
||||
if (!symbol) return false;
|
||||
if (!symbol) return null;
|
||||
const owner = symbol.declarations.find(
|
||||
(node) =>
|
||||
(ts.isFunctionDeclaration(node) && node.body !== undefined) ||
|
||||
@@ -45,7 +49,15 @@ function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean {
|
||||
declaration.asteriskToken ||
|
||||
declaration.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)
|
||||
)
|
||||
return false;
|
||||
return null;
|
||||
return declaration;
|
||||
}
|
||||
|
||||
/** An ordinary constructor's explicit object return replaces its receiver.
|
||||
* Without observing this or new.target, it can use the function's call ABI. */
|
||||
function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean {
|
||||
const declaration = jsConstructorFunctionOf(lowerer, expr);
|
||||
if (!declaration?.body) return false;
|
||||
const objectReturn = (expression: ts.Expression): boolean =>
|
||||
ts.isObjectLiteralExpression(expression) ||
|
||||
ts.isArrayLiteralExpression(expression) ||
|
||||
@@ -81,8 +93,13 @@ function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean {
|
||||
|
||||
/** Collection must register the shared result before reaching factory
|
||||
* bodies: those bodies can reference later globals in the same module. */
|
||||
/** The storage of a JavaScript module binding initialized with `new` of an
|
||||
* ordinary function: a factory's result and a constructor function's
|
||||
* receiver (`const head = new Segment()`) are both checked-dynamic
|
||||
* values, which functions declared elsewhere in the module read through
|
||||
* the global. */
|
||||
export function objectFactoryGlobalType(lowerer: Lowerer, expr: ts.NewExpression): IrType | null {
|
||||
return isObjectFactoryNew(lowerer, expr) ? DYN : null;
|
||||
return jsConstructorFunctionOf(lowerer, expr) !== null ? DYN : null;
|
||||
}
|
||||
|
||||
export function objectFactorySignature(lowerer: Lowerer, expr: ts.NewExpression): FnSig | null {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7680-typed-array-search.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7681-typed-array-search-dynamic.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7682-constructor-function-globals.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -3341,6 +3341,9 @@ static bool scr_bytes_search_start(size_t len, double from, size_t *start) {
|
||||
}
|
||||
|
||||
double scr_bytes_index_of_elem(const ScrBytes *b, double x, double from, bool last) {
|
||||
/* A Buffer viewed as a Uint8Array keeps Buffer's own search: the needle
|
||||
* is taken as a byte, and a NaN byteOffset searches the whole buffer. */
|
||||
if (scr_bytes_is_buffer(b)) return scr_bytes_index_of_num(b, x, from, !last);
|
||||
SCR_BYTES_SNAPSHOT(b);
|
||||
size_t len = b->len;
|
||||
size_t size = scr_bytes_elem_size(b->elem);
|
||||
@@ -3361,6 +3364,7 @@ double scr_bytes_index_of_elem(const ScrBytes *b, double x, double from, bool la
|
||||
}
|
||||
|
||||
bool scr_bytes_includes_elem(const ScrBytes *b, double x, double from) {
|
||||
if (scr_bytes_is_buffer(b)) return scr_bytes_index_of_num(b, x, from, true) >= 0;
|
||||
SCR_BYTES_SNAPSHOT(b);
|
||||
size_t len = b->len;
|
||||
size_t size = scr_bytes_elem_size(b->elem);
|
||||
|
||||
@@ -541,10 +541,12 @@ double scr_bytes_index_of_num(const ScrBytes *b, double v, double off, bool fwd)
|
||||
/* TypedArray indexOf (last false) and lastIndexOf (last true) over any
|
||||
* element kind, with strict equality: NaN is never found and -0 matches 0.
|
||||
* `from` is the fromIndex argument (ToIntegerOrInfinity applies); the
|
||||
* caller passes 0, or +Infinity for lastIndexOf, when it is omitted. */
|
||||
* caller passes 0, or +Infinity for lastIndexOf, when it is omitted. A
|
||||
* Buffer (a Uint8Array receiver can be one) searches as Buffer's
|
||||
* indexOf and lastIndexOf do. */
|
||||
double scr_bytes_index_of_elem(const ScrBytes *b, double x, double from, bool last);
|
||||
/* TypedArray includes: indexOf's search from `from` with SameValueZero,
|
||||
* so NaN is found. */
|
||||
* so NaN is found. A Buffer searches as Buffer's includes does. */
|
||||
bool scr_bytes_includes_elem(const ScrBytes *b, double x, double from);
|
||||
ScrBytes *scr_bytes_fill(ScrBytes *b, const ScrBytes *pattern, double nargs, double offset, double end);
|
||||
ScrBytes *scr_bytes_fill_num(ScrBytes *b, double v, double nargs, double offset, double end);
|
||||
|
||||
@@ -388,8 +388,80 @@ static SCR_TL bool scr_stdin_destroyed = false;
|
||||
static SCR_TL bool scr_stdin_paused = false;
|
||||
static SCR_TL bool scr_stdin_resumed = false;
|
||||
static SCR_TL bool scr_stdin_read_eof = false;
|
||||
static SCR_TL ScrBytes *scr_stdin_buffer;
|
||||
/* Input held for read() and later consumers, as Node's BufferList: the
|
||||
* chunks in arrival order and their total length, so an arrival appends
|
||||
* without copying what is already held. */
|
||||
static SCR_TL ScrBytes **scr_stdin_held = NULL;
|
||||
static SCR_TL size_t scr_stdin_nheld = 0, scr_stdin_held_cap = 0;
|
||||
static SCR_TL size_t scr_stdin_buffered = 0;
|
||||
static SCR_TL size_t scr_stdin_read_demand;
|
||||
/* 'readable' listeners (paused mode, as Node's once one is added): input
|
||||
* stays buffered for read(), and the event fires on the next tick when
|
||||
* input arrives while a read wanted more (`need`), and once at EOF. */
|
||||
static SCR_TL ScrStdinEndL *scr_stdin_readable = NULL;
|
||||
static SCR_TL size_t scr_stdin_nreadable = 0, scr_stdin_readable_cap = 0;
|
||||
static SCR_TL bool scr_stdin_readable_need = false;
|
||||
static SCR_TL bool scr_stdin_readable_queued = false;
|
||||
|
||||
/* Appends an arrived chunk (moves) to the held input. */
|
||||
static void scr_stdin_hold(ScrBytes *chunk) {
|
||||
if (chunk->len == 0) {
|
||||
scr_bytes_release(chunk);
|
||||
return;
|
||||
}
|
||||
if (scr_stdin_nheld == scr_stdin_held_cap) {
|
||||
scr_stdin_held_cap = scr_stdin_held_cap ? scr_stdin_held_cap * 2 : 4;
|
||||
scr_stdin_held = realloc(scr_stdin_held, scr_stdin_held_cap * sizeof *scr_stdin_held);
|
||||
if (!scr_stdin_held) scr_events_oom();
|
||||
}
|
||||
scr_stdin_held[scr_stdin_nheld++] = chunk;
|
||||
scr_stdin_buffered += chunk->len;
|
||||
}
|
||||
|
||||
/* The first n held bytes as one chunk (+1), 0 < n <= scr_stdin_buffered.
|
||||
* A whole first chunk moves out without a copy; otherwise the bytes are
|
||||
* joined once, and a partly taken chunk keeps its rest. */
|
||||
static ScrBytes *scr_stdin_take(size_t n) {
|
||||
ScrBytes *first = scr_stdin_held[0];
|
||||
size_t used = 0;
|
||||
ScrBytes *out;
|
||||
if (n == first->len) {
|
||||
out = first;
|
||||
used = 1;
|
||||
} else if (n < first->len) {
|
||||
out = scr_bytes_slice(first, 0, (double)n);
|
||||
scr_stdin_held[0] = scr_bytes_slice(first, (double)n, (double)first->len);
|
||||
scr_bytes_release(first);
|
||||
} else {
|
||||
out = scr_bytes_new(SCR_BYTES_U8, (double)n);
|
||||
size_t at = 0;
|
||||
while (at < n) {
|
||||
ScrBytes *chunk = scr_stdin_held[used];
|
||||
size_t part = chunk->len <= n - at ? chunk->len : n - at;
|
||||
memcpy(out->data + at, chunk->data, part);
|
||||
at += part;
|
||||
if (part == chunk->len) {
|
||||
scr_bytes_release(chunk);
|
||||
used++;
|
||||
} else {
|
||||
scr_stdin_held[used] = scr_bytes_slice(chunk, (double)part, (double)chunk->len);
|
||||
scr_bytes_release(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
memmove(scr_stdin_held, scr_stdin_held + used, (scr_stdin_nheld - used) * sizeof *scr_stdin_held);
|
||||
scr_stdin_nheld -= used;
|
||||
scr_stdin_buffered -= n;
|
||||
return out;
|
||||
}
|
||||
|
||||
static void scr_stdin_drop_held(void) {
|
||||
for (size_t i = 0; i < scr_stdin_nheld; i++) scr_bytes_release(scr_stdin_held[i]);
|
||||
free(scr_stdin_held);
|
||||
scr_stdin_held = NULL;
|
||||
scr_stdin_nheld = scr_stdin_held_cap = 0;
|
||||
scr_stdin_buffered = 0;
|
||||
}
|
||||
|
||||
#ifdef SCR_STDIO_EXTENDED
|
||||
/* process.stdin.setEncoding(enc): the canonical encoding name (NULL until
|
||||
@@ -475,6 +547,78 @@ void scr_stdin_on_end(ScrClosure *cb /*moves*/, bool once) {
|
||||
scr_stdin_nend++;
|
||||
}
|
||||
|
||||
static void scr_stdin_finish(bool fire_end);
|
||||
|
||||
static void scr_stdin_emit_readable(void) {
|
||||
scr_stdin_readable_queued = false;
|
||||
size_t length = scr_stdin_buffered;
|
||||
if (scr_stdin_eof || scr_stdin_destroyed || (!length && !scr_stdin_read_eof)) return;
|
||||
size_t n = scr_stdin_nreadable;
|
||||
ScrStdinEndL *snap = malloc((n ? n : 1) * sizeof *snap);
|
||||
if (!snap) scr_events_oom();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
snap[i] = scr_stdin_readable[i];
|
||||
scr_closure_retain(snap[i].cb);
|
||||
}
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (snap[i].once) {
|
||||
for (size_t j = 0; j < scr_stdin_nreadable; j++) {
|
||||
if (scr_stdin_readable[j].cb == snap[i].cb) {
|
||||
scr_closure_release(scr_stdin_readable[j].cb);
|
||||
memmove(scr_stdin_readable + j, scr_stdin_readable + j + 1,
|
||||
(scr_stdin_nreadable - j - 1) * sizeof *scr_stdin_readable);
|
||||
scr_stdin_nreadable--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!scr_exc_pending() && !scr_stdin_eof && !scr_stdin_destroyed)
|
||||
((void (*)(ScrClosure *))snap[i].cb->fn)(snap[i].cb);
|
||||
scr_closure_release(snap[i].cb);
|
||||
}
|
||||
free(snap);
|
||||
length = scr_stdin_buffered;
|
||||
/* Another event is due once the buffer reaches the 64 KiB mark, or the
|
||||
* larger size a listener's read(size) asked for. */
|
||||
scr_stdin_readable_need =
|
||||
!scr_stdin_read_eof && (length <= 65536 || length < scr_stdin_read_demand);
|
||||
}
|
||||
|
||||
static void scr_stdin_queue_readable(void) {
|
||||
if (scr_stdin_readable_queued || !scr_stdin_nreadable) return;
|
||||
scr_stdin_readable_queued = true;
|
||||
scr_next_tick_raw(scr_stdin_emit_readable);
|
||||
}
|
||||
|
||||
/* Adding a 'readable' listener starts reading ahead to the 64 KiB mark; a
|
||||
* buffer that already holds input fires the event at once (next tick). */
|
||||
static void scr_stdin_on_readable(ScrClosure *cb /*moves*/, bool once) {
|
||||
if (scr_stdin_eof || scr_stdin_destroyed) {
|
||||
scr_closure_release(cb);
|
||||
return;
|
||||
}
|
||||
if (scr_stdin_nreadable == scr_stdin_readable_cap) {
|
||||
scr_stdin_readable_cap = scr_stdin_readable_cap ? scr_stdin_readable_cap * 2 : 2;
|
||||
scr_stdin_readable = realloc(scr_stdin_readable, scr_stdin_readable_cap * sizeof *scr_stdin_readable);
|
||||
if (!scr_stdin_readable) scr_events_oom();
|
||||
}
|
||||
scr_stdin_readable[scr_stdin_nreadable].cb = cb;
|
||||
scr_stdin_readable[scr_stdin_nreadable].once = once;
|
||||
scr_stdin_nreadable++;
|
||||
scr_stdin_readable_need = true;
|
||||
if (scr_stdin_read_demand < 65536) scr_stdin_read_demand = 65536;
|
||||
if (scr_stdin_buffered) scr_stdin_queue_readable();
|
||||
}
|
||||
|
||||
/* The input ended: with 'readable' listeners the event fires once more and
|
||||
* 'end' waits for the read that finds nothing left; otherwise 'end' fires
|
||||
* once the buffer is drained. */
|
||||
static void scr_stdin_reached_eof(void) {
|
||||
scr_stdin_read_eof = true;
|
||||
if (scr_stdin_nreadable) scr_stdin_queue_readable();
|
||||
else if (!scr_stdin_buffered) scr_stdin_finish(true);
|
||||
}
|
||||
|
||||
void scr_stdin_on_error(ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once) {
|
||||
if (scr_stdin_eof || scr_stdin_destroyed) {
|
||||
scr_closure_release(cb);
|
||||
@@ -516,10 +660,15 @@ bool scr_stdin_ended(void) { return scr_stdin_eof || scr_stdin_destroyed; }
|
||||
* alive (a paused Node stdin with only those never flows — the process
|
||||
* exits with them registered). */
|
||||
static bool scr_stdin_watching(void) {
|
||||
return !scr_stdin_eof && !scr_stdin_destroyed &&
|
||||
((!scr_stdin_paused && (scr_stdin_ndata > 0 || scr_stdin_resumed)) ||
|
||||
scr_stdin_waiter != NULL ||
|
||||
(!scr_stdin_read_eof && scr_stdin_read_demand > (scr_stdin_buffer ? scr_stdin_buffer->len : 0)));
|
||||
if (scr_stdin_eof || scr_stdin_destroyed) return false;
|
||||
/* 'readable' listeners keep the stream paused, as in Node, where
|
||||
* resume() and 'data' listeners do not make it flow: it reads ahead only
|
||||
* while a read wants more than is held (the 64 KiB mark, or a larger
|
||||
* read(size)), and stops until something drains it. */
|
||||
if (scr_stdin_nreadable) return !scr_stdin_read_eof && scr_stdin_read_demand > scr_stdin_buffered;
|
||||
return (!scr_stdin_paused && (scr_stdin_ndata > 0 || scr_stdin_resumed)) ||
|
||||
scr_stdin_waiter != NULL ||
|
||||
(!scr_stdin_read_eof && scr_stdin_read_demand > scr_stdin_buffered);
|
||||
}
|
||||
|
||||
bool scr_stdin_pending(void) { return scr_stdin_watching(); }
|
||||
@@ -528,6 +677,10 @@ static void scr_stdin_drop_listeners(void) {
|
||||
for (size_t i = 0; i < scr_stdin_ndata; i++) scr_closure_release(scr_stdin_data[i].cb);
|
||||
for (size_t i = 0; i < scr_stdin_nend; i++) scr_closure_release(scr_stdin_end[i].cb);
|
||||
for (size_t i = 0; i < scr_stdin_nerr; i++) scr_closure_release(scr_stdin_err[i].cb);
|
||||
for (size_t i = 0; i < scr_stdin_nreadable; i++) scr_closure_release(scr_stdin_readable[i].cb);
|
||||
free(scr_stdin_readable);
|
||||
scr_stdin_readable = NULL;
|
||||
scr_stdin_nreadable = scr_stdin_readable_cap = 0;
|
||||
free(scr_stdin_data);
|
||||
free(scr_stdin_end);
|
||||
free(scr_stdin_err);
|
||||
@@ -593,8 +746,7 @@ static void scr_stdin_finish(bool fire_end) {
|
||||
void scr_stdin_destroy(void) {
|
||||
if (scr_stdin_destroyed) return;
|
||||
scr_stdin_destroyed = true;
|
||||
scr_bytes_release(scr_stdin_buffer);
|
||||
scr_stdin_buffer = NULL;
|
||||
scr_stdin_drop_held();
|
||||
scr_stdin_read_demand = 0;
|
||||
scr_stdin_settle_done();
|
||||
scr_stdin_drop_listeners();
|
||||
@@ -627,16 +779,9 @@ static void scr_stdin_deliver(ScrBytes *chunk);
|
||||
/* A chunk that arrived (moves): delivered, or buffered while paused or
|
||||
* without a consumer. */
|
||||
static void scr_stdin_arrived(ScrBytes *chunk) {
|
||||
if (scr_stdin_paused || (!scr_stdin_ndata && !scr_stdin_resumed && !scr_stdin_waiter)) {
|
||||
if (scr_stdin_buffer) {
|
||||
size_t before = scr_stdin_buffer->len;
|
||||
ScrBytes *joined = scr_bytes_new(SCR_BYTES_U8, (double)(before + chunk->len));
|
||||
memcpy(joined->data, scr_stdin_buffer->data, before);
|
||||
memcpy(joined->data + before, chunk->data, chunk->len);
|
||||
scr_bytes_release(scr_stdin_buffer);
|
||||
scr_bytes_release(chunk);
|
||||
scr_stdin_buffer = joined;
|
||||
} else scr_stdin_buffer = chunk;
|
||||
if (scr_stdin_paused || scr_stdin_nreadable || (!scr_stdin_ndata && !scr_stdin_resumed && !scr_stdin_waiter)) {
|
||||
if (scr_stdin_nreadable && scr_stdin_readable_need) scr_stdin_queue_readable();
|
||||
scr_stdin_hold(chunk);
|
||||
return;
|
||||
}
|
||||
scr_stdin_deliver(chunk);
|
||||
@@ -645,15 +790,18 @@ static void scr_stdin_arrived(ScrBytes *chunk) {
|
||||
|
||||
static void scr_stdin_service(void) {
|
||||
if (!scr_stdin_watching()) return;
|
||||
if (!scr_stdin_paused && scr_stdin_buffer) {
|
||||
ScrBytes *chunk = scr_stdin_buffer;
|
||||
scr_stdin_buffer = NULL;
|
||||
/* Buffered input goes to a consumer: 'data' listeners, a resumed
|
||||
* stream, or a parked for-await read. After the last 'readable' listener
|
||||
* it stays buffered until one arrives, as in Node. */
|
||||
if (!scr_stdin_paused && !scr_stdin_nreadable && scr_stdin_buffered &&
|
||||
(scr_stdin_ndata > 0 || scr_stdin_resumed || scr_stdin_waiter != NULL)) {
|
||||
ScrBytes *chunk = scr_stdin_take(scr_stdin_buffered);
|
||||
scr_stdin_deliver(chunk);
|
||||
scr_bytes_release(chunk);
|
||||
return;
|
||||
}
|
||||
if (scr_stdin_read_eof) {
|
||||
if (!scr_stdin_buffer) scr_stdin_finish(true);
|
||||
if (!scr_stdin_buffered && !scr_stdin_nreadable) scr_stdin_finish(true);
|
||||
return;
|
||||
}
|
||||
if (scr_stdin_fed) {
|
||||
@@ -662,10 +810,7 @@ static void scr_stdin_service(void) {
|
||||
bool ended = false;
|
||||
ScrBytes *chunk = scr_stdin_fed_take(&ended);
|
||||
if (chunk) scr_stdin_arrived(chunk);
|
||||
else if (ended) {
|
||||
scr_stdin_read_eof = true;
|
||||
if (!scr_stdin_buffer) scr_stdin_finish(true);
|
||||
}
|
||||
else if (ended) scr_stdin_reached_eof();
|
||||
return;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
@@ -727,8 +872,7 @@ static void scr_stdin_service(void) {
|
||||
return;
|
||||
}
|
||||
if (n == 0) {
|
||||
scr_stdin_read_eof = true;
|
||||
if (!scr_stdin_buffer) scr_stdin_finish(true);
|
||||
scr_stdin_reached_eof();
|
||||
return;
|
||||
}
|
||||
ScrBytes *chunk = scr_bytes_new(SCR_BYTES_U8, (double)n);
|
||||
@@ -802,6 +946,8 @@ static void scr_stdin_deliver(ScrBytes *chunk) {
|
||||
#endif
|
||||
ScrPromise *p = scr_stdin_waiter;
|
||||
scr_stdin_waiter = NULL;
|
||||
/* Node's stdin yields Buffers. */
|
||||
chunk->is_buffer = true;
|
||||
scr_promise_fulfill_ref(p, scr_bytes_retain(chunk), scr_bytes_retain_v,
|
||||
scr_bytes_release_v, NULL);
|
||||
scr_promise_release(p);
|
||||
@@ -878,6 +1024,7 @@ static void scr_stdio_data_text(ScrClosure *cb, ScrStr *text) {
|
||||
}
|
||||
#endif
|
||||
static void scr_stdio_end(ScrClosure *cb) { scr_stdio_fire(cb, NULL, 0); }
|
||||
static void scr_stdio_readable(ScrClosure *cb) { scr_stdio_fire(cb, NULL, 0); }
|
||||
static void scr_stdio_error(ScrClosure *cb, ScrStr *message) {
|
||||
ScrError *error = scr_error_new(SCR_ERR_ERROR, message);
|
||||
ScrDyn *arg = scr_dyn_from_error(error);
|
||||
@@ -971,7 +1118,7 @@ static void scr_stdio_queue_write_error(ScrStdio *stream, ScrDyn *callback, int
|
||||
}
|
||||
|
||||
static void scr_stdio_finish_read(void) {
|
||||
if (scr_stdin_read_eof && !scr_stdin_buffer && !scr_stdin_eof && !scr_stdin_destroyed)
|
||||
if (scr_stdin_read_eof && !scr_stdin_buffered && !scr_stdin_eof && !scr_stdin_destroyed)
|
||||
scr_stdin_finish(true);
|
||||
}
|
||||
|
||||
@@ -980,21 +1127,21 @@ static ScrDyn *scr_stdio_read(ScrDyn *size) {
|
||||
if (!all && (size->kind != SCR_DYN_NUM || !isfinite(size->v.num) ||
|
||||
size->v.num < 0 || size->v.num > 1073741824 || floor(size->v.num) != size->v.num))
|
||||
return scr_stdio_refusal("read size");
|
||||
size_t available = scr_stdin_buffer ? scr_stdin_buffer->len : 0;
|
||||
size_t available = scr_stdin_buffered;
|
||||
size_t wanted = all ? available : (size_t)size->v.num;
|
||||
if (scr_stdin_read_eof && wanted > available) wanted = available;
|
||||
if (wanted == 0 || wanted > available) {
|
||||
if (!scr_stdin_eof && !scr_stdin_destroyed) {
|
||||
scr_stdin_readable_need = true;
|
||||
scr_stdin_read_demand = wanted > 65536 ? wanted : 65536;
|
||||
if (scr_stdin_read_eof) scr_next_tick_raw(scr_stdio_finish_read);
|
||||
}
|
||||
return scr_dyn_new_null();
|
||||
}
|
||||
ScrBytes *chunk = scr_bytes_slice(scr_stdin_buffer, 0, (double)wanted);
|
||||
ScrBytes *rest = wanted < available ? scr_bytes_slice(scr_stdin_buffer, (double)wanted, (double)available) : NULL;
|
||||
scr_bytes_release(scr_stdin_buffer);
|
||||
scr_stdin_buffer = rest;
|
||||
ScrBytes *chunk = scr_stdin_take(wanted);
|
||||
bool rest = scr_stdin_buffered > 0;
|
||||
scr_stdin_read_demand = 65536;
|
||||
if (!rest && !scr_stdin_read_eof) scr_stdin_readable_need = true;
|
||||
scr_stdin_deliver(chunk);
|
||||
ScrDyn *result = scr_dyn_new_chunk(chunk);
|
||||
scr_bytes_release(chunk);
|
||||
@@ -1150,12 +1297,14 @@ static ScrDyn *scr_stdio_method(ScrClosure *cb, ScrDyn *const *args, size_t argc
|
||||
bool data = arg->kind == SCR_DYN_STR && arg->v.str->len == 4 && memcmp(event, "data", 4) == 0;
|
||||
bool end = arg->kind == SCR_DYN_STR && arg->v.str->len == 3 && memcmp(event, "end", 3) == 0;
|
||||
bool error = arg->kind == SCR_DYN_STR && arg->v.str->len == 5 && memcmp(event, "error", 5) == 0;
|
||||
bool readable = stream->fd == 0 && arg->kind == SCR_DYN_STR && arg->v.str->len == 8 &&
|
||||
memcmp(event, "readable", 8) == 0;
|
||||
#ifdef SCR_STDIO_EXTENDED
|
||||
bool resize = stream->fd != 0 && arg->kind == SCR_DYN_STR && arg->v.str->len == 6 &&
|
||||
memcmp(event, "resize", 6) == 0;
|
||||
if ((!data && !end && !error && !resize) || (stream->fd != 0 && !error && !resize)) {
|
||||
if ((!data && !end && !error && !readable && !resize) || (stream->fd != 0 && !error && !resize)) {
|
||||
#else
|
||||
if ((!data && !end && !error) || (stream->fd != 0 && !error)) {
|
||||
if ((!data && !end && !error && !readable) || (stream->fd != 0 && !error)) {
|
||||
#endif
|
||||
result = scr_stdio_refusal("event");
|
||||
goto done;
|
||||
@@ -1175,6 +1324,7 @@ static ScrDyn *scr_stdio_method(ScrClosure *cb, ScrDyn *const *args, size_t argc
|
||||
if (data) scr_stdin_on_data(scr_stdio_callback(listener, (void *)scr_stdio_data), scr_stdio_data, once);
|
||||
#endif
|
||||
if (end) scr_stdin_on_end(scr_stdio_callback(listener, (void *)scr_stdio_end), once);
|
||||
if (readable) scr_stdin_on_readable(scr_stdio_callback(listener, (void *)scr_stdio_readable), once);
|
||||
if (error) scr_stdin_on_error(scr_stdio_callback(listener, (void *)scr_stdio_error), scr_stdio_error, once);
|
||||
} else {
|
||||
#define REMOVE_STDIO_LISTENER(list, count, fire) \
|
||||
@@ -1187,6 +1337,7 @@ static ScrDyn *scr_stdio_method(ScrClosure *cb, ScrDyn *const *args, size_t argc
|
||||
}
|
||||
if (data) { REMOVE_STDIO_LISTENER(scr_stdin_data, scr_stdin_ndata, scr_stdio_data); }
|
||||
if (end) { REMOVE_STDIO_LISTENER(scr_stdin_end, scr_stdin_nend, scr_stdio_end); }
|
||||
if (readable) { REMOVE_STDIO_LISTENER(scr_stdin_readable, scr_stdin_nreadable, scr_stdio_readable); }
|
||||
if (error) { REMOVE_STDIO_LISTENER(scr_stdin_err, scr_stdin_nerr, scr_stdio_error); }
|
||||
#undef REMOVE_STDIO_LISTENER
|
||||
}
|
||||
@@ -1246,7 +1397,7 @@ static ScrDyn *scr_stdio_get(void *ptr, const char *key, size_t length) {
|
||||
return dimension < 0 ? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(dimension);
|
||||
}
|
||||
if (stream->fd == 0 && length == 14 && memcmp(key, "readableLength", 14) == 0)
|
||||
return scr_dyn_new_num(scr_stdin_buffer ? (double)scr_stdin_buffer->len : 0);
|
||||
return scr_dyn_new_num((double)scr_stdin_buffered);
|
||||
if (stream->fd == 0 && length == 5 && memcmp(key, "isRaw", 5) == 0)
|
||||
return scr_process_is_tty(0) ? scr_dyn_new_bool(scr_stdio_raw) : scr_dyn_retain(scr_dyn_undefined());
|
||||
if (stream->fd == 0 && length == 9 && memcmp(key, "destroyed", 9) == 0) return scr_dyn_new_bool(scr_stdin_destroyed);
|
||||
@@ -1531,8 +1682,7 @@ static void scr_events_cleanup_atexit(void) {
|
||||
if (scr_context_is_main()) scr_sig_cleanup();
|
||||
scr_stdin_settle_done();
|
||||
scr_stdin_drop_listeners();
|
||||
scr_bytes_release(scr_stdin_buffer);
|
||||
scr_stdin_buffer = NULL;
|
||||
scr_stdin_drop_held();
|
||||
if (scr_stdio_initialized) scr_stdio_cleanup();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// TypedArray.prototype.indexOf, lastIndexOf and includes on every numeric
|
||||
// element kind: strict equality (NaN never found) for indexOf/lastIndexOf,
|
||||
// SameValueZero (NaN found, -0 equals 0) for includes, and fromIndex as
|
||||
// ToIntegerOrInfinity, counted from the end when negative.
|
||||
const bytes = new Uint8Array([1, 2, 10, 3, 10, 255]);
|
||||
console.log("u8", bytes.indexOf(10), bytes.indexOf(10, 3), bytes.indexOf(10, 5), bytes.lastIndexOf(10));
|
||||
console.log("u8 from", bytes.indexOf(10, -2), bytes.indexOf(10, -100), bytes.indexOf(1, Infinity), bytes.indexOf(10, 2.9));
|
||||
console.log("u8 last", bytes.lastIndexOf(10, 3), bytes.lastIndexOf(10, -3), bytes.lastIndexOf(1, -100), bytes.lastIndexOf(255, 99));
|
||||
console.log("u8 values", bytes.indexOf(256 + 10), bytes.indexOf(-246), bytes.indexOf(10.5), bytes.includes(255), bytes.includes(0));
|
||||
console.log("u8 includes from", bytes.includes(1, 1), bytes.includes(255, -1), bytes.includes(2, -5));
|
||||
|
||||
const clamped = new Uint8ClampedArray([0, 300, -5, 127.5]);
|
||||
console.log("u8c", clamped.indexOf(255), clamped.indexOf(0, 1), clamped.lastIndexOf(0), clamped.indexOf(128));
|
||||
|
||||
const signed = new Int8Array([-1, 127, -128, 0]);
|
||||
console.log("i8", signed.indexOf(-1), signed.indexOf(255), signed.indexOf(-128), signed.lastIndexOf(0));
|
||||
|
||||
const shorts = new Int16Array([5, -300, 5, 7]);
|
||||
const ushorts = new Uint16Array([65535, 1, 65535]);
|
||||
console.log("16", shorts.indexOf(-300), shorts.lastIndexOf(5), ushorts.indexOf(-1), ushorts.lastIndexOf(65535, 1));
|
||||
|
||||
const ints = new Int32Array([-2147483648, 0, 2147483647, 0]);
|
||||
const uints = new Uint32Array([4294967295, 3]);
|
||||
console.log("32", ints.indexOf(2147483647), ints.lastIndexOf(0), uints.indexOf(4294967295), uints.includes(-1));
|
||||
|
||||
const floats = new Float64Array([1.5, NaN, -0, 0, Infinity]);
|
||||
console.log("f64", floats.indexOf(NaN), floats.lastIndexOf(NaN), floats.includes(NaN), floats.indexOf(0), floats.indexOf(-0));
|
||||
console.log("f64 more", floats.lastIndexOf(0), floats.indexOf(Infinity), floats.includes(-Infinity), floats.includes(NaN, 2));
|
||||
|
||||
const singles = new Float32Array([1.1, 0.5, NaN]);
|
||||
console.log("f32", singles.indexOf(1.1), singles.indexOf(Math.fround(1.1)), singles.indexOf(0.5), singles.includes(NaN), singles.indexOf(NaN));
|
||||
|
||||
const empty = new Float64Array(0);
|
||||
console.log("empty", empty.indexOf(0), empty.lastIndexOf(0), empty.includes(0), empty.includes(0, -1));
|
||||
|
||||
// Views search their own window of the buffer.
|
||||
const buffer = new ArrayBuffer(16);
|
||||
const whole = new Uint8Array(buffer);
|
||||
whole.set([9, 9, 1, 2, 9, 3, 9, 9]);
|
||||
const view = new Uint8Array(buffer, 2, 4);
|
||||
const sub = whole.subarray(3, 6);
|
||||
console.log("views", view.indexOf(9), view.lastIndexOf(9), view.indexOf(1), sub.indexOf(9), sub.includes(1), sub.lastIndexOf(3));
|
||||
const words = new Uint16Array(buffer, 2, 3);
|
||||
console.log("word view", words.indexOf(0x0201), words.length);
|
||||
|
||||
// Shared memory searches the same way.
|
||||
const shared = new Int32Array(new SharedArrayBuffer(16));
|
||||
shared[1] = 7;
|
||||
shared[3] = 7;
|
||||
console.log("shared", shared.indexOf(7), shared.lastIndexOf(7), shared.includes(0), shared.indexOf(7, 2));
|
||||
|
||||
// A loop that finds every occurrence, as a line splitter does.
|
||||
const text = new TextEncoder().encode("one\ntwo\n\nthree\n");
|
||||
const lines: number[] = [];
|
||||
for (let at = text.indexOf(10); at !== -1; at = text.indexOf(10, at + 1)) lines.push(at);
|
||||
console.log("lines", lines.join(","));
|
||||
|
||||
// A Buffer passed as a Uint8Array keeps Buffer's search: a number needle
|
||||
// is taken as a byte, and a NaN byteOffset searches the whole buffer.
|
||||
function searchView(view: Uint8Array): string {
|
||||
return [view.indexOf(97 + 256), view.lastIndexOf(98, NaN), view.includes(353), view.indexOf(98, -1)].join(",");
|
||||
}
|
||||
console.log(searchView(Buffer.from("abcab")), searchView(new Uint8Array([97, 98, 99, 97, 98])));
|
||||
|
||||
// An explicit undefined fromIndex converts to NaN, which searches from 0:
|
||||
// lastIndexOf then looks at index 0 only.
|
||||
const values = new Int16Array([3, 1, 3, 2, 3]);
|
||||
const absent: number | undefined = process.argv.length > 99 ? 1 : undefined;
|
||||
console.log("undefined from", values.indexOf(3, undefined), values.lastIndexOf(3, undefined), values.lastIndexOf(1, undefined), values.includes(2, undefined));
|
||||
console.log("maybe undefined from", values.indexOf(3, absent), values.lastIndexOf(3, absent), values.includes(1, absent));
|
||||
@@ -0,0 +1,28 @@
|
||||
// TypedArray search on untyped values (parameters of untyped functions and
|
||||
// object members): needles that are not numbers are never found (strict
|
||||
// equality), fromIndex may be undefined, a string or a fraction, and
|
||||
// Buffers keep their own indexOf, which wraps number needles and takes
|
||||
// strings.
|
||||
function views(bytes, floats, shorts) {
|
||||
return { bytes, floats, shorts };
|
||||
}
|
||||
const { bytes, floats, shorts } = views(
|
||||
new Uint8Array([1, 2, 10, 3, 10]),
|
||||
new Float64Array([0.5, NaN, -0]),
|
||||
new Int16Array([-1, 300, -1]),
|
||||
);
|
||||
console.log(bytes.indexOf(10), bytes.indexOf(10, 3), bytes.lastIndexOf(10), bytes.includes(3), bytes.includes(4));
|
||||
console.log(bytes.lastIndexOf(1, undefined), bytes.lastIndexOf(10), bytes.indexOf(10, undefined), bytes.includes(1, undefined));
|
||||
console.log(bytes.indexOf(10, '3'), bytes.indexOf(10, 2.9), bytes.indexOf(10, -1), bytes.lastIndexOf(10, -2), bytes.indexOf(10, null));
|
||||
console.log(bytes.indexOf('10'), bytes.includes('1'), bytes.indexOf(10n), bytes.indexOf(), bytes.includes(), bytes.indexOf(266));
|
||||
console.log(floats.indexOf(NaN), floats.includes(NaN), floats.indexOf(0), floats.lastIndexOf(-0), floats.includes(0.5, 1));
|
||||
console.log(shorts.indexOf(-1), shorts.lastIndexOf(-1), shorts.indexOf(300, -2), shorts.includes(65535));
|
||||
const holder = { view: bytes.subarray(2) };
|
||||
console.log(holder.view.indexOf(10), holder.view.lastIndexOf(10), holder.view.includes(1));
|
||||
const buffer = Buffer.from([1, 2, 10, 3, 10]);
|
||||
const boxed = [buffer];
|
||||
console.log(boxed[0].indexOf(10), boxed[0].indexOf(266), boxed[0].indexOf('\n'), boxed[0].includes(Buffer.from([3, 10])));
|
||||
console.log(boxed[0].subarray(3).indexOf(10), Buffer.isBuffer(boxed[0].subarray(3)), boxed[0].lastIndexOf(10, -2));
|
||||
let count = 0;
|
||||
for (let at = bytes.indexOf(10); at !== -1; at = bytes.indexOf(10, at + 1)) count++;
|
||||
console.log('count', count);
|
||||
@@ -0,0 +1,55 @@
|
||||
// Module bindings that hold `new F()` of an ordinary constructor function
|
||||
// (const, let and var), read and reassigned by functions declared elsewhere
|
||||
// in the module, such as the head and tail of a linked list.
|
||||
'use strict';
|
||||
function Segment(prev) {
|
||||
this.prev = prev;
|
||||
this.next = null;
|
||||
this.data = new Array(4);
|
||||
this.pos = 0;
|
||||
}
|
||||
const head = new Segment();
|
||||
let current = head;
|
||||
var spare = new Segment(head);
|
||||
|
||||
function append(value) {
|
||||
if (current.pos === current.data.length) {
|
||||
current = current.next || (current.next = new Segment(current));
|
||||
}
|
||||
current.data[current.pos++] = value;
|
||||
}
|
||||
|
||||
function collect() {
|
||||
const out = [];
|
||||
for (let node = head; node; node = node.next) out.push(node.data.slice(0, node.pos).join(''));
|
||||
return out.join('|');
|
||||
}
|
||||
|
||||
function reset() {
|
||||
current = head;
|
||||
for (let node = head; node; node = node.next) node.pos = 0;
|
||||
}
|
||||
|
||||
for (const ch of 'abcdefghij') append(ch);
|
||||
console.log(collect(), current === head, current.prev === head.next ? 'two' : 'more');
|
||||
reset();
|
||||
append('z');
|
||||
console.log(collect(), current === head, head.next !== null);
|
||||
console.log(spare.prev === head, spare instanceof Segment, typeof spare.data);
|
||||
spare = new Segment(spare);
|
||||
console.log(spare.prev.prev === head);
|
||||
|
||||
function Counter(start) {
|
||||
this.value = start;
|
||||
}
|
||||
Counter.prototype.bump = function () {
|
||||
return ++this.value;
|
||||
};
|
||||
let counter = new Counter(10);
|
||||
function bumpTwice() {
|
||||
counter.bump();
|
||||
return counter.bump();
|
||||
}
|
||||
console.log(bumpTwice(), counter.value);
|
||||
counter = new Counter(0);
|
||||
console.log(bumpTwice());
|
||||
@@ -0,0 +1,10 @@
|
||||
// for await over process.stdin yields Buffers: Buffer.isBuffer holds and
|
||||
// indexOf takes a byte and a start offset (only chunking-independent facts
|
||||
// are printed).
|
||||
const chunks = [];
|
||||
for await (const chunk of process.stdin) chunks.push(chunk);
|
||||
console.log('buffers', chunks.every((chunk) => Buffer.isBuffer(chunk)));
|
||||
const all = Buffer.concat(chunks);
|
||||
const newlines = [];
|
||||
for (let at = all.indexOf(10); at !== -1; at = all.indexOf(10, at + 1)) newlines.push(at);
|
||||
console.log('newlines', newlines.join(','), all.subarray(newlines[0] + 1).indexOf('#'));
|
||||
@@ -0,0 +1,27 @@
|
||||
// 'readable', 'data' and resume() together, with no read() for a while:
|
||||
// 'readable' keeps the stream paused, so it stops reading at its read-ahead
|
||||
// mark until a read drains it, and 'data' sees what read() returns.
|
||||
const input = process.stdin;
|
||||
let draining = false;
|
||||
let peak = 0;
|
||||
let dataBytes = 0;
|
||||
let readBytes = 0;
|
||||
const drain = () => {
|
||||
let chunk;
|
||||
while ((chunk = input.read()) !== null) readBytes += chunk.length;
|
||||
};
|
||||
input.on('data', (chunk) => {
|
||||
dataBytes += chunk.length;
|
||||
});
|
||||
input.on('readable', () => {
|
||||
peak = Math.max(peak, input.readableLength);
|
||||
if (draining) drain();
|
||||
});
|
||||
input.resume();
|
||||
setTimeout(() => {
|
||||
peak = Math.max(peak, input.readableLength);
|
||||
console.log('held below 128 KiB while not draining', peak <= 131072);
|
||||
draining = true;
|
||||
drain();
|
||||
}, 400);
|
||||
input.on('end', () => console.log('read', readBytes, 'data', dataBytes));
|
||||
@@ -0,0 +1,9 @@
|
||||
// read(size) above the 64 KiB read-ahead mark: 'readable' keeps firing as
|
||||
// input arrives until the requested size is buffered.
|
||||
const input = process.stdin;
|
||||
const sizes = [];
|
||||
input.on('readable', () => {
|
||||
let chunk;
|
||||
while ((chunk = input.read(150000)) !== null) sizes.push(chunk.length);
|
||||
});
|
||||
input.on('end', () => console.log(sizes.join(',')));
|
||||
@@ -0,0 +1,12 @@
|
||||
// A once('readable') listener that reads part of the input: the rest stays
|
||||
// buffered for a 'data' listener added afterwards.
|
||||
const input = process.stdin;
|
||||
input.once('readable', () => {
|
||||
const head = input.read(3);
|
||||
console.log('head', head === null ? null : head.toString('ascii'));
|
||||
setTimeout(() => {
|
||||
const rest = [];
|
||||
input.on('data', (chunk) => rest.push(chunk.toString('ascii')));
|
||||
input.on('end', () => console.log('rest', rest.join('')));
|
||||
}, 50);
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
// Fixed-size reads of stdin: read(n) answers null until n bytes are
|
||||
// buffered, and the rest once the input ended.
|
||||
const input = process.stdin;
|
||||
const chunks = [];
|
||||
const counts = [];
|
||||
input.on('readable', () => {
|
||||
let reads = 0;
|
||||
let chunk;
|
||||
while ((chunk = input.read(4)) !== null) {
|
||||
chunks.push(chunk.toString('ascii'));
|
||||
reads++;
|
||||
}
|
||||
counts.push(reads);
|
||||
});
|
||||
input.on('end', () => {
|
||||
console.log(chunks.join('|'));
|
||||
console.log(counts.join(','));
|
||||
});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// process.stdin in paused mode through 'readable': each event drains the
|
||||
// buffer with read(), one more event follows EOF, and 'end' comes after the
|
||||
// read that finds the buffer empty.
|
||||
const input = process.stdin;
|
||||
const events = [];
|
||||
let text = '';
|
||||
input.on('readable', () => {
|
||||
let reads = 0;
|
||||
let chunk;
|
||||
while ((chunk = input.read()) !== null) {
|
||||
text += String(chunk);
|
||||
reads++;
|
||||
}
|
||||
events.push(`readable ${reads}`);
|
||||
});
|
||||
input.on('end', () => {
|
||||
console.log(events.join(', '));
|
||||
console.log(JSON.stringify(text), input.readableLength);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2204, "san": 3060 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1649, "san": 958 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1623, "san": 962 }
|
||||
@@ -34,6 +34,66 @@ export const eventLoopCases: EventLoopCase[] = [
|
||||
fixture: "stdio-paused-exit.mjs",
|
||||
script: { writes: [], end: false },
|
||||
},
|
||||
{
|
||||
title: "stdio values: 'readable' drains with read(), then fires once more at EOF",
|
||||
fixture: "stdio-readable.mjs",
|
||||
script: {
|
||||
writes: [
|
||||
{ delayMs: 0, data: "alpha " },
|
||||
{ delayMs: 60, data: "beta\n" },
|
||||
],
|
||||
end: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "stdio values: 'readable' with read(size) holds a partial tail until EOF",
|
||||
fixture: "stdio-readable-size.mjs",
|
||||
script: { writes: [{ delayMs: 0, data: "abcdefghij" }], end: true },
|
||||
},
|
||||
{
|
||||
title: "stdio values: input a once('readable') listener leaves stays buffered for 'data'",
|
||||
fixture: "stdio-readable-once-rest.mjs",
|
||||
script: { writes: [{ delayMs: 0, data: "abcdefgh" }], end: true },
|
||||
},
|
||||
{
|
||||
title: "stdio values: 'readable' with a read(size) above the read-ahead mark",
|
||||
fixture: "stdio-readable-large-size.mjs",
|
||||
script: {
|
||||
writes: [
|
||||
{ delayMs: 0, data: "a".repeat(70000) },
|
||||
{ delayMs: 30, data: "b".repeat(70000) },
|
||||
{ delayMs: 30, data: "c".repeat(60000) },
|
||||
],
|
||||
end: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "stdio values: 'readable' with 'data' and resume() holds input at the read-ahead mark",
|
||||
fixture: "stdio-readable-backpressure.mjs",
|
||||
script: {
|
||||
writes: Array.from({ length: 16 }, (_, i) => ({
|
||||
delayMs: i === 0 ? 0 : 5,
|
||||
data: String.fromCharCode(97 + i).repeat(65536),
|
||||
})),
|
||||
end: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "stdio values: 'readable' on an immediately ended pipe",
|
||||
fixture: "stdio-readable.mjs",
|
||||
script: { writes: [], end: true },
|
||||
},
|
||||
{
|
||||
title: "for-await: process.stdin yields Buffers",
|
||||
fixture: "stdin-for-await-buffers.mjs",
|
||||
script: {
|
||||
writes: [
|
||||
{ delayMs: 0, data: "#one\nabcd\n" },
|
||||
{ delayMs: 40, data: "#two\nefgh\n" },
|
||||
],
|
||||
end: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "read-all: one instant write, then EOF",
|
||||
fixture: "stdin-read-all.ts",
|
||||
|
||||
Reference in New Issue
Block a user