Files
organic/src/parser/element_parser.rs
Tom Alexander 758e224e6d Move consuming trailing element whitespace inside the parsers.
This ensures the parsers can take into account the affiliated keywords when setting their source without needing the SetSource trait.
2023-10-06 12:02:14 -04:00

205 lines
7.7 KiB
Rust

use nom::branch::alt;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::sequence::tuple;
#[cfg(feature = "tracing")]
use tracing::span;
use super::babel_call::babel_call;
use super::clock::clock;
use super::comment::comment;
use super::comment::detect_comment;
use super::diary_sexp::detect_diary_sexp;
use super::diary_sexp::diary_sexp;
use super::drawer::drawer;
use super::dynamic_block::dynamic_block;
use super::fixed_width_area::detect_fixed_width_area;
use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::detect_footnote_definition;
use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::affiliated_keyword_as_regular_keyword;
use super::keyword::keyword;
use super::latex_environment::latex_environment;
use super::lesser_block::comment_block;
use super::lesser_block::example_block;
use super::lesser_block::export_block;
use super::lesser_block::src_block;
use super::lesser_block::verse_block;
use super::org_source::OrgSource;
use super::paragraph::paragraph;
use super::plain_list::detect_plain_list;
use super::plain_list::plain_list;
use super::table::detect_table;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::table::org_mode_table;
use crate::types::Element;
pub(crate) const fn element(
can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
move |context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _element<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> {
let plain_list_matcher = parser_with_context!(plain_list)(context);
let greater_block_matcher = parser_with_context!(greater_block)(context);
let dynamic_block_matcher = parser_with_context!(dynamic_block)(context);
let footnote_definition_matcher = parser_with_context!(footnote_definition)(context);
let comment_matcher = parser_with_context!(comment)(context);
let drawer_matcher = parser_with_context!(drawer)(context);
let table_matcher = parser_with_context!(org_mode_table)(context);
let verse_block_matcher = parser_with_context!(verse_block)(context);
let comment_block_matcher = parser_with_context!(comment_block)(context);
let example_block_matcher = parser_with_context!(example_block)(context);
let export_block_matcher = parser_with_context!(export_block)(context);
let src_block_matcher = parser_with_context!(src_block)(context);
let clock_matcher = parser_with_context!(clock)(context);
let diary_sexp_matcher = parser_with_context!(diary_sexp)(context);
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context);
let (mut remaining, mut maybe_element) = {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Main element block");
#[cfg(feature = "tracing")]
let _enter = span.enter();
opt(alt((
map(plain_list_matcher, Element::PlainList),
greater_block_matcher,
map(dynamic_block_matcher, Element::DynamicBlock),
map(footnote_definition_matcher, Element::FootnoteDefinition),
map(comment_matcher, Element::Comment),
map(drawer_matcher, Element::Drawer),
map(table_matcher, Element::Table),
map(verse_block_matcher, Element::VerseBlock),
map(comment_block_matcher, Element::CommentBlock),
map(example_block_matcher, Element::ExampleBlock),
map(export_block_matcher, Element::ExportBlock),
map(src_block_matcher, Element::SrcBlock),
map(clock_matcher, Element::Clock),
map(diary_sexp_matcher, Element::DiarySexp),
map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword),
)))(input)?
};
if maybe_element.is_none() && can_be_paragraph {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Paragraph with affiliated keyword.");
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, paragraph_with_affiliated_keyword) = opt(map(
tuple((
peek(affiliated_keyword),
map(paragraph_matcher, Element::Paragraph),
)),
|(_, paragraph)| paragraph,
))(remaining)?;
if paragraph_with_affiliated_keyword.is_some() {
remaining = remain;
maybe_element = paragraph_with_affiliated_keyword;
}
}
if maybe_element.is_none() {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Affiliated keyword as regular keyword."
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, kw) = opt(map(
parser_with_context!(affiliated_keyword_as_regular_keyword)(context),
Element::Keyword,
))(remaining)?;
if kw.is_some() {
maybe_element = kw;
remaining = remain;
}
}
if maybe_element.is_none() && can_be_paragraph {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Paragraph without affiliated keyword."
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, paragraph_without_affiliated_keyword) =
map(paragraph_matcher, Element::Paragraph)(remaining)?;
remaining = remain;
maybe_element = Some(paragraph_without_affiliated_keyword);
}
if maybe_element.is_none() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element.",
))));
}
let element = maybe_element.expect("The above if-statement ensures this is Some().");
Ok((remaining, element))
}
pub(crate) const fn detect_element(
can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()>
{
move |context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _detect_element<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> {
if alt((
parser_with_context!(detect_plain_list)(context),
detect_footnote_definition,
detect_diary_sexp,
detect_comment,
detect_fixed_width_area,
detect_table,
))(input)
.is_ok()
{
return Ok((input, ()));
}
if _element(context, input, can_be_paragraph).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element detected.".into(),
))));
}