unidok-parser 0.2.0

Parser for the Unidok document format
Documentation
use unidok_repr::ast::blocks::{BlockAst, HeadingAst, HeadingKind, ParagraphAst};

use crate::blocks::*;
use crate::inlines::Segments;
use crate::macros::ParseBlockMacro;
use crate::parsing_mode::ParsingMode;
use crate::state::ParsingState;
use crate::utils::ParseLineBreak;
use crate::{Context, Input, Parse};

#[derive(Debug, Clone, Copy)]
pub(crate) struct ParseBlock<'a> {
    mode: Option<ParsingMode>,
    state: ParsingState<'a>,
}

impl<'a> ParseBlock<'a> {
    pub(crate) fn new(mode: Option<ParsingMode>, state: ParsingState<'a>) -> Self {
        ParseBlock { mode, state }
    }

    pub(crate) fn new_multi(mode: Option<ParsingMode>, state: ParsingState<'a>) -> ParseBlocks<'a> {
        ParseBlocks { mode, state }
    }
}

impl ParseBlock<'_> {
    fn consume_empty_lines(&mut self, input: &mut Input) {
        let context = self.state.context();
        if let Context::BlockBraces | Context::Heading | Context::BlockHtml(_) | Context::Global =
            context
        {
            let ind = self.state.ind();
            while input.parse(ParseLineBreak(ind)).is_some() && !input.is_empty() {}
        }
    }
}

impl Parse for ParseBlock<'_> {
    type Output = BlockAst;

    fn parse(&mut self, input: &mut Input) -> Option<Self::Output> {
        let ind = self.state.ind();
        let mode = self.mode.unwrap_or_else(ParsingMode::new_all);
        let ac = self.state.special_chars();
        let context = self.state.context();

        if mode.is(ParsingMode::COMMENTS) {
            if let Some(comment) = input.parse(ParseComment) {
                self.consume_empty_lines(input);
                return Some(BlockAst::Comment(comment));
            }
        }

        if mode.is(ParsingMode::THEMATIC_BREAKS) {
            if let Some(tb) = input.parse(ParseThematicBreak { ind }) {
                self.consume_empty_lines(input);
                return Some(BlockAst::ThematicBreak(tb));
            }
        }

        if mode.is(ParsingMode::CODE_BLOCKS) {
            if let Some(block) = input.parse(ParseCodeBlock { ind, mode: self.mode, ac }) {
                self.consume_empty_lines(input);
                return Some(BlockAst::CodeBlock(block));
            }
        }

        if mode.is(ParsingMode::TABLES) {
            if let Some(table) = input.parse(ParseTable { ind, ac }) {
                self.consume_empty_lines(input);
                return Some(BlockAst::Table(table));
            }
        }

        if mode.is(ParsingMode::HEADINGS) {
            if let Some(heading) = input.parse(ParseHeading { ind, ac }) {
                self.consume_empty_lines(input);
                return Some(BlockAst::Heading(heading));
            }
        }

        if mode.is(ParsingMode::LISTS) {
            if let Some(list) = input.parse(ParseList { ind, mode: self.mode, ac }) {
                self.consume_empty_lines(input);
                return Some(BlockAst::List(list));
            }
        }

        if mode.is(ParsingMode::QUOTES) {
            if let Some(quote) = input.parse(ParseQuote { ind, mode: self.mode, ac }) {
                self.consume_empty_lines(input);
                return Some(BlockAst::Quote(quote));
            }
        }

        if mode.is(ParsingMode::LINKS_IMAGES) {
            if let Some(lrd) = input.parse(ParseLinkRefDef { ind }) {
                self.consume_empty_lines(input);
                return Some(BlockAst::LinkRefDef(lrd));
            }
        }

        if mode.is(ParsingMode::MACROS) {
            let parser = ParseBlockMacro::new(self.mode, self.state);
            if let Some(mac) = input.parse(parser) {
                self.consume_empty_lines(input);
                return Some(BlockAst::BlockMacro(mac));
            }
        }

        let segments = input.parse(Segments::parser(ind, context, mode, ac))?;
        self.consume_empty_lines(input);

        match segments {
            Segments::Empty if context == Context::CodeBlock && !input.is_empty() => {
                Some(BlockAst::Paragraph(ParagraphAst { segments: vec![] }))
            }
            Segments::Empty => None,
            Segments::Some { segments, underline: None } => {
                Some(BlockAst::Paragraph(ParagraphAst { segments }))
            }
            Segments::Some { segments, underline: Some(u) } if mode.is(ParsingMode::HEADINGS) => {
                Some(BlockAst::Heading(HeadingAst {
                    level: u.level(),
                    kind: HeadingKind::Setext,
                    segments,
                }))
            }
            _ => panic!("Parsed an underlined heading where no headings are allowed"),
        }
    }

    fn can_parse(&mut self, _: &mut Input) -> bool {
        true
    }
}

#[derive(Debug)]
pub(crate) struct ParseBlocks<'a> {
    mode: Option<ParsingMode>,
    state: ParsingState<'a>,
}

impl Parse for ParseBlocks<'_> {
    type Output = Vec<BlockAst>;

    fn parse(&mut self, input: &mut Input) -> Option<Self::Output> {
        loop {
            if input.parse(ParseLineBreak(self.state.ind())).is_none() {
                break;
            }
            if input.is_empty() {
                return Some(vec![]);
            }
        }

        let parser = ParseBlock { mode: self.mode, state: self.state };

        let mut v = Vec::new();
        while let Some(node) = input.parse(parser) {
            v.push(node);
        }
        Some(v)
    }
}