/* * 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 writers that encode chars into bytes, and its functions.///pubmod EncodingWriter {import java.nio.charset.CharsetEncoderimport java.nio.charset.CodingErrorActionimport java.nio.ByteBufferimport java.nio.CharBuffer////// Type of writers that encode chars into bytes.////// An `EncodingWriter` adapts a `Writable` of bytes to a `Writable` of chars.////// `byteWriter`: the underlying byte writer, an instance of `Writable` where `Writable.Elm[t] ~ Int8`./// `encoder`: used to encode bytes before writing them to the `byteReader`.///pubenumEncodingWriter[t]({encoder = CharsetEncoder, byteWriter = t})instanceWritable[EncodingWriter[t]] withWritable[t] whereWritable.Elm[t] ~ Int8 {typeElm = ChartypeAef = Writable.Aef[t]pubdefwrite(buffer: Array[Char, r], writer: EncodingWriter[t]): Result[IoError, Int32] \ r + Writable.Aef[t] = EncodingWriter.write(buffer, writer) }////// Wraps `byteWriter` in an `EncodingWriter`.///pubdefwrap(charset: CharacterSet, byteWriter: t): EncodingWriter[t] \ IO =let CharacterSet.CharacterSet(cs) = charset; EncodingWriter({ encoder = cs.newEncoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE), byteWriter = byteWriter })////// Reads `k` items from `src` and writes them to `writer`.////// Returns `Ok(k)` to signify that `k` items were successfully read and written to `r`.////// Guarantees that `0 <= k <= length(b)`.////// Returns `Err(e)` if some underlying I/O error occurs.///pubdefwrite(src: Array[Char, r], writer: EncodingWriter[t]): Result[IoError, Int32] \ r + Writable.Aef[t] withWritable[t] whereWritable.Elm[t] ~ Int8 =let EncodingWriter.EncodingWriter({encoder, byteWriter}) = writer;regionrc {letdest = encoder |> encode(rc, src);forM (_ <- byteWriter |> writeBytes(rc, dest) ) yield {src |> Array.length } }////// Encodes the chars in `src` using `encoder`.////// Returns an array of the encoded bytes whose size is the number of bytes encoded.///defencode(rc: Region[rc], src: Array[Char, r], encoder: CharsetEncoder): Array[Int8, rc] \ rc + r =// Encoding is effectively a pure operation, so IO is casted awaydefdo() = {letnumChars = Array.length(src);letmaxBytesPerChar = encoder.maxBytesPerChar() |> Float32.ceil |> Float32.clampToInt32(min = 1, max = Int32.maxValue(), nanValue = 4);letdest: Array[Int8, rc] = Array.empty(rc, numChars*maxBytesPerChar);letcharBuffer = CharBuffer.wrap(src);letbyteBuffer = ByteBuffer.wrap(dest);checked_ecast(encoder |> encodeBuffered(rc, charBuffer, byteBuffer)) };checked_ecast(unsafeIO { do() })////// Encodes the chars from `charBuffer` into `byteBuffer` using `encoder`.////// Returns an array of the encoded bytes whose size is the number of bytes encoded.///defencodeBuffered(rc: Region[rc], charBuffer: CharBuffer, byteBuffer: ByteBuffer, encoder: CharsetEncoder): Array[Int8, rc] \ rc =// Encoding is effectively a pure operation, so IO is casted awaydefdo() = {letresult = encoder.encode(charBuffer, byteBuffer, false);letbytesEncoded = byteBuffer.position();letdest = byteBuffer.array() |> Array.take(rc, bytesEncoded);if (result.isOverflow()) {charBuffer.flip();byteBuffer.clear();letrest = encoder |> encodeBuffered(rc, charBuffer, byteBuffer);Array.append(rc, dest, rest) } else {dest } };checked_ecast(unsafeIO { do() })////// Writes all bytes from `dest` into `byteWriter`.///defwriteBytes(rc: Region[r], dest: Array[Int8, r], byteWriter: t): Result[IoError, Unit] \ r + Writable.Aef[t] withWritable[t] whereWritable.Elm[t] ~ Int8 =defloop(arr) = {if (Array.isEmpty(arr)) { Ok(()) } else {forM (bytesWritten <- Writable.write(arr, byteWriter);_ <- loop(arr |> Array.drop(rc, bytesWritten)) ) yield() } };loop(dest)}