
Paragraph's exit matcher which detects elements was causing the plain list parser to exit after the first item was parsed which was causing significant amounts of re-parsing.
236 lines
8.1 KiB
Rust
236 lines
8.1 KiB
Rust
use std::rc::Rc;
|
|
|
|
use nom::combinator::eof;
|
|
use nom::IResult;
|
|
|
|
use super::list::List;
|
|
use super::list::Node;
|
|
use super::org_source::OrgSource;
|
|
use super::Context;
|
|
use super::Object;
|
|
use crate::error::CustomError;
|
|
use crate::error::MyError;
|
|
use crate::error::Res;
|
|
use crate::parser::exiting::ExitClass;
|
|
|
|
type Matcher =
|
|
dyn for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ContextTree<'r, 's> {
|
|
tree: List<ContextElement<'r, 's>>,
|
|
}
|
|
|
|
impl<'r, 's> ContextTree<'r, 's> {
|
|
pub fn new() -> Self {
|
|
ContextTree { tree: List::new() }
|
|
}
|
|
|
|
pub fn branch_from(trunk: &Rc<Node<ContextElement<'r, 's>>>) -> Self {
|
|
ContextTree {
|
|
tree: List::branch_from(trunk),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn ptr_eq<'x, 'y>(&self, other: &ContextTree<'x, 'y>) -> bool {
|
|
self.tree.ptr_eq(&other.tree)
|
|
}
|
|
|
|
pub fn with_additional_node(&self, data: ContextElement<'r, 's>) -> ContextTree<'r, 's> {
|
|
let new_list = self.tree.push_front(data);
|
|
ContextTree { tree: new_list }
|
|
}
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &Rc<Node<ContextElement<'r, 's>>>> {
|
|
self.tree.iter()
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
pub fn check_exit_matcher(
|
|
&'r self,
|
|
i: OrgSource<'s>,
|
|
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
|
|
// Special check for EOF. We don't just make this a document-level exit matcher since the IgnoreParent ChainBehavior could cause early exit matchers to not run.
|
|
let at_end_of_file = eof(i);
|
|
if at_end_of_file.is_ok() {
|
|
return at_end_of_file;
|
|
}
|
|
|
|
// let blocked_context =
|
|
// self.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
// exit_matcher: ChainBehavior::IgnoreParent(Some(&always_fail)),
|
|
// }));
|
|
|
|
let mut current_class_filter = ExitClass::Gamma;
|
|
for current_node in self.iter() {
|
|
let context_element = current_node.get_data();
|
|
match context_element {
|
|
ContextElement::ExitMatcherNode(exit_matcher) => {
|
|
if exit_matcher.class as u32 <= current_class_filter as u32 {
|
|
current_class_filter = exit_matcher.class;
|
|
let local_context = ContextTree::branch_from(current_node);
|
|
let local_result = (exit_matcher.exit_matcher)(&local_context, i);
|
|
if local_result.is_ok() {
|
|
return local_result;
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
};
|
|
}
|
|
// TODO: Make this a specific error instead of just a generic MyError
|
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
"NoExit".into(),
|
|
))));
|
|
}
|
|
|
|
/// Indicates if elements should consume the whitespace after them.
|
|
///
|
|
/// Defaults to true.
|
|
pub fn should_consume_trailing_whitespace(&self) -> bool {
|
|
self._should_consume_trailing_whitespace().unwrap_or(true)
|
|
}
|
|
|
|
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
|
|
for current_node in self.iter() {
|
|
let context_element = current_node.get_data();
|
|
match context_element {
|
|
ContextElement::ConsumeTrailingWhitespace(should) => {
|
|
return Some(*should);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ContextElement<'r, 's> {
|
|
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
|
|
ExitMatcherNode(ExitMatcherNode<'r>),
|
|
Context(&'r str),
|
|
|
|
/// Stores the name of the greater block.
|
|
GreaterBlock(&'s str),
|
|
|
|
/// Indicates if elements should consume the whitespace after them.
|
|
ConsumeTrailingWhitespace(bool),
|
|
|
|
/// The contents of a radio target.
|
|
///
|
|
/// If any are found, this will force a 2nd parse through the
|
|
/// org-mode document since text needs to be re-parsed to look for
|
|
/// radio links matching the contents of radio targets.
|
|
RadioTarget(Vec<&'r Vec<Object<'s>>>),
|
|
|
|
/// Stores the current bracket depth inside a footnote reference's definition.
|
|
///
|
|
/// The definition inside a footnote reference must have balanced
|
|
/// brackets [] inside the definition, so this stores the amount
|
|
/// of opening brackets subtracted by the amount of closing
|
|
/// brackets within the definition must equal zero.
|
|
///
|
|
/// A reference to the position in the string is also included so
|
|
/// unbalanced brackets can be detected in the middle of an
|
|
/// object.
|
|
FootnoteReferenceDefinition(FootnoteReferenceDefinition<'s>),
|
|
|
|
/// Stores the current bracket depth inside a citation.
|
|
///
|
|
/// The global prefix, global suffix, key prefix, and key suffix
|
|
/// inside a footnote reference must have balanced brackets []
|
|
/// inside the definition, so this stores the amount of opening
|
|
/// brackets subtracted by the amount of closing brackets within
|
|
/// the definition must equal zero. None of the prefixes or
|
|
/// suffixes can be nested inside each other so we can use a
|
|
/// single type for this without conflict.
|
|
///
|
|
/// A reference to the position in the string is also included so
|
|
/// unbalanced brackets can be detected in the middle of an
|
|
/// object.
|
|
CitationBracket(CitationBracket<'s>),
|
|
|
|
/// Stores the current bracket or parenthesis depth inside an inline babel call.
|
|
///
|
|
/// Inside an inline babel call the headers must have balanced
|
|
/// parentheses () and the arguments must have balanced brackets
|
|
/// [], so this stores the amount of opening brackets subtracted
|
|
/// by the amount of closing brackets within the definition must
|
|
/// equal zero.
|
|
///
|
|
/// A reference to the position in the string is also included so
|
|
/// unbalanced brackets can be detected in the middle of an
|
|
/// object.
|
|
BabelHeaderBracket(BabelHeaderBracket<'s>),
|
|
|
|
/// Stores the current bracket or parenthesis depth inside an inline babel call.
|
|
///
|
|
/// Inside an inline babel call the headers must have balanced
|
|
/// parentheses () and the arguments must have balanced brackets
|
|
/// [], so this stores the amount of opening brackets subtracted
|
|
/// by the amount of closing brackets within the definition must
|
|
/// equal zero.
|
|
///
|
|
/// A reference to the position in the string is also included so
|
|
/// unbalanced brackets can be detected in the middle of an
|
|
/// object.
|
|
InlineSourceBlockBracket(InlineSourceBlockBracket<'s>),
|
|
|
|
/// Stores the current bracket or parenthesis depth inside a
|
|
/// superscript or superscript.
|
|
///
|
|
/// Inside the braces of a subscript or superscript there must be
|
|
/// balanced braces {}, so this stores the amount of opening
|
|
/// braces subtracted by the amount of closing braces within the
|
|
/// definition must equal zero.
|
|
///
|
|
/// A reference to the position in the string is also included so
|
|
/// unbalanced braces can be detected in the middle of an object.
|
|
SubscriptSuperscriptBrace(SubscriptSuperscriptBrace<'s>),
|
|
}
|
|
|
|
pub struct ExitMatcherNode<'r> {
|
|
pub exit_matcher: &'r Matcher,
|
|
pub class: ExitClass,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct FootnoteReferenceDefinition<'s> {
|
|
pub position: OrgSource<'s>,
|
|
pub depth: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct CitationBracket<'s> {
|
|
pub position: OrgSource<'s>,
|
|
pub depth: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BabelHeaderBracket<'s> {
|
|
pub position: OrgSource<'s>,
|
|
pub depth: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct InlineSourceBlockBracket<'s> {
|
|
pub position: OrgSource<'s>,
|
|
pub depth: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SubscriptSuperscriptBrace<'s> {
|
|
pub position: OrgSource<'s>,
|
|
pub depth: usize,
|
|
}
|
|
|
|
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let mut formatter = f.debug_struct("ExitMatcherNode");
|
|
formatter.field("class", &self.class.to_string());
|
|
formatter.finish()
|
|
}
|
|
}
|