char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somstdio: add synchronous standard stream interfaces5f5bbad

main
3.6 KiB84 linesraw

Standard I/O

avril:std exposes stdin as a readable Web Stream and stdout/stderr as writable Web Streams. avril:std/sync adds separate, frozen blocking interfaces:

import { stdin, stdout, stderr } from "avril:std/sync";

const buffer = new Uint8Array(4096);
for (;;) {
  const count = stdin.read(buffer);
  if (count === null) break;
  stdout.write(buffer.subarray(0, count));
}
stderr.write(new TextEncoder().encode("done\n"));

No CLI flag or filesystem capability is needed. These interfaces borrow the inherited process streams; they have no close() or disposal method. The existing async exports are unchanged.

Reads

stdin.read(buffer: Uint8Array): number | null fills the supplied view and returns the number of bytes read, or null at EOF. Reads can be partial. An empty view returns 0 without consuming input. Detached or out-of-bounds views and values other than Uint8Array throw TypeError. Shared and resizable buffers are supported.

A sync read blocks its isolate without running JavaScript callbacks. On a terminal, normal line buffering applies; this API does not enable raw mode.

There can be only one outstanding native stdin read. A nonempty sync read throws errors.Busy if an async read is pending, even if the OS has already supplied its bytes but the async completion has not been processed. Prefer using one input API rather than mixing direct reads with stream buffering.

Canceling the async stdin stream also closes the sync input: subsequent nonempty reads return null. Interrupting a native read wait likewise closes that input permanently, because an OS read already in progress cannot be portably canceled or allowed to compete with a new reader.

Guest stdin remains unavailable in the REPL and workers; sync reads there throw errors.NotSupported, including empty reads. The REPL owns its input.

Writes and ordering

stdout.write(bytes: Uint8Array): number and stderr.write(bytes: Uint8Array): number snapshot the supplied view and wait until its bytes have been written and the native writer flushed. They return the byte count, or throw on failure. Empty writes return 0. A full pipe can block until another process or thread drains it; a callback in the blocked isolate cannot do so.

Sync writes use the same per-isolate native queues as async stream writes and console output. Already-submitted writes precede them. A queued Web Stream write may not have been submitted yet; await it to guarantee ordering:

import { stdout as stream } from "avril:std";
import { stdout } from "avril:std/sync";

const writer = stream.getWriter();
await writer.write(new TextEncoder().encode("first\n"));
stdout.write(new TextEncoder().encode("second\n"));

Closing an async output stream flushes it but does not close the inherited process stream; the sync interface remains usable. There is no ordering promise between stdout and stderr or between different isolates.

Errors use the avril:std error classes, including BrokenPipe. An output failure remains a failure for subsequent writes and the CLI's shutdown flush, so catching the write error does not make the CLI exit successfully.

Interruption

Worker termination and REPL Ctrl-C interrupt blocking native waits without pumping JavaScript. Forced worker shutdown also interrupts its flush wait.

Interruption stops waiting, not the underlying OS operation: accepted output may still finish later and is not rolled back. If a pipe stays full, later writes to that destination and the main process's final flush may still wait for a reader. Console output and REPL result printing retain their existing blocking behavior.

1# Standard I/O
2
3`avril:std` exposes stdin as a readable Web Stream and stdout/stderr as writable
4Web Streams. `avril:std/sync` adds separate, frozen blocking interfaces:
5
6```ts
7import { stdin, stdout, stderr } from "avril:std/sync";
8
9const buffer = new Uint8Array(4096);
10for (;;) {
11  const count = stdin.read(buffer);
12  if (count === null) break;
13  stdout.write(buffer.subarray(0, count));
14}
15stderr.write(new TextEncoder().encode("done\n"));
16```
17
18No CLI flag or filesystem capability is needed. These interfaces borrow the
19inherited process streams; they have no `close()` or disposal method. The
20existing async exports are unchanged.
21
22## Reads
23
24`stdin.read(buffer: Uint8Array): number | null` fills the supplied view and
25returns the number of bytes read, or `null` at EOF. Reads can be partial. An
26empty view returns `0` without consuming input. Detached or out-of-bounds views
27and values other than `Uint8Array` throw `TypeError`. Shared and resizable
28buffers are supported.
29
30A sync read blocks its isolate without running JavaScript callbacks. On a
31terminal, normal line buffering applies; this API does not enable raw mode.
32
33There can be only one outstanding native stdin read. A nonempty sync read
34throws `errors.Busy` if an async read is pending, even if the OS has already
35supplied its bytes but the async completion has not been processed. Prefer
36using one input API rather than mixing direct reads with stream buffering.
37
38Canceling the async stdin stream also closes the sync input: subsequent
39nonempty reads return `null`. Interrupting a native read wait likewise closes
40that input permanently, because an OS read already in progress cannot be
41portably canceled or allowed to compete with a new reader.
42
43Guest stdin remains unavailable in the REPL and workers; sync reads there
44throw `errors.NotSupported`, including empty reads. The REPL owns its input.
45
46## Writes and ordering
47
48`stdout.write(bytes: Uint8Array): number` and `stderr.write(bytes: Uint8Array): number`
49snapshot the supplied view and wait until its bytes have been written and the
50native writer flushed. They return the byte count, or throw on failure. Empty
51writes return `0`. A full pipe can block until another process or thread drains
52it; a callback in the blocked isolate cannot do so.
53
54Sync writes use the same per-isolate native queues as async stream writes and
55console output. Already-submitted writes precede them. A queued Web Stream
56write may not have been submitted yet; await it to guarantee ordering:
57
58```ts
59import { stdout as stream } from "avril:std";
60import { stdout } from "avril:std/sync";
61
62const writer = stream.getWriter();
63await writer.write(new TextEncoder().encode("first\n"));
64stdout.write(new TextEncoder().encode("second\n"));
65```
66
67Closing an async output stream flushes it but does not close the inherited
68process stream; the sync interface remains usable. There is no ordering
69promise between stdout and stderr or between different isolates.
70
71Errors use the `avril:std` error classes, including `BrokenPipe`. An output
72failure remains a failure for subsequent writes and the CLI's shutdown flush,
73so catching the write error does not make the CLI exit successfully.
74
75## Interruption
76
77Worker termination and REPL Ctrl-C interrupt blocking native waits without
78pumping JavaScript. Forced worker shutdown also interrupts its flush wait.
79
80Interruption stops waiting, not the underlying OS operation: accepted output
81may still finish later and is not rolled back. If a pipe stays full, later
82writes to that destination and the main process's final flush may still wait
83for a reader. Console output and REPL result printing retain their existing
84blocking behavior.