use super::{MAX_OUTPUT, PRINT_FUEL};
#[derive(Debug)]
pub(crate) struct Output {
buf: String,
limit: usize,
fuel: usize,
failed: bool,
last: Option<char>,
}
impl Output {
pub(crate) fn for_input(len: usize) -> Self {
let fuel = len.saturating_mul(256).saturating_add(1 << 14);
Self::with_limits(MAX_OUTPUT, fuel.min(PRINT_FUEL))
}
pub(crate) fn with_limits(limit: usize, fuel: usize) -> Self {
Self {
buf: String::new(),
limit,
fuel,
failed: false,
last: None,
}
}
pub(crate) fn push(&mut self, text: &str) {
if self.failed {
return;
}
match self.buf.len().checked_add(text.len()) {
Some(len) if len <= self.limit => {
self.buf.push_str(text);
if let Some(c) = text.chars().next_back() {
self.last = Some(c);
}
}
_ => self.failed = true,
}
}
pub(crate) fn push_char(&mut self, c: char) {
let mut tmp = [0u8; 4];
self.push(c.encode_utf8(&mut tmp));
}
pub(crate) fn push_bytes(&mut self, bytes: &[u8]) {
match std::str::from_utf8(bytes) {
Ok(text) => self.push(text),
Err(_) => {
let text = String::from_utf8_lossy(bytes);
self.push(&text);
}
}
}
pub(crate) fn push_u64(&mut self, value: u64) {
let mut digits = [0u8; 20];
let mut at = digits.len();
let mut rest = value;
loop {
at = at.saturating_sub(1);
if let Some(slot) = digits.get_mut(at) {
*slot = b'0'.saturating_add((rest % 10) as u8);
}
rest /= 10;
if rest == 0 || at == 0 {
break;
}
}
self.push_bytes(digits.get(at..).unwrap_or_default());
}
pub(crate) fn step(&mut self) -> bool {
if self.failed {
return false;
}
match self.fuel.checked_sub(1) {
Some(rest) => {
self.fuel = rest;
true
}
None => {
self.failed = true;
false
}
}
}
pub(crate) fn fail(&mut self) {
self.failed = true;
}
pub(crate) fn failed(&self) -> bool {
self.failed
}
pub(crate) fn last_char(&self) -> Option<char> {
self.last
}
pub(crate) fn len(&self) -> usize {
self.buf.len()
}
pub(crate) fn truncate(&mut self, len: usize) {
if len <= self.buf.len() && self.buf.is_char_boundary(len) {
self.buf.truncate(len);
}
}
pub(crate) fn finish(self) -> Option<String> {
if self.failed { None } else { Some(self.buf) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn caps_size_and_fuel() {
let mut out = Output::with_limits(4, 2);
out.push("abc");
out.push("de");
assert!(out.failed());
let mut out = Output::with_limits(100, 2);
assert!(out.step());
assert!(out.step());
assert!(!out.step());
assert!(out.finish().is_none());
}
#[test]
fn numbers() {
let mut out = Output::for_input(0);
out.push_u64(0);
out.push(" ");
out.push_u64(u64::MAX);
assert_eq!(out.finish().as_deref(), Some("0 18446744073709551615"));
}
}