char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
5f5bbad
main
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 (;;) { 11const count = stdin.read(buffer); 12if (count === null) break; 13stdout.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.