Merge branch 'optional_value'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed

This commit is contained in:
Tom Alexander 2023-10-11 19:14:46 -04:00
commit 7da4e4a29b
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
4 changed files with 112 additions and 24 deletions

View File

@ -0,0 +1,8 @@
#+CAPTION[foo]: *bar*
#+CAPTION[*lorem* ipsum]: dolar
1. baz
#+CAPTION[foo]: *bar*
#+CAPTION[*lorem* ipsum]: dolar
# Comments cannot have affiliated keywords so those become regular keywords.

View File

@ -416,14 +416,15 @@ where
/// ///
/// Org-mode seems to store these as a 3-deep list: /// Org-mode seems to store these as a 3-deep list:
/// - Outer list with 1 element per #+caption keyword (or other parsed keyword). /// - Outer list with 1 element per #+caption keyword (or other parsed keyword).
/// - Middle list that seems to always have 1 element. /// - Middle list which has:
/// - Inner list of the objects from each #+caption keyword (or other parsed keyword). /// - first element is a list of objects representing the value after the colon.
/// - every additional element is a list of objects from inside the square brackets (the optional value).
pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes< pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
'b, 'b,
's, 's,
'x, 'x,
R, R,
RG: Fn(R) -> Option<&'b Vec<Vec<Object<'s>>>>, RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>,
>( >(
source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
@ -459,11 +460,16 @@ pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
(Some(value), Some(rust_value)) => (value, rust_value), (Some(value), Some(rust_value)) => (value, rust_value),
}; };
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
// Iterate the outer lists // Iterate the outer lists
for (value, rust_value) in value.iter().zip(rust_value.iter()) { for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) {
// Assert the middle list is a length of 1 because I've never seen it any other way. let mut middle_value = value.as_list()?.iter();
let value = value.as_list()?; // First element of middle list is the mandatory value (the value past the colon).
if value.len() != 1 { let mandatory_value = middle_value.next();
let mandatory_value = match mandatory_value {
Some(mandatory_value) => mandatory_value,
None => {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
@ -471,20 +477,59 @@ pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
// Drill past the middle list to the inner list. };
let value = value
.first() // Compare optional value
.expect("The above if-statement asserts this exists."); if let Some(rust_optional) = rust_optional {
let value = value.as_list()?;
// Compare inner lists
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len()); let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
for (e, r) in value.iter().zip(rust_value) { if rust_optional.len() != middle_value.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
middle_value.len(),
rust_optional.len(),
rust_optional
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in middle_value.zip(rust_optional) {
child_status.push(compare_ast_node(source, e, r.into())?); child_status.push(compare_ast_node(source, e, r.into())?);
} }
let diff_scope = artificial_owned_diff_scope(emacs_field, child_status)?; if !child_status.is_empty() {
return Ok(ComparePropertiesResult::DiffEntry(diff_scope)); let diff_scope = artificial_diff_scope("optional value", child_status)?;
full_status.push(diff_scope);
} }
}
// Compare mandatory value
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
let mandatory_value = mandatory_value.as_list()?;
if rust_value.len() != mandatory_value.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
mandatory_value.len(),
rust_value.len(),
rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in mandatory_value.iter().zip(rust_value) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
if !child_status.is_empty() {
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
full_status.push(diff_scope);
}
}
if full_status.is_empty() {
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} else {
let diff_scope = artificial_owned_diff_scope(emacs_field, full_status)?;
Ok(ComparePropertiesResult::DiffEntry(diff_scope))
}
} }
pub(crate) fn compare_property_number_lines< pub(crate) fn compare_property_number_lines<

View File

@ -1,9 +1,21 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_until;
use nom::character::complete::anychar;
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::map_parser;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::util::confine_context;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::Context; use crate::context::Context;
use crate::context::ContextElement; use crate::context::ContextElement;
@ -45,6 +57,25 @@ pub(crate) fn parse_affiliated_keywords<'g, 's>(
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context)); let initial_context = Context::new(global_settings, List::new(&initial_context));
let (_remaining, optional_objects) = opt(all_consuming(map(
tuple((
take_until("["),
tag("["),
map_parser(
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
confine_context(|i| {
all_consuming(many0(parser_with_context!(standard_set_object)(
&initial_context,
)))(i)
}),
),
tag("]"),
eof,
)),
|(_, _, objects, _, _)| objects,
)))(kw.key.into())
.expect("Object parser should always succeed.");
// TODO: This should be omitting footnote references // TODO: This should be omitting footnote references
let (_remaining, objects) = all_consuming(many0(parser_with_context!( let (_remaining, objects) = all_consuming(many0(parser_with_context!(
standard_set_object standard_set_object
@ -55,7 +86,7 @@ pub(crate) fn parse_affiliated_keywords<'g, 's>(
}); });
match list_of_lists { match list_of_lists {
AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => { AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => {
list_of_lists.push(objects); list_of_lists.push((optional_objects, objects));
} }
_ => panic!("Invalid AffiliatedKeywordValue type."), _ => panic!("Invalid AffiliatedKeywordValue type."),
} }
@ -75,12 +106,16 @@ pub(crate) fn parse_affiliated_keywords<'g, 's>(
} }
fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String { fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String {
let name_until_optval = name
.split_once("[")
.map(|(before, _after)| before)
.unwrap_or(name);
for (src, dst) in global_settings.element_keyword_translation_alist { for (src, dst) in global_settings.element_keyword_translation_alist {
if name.eq_ignore_ascii_case(src) { if name_until_optval.eq_ignore_ascii_case(src) {
return dst.to_lowercase(); return dst.to_lowercase();
} }
} }
name.to_lowercase() name_until_optval.to_lowercase()
} }
fn is_single_string_keyword<'g, 's>( fn is_single_string_keyword<'g, 's>(

View File

@ -6,7 +6,7 @@ use super::Object;
pub enum AffiliatedKeywordValue<'s> { pub enum AffiliatedKeywordValue<'s> {
SingleString(&'s str), SingleString(&'s str),
ListOfStrings(Vec<&'s str>), ListOfStrings(Vec<&'s str>),
ListOfListsOfObjects(Vec<Vec<Object<'s>>>), ListOfListsOfObjects(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
} }
#[derive(Debug)] #[derive(Debug)]