unidok-parser 0.2.0

Parser for the Unidok document format
Documentation
use std::convert::TryInto;

use aho_corasick::AhoCorasick;
use detached_str::StrSlice;
use unidok_repr::ast::blocks::{CodeBlockAst, FenceType};
use unidok_repr::Span;

use crate::parsing_mode::ParsingMode;
use crate::state::ParsingState;
use crate::utils::{ParseLineBreak, ParseSpacesU8, ParseWsAndLineEnd, Until, While};
use crate::{Context, Indents, Input, Parse};

use super::ParseBlock;

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

impl Parse for ParseCodeBlock<'_> {
    type Output = CodeBlockAst;

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

        let indent = input.parse(ParseSpacesU8)?;
        let ind = self.ind.push_indent(indent);

        let mut closing_fence = None;
        let (fence_type, opening_fence) = input.parse(ParseFence)?;
        let info = input.parse(ParseInfo(fence_type))?;

        let mode = self.mode.unwrap_or_else(ParsingMode::new_nothing);
        let context = Context::CodeBlock;

        let mut lines = Vec::new();
        while !input.is_empty() {
            if input.parse(ParseLineBreak(ind)).is_none() {
                break;
            }

            let mut input2 = input.start();
            if let Some((cf_type, span)) = input2.parse(ParseFence) {
                if cf_type.can_close(fence_type) && input2.parse(ParseWsAndLineEnd).is_some() {
                    closing_fence = Some(span);
                    input2.apply();
                    break;
                }
            }
            drop(input2);

            let line = input
                .parse(ParseBlock::new(Some(mode), ParsingState::new(ind, context, self.ac)))?;
            lines.push(line);
        }

        let closing_fence = closing_fence.unwrap_or_else(|| input.prev_slice_bytes(0).into());

        input.apply();
        Some(CodeBlockAst { info, fence_type, lines, indent, opening_fence, closing_fence })
    }
}

struct ParseFence;

impl Parse for ParseFence {
    type Output = (FenceType, Span);

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

        if input.can_parse("```") {
            let slice = input.parse_i(While('`'));
            let count = slice.len();
            let count = count.try_into().ok()?;

            input.apply();
            Some((FenceType::Backticks(count), slice.into()))
        } else if input.can_parse("~~~") {
            let slice = input.parse_i(While('~'));
            let count = slice.len();
            let count = count.try_into().ok()?;

            input.apply();
            Some((FenceType::Tildes(count), slice.into()))
        } else {
            None
        }
    }
}

struct ParseInfo(FenceType);

impl Parse for ParseInfo {
    type Output = StrSlice;

    fn parse(&mut self, input: &mut Input) -> Option<Self::Output> {
        let s = input.parse_i(Until(|c| matches!(c, '\n' | '\r')));

        let c = match self.0 {
            FenceType::Backticks(_) => '`',
            FenceType::Tildes(_) => '~',
        };
        if s.to_str(&input.text).contains(c) {
            return None;
        }

        Some(s)
    }
}