// This file is based on the Go implementation found here:
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/io/io.go
// which has the copyright notice:
// copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/// Package io provides basic interfaces to I/O primitives.
/// Its primary job is to wrap existing implementations of such primitives,
/// such as those in package os, into shared public interfaces that
/// abstract the functionality, plus some other related primitives.
///
/// Because these interfaces and primitives wrap lower-level operations with
/// various implementations, unless otherwise informed clients should not
/// assume they are safe for parallel execution.
///|
/// Seek whence values.
pub(all) enum Whence {
/// seek relative to the origin of the file
SeekStart
/// seek relative to the current offset
SeekCurrent
/// seek relative to the end
SeekEnd
}
///|
/// An IOError can be tested with equality checks.
pub(all) suberror IOError {
IOError(String)
} derive(Debug, Eq)
///|
pub impl Show for IOError with fn output(self, logger) {
let IOError(msg) = self
logger.write_string(
(
$|IOError(\{msg.escape(quote=true)})
),
)
}
///|
test "IOError show interface" {
let err = IOError("test error")
inspect(err, content="IOError(\"test error\")")
let err = IOError("test error with \"quotes\" and \\backslashes\\")
inspect(
err,
content=(
#|IOError("test error with \"quotes\" and \\backslashes\\")
),
)
}
///|
/// err_short_write means that a write accepted fewer bytes than requested
/// but failed to return an explicit error.
pub let err_short_write : IOError = IOError("short write")
///|
/// err_invalid_write means that a write returned an impossible count.
pub let err_invalid_write : IOError = IOError("invalid write result")
///|
/// err_short_buffer means that a read required a longer buffer than was provided.
pub let err_short_buffer : IOError = IOError("short buffer")
///|
/// eof is the error returned by Read when no more input is available.
/// (Read must return eof itself, not an error wrapping eof,
/// because callers will test for eof using ==.)
/// Functions should return eof only to signal a graceful end of input.
/// If the eof occurs unexpectedly in a structured data stream,
/// the appropriate error is either [err_unexpected_eof] or some other error
/// giving more detail.
pub let eof : IOError = IOError("eof")
///|
/// err_unexpected_eof means that eof was encountered in the
/// middle of reading a fixed-size block or data structure.
pub let err_unexpected_eof : IOError = IOError("unexpected eof")
///|
/// err_no_progress is returned by some clients of a [Reader] when
/// many calls to Read have failed to return any data or error,
/// usually the sign of a broken [Reader] implementation.
pub let err_no_progress : IOError = IOError(
"multiple Read calls return no data or error",
)
///|
/// Reader is the interface that wraps the basic Read method.
///
/// Read reads up to len(p) bytes into p. It returns the number of bytes
/// read (0 <= n <= len(p)) and any error encountered. Even if Read
/// returns n < len(p), it may use all of p as scratch space during the call.
/// If some data is available but not len(p) bytes, Read conventionally
/// returns what is available instead of waiting for more.
///
/// When Read encounters an error or end-of-file condition after
/// successfully reading n > 0 bytes, it returns the number of
/// bytes read. It may return the (non-None) error from the same call
/// or return the error (and n == 0) from a subsequent call.
/// An instance of this general case is that a Reader returning
/// a non-zero number of bytes at the end of the input stream may
/// return either err == eof or err == None. The next Read should
/// return 0, eof.
///
/// Callers should always process the n > 0 bytes returned before
/// considering the error err. Doing so correctly handles I/O errors
/// that happen after reading some bytes and also both of the
/// allowed eof behaviors.
///
/// If len(p) == 0, Read should always return n == 0. It may return a
/// non-None error if some error condition is known, such as eof.
///
/// Implementations of Read are discouraged from returning a
/// zero byte count with a None error, except when len(p) == 0.
/// Callers should treat a return of 0 and None as indicating that
/// nothing happened; in particular it does not indicate eof.
///
/// Implementations must not retain p.
pub(open) trait Reader {
fn read(Self, Slice[Byte]) -> (Int, IOError?)
}
///|
/// Writer is the interface that wraps the basic Write method.
///
/// Write writes len(p) bytes from p to the underlying data stream.
/// It returns the number of bytes written from p (0 <= n <= len(p))
/// and any error encountered that caused the write to stop early.
/// Write must return a non-None error if it returns n < len(p).
/// Write must not modify the slice data, even temporarily.
///
/// Implementations must not retain p.
pub(open) trait Writer {
fn write(Self, Slice[Byte]) -> (Int, IOError?)
}
///|
/// Closer is the interface that wraps the basic Close method.
///
/// The behavior of Close after the first call is undefined.
/// Specific implementations may document their own behavior.
pub(open) trait Closer {
fn close(Self) -> IOError?
}
///|
/// Seeker is the interface that wraps the basic Seek method.
///
/// Seek sets the offset for the next Read or Write to offset,
/// interpreted according to whence:
/// [SeekStart] means relative to the start of the file,
/// [SeekCurrent] means relative to the current offset, and
/// [SeekEnd] means relative to the end
/// (for example, offset = -2 specifies the penultimate byte of the file).
/// Seek returns the new offset relative to the start of the
/// file or an error, if any.
///
/// Seeking to an offset before the start of the file is an error.
/// Seeking to any positive offset may be allowed, but if the new offset exceeds
/// the size of the underlying object the behavior of subsequent I/O operations
/// is implementation-dependent.
pub(open) trait Seeker {
fn seek(Self, Int64, Whence) -> (Int64, IOError?)
}
///|
/// ReadWriter is the interface that groups the basic Read and Write methods.
pub(open) trait ReadWriter: Reader + Writer {}
///|
/// ReadCloser is the interface that groups the basic Read and Close methods.
pub(open) trait ReadCloser: Reader + Closer {}
///|
/// WriteCloser is the interface that groups the basic Write and Close methods.
pub(open) trait WriteCloser: Writer + Closer {}
///|
/// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
pub(open) trait ReadWriteCloser: Reader + Writer + Closer {}
///|
/// ReadSeeker is the interface that groups the basic Read and Seek methods.
pub(open) trait ReadSeeker: Reader + Seeker {}
///|
/// ReadSeekCloser is the interface that groups the basic Read, Seek and Close
/// methods.
pub(open) trait ReadSeekCloser: Reader + Seeker + Closer {}
///|
/// WriteSeeker is the interface that groups the basic Write and Seek methods.
pub(open) trait WriteSeeker: Writer + Seeker {}
///|
/// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
pub(open) trait ReadWriteSeeker: Reader + Writer + Seeker {}
///|
/// ReaderFrom is the interface that wraps the read_from method.
///
/// read_from reads data from r until eof or error.
/// The return value n is the number of bytes read.
/// Any error except eof encountered during the read is also returned.
///
/// The [copy] function uses [ReaderFrom] if available.
pub(open) trait ReaderFrom {
fn read_from(Self, &Reader) -> (Int64, IOError?)
}
///|
/// WriterTo is the interface that wraps the write_to method.
///
/// write_to writes data to w until there's no more data to write or
/// when an error occurs. The return value n is the number of bytes
/// written. Any error encountered during the write is also returned.
///
/// The copy function uses WriterTo if available.
pub(open) trait WriterTo {
fn write_to(Self, &Writer) -> (Int64, IOError?)
}
///|
/// ReaderAt is the interface that wraps the basic read_at method.
///
/// read_at reads len(p) bytes into p starting at offset off in the
/// underlying input source. It returns the number of bytes
/// read (0 <= n <= len(p)) and any error encountered.
///
/// When read_at returns n < len(p), it returns a non-None error
/// explaining why more bytes were not returned. In this respect,
/// read_at is stricter than Read.
///
/// Even if read_at returns n < len(p), it may use all of p as scratch
/// space during the call. If some data is available but not len(p) bytes,
/// read_at blocks until either all the data is available or an error occurs.
/// In this respect read_at is different from Read.
///
/// If the n = len(p) bytes returned by read_at are at the end of the
/// input source, read_at may return either err == eof or err == None.
///
/// If read_at is reading from an input source with a seek offset,
/// read_at should not affect nor be affected by the underlying
/// seek offset.
///
/// Clients of read_at can execute parallel read_at calls on the
/// same input source.
///
/// Implementations must not retain p.
pub(open) trait ReaderAt {
fn read_at(Self, Slice[Byte], Int64) -> (Int, IOError?)
}
///|
/// WriterAt is the interface that wraps the basic write_at method.
///
/// write_at writes len(p) bytes from p to the underlying data stream
/// at offset off. It returns the number of bytes written from p (0 <= n <= len(p))
/// and any error encountered that caused the write to stop early.
/// write_at must return a non-None error if it returns n < len(p).
///
/// If write_at is writing to a destination with a seek offset,
/// write_at should not affect nor be affected by the underlying
/// seek offset.
///
/// Clients of write_at can execute parallel write_at calls on the same
/// destination if the ranges do not overlap.
///
/// Implementations must not retain p.
pub(open) trait WriterAt {
fn write_at(Self, Slice[Byte], Int64) -> (Int, IOError?)
}
///|
/// ByteReader is the interface that wraps the read_byte method.
///
/// read_byte reads and returns the next byte from the input or
/// any error encountered. If read_byte returns an error, no input
/// byte was consumed, and the returned byte value is undefined.
///
/// read_byte provides an efficient interface for byte-at-time
/// processing. A [Reader] that does not implement ByteReader
/// can be wrapped using bufio.NewReader to add this method.
pub(open) trait ByteReader {
fn read_byte(Self) -> (Byte, IOError?)
}
///|
/// ByteScanner is the interface that adds the unread_byte method to the
/// basic read_byte method.
///
/// unread_byte causes the next call to read_byte to return the last byte read.
/// If the last operation was not a successful call to read_byte, unread_byte may
/// return an error, unread the last byte read (or the byte prior to the
/// last-unread byte), or (in implementations that support the [Seeker] interface)
/// seek to one byte before the current offset.
pub(open) trait ByteScanner: ByteReader {
fn unread_byte(Self) -> IOError?
}
///|
/// ByteWriter is the interface that wraps the write_byte method.
pub(open) trait ByteWriter {
fn write_byte(Self, Byte) -> IOError?
}
///|
/// read_at_least reads from r into buf until it has read at least min bytes.
/// It returns the number of bytes copied and an error if fewer bytes were read.
/// The error is eof only if no bytes were read.
/// If an eof happens after reading fewer than min bytes,
/// read_at_least returns [err_unexpected_eof].
/// If min is greater than the length of buf, read_at_least returns [err_short_buffer].
/// On return, n >= min if and only if err == None.
/// If r returns an error having read at least min bytes, the error is dropped.
fn read_at_least(r : &Reader, buf : Slice[Byte], min : Int) -> (Int, IOError?) {
let mut n = 0
let mut err = None
if buf.length() < min {
return (0, Some(err_short_buffer))
}
while n < min && err == None {
let v = r.read(buf[n:])
n += v.0
err = v.1
}
if n >= min {
err = None
} else if n > 0 && err == Some(eof) {
err = Some(err_unexpected_eof)
}
(n, err)
}
///|
/// read_full reads exactly len(buf) bytes from r into buf.
/// It returns the number of bytes copied and an error if fewer bytes were read.
/// The error is eof only if no bytes were read.
/// If an eof happens after reading some but not all the bytes,
/// read_full returns [err_unexpected_eof].
/// On return, n == len(buf) if and only if err == None.
/// If r returns an error having read at least len(buf) bytes, the error is dropped.
pub fn read_full(r : &Reader, buf : Slice[Byte]) -> (Int, IOError?) {
read_at_least(r, buf, buf.length())
}
///|
/// copy_n copies n bytes (or until an error) from src to dst.
/// It returns the number of bytes copied and the earliest
/// error encountered while copying.
/// On return, written == n if and only if err == None.
///
/// If dst implements [ReaderFrom], the copy is implemented using it.
pub fn copy_n(dst : &Writer, src : &Reader, n : Int64) -> (Int64, IOError?) {
let v = copy(dst, LimitedReader::new(src, n))
let written = v.0
let mut err = v.1
if written == n {
return (n, None)
}
if written < n && err == None {
// src stopped early; must have been eof.
err = Some(eof)
}
(written, err)
}
///|
/// copy copies from src to dst until either eof is reached
/// on src or an error occurs. It returns the number of bytes
/// copied and the first error encountered while copying, if any.
///
/// A successful copy returns err == None, not err == eof.
/// Because copy is defined to read from src until eof, it does
/// not treat an eof from Read as an error to be reported.
///
/// If src implements [WriterTo],
/// the copy is implemented by calling src.write_to(dst).
/// Otherwise, if dst implements [ReaderFrom],
/// the copy is implemented by calling dst.read_from(src).
pub fn copy(dst : &Writer, src : &Reader) -> (Int64, IOError?) {
return copy_buffer(dst, src, None)
}
///|
/// copy_buffer is identical to copy except that it stages through the
/// provided buffer (if one is required) rather than allocating a
/// temporary one. If buf is None, one is allocated; otherwise if it has
/// zero length, copy_buffer panics.
///
/// If either src implements [WriterTo] or dst implements [ReaderFrom],
/// buf will not be used to perform the copy.
pub fn copy_buffer(
dst : &Writer,
src : &Reader,
buf : Slice[Byte]?,
) -> (Int64, IOError?) {
if buf != None && buf.unwrap().length() == 0 {
return (0, Some(IOError("empty buffer in copy_buffer")))
}
let mut buf = buf
// TODO:
// // If the reader has a write_to method, use it to do the copy.
// // Avoids an allocation and a copy.
// if wt, ok := src.(WriterTo); ok {
// return wt.write_to(dst)
// }
// // Similarly, if the writer has a read_from method, use it to do the copy.
// if rf, ok := dst.(ReaderFrom); ok {
// return rf.read_from(src)
// }
//
if buf == None {
let size = 32 * 1024
// TODO:
// if l, ok := src.(*LimitedReader); ok && Int64(size) > l.N {
// if l.N < 1 {
// size = 1
// } else {
// size = int(l.N)
// }
// }
buf = Some(Slice::new(Array::make(size, b'\x00')))
}
let buf = buf.unwrap()
//
let mut written = 0L
let mut err = None
for ;; {
let (nr, er) = src.read(buf)
if nr > 0 {
let (nw, ew) = dst.write(buf[:nr])
let mut nw = nw
let mut ew = ew
if nw < 0 || nr < nw {
nw = 0
if ew == None {
ew = Some(err_invalid_write)
}
}
written += nw.to_int64()
if ew != None {
err = ew
break
}
if nr != nw {
err = Some(err_short_write)
break
}
}
if er != None {
if er != Some(eof) {
err = er
}
break
}
}
(written, err)
}
///|
/// LimitedReader::new returns a Reader that reads from r
/// but stops with eof after n bytes.
/// The underlying implementation is a LimitedReader.
pub fn LimitedReader::new(r : &Reader, n : Int64) -> LimitedReader {
{ r, n }
}
///|
let _trait3 : &Reader = LimitedReader::new(discard, 0)
///|
/// A LimitedReader reads from R but limits the amount of
/// data returned to just N bytes. Each call to Read
/// updates N to reflect the new amount remaining.
/// Read returns eof when N <= 0 or when the underlying R returns eof.
struct LimitedReader {
r : &Reader // underlying reader
mut n : Int64 // max bytes remaining
}
///|
pub impl Reader for LimitedReader with fn read(self, p) {
if self.n <= 0 {
return (0, Some(eof))
}
let mut p = p
if p.length().to_int64() > self.n {
p = p[:self.n.to_int()]
}
let (n, err) = self.r.read(p)
self.n -= n.to_int64()
(n, err)
}
///|
/// SectionReader::new returns a [SectionReader] that reads from r
/// starting at offset off and stops with eof after n bytes.
pub fn SectionReader::new(
r : &ReaderAt,
off : Int64,
n : Int64,
) -> SectionReader {
let mut remaining = 0L
if off <= @int64.MAX_VALUE - n {
remaining = n + off
} else {
// Overflow, with no way to return error.
// Assume we can read up to an offset of 1<<63 - 1.
remaining = @int64.MAX_VALUE
}
{ r, base: off, off, limit: remaining, n }
}
///|
/// SectionReader implements Read, Seek, and read_at on a section
/// of an underlying [ReaderAt].
struct SectionReader {
r : &ReaderAt // constant after creation
base : Int64 // constant after creation
mut off : Int64
limit : Int64 // constant after creation
n : Int64 // constant after creation
}
///|
pub fn SectionReader::read(
self : SectionReader,
p : Slice[Byte],
) -> (Int, IOError?) {
if self.off >= self.limit {
return (0, Some(eof))
}
let max = self.limit - self.off
let mut p = p
if p.length().to_int64() > max {
p = p[:max.to_int()]
}
let (n, err) = self.r.read_at(p, self.off)
self.off += n.to_int64()
(n, err)
}
///|
pub let err_whence : IOError = IOError("Seek: invalid whence")
///|
pub let err_offset : IOError = IOError("Seek: invalid offset")
///|
pub fn SectionReader::seek(
self : SectionReader,
offset : Int64,
whence : Whence,
) -> (Int64, IOError?) {
let mut offset = offset
match whence {
SeekStart => offset += self.base
SeekCurrent => offset += self.off
SeekEnd => offset += self.limit
}
if offset < self.base {
return (0, Some(err_offset))
}
self.off = offset
(offset - self.base, None)
}
///|
pub fn SectionReader::read_at(
self : SectionReader,
p : Slice[Byte],
off : Int64,
) -> (Int, IOError?) {
let mut off = off
if off < 0 || off >= self.size() {
return (0, Some(eof))
}
off += self.base
let max = self.limit - off
let mut p = p
if p.length().to_int64() > max {
p = p[:max.to_int()]
let (n, err) = self.r.read_at(p, off)
let mut err = err
if err == None {
err = Some(eof)
}
return (n, err)
}
self.r.read_at(p, off)
}
///|
/// Size returns the size of the section in bytes.
fn SectionReader::size(self : SectionReader) -> Int64 {
return self.limit - self.base
}
///|
/// Outer returns the underlying [ReaderAt] and offsets for the section.
///
/// The returned values are the same that were passed to [SectionReader::new]
/// when the [SectionReader] was created.
pub fn SectionReader::outer(self : SectionReader) -> (&ReaderAt, Int64, Int64) {
(self.r, self.base, self.n)
}
///|
/// An OffsetWriter maps writes at offset base to offset base+off in the underlying writer.
struct OffsetWriter {
w : &WriterAt
base : Int64 // the original offset
mut off : Int64 // the current offset
}
///|
/// OffsetWriter::new returns an [OffsetWriter] that writes to w
/// starting at offset off.
pub fn OffsetWriter::new(w : &WriterAt, off : Int64) -> OffsetWriter {
{ w, base: off, off }
}
///|
pub fn OffsetWriter::write(
self : OffsetWriter,
p : Slice[Byte],
) -> (Int, IOError?) {
let (n, err) = self.w.write_at(p, self.off)
self.off += n.to_int64()
(n, err)
}
///|
pub fn OffsetWriter::write_at(
self : OffsetWriter,
p : Slice[Byte],
off : Int64,
) -> (Int, IOError?) {
if off < 0 {
return (0, Some(err_offset))
}
let off = off + self.base
self.w.write_at(p, off)
}
///|
pub fn OffsetWriter::seek(
self : OffsetWriter,
offset : Int64,
whence : Whence,
) -> (Int64, IOError?) {
let mut offset = offset
match whence {
SeekStart => offset += self.base
SeekCurrent => offset += self.off
_ => return (0, Some(err_whence))
}
if offset < self.base {
return (0, Some(err_offset))
}
self.off = offset
(offset - self.base, None)
}
///|
/// TeeReader::new returns a [Reader] that writes to w what it reads from r.
/// All reads from r performed through it are matched with
/// corresponding writes to w. There is no internal buffering -
/// the write must complete before the read completes.
/// Any error encountered while writing is reported as a read error.
pub fn TeeReader::new(r : &Reader, w : &Writer) -> TeeReader {
{ r, w }
}
///|
struct TeeReader {
r : &Reader
w : &Writer
}
///|
let _trait : &Reader = TeeReader::new(discard, discard)
///|
pub impl Reader for TeeReader with fn read(self, p) {
let (n, err) = self.r.read(p)
if n > 0 {
let (n, err) = self.w.write(p[:n])
if err != None {
return (n, err)
}
}
(n, err)
}
///|
/// discard is a [Writer] on which all Write calls succeed
/// without doing anything.
pub let discard : Discard = Discard::{ }
///|
/// `discard` satisfies the `Writer` trait.
let _discardIsWriter : &Writer = discard
///|
/// `discard` satisfies the `Reader` trait.
let _discardIsReader : &Reader = discard
///|
struct Discard {}
///|
pub impl Reader for Discard with fn read(_self, _b) {
(0, Some(eof))
}
///|
/// Discard implements ReaderFrom as an optimization so copy to
/// io.Discard can avoid doing unnecessary work.
/// let _ : &ReaderFrom = discard
pub impl Writer for Discard with fn write(_self, p) {
// possible compiler bug - this `pub` should not be needed here but buffer_test.mbt fails if missing.
(p.length(), None)
}
///|
pub fn Discard::read_from(_self : Discard, r : &Reader) -> (Int64, IOError?) {
let buf = Slice::new(Array::make(8192, b'\x00'))
let mut n = 0L
for ;; {
let v = r.read(buf)
n += v.0.to_int64()
let err = v.1
if err != None {
if err == Some(eof) {
return (n, None)
}
return (n, err)
}
}
}
///|
/// NopCloser::new returns a [ReadCloser] with a no-op Close method wrapping
/// the provided [Reader] r.
/// If r implements [WriterTo], the returned [ReadCloser] will implement [WriterTo]
/// by forwarding calls to r.
pub fn NopCloser::new(r : &Reader) -> NopCloser {
// TODO:
// let _, ok := r.(WriterTo)
// if ok {
// return NopCloserWriterTo{r}
// }
{ r, }
}
///|
pub impl Reader for NopCloser with fn read(self, b) {
self.r.read(b)
}
///|
struct NopCloser {
r : &Reader
}
///|
pub impl Closer for NopCloser with fn close(_self) {
None
}
// TODO:
// ///| NopCloserWriterTo is for internal use only. Do not make public.
// struct NopCloserWriterTo {
// // TODO: r : &Reader
// }
//
// ///|
// fn NopCloserWriterTo::close(_self : NopCloserWriterTo) -> IOError? {
// None
// }
// TODO:
// fn write_to(self : NopCloserWriterTo, w : &Writer) -> (Int64, IOEerror?) {
// // return c.Reader.(WriterTo).WriteTo(w)
// self.r.write_to(w)
// }
///|
/// read_all reads from r until an error or eof and returns the data it read.
/// A successful call returns err == None, not err == eof. Because read_all is
/// defined to read from src until eof, it does not treat an eof from Read
/// as an error to be reported.
pub fn read_all(r : &Reader) -> (Slice[Byte], IOError?) {
let mb = 1 << 20
let mut cap = 512
let mut arr = Array::make(cap, b'\x00')
let mut b = Slice::new(arr, end=0)
for ;; {
let (n, err) = r.read(b[b.length():b.cap()])
let mut err = err
b = b[:b.length() + n]
if err != None {
if err == Some(eof) {
err = None
}
return (b, err)
}
if b.length() == cap {
if cap < mb {
// naive doubling of memory up until 1<<20
cap *= 2
} else {
// thereafter, just add another 1<<20
cap += mb
}
let new_arr = Array::make(cap, b'\x00')
// Array::unsafe_blit(new_arr, 0, arr, 0, b.length()) // Doesn't work on wasm, wasm-gc, native
for i in 0..