// 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.
///|
/// A reader that buffers the input stream and provides line reading capabilities.
struct BufferedReader {
stream : &Reader
mut buffer : StringView
mut error : Error?
}
///|
/// Create a buffered reader from a reader
pub fn buffered_reader(stream : &Reader) -> BufferedReader {
{ stream, buffer: "", error: None }
}
///|
pub impl Closable for BufferedReader with close(self) {
self.stream.close()
self.buffer = ""
}
///|
pub impl Reader for BufferedReader with read(self, builder, max_len?) {
@promise.spawn(fn() {
// if there were error not thrown, throw it
if self.error is Some(error) {
self.error = None
raise error
}
if max_len is Some(len) && self.buffer.length() > len {
builder.write_substring(
self.buffer.data(),
self.buffer.start_offset(),
self.buffer.length(),
)
self.buffer = self.buffer.view(start_offset=len)
return Some(())
} else if self.buffer.length() > 0 {
builder.write_substring(
self.buffer.data(),
self.buffer.start_offset(),
self.buffer.length(),
)
self.buffer = "".view()
return Some(())
} else {
self.stream.read(builder, max_len?).await()
}
})
}
///|
pub fn BufferedReader::next_line(self : BufferedReader) -> @promise.T[String?] {
@promise.spawn(async fn() {
let builder = StringBuilder::new()
self.read_line(builder).await().map(_ => builder.to_string())
})
}
///|
pub fn BufferedReader::read_line(
self : BufferedReader,
builder : StringBuilder,
) -> @promise.T[Unit?] {
@promise.spawn(async fn() {
if self.error is Some(e) {
self.error = None
raise e
}
if split_line(self.buffer) is Some((line, rest)) {
builder.write_substring(line.data(), line.start_offset(), line.length())
self.buffer = rest
return Some(())
}
let mut length = self.buffer.length()
builder.write_substring(
self.buffer.data(),
self.buffer.start_offset(),
self.buffer.length(),
)
let buffer = StringBuilder::new()
while self.stream.read(buffer, max_len=1024).await() is Some(_) {
let str = buffer.to_string()
if split_line(str.view()) is Some((line, rest)) {
builder.write_substring(line.data(), line.start_offset(), line.length())
self.buffer = rest
length += line.length()
break
} else {
length += str.length()
builder.write_string(str)
buffer.reset()
}
}
if length > 0 {
Some(())
} else {
None
}
})
}
///|
fn split_line(str : StringView) -> (StringView, StringView)? {
let input = str
loop str {
['\n', .. rest] => {
let before = input.view(
end_offset=rest.start_offset() - input.start_offset() - 1,
)
if before is [.. before, '\r'] {
return Some((before, rest))
} else {
return Some((before, rest))
}
}
[_, .. rest] => continue rest
[] => return None
}
}
///|
test "split line" {
let str = "Hello, world!\nThis is a test.\r\nAnother line."
let (line, rest) = split_line(str[:]).unwrap()
inspect(line, content="Hello, world!")
inspect(rest, content="This is a test.\r\nAnother line.")
let (line, rest) = split_line(str[14:]).unwrap()
inspect(line, content="This is a test.")
inspect(rest, content="Another line.")
let (line, rest) = split_line(str[30:]).unwrap()
inspect(line, content="")
inspect(rest, content="Another line.")
inspect(split_line(str[31:]), content="None")
}
///|
test "next line" {
@promise.spawn(async fn() noraise {
try {
let str = "Hello, world!\nThis is a test.\r\nAnother line."
let reader = buffered_reader(string_reader(str))
inspect(reader.next_line().await().unwrap(), content="Hello, world!")
inspect(reader.next_line().await().unwrap(), content="This is a test.")
inspect(reader.next_line().await().unwrap(), content="Another line.")
} catch {
e => {
println(e)
panic()
}
}
})
|> ignore
}