rustla/parser/
mod.rs

1/*!
2This is the `parser` module of ruSTLa.
3It contains the `Parser` type and the `state_machine` submodule,
4that hosts the state transition functions of different states.
5
6Copyright © 2020 Santtu Söderholm
7*/
8// =========
9//  Imports
10// =========
11
12// Standard library
13use std::cmp;
14
15use std::collections;
16use std::str;
17
18// External crates
19// ---------------
20use regex::Regex;
21
22// Own modules
23// -----------
24
25use super::*;
26use crate::parser::types_and_aliases::IndentedBlockResult;
27
28
29pub mod automata;
30mod regex_patterns;
31
32mod converters;
33
34pub mod types_and_aliases;
35use types_and_aliases::*;
36
37pub mod line_cursor;
38use line_cursor::{Line, LineCursor};
39
40pub mod state_machine;
41use state_machine::{State, COMPILED_INLINE_TRANSITIONS};
42
43pub mod directive_parsers;
44mod table_parsers;
45
46use crate::common::{
47    EnumDelims, EnumKind, FootnoteKind, ParsingResult, SectionLineStyle,
48};
49use crate::parser::regex_patterns::Pattern;
50use crate::doctree::tree_node::TreeNode;
51use crate::doctree::tree_node_types::TreeNodeType;
52use crate::doctree::DocTree;
53
54mod tests;
55
56// ==========================
57//  The Parser specification
58// ==========================
59
60/// The parser type. Contains an optional
61/// source line vector and a document tree.
62/// These are optional to facilitate their passing
63/// to any transitions functions via
64/// `std::option::Option::take`
65/// without invalidating the fields.
66pub struct Parser {
67
68    /// The source `String` converted to a vector of owned `String`s.
69    src_lines: Vec<String>,
70
71    /// The absolute line index of src_lines.
72    line_cursor: LineCursor,
73
74    /// The level of basic indentation that the parser is working with.
75    /// This is useful information during nested parsing sessions, where
76    /// the level of indentation of the incoming block of text to be parsed
77    /// needs to be passed to the nested parser for node comparison.
78    base_indent: usize,
79
80    /// Keeps track of the section level the parser is currently focused on.
81    /// Level 0 indicates document root.
82    section_level: usize,
83
84    /// An `Option`al document tree. The optionality is necessary,
85    /// as this needs to be given to transition functions for modification
86    /// via `Option::take`.
87    doctree: Option<DocTree>,
88
89    /// A stack of states that function as keys to vectors of state transitions.
90    /// The set of transitios is chosen based on the current state on top of the stack.
91    state_stack: Vec<State>,
92}
93
94impl Parser {
95    /// The `Parser` constructor. Transforms a given source string
96    /// into a vector of lines and wraps this and a given `DocTree`
97    /// in `Option`s. This wrapping allows the passing of these to owned
98    /// state machnes via swapping the optional contents
99    /// to `None` before granting ownership of the original contents.
100    pub fn new(
101        src: Vec<String>,
102        doctree: DocTree,
103        base_indent: usize,
104        base_line: Line,
105        initial_state: State,
106        section_level: usize,
107    ) -> Self {
108        Self {
109            src_lines: src, //.lines().map(|s| s.to_string()).collect::<Vec<String>>(),
110            line_cursor: LineCursor::new(0, base_line),
111            base_indent: base_indent,
112            section_level: section_level,
113            doctree: Some(doctree),
114            state_stack: vec![initial_state],
115        }
116    }
117
118    /// Starts the parsing process for a single file.
119    /// Returns the `DocTree` generated by the `StateMachine`s.
120    pub fn parse(&mut self) -> ParsingResult {
121        // println!("=====================\n Initiating parse...\n=====================\n");
122
123        // eprintln!("... with base indent {:#?} on line {:#?}\n", self.base_indent, self.line_cursor.sum_total());
124
125        let mut line_changed: bool = false;
126        let mut line_not_changed_count: u32 = 0;
127
128        // The parsing loop
129        loop {
130            // eprintln!("Section level: {:#?}", self.section_level);
131            // eprintln!("Line {:#?} state stack: {:#?}\n", self.line_cursor.sum_total(), self.state_stack);
132            // eprintln!("Focused on {:#?}\n", self.doctree.as_ref().unwrap().shared_node_data());
133
134            if ! line_changed && line_not_changed_count >= 10 {
135                return ParsingResult::Failure {
136                    message: format!("Line not advanced even after {} iterations of the parsing loop on line {}. Clearly something is amiss...", line_not_changed_count, self.line_cursor.sum_total()),
137                    doctree: if let Some(doctree) = self.doctree.take() {
138                        doctree
139                    } else {
140                        panic!(
141                            "Doctree lost during parsing process around line {}. Computer says no...",
142                            self.line_cursor.sum_total()
143                        )
144                    }
145                };
146            }
147
148            line_changed = false;
149
150            let mut match_found = false;
151
152            // Retrieving a clone of the transitions stored in the latest state
153            // A clone is needed because the below for loop takes
154            // ownership of a reference given to it, which would prevent us from
155            // modifying the machine stack.
156            let latest_state_transitions = if let Some(machine) = self.state_stack.last() {
157                match machine {
158                    State::EOF => {
159                        match self.doctree.take() {
160                            Some(doctree) => {
161                                return ParsingResult::EOF {
162                                    doctree: doctree,
163                                    state_stack: self
164                                        .state_stack
165                                        .drain(..self.state_stack.len() - 1)
166                                        .collect(),
167                                }
168                            }
169                            None => {
170                                panic!("Tree should not be in the possession of a transition method after moving past EOF...")
171                            }
172                        };
173                    }
174
175                    State::Failure { .. } => {
176                        return ParsingResult::Failure {
177                            message: String::from("Parsing ended in Failure state...\n"),
178                            doctree: if let Some(doctree) = self.doctree.take() {
179                                doctree
180                            } else {
181                                panic!("Lost doctree inside parsing function before line {}. Computer says no...", self.line_cursor.sum_total())
182                            },
183                        }
184                    }
185
186                    _ => {
187                        if let Ok(transitions_ref) = machine.get_transitions(&self.line_cursor) {
188                            transitions_ref
189                        } else {
190                            return ParsingResult::Failure {
191                                message: String::from("No transitions for this state...\n"),
192                                doctree: if let Some(doctree) = self.doctree.take() {
193                                    doctree
194                                } else {
195                                    panic!("Lost doctree inside parsing function before line {}. Computer says no...", self.line_cursor.sum_total())
196                                },
197                            };
198                        }
199                    }
200                }
201            } else {
202                return ParsingResult::EmptyStateStack {
203                    state_stack: Vec::new(),
204                    doctree: if let Some(doctree) = self.doctree.take() {
205                        doctree
206                    } else {
207                        panic!("Doctree lost during parsing process around line {}. Computer says no...", self.line_cursor.sum_total())
208                    },
209                };
210            };
211
212            // Iterating over a clone of the transitions
213            for (pattern_name, regex, method) in latest_state_transitions.iter() {
214                // Fetching a reference to current line
215                let src_line: &str = match Parser::get_source_from_line(&self.src_lines, self.line_cursor.relative_offset()) {
216                    Some(line) => line,
217                    None => {
218                        return ParsingResult::Failure {
219                            message: String::from("Parsing ended prematurely because of an unqualified move past EOF..."),
220                            doctree: if let Some(doctree) = self.doctree.take() { doctree } else {
221                                panic!(
222                                    "Lost doctree inside parsing function before line {}. Computer says no...",
223                                    self.line_cursor.sum_total()
224                                )
225                            }
226                        }
227                    }
228                };
229
230                // Running the current line of text through a DFA compiled from a regex
231                if let Some(captures) = regex.captures(src_line) {
232                    // eprintln!("Found match for {:?}...\n", pattern_name);
233
234                    match_found = true;
235
236                    // eprintln!("Match: {:#?}", captures.get(0).unwrap().as_str());
237                    // eprintln!("Executing transition method...\n");
238
239                    let line_before_transition = self.line_cursor.sum_total();
240
241                    let doctree = if let Some(doctree) = self.doctree.take() {
242                        doctree
243                    } else {
244                        panic!(
245                            "Doctree lost inside transition function around line {}? Computer says no...",
246                            self.line_cursor.sum_total()
247                        )
248                    };
249                    self.doctree = match method(
250                        &self.src_lines,
251                        self.base_indent,
252                        &mut self.section_level,
253                        &mut self.line_cursor,
254                        doctree,
255                        &captures,
256                        pattern_name,
257                    ) {
258                        TransitionResult::Success {
259                            doctree,
260                            push_or_pop,
261                            line_advance,
262                        } => {
263                            match push_or_pop {
264                                PushOrPop::Push(mut states) => {
265                                    self.state_stack.append(&mut states);
266                                }
267                                PushOrPop::Pop => {
268                                    match self.state_stack.pop() {
269                                        Some(machine) => (),
270                                        None => {
271                                            return ParsingResult::Failure {
272                                                message: String::from(
273                                                    "Can't pop from empty stack...\n",
274                                                ),
275                                                doctree: if let Some(doctree) = self.doctree.take() {
276                                                    doctree
277                                                } else {
278                                                    panic!("Lost doctree inside parsing function before line {}. Computer says no...", self.line_cursor.sum_total())
279                                                },
280                                            }
281                                        }
282                                    };
283                                }
284
285                                PushOrPop::Neither => {} // No need to do anything to the stack...
286                            };
287
288                            if let LineAdvance::Some(offset) = line_advance {
289                                self.line_cursor.increment_by(offset);
290                            }
291
292                            // Incrementing the line_not_changed counter, if match was found but no incrementing occurred
293                            if self.line_cursor.sum_total() == line_before_transition {
294                                line_not_changed_count += 1;
295                            } else {
296                                line_changed = true;
297                                line_not_changed_count = 0;
298                            }
299
300                            Some(doctree)
301                        }
302
303                        TransitionResult::Failure { message, doctree } => {
304                            return ParsingResult::Failure {
305                                message: message,
306                                doctree: doctree,
307                            }
308                        }
309                    };
310
311                    break; // Match found so stop looking for matches
312                }
313            }
314
315            // No matches in current state so pop from state stack and attempt
316            // parsing in the previous state down stack
317            if ! match_found {
318                if let None = self.state_stack.pop() {
319                    return ParsingResult::EmptyStateStack {
320                        doctree: self.doctree.take().unwrap(),
321                        state_stack: self.state_stack.drain(..self.state_stack.len()).collect(),
322                    };
323                };
324                if let Some(doctree) = self.doctree.take() {
325                    self.doctree = Some(doctree.focus_on_parent());
326                } else {
327                    return ParsingResult::Failure {
328                        message: format!(
329                            "Doctree in possession of transition method after transition on line {}. Computer says no...",
330                            self.line_cursor.sum_total()
331                        ),
332                        doctree: if let Some(doctree) = self.doctree.take() { doctree } else {
333                            panic!(
334                                "Lost doctree inside parsing function before line {}. Computer says no...",
335                                self.line_cursor.sum_total()
336                            )
337                        }
338                    };
339                }
340            }
341
342            if self.line_cursor.relative_offset() >= self.src_lines.len() {
343                self.state_stack.push(State::EOF);
344            }
345        }
346    }
347
348    /// Attempts to move `self.current_line` to the given index.
349    /// Return an `Err` if not successful.
350    fn jump_to_line(&mut self, line: usize) -> Result<(), &'static str> {
351        if line < self.src_lines.len() {
352            *self.line_cursor.relative_offset_mut_ref() = line;
353        } else {
354            return Err("Attempted a move to a non-existent line.\nComputer says  no...\n");
355        }
356
357        Ok(())
358    }
359
360    /// Attempts to increment `self.current_line` by `n`.
361    /// Returns nothing if successful, otherwise returns `Err(&str)`.
362    /// The called must handle the `Err` case.
363    fn nth_next_line(&mut self, n: usize) -> Result<(), &'static str> {
364        *self.line_cursor.relative_offset_mut_ref() =
365            match self.line_cursor.relative_offset().checked_add(n) {
366                Some(value) => value,
367                None => {
368                    return Err("Attempted indexing with integer overflow.\nComputer says no...\n")
369                }
370            };
371
372        if self.line_cursor.relative_offset() > self.src_lines.len() {
373            return Err("No such line number.\nComputer says no...\n");
374        }
375
376        Ok(())
377    }
378
379    /// Attempts to decrement `self.current_line` by `n`.
380    /// Returns nothing if successful, otherwise returns `Err(&str)`.
381    /// The called must handle the `Err` case.
382    fn nth_previous_line(&mut self, n: usize) -> Result<(), &'static str> {
383        *self.line_cursor.relative_offset_mut_ref() =
384            match self.line_cursor.relative_offset().checked_sub(n) {
385                Some(value) => value,
386                None => {
387                    return Err("Attempted indexing with integer overflow. Computer says no...")
388                }
389            };
390
391        if self.line_cursor.relative_offset() > self.src_lines.len() {
392            return Err("No such line number. Computer says no...");
393        }
394
395        Ok(())
396    }
397
398    /// Attempts to retrieve the source from a given line number.
399    /// Returns an `Ok` clone of it if successful, else
400    /// returns and `Err` with a message.
401    fn get_source_from_line<'src_lines>(src_lines: &Vec<String>, line_num: usize) -> Option<&str> {
402        let src = match src_lines.get(line_num) {
403            Some(line) => line.as_str(),
404            None => {
405                eprintln!(
406                    "No such line number ({} out of bounds). Computer says no...",
407                    line_num
408                );
409                return None;
410            }
411        };
412
413        Some(src)
414    }
415
416    /// A function that parses inline text. Returns the nodes generated,
417    /// if there are any.
418    fn inline_parse(
419        inline_src_block: String,
420        mut doctree: Option<&mut DocTree>,
421        line_cursor: &mut LineCursor,
422    ) -> InlineParsingResult {
423        let mut nodes_data: Vec<TreeNodeType> = Vec::new();
424
425        let mut col: usize = 0;
426
427        let src_chars = &mut inline_src_block.chars();
428
429        loop {
430            match Parser::match_inline_str(&mut doctree, &src_chars) {
431                Some((mut node_data, offset)) => {
432                    nodes_data.append(&mut node_data);
433
434                    // Move iterator to start of next possible match
435                    for _ in 0..offset {
436                        if let Some(c) = src_chars.next() {
437                            col += 1;
438                            if c == '\n' {
439                                line_cursor.increment_by(1);
440                                col = 0;
441                            }
442                        } else {
443                            break;
444                        }
445                    }
446                }
447
448                // No match.
449                // This should not happen, as plain text should always be usable as a last resort.
450                // Return with no nodes if this should occur.
451                None => break,
452            }
453        }
454
455        if nodes_data.is_empty() {
456            return InlineParsingResult::NoNodes;
457        } else {
458            return InlineParsingResult::Nodes(nodes_data);
459        }
460    }
461
462    /// A function for checking the string representation of
463    /// a given `Chars` iterator for a regex match and executing
464    /// the corresponding parsing method. Returns the `Option`al
465    /// generated node if successful, otherwise returns with `None`.
466    fn match_inline_str<'chars>(
467        opt_doctree_ref: &mut Option<&mut DocTree>,
468        chars_iter: &'chars str::Chars,
469    ) -> Option<(Vec<TreeNodeType>, usize)> {
470        let src_str = chars_iter.as_str();
471        if src_str.is_empty() {
472            return None;
473        }
474        for (pattern_name, regexp, parsing_function) in COMPILED_INLINE_TRANSITIONS.iter() {
475            match regexp.captures(src_str) {
476                Some(capts) => {
477                    let (node_type, offset) =
478                        parsing_function(opt_doctree_ref, *pattern_name, &capts);
479                    return Some((node_type, offset));
480                }
481                None => continue, // no match, do nothing
482            };
483        }
484        None
485    }
486
487    /// Parses the first block of a node, in case it contains body level nodes
488    /// right after a marker such as an enumerator, on the same line.
489    fn parse_first_node_block(
490        doctree: DocTree,
491        src_lines: &Vec<String>,
492        base_indent: usize,
493        line_cursor: &mut LineCursor,
494        text_indent: usize,
495        first_indent: Option<usize>,
496        start_state: State,
497        section_level: &mut usize,
498        force_alignment: bool,
499    ) -> Result<(ParsingResult, usize), ParsingResult> {
500        let relative_first_indent = first_indent.unwrap_or(text_indent) - base_indent;
501        let relative_block_indent = text_indent - base_indent;
502
503        // Read indented block here. Notice we need to subtract base indent from assumed indent for this to work with nested parsers.
504        let (block, line_offset) = match Parser::read_indented_block(
505            src_lines,
506            line_cursor.relative_offset(),
507            true,
508            true,
509            Some(relative_block_indent),
510            Some(relative_first_indent),
511            force_alignment,
512        ) {
513            IndentedBlockResult::Ok {lines, minimum_indent, offset, blank_finish } => (lines, offset),
514            _ => {
515                return Err(ParsingResult::Failure {
516                    message: format!(
517                        "Error when reading in a block of text for nested parse in line {}.",
518                        line_cursor.sum_total()
519                    ),
520                    doctree: doctree,
521                })
522            }
523        };
524
525        // Run a nested `Parser` over the first indented block with base indent set to `text_indent`.
526        match Parser::new(
527            block,
528            doctree,
529            text_indent,
530            line_cursor.sum_total(),
531            start_state,
532            *section_level,
533        ).parse() {
534            ParsingResult::EOF {
535                doctree,
536                state_stack,
537            } => {
538                return Ok((
539                    ParsingResult::EOF {
540                        doctree: doctree,
541                        state_stack: state_stack,
542                    },
543                    line_offset,
544                ))
545            }
546            ParsingResult::EmptyStateStack {
547                doctree,
548                state_stack,
549            } => {
550                return Ok((
551                    ParsingResult::EmptyStateStack {
552                        doctree: doctree,
553                        state_stack: state_stack,
554                    },
555                    line_offset,
556                ))
557            }
558            ParsingResult::Failure { message, doctree } => {
559                return Err(ParsingResult::Failure {
560                    message: format!("Nested parse ended in failure: {}", message),
561                    doctree: doctree,
562                })
563            }
564        };
565    }
566
567    /// Skips empty lines until a non-empty one is found.
568    /// If the end of input is encountered, returns `None`, else returns `Some(())`.
569    fn skip_to_next_block(src_lines: &Vec<String>, line_cursor: &mut LineCursor) -> Option<()> {
570        loop {
571            if let Some(line) = src_lines.get(line_cursor.relative_offset()) {
572                if line.trim().is_empty() {
573                    line_cursor.increment_by(1);
574                } else {
575                    break Some(())
576                }
577            } else {
578                break None
579            }
580        }
581    }
582
583    /// Reads in an contiguous set of lines of text.
584    /// A text block in rST terms is a set of lines
585    /// separated from other elements by empty lines above and below.
586    /// Checks for indentation:
587    /// if indentation is not allowed but indentation is found,
588    /// returns an error message in an `Err`.
589    fn read_text_block(
590        src_lines: &Vec<String>,
591        start_line: usize,
592        indent_allowed: bool,
593        remove_indent: bool,
594        alignment: Option<usize>,
595        until_blank: bool
596    ) -> TextBlockResult {
597
598        let mut line_num = start_line;
599        let last_line = src_lines.len();
600        let mut lines: Vec<String> = Vec::with_capacity(last_line - start_line);
601
602        while line_num < last_line {
603            let mut line: String = match src_lines.get(line_num) {
604                Some(line) => line.clone(),
605                None => return TextBlockResult::Err {
606                    offset: {
607                        lines.shrink_to_fit();
608                        lines.len()
609                    },
610                    lines: lines,
611                }
612            };
613
614            if line.trim().is_empty() {
615                if until_blank {
616                    break
617                } else {
618                    lines.push(line.trim().to_string());
619                    line_num += 1;
620                    continue
621                }
622            }
623
624            let line_indent = line
625                .as_str()
626                .chars()
627                .take_while(|c| c.is_whitespace())
628                .count();
629
630            if !indent_allowed && line_indent > 0 {
631                break;
632            }
633
634            if let Some(alignment) = alignment {
635                if alignment != line_indent {
636                    break;
637                }
638            }
639
640            if remove_indent {
641                line = line.as_str().trim_start().to_string();
642            }
643
644            lines.push(line);
645            line_num += 1;
646        }
647
648        lines.shrink_to_fit();
649        let offset = lines.len();
650        TextBlockResult::Ok {
651            lines: lines,
652            offset: offset
653        }
654    }
655
656    /// Reads in a block of indented lines text.
657    /// Determines the minimum level of indentation
658    /// and uses it as a reference for ending the block.
659    fn read_indented_block(
660        src_lines: &Vec<String>,
661        start_line: usize,
662        until_blank: bool,
663        strip_indent: bool,
664        block_indent: Option<usize>,
665        first_indent: Option<usize>,
666        force_alignment: bool,
667    ) -> IndentedBlockResult {
668
669        if src_lines.is_empty() {
670            return IndentedBlockResult::EmptyLinesErr
671        }
672
673        let mut line_num = start_line;
674        let last_line_num = src_lines.len();
675
676        let mut block_lines: Vec<String> = Vec::with_capacity(last_line_num - start_line);
677
678        // Setting the initial level of minimal indentation
679        let mut minimal_indent = match block_indent {
680            Some(indent) => Some(indent),
681            None => None,
682        };
683
684        // If there is block indentation but no predetermined indentation for the first line,
685        // set the indentation of the first line equal to block indentation.
686        let first_indent = if let (Some(block_indent), None) = (block_indent, first_indent) {
687            Some(block_indent)
688        } else {
689            first_indent
690        };
691
692        // Push first line into `block_lines` and increment
693        // line number to ignore indentation (for now) if first_indent was set
694        if first_indent.is_some() {
695            let line = src_lines.get(line_num).unwrap().to_owned();
696            block_lines.push(line);
697            line_num += 1;
698        }
699
700        let mut blank_finish: bool = false;
701        let mut loop_broken: bool = false; // Used to detect whether the below while loop was broken out of
702
703        while line_num < last_line_num {
704            let line: String = match src_lines.get(line_num) {
705                Some(line) => line.clone(),
706                None => {
707                    loop_broken = true;
708                    break
709                }
710            };
711
712            let line_is_empty = line.trim().is_empty();
713
714            // Check for sufficient (or correct if block alignment was forced) indentation if line isn't empty
715            let line_indent = line
716                .as_str()
717                .chars()
718                .take_while(|c| c.is_whitespace())
719                .count();
720
721            let break_when_not_aligned: bool = if block_indent.is_some() && force_alignment {
722                line_indent != block_indent.unwrap()
723            } else if block_indent.is_some() {
724                line_indent < block_indent.unwrap()
725            } else {
726                false
727            };
728
729            if !line_is_empty && (line_indent < 1 || break_when_not_aligned) {
730                // Ended with a blank finish if the last line before unindent was blank
731                blank_finish = (line_num > start_line)
732                    && src_lines.get(line_num - 1).unwrap().trim().is_empty();
733                loop_broken = true;
734                break;
735            }
736
737            // Updating the minimal level of indentation, if line isn't blank
738            // and there isn't predetermined block indentation
739            if line_is_empty && until_blank {
740                blank_finish = true;
741                break;
742            } else if block_indent.is_none() {
743                if minimal_indent.is_none() {
744                    minimal_indent = Some(line_indent);
745                } else if line_indent > 0 {
746                    minimal_indent = Some(cmp::min(minimal_indent.unwrap(), line_indent));
747                }
748            }
749
750            block_lines.push(line);
751            line_num += 1;
752        }
753
754        if !loop_broken {
755            blank_finish = true;
756        } // Made it to the end of input
757
758        // Strip all minimal indentation from each line
759        if let Some(min_indent) = minimal_indent {
760            if strip_indent {
761                for (index, line) in block_lines.iter_mut().enumerate() {
762                    let indent = if first_indent.is_some() && index == 0 {
763                        first_indent.unwrap()
764                    } else {
765                        min_indent
766                    };
767                    *line = line.chars().skip(indent).collect::<String>();
768                }
769            }
770        }
771
772        block_lines.shrink_to_fit(); // Free unnecessary used memory
773        let line_diff = block_lines.len();
774
775        IndentedBlockResult::Ok {
776            lines: block_lines,
777            minimum_indent: minimal_indent.unwrap(),
778            offset: line_diff,
779            blank_finish: blank_finish
780        }
781    }
782
783    /// Checks how a given indentation matches with the indentation of a given parent node type.
784    fn parent_indent_matches(
785        parent: &TreeNodeType,
786        relevant_child_indent: usize,
787    ) -> IndentationMatch {
788        if let Some(indent) = parent.body_indent() {
789            if indent > relevant_child_indent {
790                IndentationMatch::TooLittle
791            } else if indent == relevant_child_indent {
792                IndentationMatch::JustRight
793            } else {
794                IndentationMatch::TooMuch
795            }
796        } else {
797            panic!("Asked for parent indentation inside a \"{}\" that is not a container. Computer says no...", parent)
798        }
799    }
800
801    /// Scans the source lines until it finds a non-empty line and returns the `Option`al indent of it.
802    fn indent_on_subsequent_lines(
803        src_lines: &Vec<String>,
804        mut current_line: usize,
805    ) -> Option<(usize, usize)> {
806        loop {
807            if let Some(line) = src_lines.get(current_line + 1) {
808                if line.trim().is_empty() {
809                    current_line += 1;
810                    continue;
811                } else {
812                    break Some((
813                        line.chars().take_while(|c| c.is_whitespace()).count(),
814                        current_line - current_line,
815                    ));
816                }
817            } else {
818                break None;
819            }
820        }
821    }
822
823    /// Takes characters from a given string, until a given index, or until a newline is encountered.
824    fn line_prefix(line: &str, end_index: usize) -> String {
825        let prefix = line
826            .chars()
827            .enumerate()
828            .take_while(|(i, c)| *c == '\n' || *i < end_index)
829            .map(|(i, c)| c)
830            .collect::<String>();
831
832        if prefix.chars().count() < end_index {
833            eprintln!("Encountered a newline or line shorter than given...")
834        }
835
836        prefix
837    }
838
839    /// Skips the first `start_index` characters of the given `line`
840    /// and returns the remainder as a string. If a new
841    fn line_suffix(line: &str, start_index: usize) -> String {
842        let suffix_len = match line.chars().count().checked_sub(start_index) {
843            Some(len) => len,
844            None => {
845                panic!("Cannot scan line suffix whose start index is greater than the line length")
846            }
847        };
848
849        let suffix = line
850            .chars()
851            .enumerate()
852            .skip_while(|(i, c)| *c == '\n' || *i < start_index)
853            .map(|(i, c)| c)
854            .collect::<String>();
855
856        if suffix.chars().count() > suffix_len {
857            eprintln!("Encountered a newline before reaching suffix. Returning an empty string...");
858            String::new()
859        } else {
860            suffix
861        }
862    }
863
864    /// Increments the given line cursor while empty lines are found.
865    /// Returns the number of lines skipped.
866    fn skip_empty_lines(src_lines: &Vec<String>, line_cursor: &mut LineCursor) -> usize {
867        let mut lines_skipped = 0 as usize;
868
869        while let Some(line) = src_lines.get(line_cursor.relative_offset()) {
870            if line.trim().is_empty() {
871                line_cursor.increment_by(1); // Jump over empty lines
872                lines_skipped += 1;
873            } else {
874                break;
875            }
876        }
877
878        lines_skipped
879    }
880
881    /// Returns an indentation level based on the indentation of the next line.
882    /// If the next line is empty or `None` (at the end of input),
883    /// returns the given indent after a markup (footnote, citation, field list)
884    /// marker. If the line is *not* empty, scans it for indentation and if it is greater than
885    /// the given marker indent, returns it as is. Otherwise returns the indentation after the marker.
886    ///
887    /// This is mainly useful with markup elements like footnotes, citations and field list items,
888    /// that decide their body indentation based on the line directly after their respecive markup marker.
889    fn indent_from_next_line (
890        src_lines: &Vec<String>,
891        base_indent: usize,
892        marker_indent: usize,
893        indent_after_marker: usize,
894        line_cursor: &LineCursor
895    ) -> usize {
896        match src_lines.get(line_cursor.relative_offset() + 1) {
897            Some(line) => if line.trim().is_empty() {
898                indent_after_marker
899            } else {
900                let indent = line
901                    .chars()
902                    .take_while(|c| c.is_whitespace())
903                    .count() + base_indent;
904                if indent < marker_indent + 1 {
905                    indent_after_marker
906                } else {
907                    indent
908                }
909            }
910            None => indent_after_marker
911        }
912    }
913}