use std::borrow::Cow;
use crate::{
exceptions::{ExcType, MontyException},
resource::ResourceError,
};
pub const DEFAULT_MAX_PRINT_COLLECT_BYTES: usize = 10 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrintStream {
Stdout,
Stderr,
}
pub enum PrintWriter<'a> {
Disabled,
Stdout,
CollectString(&'a mut String, Option<usize>),
CollectStreams(&'a mut Vec<(PrintStream, String)>, Option<usize>),
Callback(&'a mut dyn PrintWriterCallback),
}
impl PrintWriter<'_> {
pub fn collect_string(buf: &mut String) -> PrintWriter<'_> {
PrintWriter::CollectString(buf, Some(DEFAULT_MAX_PRINT_COLLECT_BYTES))
}
pub fn collect_streams(buf: &mut Vec<(PrintStream, String)>) -> PrintWriter<'_> {
PrintWriter::CollectStreams(buf, Some(DEFAULT_MAX_PRINT_COLLECT_BYTES))
}
pub fn reborrow(&mut self) -> PrintWriter<'_> {
match self {
Self::Disabled => PrintWriter::Disabled,
Self::Stdout => PrintWriter::Stdout,
Self::CollectString(buf, max) => PrintWriter::CollectString(buf, *max),
Self::CollectStreams(buf, max) => PrintWriter::CollectStreams(buf, *max),
Self::Callback(cb) => PrintWriter::Callback(&mut **cb),
}
}
pub fn stdout_write(&mut self, output: Cow<'_, str>) -> Result<(), MontyException> {
match self {
Self::Disabled => Ok(()),
Self::Stdout => {
print!("{output}");
Ok(())
}
Self::CollectString(buf, max_bytes) => {
check_print_collect_limit(buf.len(), output.len(), *max_bytes)?;
buf.push_str(&output);
Ok(())
}
Self::CollectStreams(buf, max_bytes) => append_streams_str(buf, PrintStream::Stdout, &output, *max_bytes),
Self::Callback(cb) => cb.stdout_write(output),
}
}
pub fn stdout_push(&mut self, end: char) -> Result<(), MontyException> {
match self {
Self::Disabled => Ok(()),
Self::Stdout => {
print!("{end}");
Ok(())
}
Self::CollectString(buf, max_bytes) => {
check_print_collect_limit(buf.len(), end.len_utf8(), *max_bytes)?;
buf.push(end);
Ok(())
}
Self::CollectStreams(buf, max_bytes) => append_streams_char(buf, PrintStream::Stdout, end, *max_bytes),
Self::Callback(cb) => cb.stdout_push(end),
}
}
}
pub fn check_print_collect_limit(
current_len: usize,
add: usize,
max_bytes: Option<usize>,
) -> Result<(), MontyException> {
let Some(limit) = max_bytes else {
return Ok(());
};
let used = current_len.saturating_add(add);
if used > limit {
Err(MontyException::new(
ExcType::MemoryError,
Some(ResourceError::Memory { limit, used }.to_string()),
))
} else {
Ok(())
}
}
fn streams_byte_len(buf: &[(PrintStream, String)]) -> usize {
buf.iter().map(|(_, s)| s.len()).sum()
}
fn append_streams_str(
buf: &mut Vec<(PrintStream, String)>,
stream: PrintStream,
text: &str,
max_bytes: Option<usize>,
) -> Result<(), MontyException> {
check_print_collect_limit(streams_byte_len(buf), text.len(), max_bytes)?;
match buf.last_mut() {
Some((s, existing)) if *s == stream => existing.push_str(text),
_ => buf.push((stream, text.to_owned())),
}
Ok(())
}
fn append_streams_char(
buf: &mut Vec<(PrintStream, String)>,
stream: PrintStream,
ch: char,
max_bytes: Option<usize>,
) -> Result<(), MontyException> {
check_print_collect_limit(streams_byte_len(buf), ch.len_utf8(), max_bytes)?;
match buf.last_mut() {
Some((s, existing)) if *s == stream => existing.push(ch),
_ => buf.push((stream, String::from(ch))),
}
Ok(())
}
pub trait PrintWriterCallback {
fn stdout_write(&mut self, output: Cow<'_, str>) -> Result<(), MontyException>;
fn stdout_push(&mut self, end: char) -> Result<(), MontyException>;
}