// Copyright 2025 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.
///|
pub(open) trait Reader {
// API for internal implementation only, do not use
fn _get_internal_buffer(Self) -> ReaderBuffer
// API for internal implementation only, do not use
async fn _direct_read(Self, FixedArray[Byte], offset~ : Int, max_len~ : Int) -> Int
async fn read(Self, FixedArray[Byte], offset? : Int, max_len? : Int) -> Int = _
async fn drop(Self, Int) -> Int = _
async fn read_exactly(Self, len : Int) -> Bytes = _
async fn read_some(Self, max_len? : Int) -> Bytes? = _
async fn read_all(Self) -> &Data = _
async fn read_until(Self, StringView) -> String? = _
}
///|
pub(all) suberror ReaderClosed derive(Debug, ToJson)
///|
/// `reader.read(dst, offset~, max_len~)` read at most `max_len` bytes from the reader,
/// storing the result in `dst[offset:]`.
/// The number of bytes actually read is returned.
/// Note that the return value may be smaller than `max_len`
/// if not enough data is *immediately* available.
/// If EOF is reached and no more data is available, zero will be returned.
impl Reader with fn read(
self,
dst,
offset? = 0,
max_len? = dst.length() - offset,
) {
let buf = self._get_internal_buffer()
if buf.len > 0 {
let n = @cmp.minimum(max_len, buf.len)
buf.buf.blit_to(dst, src_offset=buf.start, dst_offset=offset, len=n)
buf.0.drop(n)
n
} else {
self._direct_read(dst, offset~, max_len~)
}
}
///|
/// `reader.drop(len)` drops `len` number of bytes from the reader.
/// The number of bytes actually dropped is returned.
/// `drop` will wait until enough number of bytes is available,
/// so the return value would be smaller than `len` only when EOF is reached.
impl Reader with fn drop(self, len) {
let buf = self._get_internal_buffer()
if buf.len >= len {
buf.0.drop(len)
len
} else {
let buffered = buf.len
buf.0.drop(buffered)
buf.0.enlarge_to(1)
for dropped = buffered; dropped < len; {
buf.len = self._direct_read(buf.buf, offset=0, max_len=buf.buf.length())
if buf.len == 0 {
return dropped
}
continue dropped + buf.len
} nobreak {
buf.start = buf.len - (dropped - len)
buf.len = dropped - len
len
}
}
}
///|
/// `reader.read_exactly(len)` read exactly `len` number of bytes from the reader.
/// It will wait until enough number of bytes is available.
/// If EOF is reached before enough data is read, `ReaderClosed` is raised.
impl Reader with fn read_exactly(self, len) {
let buf = FixedArray::make(len, b'0')
for received = 0; received < len; {
let new_received = self.read(buf, offset=received, max_len=len - received)
if new_received == 0 {
raise ReaderClosed
}
continue received + new_received
}
buf.unsafe_reinterpret_as_bytes()
}
///|
fn ReaderBuffer::take_owned(
self : ReaderBuffer,
start : Int,
len : Int,
) -> Bytes {
if start == 0 && len == self.buf.length() {
let buf = self.buf
self.buf = FixedArray::make(len, b'\x00')
buf.unsafe_reinterpret_as_bytes()
} else {
self.buf.unsafe_reinterpret_as_bytes()[start:start + len].to_owned()
}
}
///|
/// `reader.read_some(max_len?)` fetch and return a chunk of data from the reader.
/// It will return as fast as possible when new data become available.
/// If EOF is reached, `None` is returned.
///
/// If `max_len` is specified, the returned chunk will not be larger than `max_len`.
/// Since `read_some` will return immediately when new data become available,
/// the returned chunk may be smaller than `max_len`,
/// even if there are still data available in the reader.
impl Reader with fn read_some(self, max_len?) {
let buf = self._get_internal_buffer()
if buf.len > 0 {
if max_len is Some(max_len) && max_len < buf.len {
let data = buf.take_owned(buf.start, max_len)
buf.start += max_len
buf.len -= max_len
return Some(data)
} else {
let data = buf.take_owned(buf.start, buf.len)
buf.start = 0
buf.len = 0
return Some(data)
}
}
buf.0.enlarge_to(if max_len is Some(max_len) { max_len } else { 1 })
let n = self._direct_read(buf.buf, offset=0, max_len=buf.buf.length())
if n == 0 {
return None
}
if max_len is Some(max_len) && max_len < n {
buf.start = max_len
buf.len = n - max_len
Some(buf.take_owned(0, max_len))
} else {
buf.start = 0
buf.len = 0
Some(buf.take_owned(0, n))
}
}
///|
#internal(internal, "for internal use only")
#doc(hidden)
pub async fn[R : Reader] read_all(
reader : R,
prefix_list? : FixedArray[BytesView] = [],
chunk_size? : Int = 4096,
) -> Bytes {
let buffer_list = []
let mut buffer = FixedArray::make(chunk_size, b'\x00')
let mut offset = 0
while reader.read(buffer, offset~, max_len=buffer.length() - offset) is n &&
n > 0 {
offset += n
if offset == buffer.length() {
buffer_list.push(buffer)
buffer = FixedArray::make(chunk_size, b'0')
offset = 0
}
}
let prefix_len = for prefix in prefix_list; len = 0 {
continue len + prefix.length()
} nobreak {
len
}
if buffer_list.length() >= (@int.MAX_VALUE - offset - prefix_len) / chunk_size {
raise Failure::Failure("@io.read_all(): data length exceed range of `Int`")
}
let total_size = prefix_len + buffer_list.length() * chunk_size + offset
let result = FixedArray::make(total_size, b'0')
for prefix in prefix_list; len = 0; len = len + prefix.length() {
result.blit_from_bytesview(0, prefix)
}
for i, buf in buffer_list {
result.unsafe_blit(prefix_len + i * chunk_size, buf, 0, chunk_size)
}
result.unsafe_blit(
prefix_len + buffer_list.length() * chunk_size,
buffer,
0,
offset,
)
result.unsafe_reinterpret_as_bytes()
}
///|
/// Read all remaining content from the reader.
/// The return value is a `&io.Data` object, which can be converted to different formats
/// using `.binary()`, `.text()` or `.json()`.
impl Reader with fn read_all(self) {
read_all(self)
}
///|
/// `reader.read_until(sep)` read UTF-8 encoded text from the input stream
/// until `sep` or EOF is reached.
/// The segment of input from start of reader to start of `sep` or EOF is returned.
/// `sep` will be conusmed from the input, but is not included in the return value.
impl Reader with fn read_until(self, sep) {
let sep = @utf8.encode(sep)
let buf = self._get_internal_buffer()
match buf.find_opt(sep, reader=self) {
None if buf.len > 0 => {
let buf_bytes = buf.buf.unsafe_reinterpret_as_bytes()
let remaining = @utf8.decode(buf_bytes[buf.start:buf.start + buf.len])
buf.0.drop(buf.len)
Some(remaining)
}
None => None
Some(index) => {
let buf_bytes = buf.buf.unsafe_reinterpret_as_bytes()
let result = @utf8.decode(buf_bytes[buf.start:buf.start + index])
buf.0.drop(index + sep.length())
Some(result)
}
}
}