unidok-parser 0.2.0

Parser for the Unidok document format
Documentation
use aho_corasick::AhoCorasick;
use unidok_repr::ast::blocks::{Bullet, ListAst};

use crate::parsing_mode::ParsingMode;
use crate::state::ParsingState;
use crate::utils::{ParseLineBreak, ParseLineEnd, ParseNSpaces, ParseSpacesU8, While};
use crate::{Context, Indents, Parse};

use super::ParseBlock;

pub(crate) struct ParseList<'a> {
    pub ind: Indents<'a>,
    pub mode: Option<ParsingMode>,
    pub ac: &'a AhoCorasick,
}

impl Parse for ParseList<'_> {
    type Output = ListAst;

    fn parse(&mut self, input: &mut crate::Input) -> Option<Self::Output> {
        let mut input = input.start();

        let (mut indent_spaces, bullet) = input.parse(ParseBullet { first: true })?;

        let mut items = Vec::new();
        loop {
            let ind = self.ind.push_indent(indent_spaces);

            let content_parser =
                ParseBlock::new_multi(self.mode, ParsingState::new(ind, Context::Global, self.ac));
            items.push(input.parse(content_parser)?);

            if input.parse(ParseLineBreak(self.ind)).is_none() {
                break;
            }

            let mut input2 = input.start();
            if let Some((is, b)) = input2.parse(ParseBullet { first: false }) {
                if b.kind() == bullet.kind() {
                    indent_spaces = is;
                    input2.apply();
                    continue;
                }
            }
            break;
        }

        input.apply();
        Some(ListAst { indent_spaces, bullet, items })
    }
}

struct ParseBullet {
    #[allow(unused)]
    first: bool,
}

impl Parse for ParseBullet {
    type Output = (u8, Bullet);

    fn parse(&mut self, input: &mut crate::Input) -> Option<Self::Output> {
        let mut input = input.start();

        let indent = input.parse(ParseSpacesU8)?;
        if indent > (u8::MAX - 16) {
            return None;
        }

        let result = match input.peek_char() {
            Some('-') => {
                input.bump(1);
                (indent + 2, Bullet::Dash)
            }
            Some('+') => {
                input.bump(1);
                (indent + 2, Bullet::Plus)
            }
            Some('*') => {
                input.bump(1);
                (indent + 2, Bullet::Star)
            }
            Some('0'..='9') => {
                let num = input.parse_i(While(|c: char| c.is_ascii_digit()));
                if num.len() > 9 {
                    return None;
                }
                let start = num.to_str(&input.text).parse::<u32>().unwrap();

                let bullet = if input.parse('.').is_some() {
                    Bullet::Dot { start }
                } else if input.parse(')').is_some() {
                    Bullet::Paren { start }
                } else {
                    return None;
                };

                (indent + num.len() as u8 + 2, bullet)
            }
            _ => return None,
        };

        if input.parse(ParseNSpaces(1)).is_none() && !input.can_parse(ParseLineEnd) {
            return None;
        }

        input.apply();
        Some(result)
    }
}