/* * Copyright 2025 Ry Wiese * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */pubmod BufReader {////// A `Readable` implementation that wraps an underlying `Readable` instance/// and stores the items read from the underlying instance in an internal buffer.////// A call to `read` will read directly from the buffer if items are available, and/// will trigger a `read` from the underlying `reader` when the buffer is empty.////// This has the effect of reducing the overall number of `read`s to the underlying/// `Readable`.////// A call to `read` might read more than the requested number of items from the/// underlying reader, in order to fill the entire buffer.////// A call to `read` might not read from the underlying `reader` at all, if there are/// enough items in the buffer.////// `buffer`: the underlying buffer./// `cursor`: the index in `buffer` of the first element logically considered to be in the buffer./// `size`: the number of elements (starting at `cursor`) logically considered to be in the buffer./// `reader`: the underlying `Readable` being read from.///pubstructBufReader[a: Type, t: Type, r: Region] { buffer: Array[a, r],mut cursor: Int32,mut size: Int32, reader: t }instanceReadable[BufReader[a, t, rr]] withReadable[t] whereReadable.Elm[t] ~ a {typeElm = atypeAef = rr + Readable.Aef[t]pubdefread(buffer: Array[a, rb], bufReader: BufReader[a, t, rr]): Result[IoError, Int32] \ rb + rr + Readable.Aef[t] =BufReader.read(buffer, bufReader) }instancePeekable[BufReader[a, t, r]] withReadable[t] whereReadable.Elm[t] ~ a {pubdefpeek(bufReader: BufReader[a, t, r]): Result[IoError, Option[Readable.Elm[t]]] \ Readable.Aef[t] + r = BufReader.peek(bufReader)pubdefskip(n: Int32, bufReader: BufReader[a, t, r]): Result[IoError, Int32] \ Readable.Aef[t] + r = BufReader.skip(n, bufReader)pubdefreadWhile(p: Readable.Elm[t] -> Bool, bufReader: BufReader[a, t, r]): Result[IoError, Vector[Readable.Elm[t]]] \ Readable.Aef[t] + r = BufReader.readWhile(p, bufReader) }////// Constructs a `BufReader` wrapping `reader` with a default capacity of 4096 items.///pubdefwithDefaultCapacity(rc: Region[r], reader: t): BufReader[a, t, r] \ rwithReadable[t] =withCapacity(rc, defaultBufferCapacity(), reader)////// Constructs a `BufReader` wrapping `reader` with the specified `capacity` in items.////// If `capacity <= 0`, the `BufReader` will delegate directly to `reader` without buffering.///pubdefwithCapacity(rc: Region[r], capacity: Int32, reader: t): BufReader[a, t, r] \ rwithReadable[t] =new BufReader @ rc { buffer = Array.empty(rc, capacity), cursor = 0, size = 0, reader = reader }////// Reads `k` items from the underlying `Readable` into `dest`, storing items read into/// the internal buffer.////// Returns `Ok(k)` to signify that `k` items were successfully read and written to `dest`.////// Guarantees that `0 <= k <= length(dest)`.////// Returns `Err(e)` if some underlying error occurs.///pubdefread(dest: Array[Readable.Elm[t], rb], bufReader: BufReader[a, t, rr]): Result[IoError, Int32] \ rb + rr + Readable.Aef[t] withReadable[t] whereReadable.Elm[t] ~ a =if (Array.size(bufReader->buffer) >0) {readFromCursor(0, dest, bufReader) } else {Readable.read(dest, bufReader->reader) }////// Returns `Ok(Some(x))` where x is the next value to be read, or `Ok(None)` if the `bufReader` is empty./// Does not advance the `bufReader`.////// Returns `Err(err)` if there is an IO error.///pubdefpeek(bufReader: BufReader[a, t, r]): Result[IoError, Option[Readable.Elm[t]]] \ Readable.Aef[t] + rwithReadable[t] whereReadable.Elm[t] ~ a =if (bufReader->size !=0) { Ok(Some(Array.get(bufReader->cursor, bufReader->buffer))) } else {forM (itemsFilled <- fillBuffer(bufReader) ) yield {if (itemsFilled!=0) { Some(Array.get(bufReader->cursor, bufReader->buffer)) } else { None } } }////// Advances the reader by `n`, skipping over the elements without reading them.////// Returns `Ok(k)` where `k` is the number of items skipped.////// Guarantees that `0 <= k <= max(n, 0)`. If `k < n`, then EOF has been reached.////// Returns `Err(err)` if there is an IO error.///pubdefskip(n: Int32, bufReader: BufReader[a, t, r]): Result[IoError, Int32] \ Readable.Aef[t] + rwithReadable[t] whereReadable.Elm[t] ~ a =if (n<=0) {// Done Ok(0) } elseif (bufReader->size !=0) {// Nonempty Buffer: process itemsletitemsSkipped = skipFromBuffer(n, bufReader);// recurse into Done or Empty Buffer branchforM (rest <- skip(n-itemsSkipped, bufReader) ) yielditemsSkipped+rest } else {// Empty Buffer: refill itforM (itemsFilled <- fillBuffer(bufReader);skipped <- if (itemsFilled==0) {// Done: Eof Ok(0) } else {// Nonempty Buffer: continue processingskip(n, bufReader) } ) yieldskipped }////// Advances the reader by `n` or the size of the buffer, whichever is smaller, skipping over the elements without reading them.////// If the buffer runs out, does NOT trigger a read to the underlying reader.////// Guarantees that `0 <= k <= min(n, size(buffer))`.////// Assumes `n > 0`.///defskipFromBuffer(n: Int32, bufReader: BufReader[a, t, r]): Int32 \ rwithReadable[t] whereReadable.Elm[t] ~ a =letitemsToSkip = Int32.min(n, bufReader->size);bufReader->cursor = bufReader->cursor +itemsToSkip;bufReader->size = bufReader->size -itemsToSkip;itemsToSkip////// Reads from `reader` until an element is reached that does not satisfy predicate `p`.////// Returns `Ok(vec)` where `vec` is a vector of the first `k` elements where `k` is the index of the first element that does not satisfy `p`./// This element is not included in the vector, and will be read on the next read.////// All elements of `vec` are guaranteed to satisfy `p`.////// Returns `Err(err)` if there is an IO error.///pubdefreadWhile(p: a -> Bool, bufReader: BufReader[a, t, r]): Result[IoError, Vector[a]] \ Readable.Aef[t] + rwithReadable[t] whereReadable.Elm[t] ~ a =regionrc {letmutList = MutList.empty(rc);forM (_ <- readWhileInto(rc, p, mutList, bufReader) ) yieldmutList |> MutList.toVector }////// Like `readWhile`, but appends results into `results` and returns `Ok(())` on success.///defreadWhileInto(rc: Region[rl], p: a -> Bool, results: MutList[a, rl], bufReader: BufReader[a, t, rb]): Result[IoError, Unit] \ Readable.Aef[t] + rb + rlwithReadable[t] whereReadable.Elm[t] ~ a =if (bufReader->size !=0) {////// Finds the index of the first element in the buffer that does not match predicate `p`./// If all elements in the buffer match, returns `i + 1` where `i = bufReader->cursor + bufReader->size`/// is the last element in the logical buffer.///deffindIndex(i) = {if (i<bufReader->cursor +bufReader->size) {letx = Array.get(i, bufReader->buffer);if (p(x)) findIndex(i+1) elsei } else {i } };leti = findIndex(bufReader->cursor);if (i==bufReader->cursor) { Ok(()) } else {letvec = Array.slice(rc, start = bufReader->cursor, end = i, bufReader->buffer) |> Array.toVector;MutList.append(vec, results);letitemsRead = Vector.length(vec);bufReader->cursor = bufReader->cursor +itemsRead;bufReader->size = bufReader->size -itemsRead;readWhileInto(rc, p, results, bufReader) } } else {forM (itemsFilled <- fillBuffer(bufReader);unit <- if (itemsFilled==0) Ok(()) elsereadWhileInto(rc, p, results, bufReader) ) yieldunit }////// Like `read`, but starting at a `cursor` which indicates the next index in `dest` to be written.////// Reads until `dest` is full or until there is nothing left to read from the underlying resource.////// Returns `Ok(c)` where `c` is a cursor indicating the next index in `dest` that can be written. If `dest` is full,/// `c == length(dest)`. This is equivalent to the total number of items written since `cursor` was 0.////// Guarantees that `0 <= c <= length(dest)`.////// Assumes that `cursor <= length(dest)`.///defreadFromCursor(cursor: Int32, dest: Array[a, rb], bufReader: BufReader[a, t, rr]): Result[IoError, Int32] \ rb + rr + Readable.Aef[t] withReadable[t] whereReadable.Elm[t] ~ a =if (cursor==Array.length(dest)) { Ok(cursor) } elseif (bufReader->size !=0) {letitemsCopied = copyFromBuffer(cursor, dest, bufReader);readFromCursor(cursor+itemsCopied, dest, bufReader) } else {forM (itemsFilled <- fillBuffer(bufReader);newCursor <- if (itemsFilled==0) { Ok(cursor) } else {readFromCursor(cursor, dest, bufReader) } ) yieldnewCursor }////// Copies from the buffer into the destination, removing copied items from the buffer.////// Does not read from the underlying reader.////// Returns the number of items copied.////// Assumes that `cursor < length(dest)`.///defcopyFromBuffer(cursor: Int32, dest: Array[a, rb], bufReader: BufReader[a, t, rr]): Int32 \ rb + rrwithReadable[t] =letitemsToRead = Int32.min(Array.length(dest) -cursor, bufReader->size);defcopy(i) = {if (i>=itemsToRead) {() } else {letitem = Array.get(bufReader->cursor +i, bufReader->buffer);Array.put(item, cursor+i, dest);copy(i+1) } };copy(0);bufReader->cursor = bufReader->cursor +itemsToRead;bufReader->size = bufReader->size -itemsToRead;itemsToRead////// Reads from the underlying reader into the buffer, updating the size/// and resetting the buffer's cursor back to 0.////// Returns `Ok(k)` where `k` is the number of items read.////// Assumes that `bufReader->size == 0`.///deffillBuffer(bufReader: BufReader[a, t, rr]): Result[IoError, Int32] \ rr + Readable.Aef[t] withReadable[t] whereReadable.Elm[t] ~ a =forM (itemsRead <- Readable.read(bufReader->buffer, bufReader->reader) ) yield {bufReader->cursor = 0;bufReader->size = itemsRead;itemsRead }defdefaultBufferCapacity(): Int32 = 4096}