/* * 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. */////// Type of readers that decode bytes into chars, and its functions.///pubmod DecodingReader {import java.nio.charset.CharsetDecoderimport java.nio.charset.CodingErrorActionimport java.nio.ByteBufferimport java.nio.CharBuffer////// Type of readers that decode bytes into chars.////// A `DecodingReader` adapts a `Readable` of bytes to a `Readable` of chars.////// `byteReader`: the underlying byte reader, an instance of `Readable where Readable.Elm[t] ~ Int8`./// `decoder`: used to decode bytes read from `byteReader`./// `leftovers`: stores bytes that have been consumed from `byteReader` but have not yet been decoded;/// either because they are lead bytes whose continuation bytes have not been read, or/// because `DecodingReader` overestimated the number of bytes needed to fill a char buffer.///pubstructDecodingReader[t, r] { rc: Region[r], byteReader: t, decoder: CharsetDecoder,mut leftovers: Array[Int8, r] }instanceReadable[DecodingReader[t, rr]] withReadable[t] whereReadable.Elm[t] ~ Int8 {typeElm = ChartypeAef = Readable.Aef[t] + IO + rrpubdefread(buffer: Array[Char, rb], reader: DecodingReader[t, rr]): Result[IoError, Int32] \ rb + rr + Readable.Aef[t] + IO = DecodingReader.read(buffer, reader) }////// Wraps `byteReader` in a `DecodingReader`.///pubdefwrap(rc: Region[rr], charset: CharacterSet, byteReader: t): DecodingReader[t, rr] \ rr + IO =let CharacterSet.CharacterSet(cs) = charset;new DecodingReader @ rc { rc = rc, decoder = cs.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE), byteReader = byteReader, leftovers = Array.empty(rc, 0) }////// Reads bytes from the underlying byteReader and decodes the result into `dest`.////// Returns `Ok(k)` to signify that `k` chars were successfully read and written to `dest`.////// Guarantees that `0 <= k <= length(dest)`.////// Returns `Err(e)` if some underlying error occurs.///pubdefread(dest: Array[Char, rb], reader: DecodingReader[t, rr]): Result[IoError, Int32] \ IO + rr + rb + Readable.Aef[t] withReadable[t] whereReadable.Elm[t] ~ Int8 =forM ( (src, endOfInput) <- {// At most 4 bytes per char, guarantees we can fill dest if there are enough bytes to do solettotalBytesNeeded = 4*Array.length(dest);readBytes(totalBytesNeeded, reader) };charsRead <- {letcharsRead = decode(src, dest, endOfInput, reader);// charsRead == 0 would imply end of input to the caller.// block again until end of input or we have at least one char to returnif (notendOfInputandcharsRead==0) read(dest, reader) else Ok(charsRead) } ) yieldcharsRead////// Returns `Ok(bytes, endOfInput)` where `bytes` are the next bytes to be decoded. `endOfInput` means that the/// underlying reader returned EOF, and that `bytes` is exactly the leftovers.////// `bytes` contains `leftovers` from the last read prepended to new bytes read from the `byteReader` (if necessary).////// `0 <= length(bytes) <= numBytes`.///defreadBytes(numBytes: Int32, reader: DecodingReader[t, r]): Result[IoError, (Array[Int8, r], Bool)] \ r + Readable.Aef[t] withReadable[t] whereReadable.Elm[t] ~ Int8 =letbytesToRead = numBytes-Array.length(reader->leftovers);if (bytesToRead>0) {letsrc = Array.empty(reader->rc, bytesToRead);forM (bytesRead <- Readable.read(src, reader->byteReader) ) yield {letnewBytes = Array.take(reader->rc, bytesRead, src);letbytes = Array.append(reader->rc, reader->leftovers, newBytes);letendOfInput = bytesRead==0; (bytes, endOfInput) } } else { Ok((reader->leftovers, false)) }////// Decodes the bytes in `src`` as far as possible, and stores any remaining `leftovers` back into the `reader`.///defdecode(src: Array[Int8, rb1], dest: Array[Char, rb2], endOfInput: Bool, reader: DecodingReader[t, rr]): Int32 \ IO + rr + rb1 + rb2withReadable[t] whereReadable.Elm[t] ~ Int8 =letbyteBuffer = ByteBuffer.wrap(src);letcharBuffer = CharBuffer.wrap(dest);reader->decoder.decode(byteBuffer, charBuffer, endOfInput);reader->leftovers = Array.slice(reader->rc, start = byteBuffer.position(), end = byteBuffer.limit(), byteBuffer.array());checked_ecast(charBuffer.position())}