use assert_unchecked::assert_unchecked;
#[derive(Debug, Default, Clone)]
pub struct CodeBuffer {
buf: Vec<u8>,
}
impl CodeBuffer {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self { buf: Vec::with_capacity(capacity) }
}
#[inline]
pub fn len(&self) -> usize {
self.buf.len()
}
#[inline]
pub fn capacity(&self) -> usize {
self.buf.capacity()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.buf.reserve(additional);
}
#[inline]
#[must_use = "Peeking is pointless if the peeked char isn't used"]
pub fn peek_nth_char_back(&self, n: usize) -> Option<char> {
unsafe { std::str::from_utf8_unchecked(&self.buf) }.chars().nth_back(n)
}
#[inline]
#[must_use = "Peeking is pointless if the peeked char isn't used"]
pub fn peek_nth_byte_back(&self, n: usize) -> Option<u8> {
let len = self.len();
if n < len {
Some(self.buf[len - 1 - n])
} else {
None
}
}
#[inline]
pub fn last_byte(&self) -> Option<u8> {
self.buf.last().copied()
}
#[inline]
pub fn last_char(&self) -> Option<char> {
self.peek_nth_char_back(0)
}
#[inline]
pub fn print_ascii_byte(&mut self, byte: u8) {
assert!(byte.is_ascii(), "byte {byte} is not ASCII");
unsafe { self.print_byte_unchecked(byte) }
}
#[inline]
pub unsafe fn print_byte_unchecked(&mut self, byte: u8) {
#[cold]
#[inline(never)]
fn push_slow(code_buffer: &mut CodeBuffer, byte: u8) {
let buf = &mut code_buffer.buf;
unsafe { assert_unchecked!(buf.len() == buf.capacity()) }
buf.push(byte);
}
#[expect(clippy::if_not_else)]
if self.buf.len() != self.buf.capacity() {
self.buf.push(byte);
} else {
push_slow(self, byte);
}
}
#[inline]
pub fn print_char(&mut self, ch: char) {
let mut b = [0; 4];
self.buf.extend(ch.encode_utf8(&mut b).as_bytes());
}
#[inline]
pub fn print_str<S: AsRef<str>>(&mut self, s: S) {
self.buf.extend(s.as_ref().as_bytes());
}
pub fn print_ascii_bytes<I>(&mut self, bytes: I)
where
I: IntoIterator<Item = u8>,
{
let iter = bytes.into_iter();
let hint = iter.size_hint();
self.buf.reserve(hint.1.unwrap_or(hint.0));
for byte in iter {
self.print_ascii_byte(byte);
}
}
#[inline]
pub unsafe fn print_bytes_unchecked<I>(&mut self, bytes: I)
where
I: IntoIterator<Item = u8>,
{
self.buf.extend(bytes);
}
#[inline]
pub fn print_indent(&mut self, n: usize) {
const CHUNK_SIZE: usize = 16;
#[cold]
#[inline(never)]
fn write_slow(code_buffer: &mut CodeBuffer, n: usize) {
code_buffer.buf.extend(std::iter::repeat(b'\t').take(n));
}
let len = self.len();
let spare_capacity = self.capacity() - len;
if n > CHUNK_SIZE || spare_capacity < CHUNK_SIZE {
write_slow(self, n);
return;
}
unsafe {
let ptr = self.buf.as_mut_ptr().add(len).cast::<[u8; CHUNK_SIZE]>();
ptr.write([b'\t'; CHUNK_SIZE]);
}
unsafe {
self.buf.set_len(len + n);
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.buf
}
#[must_use]
#[inline]
pub fn into_string(self) -> String {
if cfg!(debug_assertions) {
String::from_utf8(self.buf).unwrap()
} else {
unsafe { String::from_utf8_unchecked(self.buf) }
}
}
}
impl AsRef<[u8]> for CodeBuffer {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl From<CodeBuffer> for String {
#[inline]
fn from(code: CodeBuffer) -> Self {
code.into_string()
}
}
#[cfg(test)]
mod test {
use super::CodeBuffer;
#[test]
fn empty() {
let code = CodeBuffer::default();
assert!(code.is_empty());
assert_eq!(code.len(), 0);
assert_eq!(String::from(code), "");
}
#[test]
fn string_isomorphism() {
let s = "Hello, world!";
let mut code = CodeBuffer::with_capacity(s.len());
code.print_str(s);
assert_eq!(code.len(), s.len());
assert_eq!(String::from(code), s.to_string());
}
#[test]
fn into_string() {
let s = "Hello, world!";
let mut code = CodeBuffer::with_capacity(s.len());
code.print_str(s);
let source = code.into_string();
assert_eq!(source, s);
}
#[test]
#[allow(clippy::byte_char_slices)]
fn print_ascii_byte() {
let mut code = CodeBuffer::new();
code.print_ascii_byte(b'f');
code.print_ascii_byte(b'o');
code.print_ascii_byte(b'o');
assert_eq!(code.len(), 3);
assert_eq!(code.as_bytes(), &[b'f', b'o', b'o']);
assert_eq!(String::from(code), "foo");
}
#[test]
#[allow(clippy::byte_char_slices)]
fn print_byte_unchecked() {
let mut code = CodeBuffer::new();
unsafe {
code.print_byte_unchecked(b'f');
code.print_byte_unchecked(b'o');
code.print_byte_unchecked(b'o');
}
assert_eq!(code.len(), 3);
assert_eq!(code.as_bytes(), &[b'f', b'o', b'o']);
assert_eq!(String::from(code), "foo");
}
#[test]
#[allow(clippy::byte_char_slices)]
fn print_ascii_bytes() {
let mut code = CodeBuffer::new();
code.print_ascii_bytes([b'f', b'o', b'o']);
assert_eq!(code.len(), 3);
assert_eq!(code.as_bytes(), &[b'f', b'o', b'o']);
assert_eq!(String::from(code), "foo");
}
#[test]
fn peek_nth_char_back() {
let mut code = CodeBuffer::new();
code.print_str("bar");
assert_eq!(code.peek_nth_char_back(0), Some('r'));
assert_eq!(code.peek_nth_char_back(1), Some('a'));
assert_eq!(code.peek_nth_char_back(2), Some('b'));
assert_eq!(code.peek_nth_char_back(3), None);
}
#[test]
fn peek_nth_byte_back() {
let mut code = CodeBuffer::new();
code.print_str("bar");
assert_eq!(code.peek_nth_byte_back(0), Some(b'r'));
assert_eq!(code.peek_nth_byte_back(1), Some(b'a'));
assert_eq!(code.peek_nth_byte_back(2), Some(b'b'));
assert_eq!(code.peek_nth_byte_back(3), None);
}
#[test]
fn last_byte() {
let mut code = CodeBuffer::new();
assert_eq!(code.last_byte(), None);
code.print_str("bar");
assert_eq!(code.last_byte(), Some(b'r'));
}
#[test]
fn last_char() {
let mut code = CodeBuffer::new();
assert_eq!(code.last_char(), None);
code.print_str("bar");
assert_eq!(code.last_char(), Some('r'));
}
}