//@NO-IMPLICIT-PRELUDE
//! Functions and types for working with `Read`ers.
let io @ { IO, wrap, flat_map, default_buf_len } = import! std.io.prim
let { show } = import! std.show
let { Option, Bool } = import! std.types
let { Disposable, dispose, is_disposed } = import! std.disposable
let { (>), (<=), (>=), min } = import! std.cmp
let { assert } = import! std.assert
let { not } = import! std.bool
let { ? } = import! std.int
let { Result } = import! std.result
let { Reference, ref, load, (<-) } = import! std.reference
let array = import! std.array
let string = import! std.string
/// Allows reading bytes from `a`. Generally, reading from a reader
/// advances its internal cursor. This means that multiple `read` calls
/// with the same arguments usually __do not__ return the same value.
#[implicit]
type Read a = {
/// Tries to read `num_bytes` byte from the reader. If the reader
/// has reached end of file or `num_bytes` was zero, `None`
/// will be returned. Otherwise, an array containing as many bytes as could be
/// read will be returned. The length of the array is guaranteed to be in the
/// range `[1, num_bytes]`.
///
/// Reaching end of file means that this reader will most likely not produce
/// more bytes. It _may_ produce more in the future.
read : a -> Int -> IO (Option (Array Byte)),
/// Read all bytes until end of file is encountered.
read_to_end : a -> IO (Array Byte)
}
let read ?read : [Read a] -> a -> Int -> IO (Option (Array Byte)) = read.read
let read_to_end ?read : [Read a] -> a -> IO (Array Byte) = read.read_to_end
/// Read all bytes until end of file is encountered into a `String`. Returns `None`
/// if the read bytes are not valid UTF-8.
let read_to_string reader : [Read a] -> a -> IO (Option String) =
do bytes = read_to_end reader
match string.from_utf8 bytes with
| Ok str -> wrap (Some str)
| Err _ -> wrap None
/// Constructs a `read_to_end` function from a `read` function. If it is more efficient,
/// implementors of `Read` should provide their own, specialized version.
let default_read_to_end read : forall a .
(a -> Int -> IO (Option (Array Byte))) -> a -> IO (Array Byte)
=
let read_to_end_rec buf reader =
do result = read reader default_buf_len
match result with
| Some new_buf -> read_to_end_rec (array.append buf new_buf) reader
| None -> wrap buf
read_to_end_rec []
/// Wraps a reader to provide buffering. Buffering is more efficient when
/// performing many small reads, because it avoids many costly reads from
/// the underlying reader.
///
/// If you are reading all data at once, buffering is not necessary.
type Buffered r = { reader : r, buf : Reference (Array Byte), capacity : Int }
/// Wraps `reader` in a `Buffered` reader to provide buffering with the specified
/// buffer capacity.
let buffered_with_capacity capacity reader : [Read a] -> Int -> a -> IO (Buffered a) =
let _ = assert (capacity > 0)
do buf = ref []
wrap
{
reader,
buf,
capacity,
}
/// Wraps `reader` in a `Buffered` reader to provide buffering.
let buffered reader : [Read a] -> a -> IO (Buffered a) =
buffered_with_capacity default_buf_len reader
let read_buffered : [Read r] -> Read (Buffered r) =
let read_from_buf buf_reader num_bytes =
do buf = load buf_reader.buf
let num_bytes = min (array.len buf) num_bytes
let read_buf = array.slice buf 0 num_bytes
let rest_buf = array.slice buf num_bytes (array.len buf)
buf_reader.buf <- rest_buf
wrap (Some read_buf)
let buffered_read buf_reader num_bytes =
do buf = load buf_reader.buf
if num_bytes <= 0 then wrap (Some [])
else if not (array.is_empty buf) then read_from_buf buf_reader num_bytes
else if num_bytes >= buf_reader.capacity then read buf_reader.reader num_bytes
else
do new_buf = read buf_reader.reader buf_reader.capacity
match new_buf with
| Some new_buf ->
buf_reader.buf <- new_buf
read_from_buf buf_reader num_bytes
| None -> wrap None
let buffered_read_to_end buf_reader =
do rest = read_to_end buf_reader.reader
do buf = load buf_reader.buf
buf_reader.buf <- []
wrap (array.append buf rest)
{
read = buffered_read,
read_to_end = buffered_read_to_end,
}
let disposable_buffered : [Disposable r] -> Disposable (Buffered r) =
{
dispose = (\buf_reader ->
buf_reader.buf <- []
dispose buf_reader.reader),
is_disposed = \buf_reader -> is_disposed buf_reader.reader,
}
{
Read,
Buffered,
read,
read_to_end,
read_to_string,
default_read_to_end,
buffered,
buffered_with_capacity,
read_buffered,
disposable_buffered,
}