From 9b2348c0ef1248c2f4c598072770a47452046b74 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Thu, 21 Sep 2023 18:34:32 -0400 Subject: [PATCH] Allow matched parenthesis inside plain links. --- .../with_parenthesis.org | 9 ++ src/parser/plain_link.rs | 145 ++++++++++++++++-- 2 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 org_mode_samples/object/subscript_and_superscript/with_parenthesis.org diff --git a/org_mode_samples/object/subscript_and_superscript/with_parenthesis.org b/org_mode_samples/object/subscript_and_superscript/with_parenthesis.org new file mode 100644 index 0000000..ecf84c2 --- /dev/null +++ b/org_mode_samples/object/subscript_and_superscript/with_parenthesis.org @@ -0,0 +1,9 @@ +foo_(bar) + +foo_(b(ar) + +foo_(b{ar) + +foo_{b(ar} + +foo_(b(a)r) diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index 2ab6d6c..d92fd0f 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -5,10 +5,14 @@ use nom::character::complete::anychar; use nom::character::complete::none_of; use nom::character::complete::one_of; use nom::combinator::eof; +use nom::combinator::not; use nom::combinator::peek; use nom::combinator::recognize; use nom::combinator::verify; +use nom::multi::many0; +use nom::multi::many1; use nom::multi::many_till; +use nom::sequence::tuple; use super::org_source::BracketDepth; use super::org_source::OrgSource; @@ -18,6 +22,7 @@ use crate::context::ContextElement; use crate::context::ContextMatcher; use crate::context::ExitClass; use crate::context::ExitMatcherNode; +use crate::context::Matcher; use crate::context::RefContext; use crate::error::CustomError; use crate::error::MyError; @@ -132,17 +137,77 @@ fn path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - // TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring" + let path_plain_end = path_plain_end(input.get_parenthesis_depth()); let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, exit_matcher: &path_plain_end, }); let parser_context = context.with_additional_node(&parser_context); - let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); + let (remaining, _components) = many1(alt(( + parser_with_context!(path_plain_no_parenthesis)(&parser_context), + parser_with_context!(path_plain_parenthesis)(&parser_context), + )))(input)?; + let source = get_consumed(input, remaining); + Ok((remaining, source)) +} + +fn path_plain_end(starting_parenthesis_depth: BracketDepth) -> impl ContextMatcher { + move |context, input: OrgSource<'_>| _path_plain_end(context, input, starting_parenthesis_depth) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn _path_plain_end<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, + starting_parenthesis_depth: BracketDepth, +) -> Res, OrgSource<'s>> { + let (remaining, _leading_punctuation) = many0(verify(anychar, |c| { + !" \t\r\n[]<>()/".contains(*c) && c.is_ascii_punctuation() + }))(input)?; + + let disallowed_character = recognize(one_of(" \t\r\n[]<>"))(remaining); + if disallowed_character.is_ok() { + return disallowed_character; + } + + let current_depth = remaining.get_parenthesis_depth() - starting_parenthesis_depth; + if current_depth == 0 { + let close_parenthesis = + tag::<&str, OrgSource<'_>, CustomError>>(")")(remaining); + if close_parenthesis.is_ok() { + return close_parenthesis; + } + + let open_parenthesis_without_match = recognize(tuple(( + peek(tag("(")), + not(parser_with_context!(path_plain_parenthesis)(context)), + )))(remaining); + if open_parenthesis_without_match.is_ok() { + return open_parenthesis_without_match; + } + } + + // many0 punctuation + Err(nom::Err::Error(CustomError::MyError(MyError( + "No path plain end".into(), + )))) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn path_plain_no_parenthesis<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, OrgSource<'s>> { let (remaining, path) = recognize(verify( - many_till(anychar, exit_matcher), + many_till( + anychar, + alt(( + peek(path_plain_no_parenthesis_disallowed_character), + parser_with_context!(exit_matcher_parser)(context), + )), + ), |(children, _exit_contents)| !children.is_empty(), ))(input)?; @@ -150,14 +215,72 @@ fn path_plain<'b, 'g, 'r, 's>( } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn path_plain_end<'b, 'g, 'r, 's>( - _context: RefContext<'b, 'g, 'r, 's>, +fn path_plain_no_parenthesis_disallowed_character<'s>( input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - recognize(many_till( - verify(anychar, |c| { - *c != '/' && (c.is_ascii_punctuation() || c.is_whitespace()) - }), - one_of(" \t\r\n()[]<>"), - ))(input) + recognize(verify(anychar, |c| { + c.is_whitespace() || "()[]<>".contains(*c) + }))(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn path_plain_parenthesis<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, OrgSource<'s>> { + let (remaining, _opening) = tag("(")(input)?; + let starting_depth = remaining.get_parenthesis_depth(); + + let (remaining, _path) = recognize(verify( + many_till( + anychar, + alt(( + peek(path_plain_parenthesis_end(starting_depth)), + parser_with_context!(exit_matcher_parser)(context), + )), + ), + |(children, _exit_contents)| !children.is_empty(), + ))(remaining)?; + let (remaining, _opening) = tag(")")(remaining)?; + let source = get_consumed(input, remaining); + + Ok((remaining, source)) +} + +fn path_plain_parenthesis_end(starting_parenthesis_depth: BracketDepth) -> impl Matcher { + move |input: OrgSource<'_>| _path_plain_parenthesis_end(input, starting_parenthesis_depth) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn _path_plain_parenthesis_end<'s>( + input: OrgSource<'s>, + starting_parenthesis_depth: BracketDepth, +) -> Res, OrgSource<'s>> { + let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth; + eprintln!( + "current_depth: {}, starting: {}, now: {}, remaining input: {}", + current_depth, + starting_parenthesis_depth, + input.get_parenthesis_depth(), + input + ); + if current_depth < 0 { + // This shouldn't be possible because if depth is 0 then a closing parenthesis should end the link. + unreachable!("Exceeded plain link parenthesis depth.") + } + if current_depth == 0 { + let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError>>(")")(input); + if close_parenthesis.is_ok() { + return close_parenthesis; + } + } + if current_depth == 1 { + let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError>>("(")(input); + if open_parenthesis.is_ok() { + return open_parenthesis; + } + } + Err(nom::Err::Error(CustomError::MyError(MyError( + "No closing parenthesis".into(), + )))) }