//@NO-IMPLICIT-PRELUDE //! Functions and types for working with `Write`rs. let string = import! std.string let array = import! std.array let { wrap } = import! std.applicative let { IO, flat_map, wrap, default_buf_len, throw } = import! std.io.prim let { Disposable, dispose, is_disposed } = import! std.disposable let { Reference, (<-), load, ref } = import! std.reference let { ? } = import! std.int let { assert } = import! std.assert let { (>=), (>), (==) } = import! std.cmp let { (+), (-) } = import! std.num let { not } = import! std.bool /// Allows writing bytes to a sink. To ensure that all data has been written, /// writes have to be paired with a `flush`. #[implicit] type Write a = { /// Write some bytes from a slice of an array into the writer. The bounds /// of the slice are given by `start` and `end` as a half open range `[start, end)`. /// /// The function returns the number of bytes that have been written. If the slice /// is not empty and the returned value is `0`, the writer will likely not accept /// more data. It _may_ accept more data in the future. write_slice : a -> Array Byte -> Int -> Int -> IO Int, /// Flushes the buffers of the writer, ensuring that all data has been written. flush : a -> IO () } let write_slice ?write : [Write a] -> a -> Array Byte -> Int -> Int -> IO Int = write.write_slice /// Like `write_slice`, but tries to write all of `buf`. let write writer buf : [Write a] -> a -> Array Byte -> IO Int = write_slice writer buf 0 (array.len buf) /// Writes the entire contents of `buf` into `writer`. The call will fail if `writer` /// does not accept all of the data. let write_all writer buf : [Write a] -> a -> Array Byte -> IO () = let buf_len = array.len buf let inner bytes_written = if bytes_written >= buf_len then wrap () else do written = write_slice writer buf bytes_written buf_len if written == 0 then throw "cannot write the whole buffer because the writer has reached end of file" else inner (bytes_written + written) inner 0 /// Writes the entire string into `writer`. The call will fail if `writer` /// does not accept all of the data. let write_string writer str : [Write a] -> a -> String -> IO () = write_all writer (string.as_bytes str) let flush ?write : [Write a] -> a -> IO () = write.flush /// Wraps a writer to provide buffering. Buffering is more efficient when /// performing many small writes, because it avoids many costly writes to /// the underlying writer. /// /// If you are writing all data at once, buffering is not necessary. type Buffered w = { writer : w, buf : Reference (Array Byte), capacity : Int } /// Wraps `writer` in a `Buffered` writer to provide buffering with the specified /// buffer capacity. let buffered_with_capacity capacity writer : [Write w] -> Int -> w -> IO (Buffered w) = let _ = assert (capacity > 0) do buf = ref [] wrap { writer, buf, capacity, } /// Wraps `writer` in a `Buffered` writer to provide buffering. let buffered writer : [Write w] -> w -> IO (Buffered w) = buffered_with_capacity default_buf_len writer /// Flushes the buffer of `buf_writer`. If an EOF is encountered, the call will fail. let flush_buffer buf_writer : [Write w] -> Buffered w -> IO () = do buf = load buf_writer.buf if array.is_empty buf then wrap () else let write_buf bytes_written = if bytes_written >= array.len buf then wrap () else do written = write_slice buf_writer.writer buf bytes_written (array.len buf) if written == 0 then throw "cannot flush buffer because the writer has reached end of file" else write_buf (bytes_written + written) do _ = write_buf 0 buf_writer.buf <- [] wrap () let write_buffered : [Write w] -> Write (Buffered w) = let buffered_write_slice buf_writer slice start end = let slice_len = end - start let _ = assert (slice_len >= 0) do _ = do buf = load buf_writer.buf // if the new data would spill the buffer, flush it first if array.len buf + slice_len >= buf_writer.capacity then flush_buffer buf_writer else wrap () // if the slice is larger than the max buffer length write it directly; // otherwise append it to the buffer. Appending can never exceed the buffer, // since the slice is always < buf_len and the buffer has been flushed if // necessary if slice_len >= buf_writer.capacity then write_slice buf_writer.writer slice start end else do buf = load buf_writer.buf buf_writer.buf <- array.append buf (array.slice slice start end) wrap slice_len let buffered_flush buf_writer = do _ = flush_buffer buf_writer flush buf_writer.writer { write_slice = buffered_write_slice, flush = buffered_flush, } let disposable_buffered : [Disposable w] -> [Write w] -> Disposable (Buffered w) = { dispose = (\buf_writer -> do _ = flush_buffer buf_writer dispose buf_writer.writer), is_disposed = \buf_writer -> is_disposed buf_writer.writer, } { Write, Buffered, write_slice, write, write_all, write_string, flush, buffered, buffered_with_capacity, write_buffered, disposable_buffered, }