whitehole 0.8.0

A simple, fast, intuitive parser combinator framework for Rust.
Documentation
//! The instantaneous state of a parser (a.k.a the "configuration" in the automata theory).
//! See [`Instant`].

use crate::digest::Digest;
use std::{ops::RangeFrom, slice::SliceIndex};

/// The instantaneous state of a parser (a.k.a the "configuration" in the automata theory).
///
/// This is cheap to clone.
#[derive(Debug, Clone)]
pub struct Instant<TextRef> {
  /// See [`Self::text`].
  text: TextRef,
  /// See [`Self::rest`].
  rest: TextRef,
  /// See [`Self::digested`].
  digested: usize,
}

impl<'text, Text: ?Sized> Instant<&'text Text> {
  /// Create a new instance with the given text.
  /// [`Self::digested`] will be set to `0`.
  #[inline]
  pub const fn new(text: &'text Text) -> Self {
    Instant {
      text,
      rest: text,
      digested: 0,
    }
  }

  /// The whole input text.
  ///
  /// This is cheap to call because the value is stored in this struct.
  /// This will never be mutated after the creation of this instance.
  #[inline]
  pub const fn text(&self) -> &'text Text {
    self.text
  }

  /// The undigested text. This might be an empty string.
  ///
  /// This is cheap to call because the value is stored in this struct.
  #[inline]
  pub const fn rest(&self) -> &'text Text {
    self.rest
  }
}

impl<TextRef> Instant<TextRef> {
  /// How many bytes are already digested.
  ///
  /// This is cheap to call because the value is stored in this struct.
  #[inline]
  pub const fn digested(&self) -> usize {
    self.digested
  }
}

impl<Text: ?Sized + Digest> Instant<&Text>
where
  RangeFrom<usize>: SliceIndex<Text, Output = Text>,
{
  /// Digest the next `n` bytes.
  /// This will update [`Self::rest`] and [`Self::digested`].
  /// # Safety
  /// You should ensure that `n` is valid according to [`Digest::validate`].
  /// This will be checked using [`debug_assert!`].
  #[inline]
  pub unsafe fn digest_unchecked(&mut self, n: usize) {
    debug_assert!(self.rest.validate(n));
    self.rest = self.rest.get_unchecked(n..);
    self.digested = self.digested.unchecked_add(n);
  }

  /// Construct a new instance by digesting `n` bytes from [`Self::rest`].
  ///
  /// This is cheap to call.
  /// # Safety
  /// You should ensure that `n` is valid according to [`Digest::validate`].
  /// This will be checked using [`debug_assert!`].
  #[inline]
  pub unsafe fn to_digested_unchecked(&self, n: usize) -> Self {
    let mut instant = self.clone();
    instant.digest_unchecked(n);
    instant
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn instant_new_getters() {
    let i = Instant::new("123");
    assert_eq!(i.digested(), 0);
    assert_eq!(i.rest(), "123");
    assert_eq!(i.text(), "123");
  }

  #[test]
  fn instant_clone() {
    let i = Instant::new("123");
    let _ = i.clone();
  }

  #[test]
  fn instant_debug() {
    let _ = format!("{:?}", Instant::new("123"));
  }

  #[test]
  fn instant_bytes_digest_unchecked() {
    let mut i = Instant::new(b"123" as &[u8]);
    unsafe { i.digest_unchecked(1) };
    assert_eq!(i.digested(), 1);
    assert_eq!(i.rest(), b"23");
    assert_eq!(i.text(), b"123");
    unsafe { i.digest_unchecked(1) };
    assert_eq!(i.digested(), 2);
    assert_eq!(i.rest(), b"3");
    assert_eq!(i.text(), b"123");
    unsafe { i.digest_unchecked(1) };
    assert_eq!(i.digested(), 3);
    assert_eq!(i.rest(), b"");
    assert_eq!(i.text(), b"123");
  }

  #[test]
  #[should_panic]
  fn instant_bytes_digest_unchecked_overflow() {
    let mut i = Instant::new(b"123" as &[u8]);
    unsafe { i.digest_unchecked(4) };
  }

  #[test]
  fn instant_str_digest_unchecked() {
    let mut i = Instant::new("123");
    unsafe { i.digest_unchecked(1) };
    assert_eq!(i.digested(), 1);
    assert_eq!(i.rest(), "23");
    assert_eq!(i.text(), "123");
    unsafe { i.digest_unchecked(1) };
    assert_eq!(i.digested(), 2);
    assert_eq!(i.rest(), "3");
    assert_eq!(i.text(), "123");
    unsafe { i.digest_unchecked(1) };
    assert_eq!(i.digested(), 3);
    assert_eq!(i.rest(), "");
    assert_eq!(i.text(), "123");
  }

  #[test]
  #[should_panic]
  fn instant_str_digest_unchecked_overflow() {
    let mut i = Instant::new("123");
    unsafe { i.digest_unchecked(4) };
  }

  #[test]
  #[should_panic]
  fn instant_str_digest_unchecked_invalid_code_point() {
    let mut i = Instant::new("");
    unsafe { i.digest_unchecked(1) };
  }

  #[test]
  fn instant_to_digested_unchecked() {
    let instant = unsafe { Instant::new("123").to_digested_unchecked(1) };
    assert_eq!(instant.digested(), 1);
    assert_eq!(instant.rest(), "23");

    let instant = unsafe { Instant::new(b"123" as &[u8]).to_digested_unchecked(1) };
    assert_eq!(instant.digested(), 1);
    assert_eq!(instant.rest(), b"23");
  }

  #[test]
  #[should_panic]
  fn instant_bytes_to_digested_unchecked_overflow() {
    let _ = unsafe { Instant::new(b"123" as &[u8]).to_digested_unchecked(4) };
  }

  #[test]
  #[should_panic]
  fn instant_str_to_digested_unchecked_invalid_utf8() {
    let _ = unsafe { Instant::new("").to_digested_unchecked(1) };
  }

  #[test]
  #[should_panic]
  fn instant_str_to_digested_unchecked_overflow() {
    let _ = unsafe { Instant::new("123").to_digested_unchecked(4) };
  }
}