Skip to main content

markdown_that/parser/block/
mod.rs

1//! Block rule chain
2mod state;
3pub use state::*;
4
5mod rule;
6pub use rule::*;
7
8#[doc(hidden)]
9pub mod builtin;
10
11use crate::common::TypeKey;
12use crate::common::ruler::Ruler;
13use crate::parser::extset::RootExtSet;
14use crate::parser::inline::InlineRoot;
15use crate::parser::node::NodeEmpty;
16use crate::{MarkdownThat, Node};
17
18type RuleFns = (
19    fn(&mut BlockState) -> Option<()>,
20    fn(&mut BlockState) -> Option<(Node, usize)>,
21);
22
23#[derive(Debug, Default)]
24/// Block-level tokenizer.
25pub struct BlockParser {
26    ruler: Ruler<TypeKey, RuleFns>,
27}
28
29impl BlockParser {
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Generate tokens for input range
35    ///
36    pub fn tokenize(&self, state: &mut BlockState) {
37        stacker::maybe_grow(64 * 1024, 1024 * 1024, || {
38            let mut has_empty_lines = false;
39
40            while state.line < state.line_max {
41                state.line = state.skip_empty_lines(state.line);
42                if state.line >= state.line_max {
43                    break;
44                }
45
46                // Termination condition for nested calls.
47                // Nested calls are currently used for blockquotes and lists
48                if state.line_indent(state.line) < 0 {
49                    break;
50                }
51
52                // If nesting level exceeded - skip tail to the end. That's not an ordinary
53                // situation, and we should not care about content.
54                if state.level >= state.md.max_nesting {
55                    state.line = state.line_max;
56                    break;
57                }
58
59                // Try all possible rules.
60                // On success, the rule should:
61                //
62                // - update `state.line`
63                // - update `state.tokens`
64                // - return true
65                let mut ok = None;
66
67                for rule in self.ruler.iter() {
68                    ok = rule.1(state);
69                    if ok.is_some() {
70                        break;
71                    }
72                }
73
74                if let Some((mut node, len)) = ok {
75                    state.line += len;
76                    if !node.is::<NodeEmpty>() {
77                        node.srcmap = state.get_map(state.line - len, state.line - 1);
78                        state.node.children.push(node);
79                    }
80                } else {
81                    // this can only happen if user disables paragraph rule
82                    // push text as is, this behavior can change in the future;
83                    // users should always have some kind of default block rule
84                    let mut content = state.get_line(state.line).to_owned();
85                    content.push('\n');
86                    let node = Node::new(InlineRoot::new(
87                        content,
88                        vec![(0, state.line_offsets[state.line].first_nonspace)],
89                    ));
90                    state.node.children.push(node);
91                    state.line += 1;
92                }
93
94                // set state.tight if we had an empty line before the current tag
95                // i.e., the latest empty line should not count
96                state.tight = !has_empty_lines;
97
98                // paragraph might "eat" one newline after it in nested lists
99                if state.is_empty(state.line - 1) {
100                    has_empty_lines = true;
101                }
102
103                if state.line < state.line_max && state.is_empty(state.line) {
104                    has_empty_lines = true;
105                    state.line += 1;
106                }
107            }
108        });
109    }
110
111    /// Process input string and push block tokens into `out_tokens`
112    ///
113    pub fn parse(
114        &self,
115        src: &str,
116        node: Node,
117        md: &MarkdownThat,
118        root_ext: &mut RootExtSet,
119    ) -> Node {
120        let mut state = BlockState::new(src, md, root_ext, node);
121        self.tokenize(&mut state);
122        state.node
123    }
124
125    pub fn add_rule<T: BlockRule>(&mut self) -> RuleBuilder<RuleFns> {
126        let item = self.ruler.add(TypeKey::of::<T>(), (T::check, T::run));
127        RuleBuilder::new(item)
128    }
129
130    pub fn has_rule<T: BlockRule>(&mut self) -> bool {
131        self.ruler.contains(TypeKey::of::<T>())
132    }
133
134    pub fn remove_rule<T: BlockRule>(&mut self) {
135        self.ruler.remove(TypeKey::of::<T>());
136    }
137}