/* * Snekdown - Custom Markdown flavour and parser * Copyright (C) 2021 Trivernis * See LICENSE for more information. */ use super::ParseResult; use crate::elements::tokens::*; use crate::elements::{ Block, CodeBlock, Import, List, ListItem, MathBlock, Metadata, Paragraph, Quote, Section, Table, }; use crate::parser::inline::ParseInline; use crate::parser::line::ParseLine; use crate::parser::ImportType; use crate::Parser; use std::collections::HashMap; pub(crate) trait ParseBlock { fn parse_block(&mut self) -> ParseResult; fn parse_section(&mut self) -> ParseResult
; fn parse_code_block(&mut self) -> ParseResult; fn parse_math_block(&mut self) -> ParseResult; fn parse_quote(&mut self) -> ParseResult; fn parse_paragraph(&mut self) -> ParseResult; fn parse_list(&mut self) -> ParseResult; fn parse_table(&mut self) -> ParseResult; fn parse_import(&mut self) -> ParseResult>; } impl ParseBlock for Parser { /// Parses a block Token fn parse_block(&mut self) -> ParseResult { if let Some(section) = self.section_return { if section <= self.section_nesting && (self.section_nesting > 0) { return Err(self.ctm.assert_error(None).into()); } else { self.section_return = None; } } let token = if let Ok(section) = self.parse_section() { log::trace!("Block::Section"); Block::Section(section) } else if let Some(_) = self.section_return { return Err(self.ctm.err().into()); } else if let Ok(list) = self.parse_list() { log::trace!("Block::List"); Block::List(list) } else if let Ok(table) = self.parse_table() { log::trace!("Block::Table"); Block::Table(table) } else if let Ok(code_block) = self.parse_code_block() { log::trace!("Block::CodeBlock"); Block::CodeBlock(code_block) } else if let Ok(math_block) = self.parse_math_block() { log::trace!("Block::MathBlock"); Block::MathBlock(math_block) } else if let Ok(quote) = self.parse_quote() { log::trace!("Block::Quote"); Block::Quote(quote) } else if let Ok(import) = self.parse_import() { if let Some(import) = import { log::trace!("Block::Import"); Block::Import(import) } else { log::trace!("Block::Null"); Block::Null } } else if let Some(_) = self.section_return { return Err(self.ctm.err().into()); } else if let Ok(pholder) = self.parse_placeholder() { log::trace!("Block::Placeholder"); Block::Placeholder(pholder) } else if let Ok(paragraph) = self.parse_paragraph() { log::trace!("Block::Paragraph"); Block::Paragraph(paragraph) } else { return Err(self.ctm.err().into()); }; Ok(token) } /// Parses a section that consists of a header and one or more blocks fn parse_section(&mut self) -> ParseResult
{ let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); if self.ctm.check_char(&HASH) { let mut size = 1; while let Some(_) = self.ctm.next_char() { if !self.ctm.check_char(&HASH) { break; } size += 1; } let mut metadata = None; if let Ok(meta) = self.parse_inline_metadata() { metadata = Some(meta); } if size <= self.section_nesting || !self.ctm.get_current().is_whitespace() { if size <= self.section_nesting { self.section_return = Some(size); } return Err(self.ctm.rewind_with_error(start_index).into()); } self.ctm.seek_any(&INLINE_WHITESPACE)?; let mut header = self.parse_header()?; header.size = size; self.section_nesting = size; self.sections.push(size); self.section_anchors.push(header.anchor.clone()); let mut section = Section::new(header); section.metadata = metadata; self.ctm.seek_whitespace(); while let Ok(block) = self.parse_block() { section.add_element(block); } self.sections.pop(); self.section_anchors.pop(); if let Some(sec) = self.sections.last() { self.section_nesting = *sec } else { self.section_nesting = 0; } Ok(section) } else { return Err(self.ctm.rewind_with_error(start_index).into()); } } /// parses a code block fn parse_code_block(&mut self) -> ParseResult { let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); self.ctm .assert_sequence(&SQ_CODE_BLOCK, Some(start_index))?; self.ctm.seek_one()?; let language = self.ctm.get_string_until_any(&[LB], &[])?; self.ctm.seek_one()?; let text = self.ctm.get_string_until_sequence(&[&SQ_CODE_BLOCK], &[])?; for _ in 0..2 { self.ctm.try_seek(); } Ok(CodeBlock { language, code: text, }) } /// parses a math block fn parse_math_block(&mut self) -> ParseResult { let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); self.ctm.assert_sequence(SQ_MATH, Some(start_index))?; self.ctm.seek_one()?; let text = self.ctm.get_string_until_sequence(&[SQ_MATH], &[])?; for _ in 0..1 { self.ctm.try_seek(); } Ok(MathBlock { expression: asciimath_rs::parse(text), }) } /// parses a quote fn parse_quote(&mut self) -> ParseResult { let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); let metadata = if let Ok(meta) = self.parse_inline_metadata() { Some(meta) } else { None }; if self.ctm.check_char(&META_CLOSE) { if self.ctm.next_char() == None { return Err(self.ctm.rewind_with_error(start_index).into()); } } let mut quote = Quote::new(metadata); while self.ctm.check_char("E_START) && self.ctm.next_char() != None && (self.ctm.check_any(&WHITESPACE)) { self.ctm.seek_any(&INLINE_WHITESPACE)?; if let Ok(text) = self.parse_text_line() { if text.subtext.len() > 0 { quote.add_text(text); } } else { break; } } quote.strip_linebreak(); if quote.text.len() == 0 { return Err(self.ctm.rewind_with_error(start_index).into()); } Ok(quote) } /// Parses a paragraph fn parse_paragraph(&mut self) -> ParseResult { let mut paragraph = Paragraph::new(); while let Ok(element) = self.parse_line() { paragraph.add_element(element); let start_index = self.ctm.get_index(); if self.ctm.check_any_sequence(&BLOCK_SPECIAL_CHARS) || self.ctm.check_any(&self.block_break_at) { self.ctm.rewind(start_index); break; } if !self.ctm.check_eof() { self.ctm.rewind(start_index); } } if paragraph.elements.len() > 0 { Ok(paragraph) } else { Err(self.ctm.err().into()) } } /// parses a list which consists of one or more list items /// The parsing is done iterative to resolve nested items fn parse_list(&mut self) -> ParseResult { let mut list = List::new(); let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); let ordered = self.ctm.get_current().is_numeric(); list.ordered = ordered; let mut list_hierarchy: Vec = Vec::new(); while let Ok(mut item) = self.parse_list_item() { while let Some(parent_item) = list_hierarchy.pop() { if parent_item.level < item.level { // the parent item is the actual parent of the next item list_hierarchy.push(parent_item); break; } else if parent_item.level == item.level { // the parent item is a sibling and has to be appended to a parent if list_hierarchy.is_empty() { list.add_item(parent_item); } else { let mut parent_parent = list_hierarchy.pop().unwrap(); parent_parent.add_child(parent_item); list_hierarchy.push(parent_parent); } break; } else { // the parent item is a child of a sibling of the current item if list_hierarchy.is_empty() { item.add_child(parent_item); } else { let mut parent_parent = list_hierarchy.pop().unwrap(); parent_parent.add_child(parent_item); list_hierarchy.push(parent_parent); } } } list_hierarchy.push(item); } // the remaining items in the hierarchy need to be combined while let Some(item) = list_hierarchy.pop() { if !list_hierarchy.is_empty() { let mut parent_item = list_hierarchy.pop().unwrap(); parent_item.add_child(item); list_hierarchy.push(parent_item); } else { list_hierarchy.push(item); break; } } list.items.append(&mut list_hierarchy); if list.items.len() > 0 { Ok(list) } else { return Err(self.ctm.rewind_with_error(start_index).into()); } } /// parses a markdown table fn parse_table(&mut self) -> ParseResult
{ let header = self.parse_row()?; if self.ctm.check_char(&LB) { self.ctm.seek_one()?; } let seek_index = self.ctm.get_index(); let mut table = Table::new(header); while let Ok(_) = self.ctm.seek_one() { self.ctm.seek_any(&INLINE_WHITESPACE)?; if !self.ctm.check_any(&[MINUS, PIPE]) || self.ctm.check_char(&LB) { break; } } if !self.ctm.check_char(&LB) { self.ctm.rewind(seek_index); return Ok(table); } self.ctm.seek_whitespace(); while let Ok(row) = self.parse_row() { table.add_row(row); } Ok(table) } /// parses an import and starts a new task to parse the document of the import fn parse_import(&mut self) -> ParseResult> { let start_index = self.ctm.get_index(); self.ctm.seek_whitespace(); self.ctm .assert_any_sequence(&[&[IMPORT_START, IMPORT_OPEN]], Some(start_index))?; let mut path = String::new(); while let Some(character) = self.ctm.next_char() { if self.ctm.check_char(&LB) || self.ctm.check_char(&IMPORT_CLOSE) { break; } path.push(character); } if self.ctm.check_char(&LB) || path.is_empty() { return Err(self.ctm.rewind_with_error(start_index).into()); } if self.ctm.check_char(&IMPORT_CLOSE) { self.ctm.seek_one()?; } // parser success if self.section_nesting > 0 { self.section_return = Some(0); return Err(self.ctm.rewind_with_error(start_index).into()); } let metadata = self .parse_inline_metadata() .ok() .map(|m| m.get_string_map()) .unwrap_or(HashMap::new()); match self.import(path.clone(), &metadata) { ImportType::Document(Ok(anchor)) => Ok(Some(Import { path, anchor })), ImportType::Stylesheet(_) => Ok(None), ImportType::Bibliography(_) => Ok(None), ImportType::Manifest(_) => Ok(None), _ => Err(self.ctm.err().into()), } } }