// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// `TextReader` is the trait for an asynchronous text input stream.
/// `TextReader` implementations are usually UTF-8 wrappers over `BinaryReader`.
///
/// `read()` fetch and returns a chunk of text from the stream.
/// The length of the returned chunk is random.
/// `read()` will return as soon as enough data has arrived.
///
/// `read_all()` returns all remaining text in the stream.
///
/// `read_until()` is similar to `BinaryReader::read_until`,
/// except that it operates over text instead of binary input.
pub(open) trait TextReader {
async fn read(Self) -> String?
async fn read_all(Self) -> String
async fn read_until(Self, StringView, include_delimiter? : Bool) -> String?
}
///|
/// A UTF-8 reader wrapper, converting a binary stream to a text stream
/// via UTF-8 decoding.
struct Utf8Reader {
mut inner : &BinaryReader?
}
///|
/// Create a new UTF-8 text reader from an underlying binary reader
pub fn[R : BinaryReader] Utf8Reader::Utf8Reader(inner : R) -> Utf8Reader {
{ inner: Some(inner), }
}
///|
#doc(hidden)
pub fn Utf8Reader::new_uninitialized() -> Utf8Reader {
{ inner: None, }
}
///|
#doc(hidden)
pub fn[R : BinaryReader] Utf8Reader::set_inner(
self : Utf8Reader,
inner : R,
) -> Unit {
self.inner = Some(inner)
}
///|
#doc(hidden)
pub fn Utf8Reader::close(self : Utf8Reader) -> Unit {
self.inner = None
}
///|
pub impl TextReader for Utf8Reader with fn read(self) {
guard! self.inner is Some(inner)
let controller = BinaryReaderImpl::get_controller(inner)
guard inner.peek() is Some(binary) else { return None }
let (data, missing_bytes) = match binary {
[.. data, 0xC0..<0xE0] => (data, 1)
[.. data, 0xE0..<0xF0] => (data, 2)
[.. data, 0xE0..<0xF0, 0x80..<0xC0] => (data, 1)
[.. data, 0xF0..<=0xF4] => (data, 3)
[.. data, 0xF0..<=0xF4, 0x80..<0xC0] => (data, 2)
[.. data, 0xF0..<=0xF4, 0x80..<0XC0, 0x80..<0xC0] => (data, 1)
data => (data, 0)
}
if data.length() > 0 {
let text = @utf8.decode(data)
inner.drop_exactly(data.length())
return Some(text)
}
guard BinaryReaderImpl::ensure_buffered(
inner,
binary.length() + missing_bytes,
) else {
// The decode operation here should fail
let data = controller.buffered()
let text = @utf8.decode(data)
inner.drop_exactly(data.length())
return Some(text)
}
// We have obtained enough data, retry again
self.read()
}
///|
pub impl TextReader for Utf8Reader with fn read_all(self) {
guard! self.inner is Some(inner)
@utf8.decode(inner.read_all())
}
///|
pub impl TextReader for Utf8Reader with fn read_until(
self,
pattern,
include_delimiter~ : Bool = false,
) {
guard! self.inner is Some(inner)
let pattern_bytes = @utf8.encode(pattern)
match inner.read_until(pattern_bytes, include_delimiter~) {
None => None
Some(result) => Some(@utf8.decode(result))
}
}
///|
pub extend Utf8Reader with TextReader::{read, read_all, read_until}