/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/io/console.nv
179 строк
7 KB
Evgeniy Golovin
docs(std/src): clean comments batch 17 — io, ffi/cstr, fs, os, net families
01 авг 2026, 12:24
01 авг 2026, 12:24
c82cd98
Код
Авторство
О чём код?
// SPDX-License-Identifier: MIT OR Apache-2.0 // std/io/console.nv — stdin / stdout / stderr via the mockable `Io` effect. // // Console byte I/O is a **plumbing effect** (`Io`, net-precedent): user code // goes through `stdin()`/`stdout()`/`stderr()` (which conform to `io.Read` / // `io.Write`), and those dispatch to the ambient `Io` handler. Production uses // `real_io()` (fd-based C hooks `io_read_fd`/`io_write_fd`); tests swap in // `mock_io(cap)` — capturing stdout/stderr and scripting stdin — with **zero** // change to the code under test (Nova's mockability differentiator). module std.io // ─── Io effect ─── /// Ambient console byte I/O. Internal dispatch point — user code uses the /// `stdin()`/`stdout()`/`stderr()` handles, not these operations directly. /// - `read_in(buf)` — fill `buf[0..n]` from stdin, return `n` (`0` = EOF). /// - `write_out/err` — write bytes to stdout/stderr, returning the count. /// /// (`read_in` fills a caller buffer and returns an `int` rather than returning a /// `[]u8`: a `Result[[]u8, _]` Ok-payload is erased to `nova_int` through the /// effect vtable — the buffer-fill shape sidesteps that, and matches `io.Read`.) #stable(since = "0.1") export type Io effect { read_in(buf mut []u8) -> Result[int, IoError] write_out(data []u8) -> Result[int, IoError] write_err(data []u8) -> Result[int, IoError] } // ─── stdin / stdout / stderr handles (io.Read / io.Write over Io) ─── /// `io.Read` handle for standard input — reads through the ambient `Io` effect. #stable(since = "0.1") export type Stdin { priv unit int } /// The standard-input reader. #stable(since = "0.1") export fn stdin() -> Stdin => { unit: 0 } /// io.Read: pull up to `buf.len()` bytes from stdin. `Ok(0)` (non-empty buf) = EOF. /// `Io` is explicit — `io.Read` is effect-agnostic BY DESIGN (core.nv module /// doc): the conformer carries its OWN plumbing effect. The raw `Io.read_in` op /// call below is the explicit dispatch to it. #stable(since = "0.1") export fn Stdin mut @read(buf mut []u8) Io -> Result[int, IoError] => Io.read_in(buf) /// `io.Write` handle for standard output — writes through the ambient `Io` effect. #stable(since = "0.1") export type Stdout { priv unit int } /// The standard-output writer. #stable(since = "0.1") export fn stdout() -> Stdout => { unit: 0 } /// io.Write: write `data` to stdout. /// `Io` is explicit — see `Stdin @read` doc. #stable(since = "0.1") export fn Stdout mut @write(data []u8) Io -> Result[int, IoError] => Io.write_out(data) /// io.Write: no-op (the fd is unbuffered on the Nova side; wrap in `BufWriter` /// to batch). #stable(since = "0.1") export fn Stdout mut @flush() -> Result[(), IoError] => Ok(()) /// `io.Write` handle for standard error — writes through the ambient `Io` effect. #stable(since = "0.1") export type Stderr { priv unit int } /// The standard-error writer. #stable(since = "0.1") export fn stderr() -> Stderr => { unit: 0 } /// io.Write: write `data` to stderr. /// `Io` is explicit — see `Stdin @read` doc. #stable(since = "0.1") export fn Stderr mut @write(data []u8) Io -> Result[int, IoError] => Io.write_err(data) /// io.Write: no-op. #stable(since = "0.1") export fn Stderr mut @flush() -> Result[(), IoError] => Ok(()) // ─── mock_io — capturing / scripting handler (the Io-mock deliverable) ─── /// Capture buffer for `mock_io`: records everything written to stdout/stderr and /// feeds scripted bytes to stdin. Heap record → the same instance the test holds /// is mutated by the handler, so after a `with Io = mock_io(cap) { … }` block the /// test inspects `cap.out_bytes()` / `cap.err_bytes()`. #stable(since = "0.1") export type IoCapture { ro input []u8 mut in_pos int mut out []u8 mut err []u8 } /// A fresh capture with `input` as the scripted stdin content. #stable(since = "0.1") export fn IoCapture.new(input []u8) -> Self => { input, in_pos: 0, out: []u8.new(), err: []u8.new() } /// Bytes captured from stdout. #stable(since = "0.1") export fn IoCapture @out_bytes() -> ro []u8 => @out /// Bytes captured from stderr. #stable(since = "0.1") export fn IoCapture @err_bytes() -> ro []u8 => @err /// Mock `Io` handler bound to `cap`: `write_out`/`write_err` append to the /// capture; `read_in` serves the scripted `input` (`[]` once exhausted = EOF). /// Deterministic console tests without a real terminal. #stable(since = "0.1") export fn mock_io(mut cap IoCapture) -> Effect[Io] { // Explicit param types on the handler ops: the effect schema erases them to // `nova_int`, so `data.len()` would be typed on `int` without the annotation // (handler-body codegen note, see std/testing/handlers.nv). effect Io { read_in(buf mut []u8) -> Result[int, IoError] { ro want = buf.len() ro rem = cap.input.len() - cap.in_pos if want == 0 || rem <= 0 { return Ok(0) } ro n = want.min(rem) mut i = 0 while i < n { buf[i] = cap.input[cap.in_pos + i] i += 1 } cap.in_pos = cap.in_pos + n Ok(n) } write_out(data []u8) -> Result[int, IoError] { cap.out.append(data) Ok(data.len()) } write_err(data []u8) -> Result[int, IoError] { cap.err.append(data) Ok(data.len()) } } } // ─── real_io — production handler over the fd hooks ─── // Module-private C hooks (io_console.h, always included via nova_rt.h). Return // bytes transferred (read: 0 = EOF); a negative value is `-errno`. extern "C" fn io_write_fd(fd int, buf *u8, len int) -> int extern "C" fn io_read_fd(fd int, buf *mut u8, len int) -> int /// Real console `Io` handler: `write_out`/`write_err` go to stdout/stderr, /// `read_in` reads from stdin, via the fd hooks. A negative hook result maps to /// `IoError.from_os(-rc)`. Install with `with Io = real_io() { … }`. /// /// `#default_handler` (bare form) — `Io` auto-constructs (lazily, once per /// thread) on first transitive use with no enclosing `with Io = …`, consistent /// with `Time`/`real_time()`. #stable(since = "0.1") #default_handler export fn real_io() -> Effect[Io] { effect Io { read_in(buf mut []u8) -> Result[int, IoError] { ro want = buf.len() if want == 0 { return Ok(0) } ro rc = unsafe { io_read_fd(0, buf.ptr(), want) } if rc < 0 { return Err(IoError.from_os(0 - rc, "read")) } Ok(rc) } write_out(data []u8) -> Result[int, IoError] { if data.len() == 0 { return Ok(0) } ro rc = unsafe { io_write_fd(1, data.ptr(), data.len()) } if rc < 0 { return Err(IoError.from_os(0 - rc, "write")) } Ok(rc) } write_err(data []u8) -> Result[int, IoError] { if data.len() == 0 { return Ok(0) } ro rc = unsafe { io_write_fd(2, data.ptr(), data.len()) } if rc < 0 { return Err(IoError.from_os(0 - rc, "write")) } Ok(rc) } } }