This ensures the parsers can take into account the affiliated keywords when setting their source without needing the SetSource trait.
139 lines
4.9 KiB
Rust
139 lines
4.9 KiB
Rust
use nom::branch::alt;
|
|
use nom::bytes::complete::is_not;
|
|
use nom::bytes::complete::tag;
|
|
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::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::maybe_consume_trailing_whitespace_if_not_exiting;
|
|
use crate::context::parser_with_context;
|
|
use crate::context::ContextElement;
|
|
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::immediate_in_section;
|
|
use crate::parser::util::start_of_line;
|
|
use crate::types::DynamicBlock;
|
|
use crate::types::Element;
|
|
use crate::types::Paragraph;
|
|
use crate::types::SetSource;
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
|
|
if immediate_in_section(context, "dynamic block") {
|
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
"Cannot nest objects of the same element".into(),
|
|
))));
|
|
}
|
|
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
|
|
|
start_of_line(remaining)?;
|
|
let (remaining, _leading_whitespace) = space0(remaining)?;
|
|
let (remaining, (_, name, parameters, _, _)) = tuple((
|
|
recognize(tuple((tag_no_case("#+begin:"), space1))),
|
|
name,
|
|
opt(tuple((space1, parameters))),
|
|
space0,
|
|
line_ending,
|
|
))(remaining)?;
|
|
let contexts = [
|
|
ContextElement::ConsumeTrailingWhitespace(true),
|
|
ContextElement::Context("dynamic block"),
|
|
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
class: ExitClass::Alpha,
|
|
exit_matcher: &dynamic_block_end,
|
|
}),
|
|
];
|
|
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 parameters = match parameters {
|
|
Some((_ws, parameters)) => Some(parameters),
|
|
None => None,
|
|
};
|
|
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) = dynamic_block_end(&parser_context, remaining)?;
|
|
|
|
let (remaining, _trailing_ws) =
|
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
|
let source = get_consumed(input, remaining);
|
|
Ok((
|
|
remaining,
|
|
DynamicBlock {
|
|
source: source.into(),
|
|
name: get_name(&affiliated_keywords),
|
|
block_name: name.into(),
|
|
parameters: parameters.map(|val| val.into()),
|
|
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)
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn dynamic_block_end<'b, 'g, 'r, 's>(
|
|
_context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
start_of_line(input)?;
|
|
let (remaining, source) = recognize(tuple((
|
|
space0,
|
|
tag_no_case("#+end"),
|
|
opt(tag(":")),
|
|
alt((eof, line_ending)),
|
|
)))(input)?;
|
|
Ok((remaining, source))
|
|
}
|