use std::{fmt, mem};
use monty_types::{ResourceError, ResourceTracker};
use crate::{exception_private::RunResult, heap::Heap, types::str::allocate_string, value::Value};
pub struct StringBuilder<'t> {
inner: String,
tracker: &'t ResourceTracker,
approved_capacity: usize,
pending_error: Option<ResourceError>,
}
impl<'t> StringBuilder<'t> {
pub fn new(tracker: &'t ResourceTracker) -> Self {
Self {
inner: String::new(),
tracker,
approved_capacity: 0,
pending_error: None,
}
}
pub fn with_capacity(capacity: usize, tracker: &'t ResourceTracker) -> Result<Self, ResourceError> {
tracker.check_allocation(capacity)?;
Ok(Self {
inner: String::with_capacity(capacity),
tracker,
approved_capacity: capacity,
pending_error: None,
})
}
pub fn push(&mut self, c: char) -> Result<(), ResourceError> {
let needed = self.inner.len().saturating_add(c.len_utf8());
self.ensure(needed)?;
self.inner.push(c);
Ok(())
}
pub fn push_str(&mut self, s: &str) -> Result<(), ResourceError> {
let needed = self.inner.len().saturating_add(s.len());
self.ensure(needed)?;
self.inner.push_str(s);
Ok(())
}
pub fn finish(mut self, heap: &Heap) -> RunResult<Value> {
if let Some(e) = self.pending_error.take() {
return Err(e.into());
}
Ok(allocate_string(mem::take(&mut self.inner), heap))
}
pub fn finish_raw(mut self) -> RunResult<String> {
if let Some(e) = self.pending_error.take() {
return Err(e.into());
}
Ok(mem::take(&mut self.inner))
}
fn ensure(&mut self, needed: usize) -> Result<(), ResourceError> {
if needed > self.approved_capacity {
let new_capacity = self.approved_capacity.saturating_mul(2).max(needed);
let additional = new_capacity - self.approved_capacity;
self.tracker.check_allocation(additional)?;
self.approved_capacity = new_capacity;
}
Ok(())
}
}
impl fmt::Write for StringBuilder<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if self.pending_error.is_some() {
return Err(fmt::Error);
}
self.push_str(s).map_err(|e| {
self.pending_error = Some(e);
fmt::Error
})
}
fn write_char(&mut self, c: char) -> fmt::Result {
if self.pending_error.is_some() {
return Err(fmt::Error);
}
self.push(c).map_err(|e| {
self.pending_error = Some(e);
fmt::Error
})
}
}