diff --git a/org_mode_samples/lesser_element/babel_call/empty.org b/org_mode_samples/lesser_element/babel_call/empty.org new file mode 100644 index 0000000..0710532 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/empty.org @@ -0,0 +1,3 @@ +#+call: + +#+call: diff --git a/org_mode_samples/lesser_element/babel_call/header_arguments.org b/org_mode_samples/lesser_element/babel_call/header_arguments.org new file mode 100644 index 0000000..5416e05 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/header_arguments.org @@ -0,0 +1,3 @@ +#+call: foo[inside](bar="baz")[outside] + +#+call: foo[](bar="baz")[] diff --git a/org_mode_samples/lesser_element/babel_call/nested_brackets.org b/org_mode_samples/lesser_element/babel_call/nested_brackets.org new file mode 100644 index 0000000..7b94850 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/nested_brackets.org @@ -0,0 +1,7 @@ +#+call: foo[inside](bar="baz")[outside] + +#+call: foo[[inside]](bar="baz")[outside] + +#+call: foo[inside]((bar="baz"))[outside] + +#+call: foo[inside](bar="baz")[[outside]] diff --git a/org_mode_samples/lesser_element/babel_call/no_closing_parenthesis.org b/org_mode_samples/lesser_element/babel_call/no_closing_parenthesis.org new file mode 100644 index 0000000..e9be825 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/no_closing_parenthesis.org @@ -0,0 +1 @@ +#+call: foo(bar="baz" diff --git a/org_mode_samples/lesser_element/babel_call/simple.org b/org_mode_samples/lesser_element/babel_call/simple.org index 281b00c..bafcb01 100644 --- a/org_mode_samples/lesser_element/babel_call/simple.org +++ b/org_mode_samples/lesser_element/babel_call/simple.org @@ -1 +1,7 @@ #+call: foo(bar="baz") + +#+call: lorem ipsum + +#+call: dolar cat(dog) + +#+call: (bat) diff --git a/org_mode_samples/lesser_element/babel_call/spaces.org b/org_mode_samples/lesser_element/babel_call/spaces.org new file mode 100644 index 0000000..55f9b85 --- /dev/null +++ b/org_mode_samples/lesser_element/babel_call/spaces.org @@ -0,0 +1,3 @@ +#+call: foo [inside] (bar="baz") [outside] + +#+call: foo (bar="baz") [outside] diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 2e6f304..2cf311e 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2403,8 +2403,6 @@ fn compare_babel_call<'b, 's>( let mut this_status = DiffStatus::Good; let mut message = None; - // TODO: Compare :call :inside-header :arguments :end-header - // TODO: Compare :caption // Compare name let name = get_property_quoted_string(emacs, ":name")?; @@ -2416,19 +2414,56 @@ fn compare_babel_call<'b, 's>( )); } - let value = unquote( - get_property(emacs, ":value")? - .ok_or("Emacs keywords should have a :value")? - .as_atom()?, - )?; + // Compare value + let value = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new()); if value != rust.value { this_status = DiffStatus::Bad; message = Some(format!( - "Mismatchs keyword values (emacs != rust) {:?} != {:?}", + "Value mismatch (emacs != rust) {:?} != {:?}", value, rust.value )) } + // Compare call + let call = get_property_quoted_string(emacs, ":call")?; + if call.as_ref().map(String::as_str) != rust.call { + this_status = DiffStatus::Bad; + message = Some(format!( + "Call mismatch (emacs != rust) {:?} != {:?}", + call, rust.call + )) + } + + // Compare arguments + let arguments = get_property_quoted_string(emacs, ":arguments")?; + if arguments.as_ref().map(String::as_str) != rust.arguments { + this_status = DiffStatus::Bad; + message = Some(format!( + "Arguments mismatch (emacs != rust) {:?} != {:?}", + arguments, rust.arguments + )) + } + + // Compare inside header + let inside_header = get_property_quoted_string(emacs, ":inside-header")?; + if inside_header.as_ref().map(String::as_str) != rust.inside_header { + this_status = DiffStatus::Bad; + message = Some(format!( + "Inside header mismatch (emacs != rust) {:?} != {:?}", + inside_header, rust.inside_header + )) + } + + // Compare end header + let end_header = get_property_quoted_string(emacs, ":end-header")?; + if end_header.as_ref().map(String::as_str) != rust.end_header { + this_status = DiffStatus::Bad; + message = Some(format!( + "End header mismatch (emacs != rust) {:?} != {:?}", + end_header, rust.end_header + )) + } + Ok(DiffResult { status: this_status, name: rust.get_elisp_name(), diff --git a/src/parser/babel_call.rs b/src/parser/babel_call.rs new file mode 100644 index 0000000..2919422 --- /dev/null +++ b/src/parser/babel_call.rs @@ -0,0 +1,235 @@ +use nom::branch::alt; +use nom::bytes::complete::tag; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::anychar; +use nom::character::complete::one_of; +use nom::character::complete::space0; +use nom::combinator::consumed; +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::tuple; +use nom::InputTake; + +use super::keyword::affiliated_keyword; +use super::org_source::BracketDepth; +use super::util::get_name; +use super::util::start_of_line; +use super::OrgSource; +use crate::context::Matcher; +use crate::context::RefContext; +use crate::error::CustomError; +use crate::error::MyError; +use crate::error::Res; +use crate::parser::util::get_consumed; +use crate::parser::util::org_line_ending; +use crate::types::BabelCall; + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +pub(crate) fn babel_call<'b, 'g, 'r, 's>( + _context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, BabelCall<'s>> { + let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?; + + start_of_line(input)?; + let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(input)?; + + if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) { + let source = get_consumed(input, remaining); + return Ok(( + remaining, + BabelCall { + source: Into::<&str>::into(source), + name: get_name(&affiliated_keywords), + value: Into::<&str>::into(line_break.take(0)), + call: None, + inside_header: None, + arguments: None, + end_header: None, + }, + )); + } + + let (remaining, _ws) = space0(remaining)?; + let (remaining, (value, (call, inside_header, arguments, end_header))) = + consumed(babel_call_value)(remaining)?; + let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?; + + let source = get_consumed(input, remaining); + + Ok(( + remaining, + BabelCall { + source: Into::<&str>::into(source), + name: get_name(&affiliated_keywords), + value: Into::<&str>::into(value).trim_end(), + call: call.map(Into::<&str>::into), + inside_header: inside_header.map(Into::<&str>::into), + arguments: arguments.map(Into::<&str>::into), + end_header: end_header.map(Into::<&str>::into), + }, + )) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn babel_call_value<'s>( + input: OrgSource<'s>, +) -> Res< + OrgSource<'s>, + ( + Option>, + Option>, + Option>, + Option>, + ), +> { + let (remaining, call) = opt(babel_call_call)(input)?; + let (remaining, inside_header) = opt(inside_header)(remaining)?; + let (remaining, arguments) = opt(arguments)(remaining)?; + let (remaining, end_header) = opt(end_header)(remaining)?; + Ok(( + remaining, + (call, inside_header, arguments.flatten(), end_header), + )) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn babel_call_call<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + verify( + recognize(many_till( + anychar, + alt(( + peek(recognize(one_of("[("))), + recognize(tuple((space0, org_line_ending))), + )), + )), + |s| s.len() > 0, + )(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn inside_header<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + let (remaining, contents) = balanced_bracket( + |i| tag("[")(i), + |i| peek(tag("]"))(i), + |i| recognize(tuple((space0, org_line_ending)))(i), + |i| tag("]")(i), + |s| s.get_bracket_depth(), + )(input)?; + let (contents_start, _) = tag("[")(input)?; + Ok((remaining, contents.unwrap_or(contents_start.take(0)))) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn arguments<'s>(input: OrgSource<'s>) -> Res, Option>> { + balanced_bracket( + |i| tag("(")(i), + |i| peek(tag(")"))(i), + |i| recognize(tuple((space0, org_line_ending)))(i), + |i| tag(")")(i), + |s| s.get_parenthesis_depth(), + )(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn end_header<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { + let (remaining, _) = space0(input)?; + verify( + recognize(many_till(anychar, peek(tuple((space0, org_line_ending))))), + |s| s.len() > 0, + )(remaining) +} + +fn balanced_bracket< + O: Matcher, + S: Matcher, + F: Matcher, + E: Matcher, + D: for<'ss> Fn(OrgSource<'ss>) -> BracketDepth, +>( + opening_parser: O, + stop_parser: S, + fail_parser: F, + end_parser: E, + depth_function: D, +) -> impl for<'s> Fn(OrgSource<'s>) -> Res, Option>> { + move |input| { + impl_balanced_bracket::<&O, &S, &F, &E, &D>( + input, + &opening_parser, + &stop_parser, + &fail_parser, + &end_parser, + &depth_function, + ) + } +} + +fn impl_balanced_bracket< + 's, + O: Matcher, + S: Matcher, + F: Matcher, + E: Matcher, + D: for<'ss> Fn(OrgSource<'ss>) -> BracketDepth, +>( + input: OrgSource<'s>, + opening_parser: O, + stop_parser: S, + fail_parser: F, + end_parser: E, + depth_function: D, +) -> Res, Option>> { + let (mut remaining, _) = opening_parser(input)?; + let contents_start = remaining; + let original_depth = depth_function(remaining); + + loop { + let bracket_depth = depth_function(remaining); + if bracket_depth == original_depth { + let (remain, stop_result) = opt(&stop_parser)(remaining)?; + remaining = remain; + if stop_result.is_some() { + break; + } + } + + if fail_parser(remaining).is_ok() { + return Err(nom::Err::Error(CustomError::MyError(MyError( + "Fail parser matched.", + )))); + } + + let (remain, _) = anychar(remaining)?; + remaining = remain; + } + let contents_end = remaining; + + let (remaining, _) = end_parser(remaining)?; + let contents = if contents_start != contents_end { + Some(contents_start.get_until(contents_end)) + } else { + None + }; + Ok((remaining, contents)) +} + +#[cfg(test)] +mod tests { + use nom::combinator::opt; + + use super::*; + + #[test] + fn simple_call() -> Result<(), Box> { + let input = OrgSource::new("()"); + let (remaining, call) = opt(babel_call_call)(input)?; + assert_eq!(Into::<&str>::into(remaining), "()"); + assert_eq!(call, None); + Ok(()) + } +} diff --git a/src/parser/element_parser.rs b/src/parser/element_parser.rs index 81dc435..b416831 100644 --- a/src/parser/element_parser.rs +++ b/src/parser/element_parser.rs @@ -6,6 +6,7 @@ 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; @@ -20,7 +21,6 @@ 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::babel_call_keyword; use super::keyword::keyword; use super::latex_environment::latex_environment; use super::lesser_block::comment_block; @@ -76,7 +76,7 @@ fn _element<'b, 'g, 'r, 's>( 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_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); diff --git a/src/parser/keyword.rs b/src/parser/keyword.rs index 34739bc..19aea56 100644 --- a/src/parser/keyword.rs +++ b/src/parser/keyword.rs @@ -26,7 +26,6 @@ use crate::error::CustomError; use crate::error::MyError; use crate::error::Res; use crate::parser::util::start_of_line; -use crate::types::BabelCall; use crate::types::Keyword; const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [ @@ -104,29 +103,6 @@ pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res, filtered_keyword(affiliated_key)(input) } -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>( - _context: RefContext<'b, 'g, 'r, 's>, - input: OrgSource<'s>, -) -> Res, BabelCall<'s>> { - let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?; - let (remaining, kw) = filtered_keyword(babel_call_key)(input)?; - Ok(( - remaining, - BabelCall { - source: kw.source, - name: get_name(&affiliated_keywords), - key: kw.key, - value: kw.value, - }, - )) -} - -#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn babel_call_key<'s>(input: OrgSource<'s>) -> Res, OrgSource<'s>> { - tag_no_case("call")(input) -} - #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn table_formula_keyword<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b3e2710..02dabf6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,4 +1,5 @@ mod angle_link; +mod babel_call; mod citation; mod citation_reference; mod clock; diff --git a/src/types/lesser_element.rs b/src/types/lesser_element.rs index 8797bbc..ee126d6 100644 --- a/src/types/lesser_element.rs +++ b/src/types/lesser_element.rs @@ -138,8 +138,11 @@ pub struct Keyword<'s> { pub struct BabelCall<'s> { pub source: &'s str, pub name: Option<&'s str>, - pub key: &'s str, pub value: &'s str, + pub call: Option<&'s str>, + pub inside_header: Option<&'s str>, + pub arguments: Option<&'s str>, + pub end_header: Option<&'s str>, } #[derive(Debug)]