use crate::fs::{fail, stash, Slot, OK};
use crate::NovaStr;
use std::io::Write as _;
fn read_and_stash(mut reader: impl std::io::Read, buf: &mut Vec<u8>) -> i64 {
match reader.read(buf) {
Ok(n) => {
buf.truncate(n);
stash(Slot::Buffer, crate::bytes::gc_bytes(buf));
OK
}
Err(e) => fail(&e),
}
}
#[no_mangle]
pub unsafe extern "C" fn nova_rt_io_stdin_read(max: i64) -> i64 {
let Ok(cap) = usize::try_from(max) else {
crate::task::abort_with("nova_rt_io_stdin_read: negative maximum")
};
let mut buf = vec![0u8; cap];
let stdin = std::io::stdin();
read_and_stash(stdin.lock(), &mut buf)
}
fn stash_count(n: usize) {
stash(
Slot::Buffer,
crate::bytes::gc_bytes(&(n as i64).to_le_bytes()),
);
}
fn write_and_stash(mut writer: impl std::io::Write, bytes: &[u8]) -> i64 {
match writer.write(bytes) {
Ok(n) => {
stash_count(n);
OK
}
Err(e) => fail(&e),
}
}
#[no_mangle]
pub unsafe extern "C" fn nova_rt_io_stdout_write(buf: *const NovaStr) -> i64 {
let bytes = unsafe { crate::bytes::as_bytes(buf) };
let stdout = std::io::stdout();
write_and_stash(stdout.lock(), bytes)
}
#[no_mangle]
pub unsafe extern "C" fn nova_rt_io_stderr_write(buf: *const NovaStr) -> i64 {
let bytes = unsafe { crate::bytes::as_bytes(buf) };
let stderr = std::io::stderr();
write_and_stash(stderr.lock(), bytes)
}
#[no_mangle]
pub unsafe extern "C" fn nova_rt_io_stdout_flush() -> i64 {
let stdout = std::io::stdout();
let mut lock = stdout.lock();
match lock.flush() {
Ok(()) => OK,
Err(e) => fail(&e),
}
}
#[no_mangle]
pub unsafe extern "C" fn nova_rt_io_stderr_flush() -> i64 {
let stderr = std::io::stderr();
let mut lock = stderr.lock();
match lock.flush() {
Ok(()) => OK,
Err(e) => fail(&e),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn take_count() -> i64 {
let ptr = crate::fs::take_for_test(Slot::Buffer) as *const NovaStr;
let bytes = unsafe { crate::bytes::as_bytes(ptr) };
let Ok(arr) = <[u8; 8]>::try_from(bytes) else {
panic!(
"stash_count always stashes exactly 8 bytes; got {} -- either \
nothing was pending or the encoding changed without updating \
this test helper to match",
bytes.len()
);
};
i64::from_le_bytes(arr)
}
#[test]
fn a_successful_stdout_write_reports_its_byte_count() {
let payload = crate::gc_bytes_for_test(b"hi");
let status = unsafe { nova_rt_io_stdout_write(payload) };
assert_eq!(status, crate::fs::OK, "writing to stdout must succeed");
assert_eq!(
take_count(),
2,
"the byte count must be the two bytes written, read back from the slot"
);
}
#[test]
fn a_successful_stderr_write_reports_its_byte_count() {
let payload = crate::gc_bytes_for_test(b"bye");
let status = unsafe { nova_rt_io_stderr_write(payload) };
assert_eq!(status, crate::fs::OK, "writing to stderr must succeed");
assert_eq!(
take_count(),
3,
"the byte count must be the three bytes written, read back from the slot"
);
}
#[test]
fn writing_an_empty_buffer_succeeds_with_a_zero_count() {
let payload = crate::gc_bytes_for_test(b"");
let status = unsafe { nova_rt_io_stdout_write(payload) };
assert_eq!(
status,
crate::fs::OK,
"writing zero bytes must still succeed"
);
assert_eq!(take_count(), 0);
}
#[test]
fn stash_count_round_trips_a_count_above_one_byte() {
stash_count(300);
assert_eq!(
take_count(),
300,
"a byte count above 255 must round-trip exactly, not truncate to one byte"
);
}
#[test]
fn a_short_read_truncates_to_what_was_actually_read() {
let mut buf = vec![0u8; 10]; let status = read_and_stash(std::io::Cursor::new(b"hi".to_vec()), &mut buf);
assert_eq!(status, OK);
let ptr = crate::fs::take_for_test(Slot::Buffer) as *const NovaStr;
let bytes = unsafe { crate::bytes::as_bytes(ptr) };
assert_eq!(
bytes, b"hi",
"a short read must stash exactly what was read, not `max` zero-padded bytes"
);
}
#[test]
fn an_empty_read_stashes_a_zero_length_payload() {
let mut buf = vec![0u8; 10];
let status = read_and_stash(std::io::Cursor::new(Vec::new()), &mut buf);
assert_eq!(status, OK);
let ptr = crate::fs::take_for_test(Slot::Buffer) as *const NovaStr;
let bytes = unsafe { crate::bytes::as_bytes(ptr) };
assert_eq!(
bytes, b"" as &[u8],
"an empty read must stash a zero-length payload -- the EOF signal \
`Read::read`'s Nova-level contract documents"
);
}
#[test]
fn stdout_flush_reports_ok() {
let status = unsafe { nova_rt_io_stdout_flush() };
assert_eq!(
status,
crate::fs::OK,
"flushing stdout must succeed under normal test conditions"
);
}
#[test]
fn stderr_flush_reports_ok() {
let status = unsafe { nova_rt_io_stderr_flush() };
assert_eq!(
status,
crate::fs::OK,
"flushing stderr must succeed under normal test conditions"
);
}
#[test]
fn no_stream_intrinsic_can_panic() {
let source = include_str!("io.rs");
let production = source.split("mod tests {").next().unwrap_or(source);
for needle in ["unwrap()", ".expect(", "panic!", "format!", "RefCell"] {
assert!(
!production.contains(needle),
"a std/io stream intrinsic must not panic: `{needle}` found in \
this file's production code, which is reachable from a \
generated poll boundary with no landing pad to unwind through"
);
}
}
}