
This is to allow for " :: " inside a description list item's tag if it is nested inside another object.
185 lines
5.6 KiB
Rust
185 lines
5.6 KiB
Rust
use std::marker::PhantomData;
|
|
|
|
use nom::combinator::eof;
|
|
use nom::IResult;
|
|
|
|
use super::exiting::ExitClass;
|
|
use super::global_settings::GlobalSettings;
|
|
use super::list::List;
|
|
use super::DynContextMatcher;
|
|
use super::RefContext;
|
|
use crate::error::CustomError;
|
|
use crate::error::MyError;
|
|
use crate::error::Res;
|
|
use crate::parser::OrgSource;
|
|
|
|
#[derive(Debug)]
|
|
pub enum ContextElement<'r, 's> {
|
|
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
|
|
ExitMatcherNode(ExitMatcherNode<'r>),
|
|
|
|
/// Stores the name of the current element to prevent directly nesting elements of the same type.
|
|
Context(&'r str),
|
|
|
|
/// Stores the name of the current object to prevent directly nesting elements of the same type.
|
|
ContextObject(&'r str),
|
|
|
|
/// Indicates if elements should consume the whitespace after them.
|
|
ConsumeTrailingWhitespace(bool),
|
|
|
|
/// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement.
|
|
Placeholder(PhantomData<&'s str>),
|
|
}
|
|
|
|
pub struct ExitMatcherNode<'r> {
|
|
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
|
|
pub exit_matcher: &'r DynContextMatcher<'r>,
|
|
pub class: ExitClass,
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Context<'g, 'r, 's> {
|
|
global_settings: &'g GlobalSettings<'g, 's>,
|
|
tree: List<'r, &'r ContextElement<'r, 's>>,
|
|
}
|
|
|
|
impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
|
pub fn new(
|
|
global_settings: &'g GlobalSettings<'g, 's>,
|
|
tree: List<'r, &'r ContextElement<'r, 's>>,
|
|
) -> Self {
|
|
Self {
|
|
global_settings,
|
|
tree,
|
|
}
|
|
}
|
|
|
|
pub fn with_additional_node(&'r self, new_element: &'r ContextElement<'r, 's>) -> Self {
|
|
let new_tree = self.tree.push(new_element);
|
|
Self::new(self.global_settings, new_tree)
|
|
}
|
|
|
|
pub fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> {
|
|
self.tree.iter()
|
|
}
|
|
|
|
pub fn iter_context(&'r self) -> Iter<'g, 'r, 's> {
|
|
Iter {
|
|
next: self.tree.iter_list(),
|
|
global_settings: self.global_settings,
|
|
}
|
|
}
|
|
|
|
pub fn get_parent(&'r self) -> Option<Self> {
|
|
self.tree.get_parent().map(|parent_tree| Self {
|
|
global_settings: self.global_settings,
|
|
tree: parent_tree.clone(),
|
|
})
|
|
}
|
|
|
|
pub fn get_data(&self) -> &ContextElement<'r, 's> {
|
|
self.tree.get_data()
|
|
}
|
|
|
|
pub fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> {
|
|
self.global_settings
|
|
}
|
|
|
|
pub fn with_global_settings<'gg>(
|
|
&self,
|
|
new_settings: &'gg GlobalSettings<'gg, 's>,
|
|
) -> Context<'gg, 'r, 's> {
|
|
Context {
|
|
global_settings: new_settings,
|
|
tree: self.tree.clone(),
|
|
}
|
|
}
|
|
|
|
#[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>>> {
|
|
let mut current_class_filter = ExitClass::Delta;
|
|
for current_node in self.iter_context() {
|
|
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_result = (exit_matcher.exit_matcher)(¤t_node, 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() {
|
|
match current_node {
|
|
ContextElement::ConsumeTrailingWhitespace(should) => {
|
|
return Some(*should);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn document_end<'b, 'g, 'r, 's>(
|
|
_context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
eof(input)
|
|
}
|
|
|
|
pub struct Iter<'g, 'r, 's> {
|
|
global_settings: &'g GlobalSettings<'g, 's>,
|
|
next: super::list::IterList<'r, &'r ContextElement<'r, 's>>,
|
|
}
|
|
|
|
impl<'g, 'r, 's> Iterator for Iter<'g, 'r, 's> {
|
|
type Item = Context<'g, 'r, 's>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let next_tree = self.next.next();
|
|
let ret =
|
|
next_tree.map(|parent_tree| Context::new(self.global_settings, parent_tree.clone()));
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl<'r, 's> ContextElement<'r, 's> {
|
|
pub fn document_context() -> Self {
|
|
Self::ExitMatcherNode(ExitMatcherNode {
|
|
exit_matcher: &document_end,
|
|
class: ExitClass::Document,
|
|
})
|
|
}
|
|
}
|