rotext_utils 0.2.0

Utilities around the official Rotext parser.
Documentation
use std::mem::MaybeUninit;

use rotext_core::{Error, Stack};

pub struct VecStack<T> {
    items: Vec<T>,
}
impl<T> Stack<T> for VecStack<T> {
    fn new() -> Self {
        Self { items: vec![] }
    }

    fn try_push(&mut self, item: T) -> Result<(), Error> {
        self.items.push(item);
        Ok(())
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn as_slice(&self) -> &[T] {
        &self.items
    }
}

pub struct ArrayStack<T, const N: usize> {
    items: [T; N],
    len: usize,
}
impl<T, const N: usize> Stack<T> for ArrayStack<T, N> {
    fn new() -> Self {
        Self {
            items: unsafe {
                #[allow(clippy::uninit_assumed_init)]
                MaybeUninit::uninit().assume_init()
            },
            len: 0,
        }
    }

    fn try_push(&mut self, item: T) -> Result<(), Error> {
        if self.len == N {
            Err(Error::OutOfStackSpace)
        } else {
            self.items[self.len] = item;
            self.len += 1;
            Ok(())
        }
    }

    fn pop(&mut self) -> Option<T> {
        if self.len == 0 {
            None
        } else {
            self.len -= 1;
            Some(std::mem::replace(&mut self.items[self.len], unsafe {
                #[allow(clippy::uninit_assumed_init)]
                MaybeUninit::uninit().assume_init()
            }))
        }
    }

    fn as_slice(&self) -> &[T] {
        &self.items[0..self.len]
    }
}

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

    use rotext_core::EventType;
    use rotext_internal_test::{
        suites::block::support::{
            assert_parse_error_with_stack, assert_parse_ok_and_output_matches_with_stack,
        },
        BlockContext,
    };

    #[test]
    fn array_stack_works() {
        let mut s = ArrayStack::<usize, 2>::new();
        assert!(s.pop().is_none());
        assert!(s.try_push(1).is_ok());
        assert!(s.try_push(2).is_ok());
        assert!(s.try_push(3).is_err());
        assert_eq!(s.pop(), Some(2));
        assert_eq!(s.pop(), Some(1));
        assert!(s.try_push(4).is_ok());
    }

    #[test]
    fn array_stack_works_with_block_parser() {
        let ctx: BlockContext<ArrayStack<_, 2>> = BlockContext::new();

        assert_parse_ok_and_output_matches_with_stack(&ctx, "", &vec![]);
        assert_parse_ok_and_output_matches_with_stack(
            &ctx,
            ">",
            &vec![
                (EventType::EnterBlockQuote, None),
                (EventType::ExitBlock, None),
            ],
        );
        assert_parse_ok_and_output_matches_with_stack(
            &ctx,
            "> >",
            &vec![
                (EventType::EnterBlockQuote, None),
                (EventType::EnterBlockQuote, None),
                (EventType::ExitBlock, None),
                (EventType::ExitBlock, None),
            ],
        );
        assert_parse_ok_and_output_matches_with_stack(
            &ctx,
            "> > foo",
            &vec![
                (EventType::EnterBlockQuote, None),
                (EventType::EnterBlockQuote, None),
                (EventType::EnterParagraph, None),
                (EventType::__Unparsed, Some("foo")),
                (EventType::ExitBlock, None),
                (EventType::ExitBlock, None),
                (EventType::ExitBlock, None),
            ],
        );
        assert_parse_error_with_stack(&ctx, "> > >", Error::OutOfStackSpace)
    }
}