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