From b556f4617f3b97fb4a14bb24777c7a5fa162edae Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Wed, 4 Oct 2023 16:58:45 -0400 Subject: [PATCH] Add src block properties. These are largely the same as example blocks but with a :language property. --- src/compare/diff.rs | 158 +++++++++++++++++++++++++++++++++++- src/parser/lesser_block.rs | 74 ++++++++++++++--- src/types/lesser_element.rs | 9 +- 3 files changed, 223 insertions(+), 18 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 4ca5de1d..f630e39c 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -1727,11 +1727,161 @@ fn compare_src_block<'b, 's>( _source: &'s str, emacs: &'b Token<'s>, rust: &'b SrcBlock<'s>, -) -> Result, Box> { - let this_status = DiffStatus::Good; - let message = None; +) -> Result, Box> { + let mut this_status = DiffStatus::Good; + let mut message = None; - // TODO: Compare :language :switches :parameters :number-lines :preserve-indent :retain-labels :use-labels :label-fmt :value + // Compare language + let language = get_property_quoted_string(emacs, ":language")?; + if language.as_ref().map(String::as_str) != rust.language { + this_status = DiffStatus::Bad; + message = Some(format!( + "Language mismatch (emacs != rust) {:?} != {:?}", + language, rust.language + )); + } + + // Compare value + let contents = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new()); + if contents != rust.contents { + this_status = DiffStatus::Bad; + message = Some(format!( + "Value mismatch (emacs != rust) {:?} != {:?}", + contents, rust.contents + )); + } + + // Compare switches + let switches = get_property_quoted_string(emacs, ":switches")?; + match (switches.as_ref().map(String::as_str), rust.switches) { + (None, None) => {} + (Some(""), None) => {} + (None, Some("")) => { + unreachable!("The organic parser would return a None instead of an empty string."); + } + (Some(e), Some(r)) if e == r => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Switches mismatch (emacs != rust) {:?} != {:?}", + switches, rust.switches + )); + } + } + + // Compare number-lines + let number_lines = get_property(emacs, ":number-lines")?; + match (number_lines, &rust.number_lines) { + (None, None) => {} + (Some(number_lines), Some(rust_number_lines)) => { + let token_list = number_lines.as_list()?; + let number_type = token_list + .get(0) + .map(Token::as_atom) + .map_or(Ok(None), |r| r.map(Some))? + .ok_or(":number-lines should have a type.")?; + let number_value = token_list + .get(2) + .map(Token::as_atom) + .map_or(Ok(None), |r| r.map(Some))? + .map(|val| val.parse::()) + .map_or(Ok(None), |r| r.map(Some))? + .ok_or(":number-lines should have a value.")?; + match (number_type, number_value, rust_number_lines) { + ("new", emacs_val, SwitchNumberLines::New(rust_val)) if emacs_val == *rust_val => {} + ("continued", emacs_val, SwitchNumberLines::Continued(rust_val)) + if emacs_val == *rust_val => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Number lines mismatch (emacs != rust) {:?} != {:?}", + number_lines, rust.number_lines + )); + } + } + } + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Number lines mismatch (emacs != rust) {:?} != {:?}", + number_lines, rust.number_lines + )); + } + }; + + // Compare preserve-indent + let preserve_indent = get_property_boolean(emacs, ":preserve-indent")?; + if preserve_indent { + // I don't know what :preserve-indent is for, but it seems to always be nil. This is here to alert me to value being non-nil so I can investigate. + this_status = DiffStatus::Bad; + message = Some(format!("Non-nil :preserve-indent {:?}", preserve_indent)); + } + + // Compare retain-labels + // retain-labels is t by default, nil if -r is set, or a number if -k and -r is set. + let retain_labels = get_property_unquoted_atom(emacs, ":retain-labels")?; + if let Some(retain_labels) = retain_labels { + if retain_labels == "t" { + match rust.retain_labels { + RetainLabels::Yes => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Retain labels mismatch (emacs != rust) {:?} != {:?}", + retain_labels, rust.retain_labels + )); + } + } + } else { + let retain_labels: CharOffsetInLine = get_property_numeric(emacs, ":retain-labels")?.expect("Cannot be None or else the earlier get_property_unquoted_atom would have been None."); + match (retain_labels, &rust.retain_labels) { + (e, RetainLabels::Keep(r)) if e == *r => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Retain labels mismatch (emacs != rust) {:?} != {:?}", + retain_labels, rust.retain_labels + )); + } + } + } + } else { + match rust.retain_labels { + RetainLabels::No => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Retain labels mismatch (emacs != rust) {:?} != {:?}", + retain_labels, rust.retain_labels + )); + } + } + } + + // Compare use-labels + let use_labels = get_property_boolean(emacs, ":use-labels")?; + if use_labels != rust.use_labels { + this_status = DiffStatus::Bad; + message = Some(format!( + "Use labels mismatch (emacs != rust) {:?} != {:?}", + use_labels, rust.use_labels + )); + } + + // Compare label-fmt + let label_format = get_property_quoted_string(emacs, ":label-fmt")?; + match (label_format.as_ref(), rust.label_format) { + (None, None) => {} + (Some(emacs_label_format), Some(rust_label_format)) + if emacs_label_format == rust_label_format => {} + _ => { + this_status = DiffStatus::Bad; + message = Some(format!( + "Label format mismatch (emacs != rust) {:?} != {:?}", + label_format, rust.label_format + )); + } + } Ok(DiffResult { status: this_status, diff --git a/src/parser/lesser_block.rs b/src/parser/lesser_block.rs index 6305b7be..4651d69b 100644 --- a/src/parser/lesser_block.rs +++ b/src/parser/lesser_block.rs @@ -176,7 +176,6 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>( remaining, ExampleBlock { source: source.into(), - name: source.into(), switches, number_lines, retain_labels, @@ -233,9 +232,9 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, SrcBlock<'s>> { - let (remaining, name) = lesser_block_begin("src")(context, input)?; + let (remaining, _name) = lesser_block_begin("src")(context, input)?; // https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block. - let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; + let (remaining, parameters) = opt(tuple((space1, src_switches)))(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?; let lesser_block_end_specialized = lesser_block_end("src"); let contexts = [ @@ -254,17 +253,35 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>( None => None, }; - let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?; + let (remaining, contents) = content(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let source = get_consumed(input, remaining); + let (switches, language, number_lines, retain_labels, use_labels, label_format) = { + if let Some(parameters) = parameters { + ( + Some(parameters.source), + parameters.language, + parameters.number_lines, + parameters.retain_labels, + parameters.use_labels, + parameters.label_format, + ) + } else { + (None, None, None, RetainLabels::Yes, true, None) + } + }; Ok(( remaining, SrcBlock { source: source.into(), - name: name.into(), - switches: parameters.map(|parameters| Into::<&str>::into(parameters)), - contents: contents.into(), + switches, + language, + number_lines, + retain_labels, + use_labels, + label_format, + contents, }, )) } @@ -329,8 +346,9 @@ fn _lesser_block_begin<'b, 'g, 'r, 's, 'c>( } #[derive(Debug)] -struct ExampleSwitches<'s> { +struct ExampleSrcSwitches<'s> { source: &'s str, + language: Option<&'s str>, number_lines: Option, retain_labels: RetainLabels, use_labels: bool, @@ -346,17 +364,50 @@ enum SwitchState { } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn example_switches<'s>(input: OrgSource<'s>) -> Res, ExampleSwitches<'s>> { +fn src_switches<'s>(input: OrgSource<'s>) -> Res, ExampleSrcSwitches<'s>> { + example_src_switches(true)(input) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn example_switches<'s>(input: OrgSource<'s>) -> Res, ExampleSrcSwitches<'s>> { + let (remaining, switches) = example_src_switches(false)(input)?; + debug_assert!(switches.language.is_none()); + Ok((remaining, switches)) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn example_src_switches( + grab_language: bool, +) -> impl for<'s> Fn(OrgSource<'s>) -> Res, ExampleSrcSwitches<'s>> { + move |input| _example_src_switches(input, grab_language) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn _example_src_switches<'s>( + input: OrgSource<'s>, + grab_language: bool, +) -> Res, ExampleSrcSwitches<'s>> { let mut number_lines = None; let mut retain_labels = RetainLabels::Yes; let mut use_labels = true; let mut label_format = None; let mut saw_r = false; + let mut language = None; let (remaining, (source, (words, _))) = consumed(tuple((separated_list1(space1, switch_word), space0)))(input)?; + let mut words_iter = words.into_iter(); + if grab_language { + match words_iter.next() { + Some(l) => { + language = Some(Into::<&str>::into(l)); + } + None => {} + } + } + let mut state = SwitchState::Normal; - for word in words { + for word in words_iter { let normalized_word = Into::<&str>::into(word); loop { match (&state, normalized_word) { @@ -436,8 +487,9 @@ fn example_switches<'s>(input: OrgSource<'s>) -> Res, ExampleSwitc Ok(( remaining, - ExampleSwitches { + ExampleSrcSwitches { source: Into::<&str>::into(source), + language, number_lines, retain_labels, use_labels, diff --git a/src/types/lesser_element.rs b/src/types/lesser_element.rs index f6fe7c91..76b2645e 100644 --- a/src/types/lesser_element.rs +++ b/src/types/lesser_element.rs @@ -49,7 +49,6 @@ pub enum RetainLabels { #[derive(Debug)] pub struct ExampleBlock<'s> { pub source: &'s str, - pub name: &'s str, pub switches: Option<&'s str>, pub number_lines: Option, pub retain_labels: RetainLabels, @@ -69,9 +68,13 @@ pub struct ExportBlock<'s> { #[derive(Debug)] pub struct SrcBlock<'s> { pub source: &'s str, - pub name: &'s str, pub switches: Option<&'s str>, - pub contents: &'s str, + pub language: Option<&'s str>, + pub number_lines: Option, + pub retain_labels: RetainLabels, + pub use_labels: bool, + pub label_format: Option<&'s str>, + pub contents: String, } #[derive(Debug)]