organic/src/context/parser_context.rs
2023-09-03 00:05:47 -04:00

259 lines
8.4 KiB
Rust

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;
use crate::types::Object;
// 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 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>),
/// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r 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>>>),
}
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<'r, 's> {
global_settings: &'s GlobalSettings<'s>,
tree: List<'r, &'r ContextElement<'r, 's>>,
}
impl<'r, 's> Context<'r, 's> {
pub fn new(
global_settings: &'s GlobalSettings<'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<'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()
}
#[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::Gamma;
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)(&current_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, 'r, 's>(
_context: RefContext<'b, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
eof(input)
}
pub struct Iter<'r, 's> {
global_settings: &'s GlobalSettings<'s>,
next: super::list::IterList<'r, &'r ContextElement<'r, 's>>,
}
impl<'r, 's> Iterator for Iter<'r, 's> {
type Item = Context<'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,
})
}
}