
This ensures the parsers can take into account the affiliated keywords when setting their source without needing the SetSource trait.
273 lines
9.1 KiB
Rust
273 lines
9.1 KiB
Rust
use nom::branch::alt;
|
|
use nom::bytes::complete::is_not;
|
|
use nom::bytes::complete::tag_no_case;
|
|
use nom::character::complete::anychar;
|
|
use nom::character::complete::line_ending;
|
|
use nom::character::complete::space0;
|
|
use nom::character::complete::space1;
|
|
use nom::combinator::consumed;
|
|
use nom::combinator::eof;
|
|
use nom::combinator::not;
|
|
use nom::combinator::opt;
|
|
use nom::combinator::peek;
|
|
use nom::combinator::recognize;
|
|
use nom::combinator::verify;
|
|
use nom::multi::many0;
|
|
use nom::multi::many_till;
|
|
use nom::sequence::preceded;
|
|
use nom::sequence::tuple;
|
|
|
|
use super::keyword::affiliated_keyword;
|
|
use super::org_source::OrgSource;
|
|
use super::util::get_name;
|
|
use super::util::in_section;
|
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
|
use crate::context::parser_with_context;
|
|
use crate::context::ContextElement;
|
|
use crate::context::ContextMatcher;
|
|
use crate::context::ExitClass;
|
|
use crate::context::ExitMatcherNode;
|
|
use crate::context::RefContext;
|
|
use crate::error::CustomError;
|
|
use crate::error::MyError;
|
|
use crate::error::Res;
|
|
use crate::parser::element_parser::element;
|
|
use crate::parser::util::blank_line;
|
|
use crate::parser::util::exit_matcher_parser;
|
|
use crate::parser::util::get_consumed;
|
|
use crate::parser::util::start_of_line;
|
|
use crate::types::CenterBlock;
|
|
use crate::types::Element;
|
|
use crate::types::Keyword;
|
|
use crate::types::Paragraph;
|
|
use crate::types::QuoteBlock;
|
|
use crate::types::SetSource;
|
|
use crate::types::SpecialBlock;
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
|
let pre_affiliated_keywords_input = input;
|
|
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
|
start_of_line(input)?;
|
|
let (remaining, _leading_whitespace) = space0(input)?;
|
|
let (remaining, (_begin, name)) = tuple((
|
|
tag_no_case("#+begin_"),
|
|
verify(name, |name: &OrgSource<'_>| {
|
|
match Into::<&str>::into(name).to_lowercase().as_str() {
|
|
"comment" | "example" | "export" | "src" | "verse" => false,
|
|
_ => true,
|
|
}
|
|
}),
|
|
))(remaining)?;
|
|
let name = Into::<&str>::into(name);
|
|
let (remaining, element) = match name.to_lowercase().as_str() {
|
|
"center" => center_block(
|
|
context,
|
|
remaining,
|
|
pre_affiliated_keywords_input,
|
|
&affiliated_keywords,
|
|
)?,
|
|
"quote" => quote_block(
|
|
context,
|
|
remaining,
|
|
pre_affiliated_keywords_input,
|
|
&affiliated_keywords,
|
|
)?,
|
|
_ => special_block(name)(
|
|
context,
|
|
remaining,
|
|
pre_affiliated_keywords_input,
|
|
&affiliated_keywords,
|
|
)?,
|
|
};
|
|
Ok((remaining, element))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn center_block<'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
pre_affiliated_keywords_input: OrgSource<'s>,
|
|
affiliated_keywords: &Vec<Keyword<'s>>,
|
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
|
let (remaining, (source, children)) = greater_block_body(
|
|
context,
|
|
input,
|
|
pre_affiliated_keywords_input,
|
|
"center",
|
|
"center block",
|
|
)?;
|
|
Ok((
|
|
remaining,
|
|
Element::CenterBlock(CenterBlock {
|
|
source,
|
|
name: get_name(&affiliated_keywords),
|
|
children,
|
|
}),
|
|
))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn quote_block<'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
pre_affiliated_keywords_input: OrgSource<'s>,
|
|
affiliated_keywords: &Vec<Keyword<'s>>,
|
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
|
let (remaining, (source, children)) = greater_block_body(
|
|
context,
|
|
input,
|
|
pre_affiliated_keywords_input,
|
|
"quote",
|
|
"quote block",
|
|
)?;
|
|
Ok((
|
|
remaining,
|
|
Element::QuoteBlock(QuoteBlock {
|
|
source,
|
|
name: get_name(&affiliated_keywords),
|
|
children,
|
|
}),
|
|
))
|
|
}
|
|
|
|
fn special_block<'s>(
|
|
name: &'s str,
|
|
) -> impl for<'b, 'g, 'r> Fn(
|
|
RefContext<'b, 'g, 'r, 's>,
|
|
OrgSource<'s>,
|
|
OrgSource<'s>,
|
|
&Vec<Keyword<'s>>,
|
|
) -> Res<OrgSource<'s>, Element<'s>>
|
|
+ 's {
|
|
let context_name = format!("special block {}", name);
|
|
move |context, input, pre_affiliated_keywords_input, affiliated_keywords| {
|
|
_special_block(
|
|
context,
|
|
input,
|
|
pre_affiliated_keywords_input,
|
|
name,
|
|
context_name.as_str(),
|
|
affiliated_keywords,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn _special_block<'c, 'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
pre_affiliated_keywords_input: OrgSource<'s>,
|
|
name: &'s str,
|
|
context_name: &'c str,
|
|
affiliated_keywords: &Vec<Keyword<'s>>,
|
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
|
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
|
|
let (remaining, (source, children)) = greater_block_body(
|
|
context,
|
|
remaining,
|
|
pre_affiliated_keywords_input,
|
|
name,
|
|
context_name,
|
|
)?;
|
|
Ok((
|
|
remaining,
|
|
Element::SpecialBlock(SpecialBlock {
|
|
source,
|
|
name: get_name(&affiliated_keywords),
|
|
children,
|
|
block_type: name,
|
|
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
|
|
}),
|
|
))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
pre_affiliated_keywords_input: OrgSource<'s>,
|
|
name: &'c str,
|
|
context_name: &'c str,
|
|
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
|
|
if in_section(context, context_name) {
|
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
"Cannot nest objects of the same element".into(),
|
|
))));
|
|
}
|
|
let exit_with_name = greater_block_end(name);
|
|
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
|
|
let contexts = [
|
|
ContextElement::ConsumeTrailingWhitespace(true),
|
|
ContextElement::Context(context_name),
|
|
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
class: ExitClass::Alpha,
|
|
exit_matcher: &exit_with_name,
|
|
}),
|
|
];
|
|
let parser_context = context.with_additional_node(&contexts[0]);
|
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
|
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
|
not(exit_matcher)(remaining)?;
|
|
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
|
blank_line,
|
|
many0(preceded(not(exit_matcher), blank_line)),
|
|
))))(remaining)?;
|
|
let leading_blank_lines =
|
|
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
|
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
|
element.set_source(source.into());
|
|
element
|
|
});
|
|
let (remaining, (mut children, _exit_contents)) =
|
|
many_till(element_matcher, exit_matcher)(remaining)?;
|
|
if let Some(lines) = leading_blank_lines {
|
|
children.insert(0, lines);
|
|
}
|
|
|
|
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
|
|
|
|
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
|
|
|
|
let (remaining, _trailing_ws) =
|
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
|
let source = get_consumed(pre_affiliated_keywords_input, remaining);
|
|
Ok((remaining, (Into::<&str>::into(source), children)))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
is_not(" \t\r\n")(input)
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
|
|
}
|
|
|
|
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
|
|
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn _greater_block_end<'b, 'g, 'r, 's, 'c>(
|
|
_context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
name: &'c str,
|
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
start_of_line(input)?;
|
|
let (remaining, _leading_whitespace) = space0(input)?;
|
|
let (remaining, (_begin, _name, _ws)) = tuple((
|
|
tag_no_case("#+end_"),
|
|
tag_no_case(name),
|
|
alt((eof, line_ending)),
|
|
))(remaining)?;
|
|
let source = get_consumed(input, remaining);
|
|
Ok((remaining, source))
|
|
}
|