Compare commits

..

No commits in common. "d2d0e9e5dd3b7097e0f61142a6b9d686623d19a0" and "dea3721b1cf301235389566a137574b36c21af39" have entirely different histories.

15 changed files with 197 additions and 483 deletions

View File

@ -1,42 +0,0 @@
* Elisp Structure
| Keyword | Single | Double | Single Optval | Double Optval |
|---------+---------------+---------------+---------------+---------------|
| CAPTION | objtree | objtree | objtree | objtree |
| DATA | quoted(:name) | quoted(:name) | - | - |
| HEADER | list(quoted) | list(quoted) | - | - |
| NAME | quoted(:name) | quoted(:name) | - | - |
| PLOT | quoted(:plot) | quoted(:plot) | - | - |
| RESULTS | optional pair | optional pair | optional pair | optional pair |
* types
** objtree
Outer list: 1 per keyword
next list: first entry = list of objects for value. remaining entries = optval
** list(quoted)
List of quoted strings, 1 per keyword
** quoted(NAME)
Quoted string under the NAME property (for example quoted(:name))
** optional pair
When optval is supplied this is an alist with the field value being the real value and the 3nd value being the optval.
#+begin_src elisp
("*f*" . "*bar*")
#+end_src
When optval is not supplied this is a list containing a single string of the last occurrence of this keyword.
#+begin_src elisp
("*c*")
#+end_src
* Default settings
#+begin_src text
org-element-dual-keywords ("CAPTION" "RESULTS")
org-element-parsed-keywords ("CAPTION")
org-element-multiple-keywords ("CAPTION" "HEADER")
org-babel-results-keyword "RESULTS"
#+end_src
* Analysis
We don't have an example of a parsed non-dual keyword
Looks like multiple triggers list 1 per keyword
dual triggers support for optval
parsed triggers objects

View File

@ -1,31 +0,0 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
file_path="${DIR}/test_document.org"
for TARGET_VARIABLE in RESULTS CAPTION HEADER DATA NAME PLOT; do
INIT_SCRIPT=$(cat <<EOF
(progn
(erase-buffer)
(require 'org)
(defun org-table-align () t)
(setq vc-handled-backends nil)
(find-file "/input/${file_path}")
(org-mode)
(replace-regexp-in-region "foo" "${TARGET_VARIABLE}")
(message "%s" (pp-to-string (org-element-parse-buffer)))
)
EOF
)
docker run --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -w /input --entrypoint "" organic-test emacs -q --no-site-file --no-splash --batch --eval "$INIT_SCRIPT" 2> "${DIR}/${TARGET_VARIABLE}"
done
# exec docker run --init --rm -i -t --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -w /input --entrypoint "" organic-test emacs -q --no-site-file --no-splash --eval "$INIT_SCRIPT"
# org-element-dual-keywords ("CAPTION" "RESULTS")
# org-element-parsed-keywords ("CAPTION")
# org-element-multiple-keywords ("CAPTION" "HEADER")
# org-babel-results-keyword "RESULTS"

View File

@ -1,25 +0,0 @@
# Single instance
#+foo: *a*
#+begin_example
#+end_example
# Two instances
#+foo: *b*
#+foo: *c*
#+begin_example
#+end_example
# Single with optval
#+foo[*bar*]: *d*
#+begin_example
#+end_example
# Two with optval
#+foo[*bar*]: *e*
#+foo[*bar*]: *f*
#+begin_example
#+end_example

View File

@ -1,16 +0,0 @@
#+results[foo]: bar
#+results[lorem]: ipsum
#+begin_example
baz
#+end_example
#+caption[lorem]: ipsum
#+caption[foo]: bar
#+begin_example
baz
#+end_example
#+header[foo]: bar
#+begin_example
baz
#+end_example

View File

@ -16,6 +16,7 @@ use super::util::get_property_unquoted_atom;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::CharOffsetInLine; use crate::types::CharOffsetInLine;
use crate::types::LineNumber; use crate::types::LineNumber;
use crate::types::Object;
use crate::types::RetainLabels; use crate::types::RetainLabels;
use crate::types::SwitchNumberLines; use crate::types::SwitchNumberLines;
@ -287,107 +288,6 @@ pub(crate) fn compare_property_set_of_quoted_string<
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} }
pub(crate) fn compare_property_optional_pair<
'b,
's,
'x,
R,
RV: AsRef<str> + std::fmt::Debug,
ROV: AsRef<str> + std::fmt::Debug,
RG: Fn(R) -> Option<(Option<ROV>, RV)>,
>(
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?
.map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?;
let rust_value = rust_value_getter(rust_node);
match (value, &rust_value) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some((Some(_), _))) if el.len() != 3 => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some((None, _))) if el.len() != 1 => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some((Some(orl), rl))) => {
let e = el
.first()
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?
.expect("Above match proved length to be 3.");
let oe = el
.get(2)
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?
.expect("Above match proved length to be 3.");
let r = rl.as_ref();
let or = orl.as_ref();
if e != r {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
if oe != or {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
}
(Some(el), Some((None, rl))) => {
let e = el
.first()
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?
.expect("Above match proved length to be 1.");
let r = rl.as_ref();
if e != r {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
}
}
Ok(ComparePropertiesResult::NoChange)
}
pub(crate) fn compare_property_boolean<'b, 's, 'x, R, RG: Fn(R) -> bool>( pub(crate) fn compare_property_boolean<'b, 's, 'x, R, RG: Fn(R) -> bool>(
_source: &'s str, _source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
@ -512,133 +412,118 @@ where
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} }
pub(crate) fn compare_property_object_tree< /// Special compare used for affiliate keywords that are parsed as objects.
///
/// Org-mode seems to store these as a 3-deep list:
/// - Outer list with 1 element per #+caption keyword (or other parsed keyword).
/// - Middle list which has:
/// - 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<
'b, 'b,
's, 's,
'x, 'x,
R, R,
RV: std::fmt::Debug + 'b, RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>,
ROV: std::fmt::Debug + 'b,
RI: Iterator<Item = &'b (Option<Vec<ROV>>, Vec<RV>)> + ExactSizeIterator + std::fmt::Debug,
RG: Fn(R) -> Option<RI>,
>( >(
source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust_node: R, rust_node: R,
emacs_field: &'x str, emacs_field: &'x str,
rust_value_getter: RG, rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> ) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
where // TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier.
AstNode<'b, 's>: From<&'b RV>, let rust_value = rust_value_getter(rust_node);
AstNode<'b, 's>: From<&'b ROV>,
{
let value = get_property(emacs, emacs_field)? let value = get_property(emacs, emacs_field)?
.map(Token::as_list) .map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?; .map_or(Ok(None), |r| r.map(Some))?;
let rust_value = rust_value_getter(rust_node); let (value, rust_value) = match (value, rust_value) {
let (outer_emacs_list, outer_rust_list) = match (value, rust_value) {
(None, None) => { (None, None) => {
return Ok(ComparePropertiesResult::NoChange); return Ok(ComparePropertiesResult::NoChange);
} }
(None, rv @ Some(_)) | (Some(_), rv @ None) => { (None, Some(_)) | (Some(_), 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) {:?} != {:?}",
emacs_field, value, rv emacs_field, value, rust_value
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
(Some(el), Some(rl)) if el.len() != rl.len() => { (Some(value), Some(rust_value)) if value.len() != rust_value.len() => {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, el, rl emacs_field, value, rust_value
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
(Some(el), Some(rl)) => (el, rl), (Some(value), Some(rust_value)) => (value, rust_value),
}; };
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
for (kw_e, kw_r) in outer_emacs_list.into_iter().zip(outer_rust_list) { let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
let kw_e = kw_e.as_list()?;
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0); // Iterate the outer lists
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length); for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) {
if let Some(or) = &kw_r.0 { let mut middle_value = value.as_list()?.iter();
// if optional value // First element of middle list is the mandatory value (the value past the colon).
let mut kw_e = kw_e.into_iter(); let mandatory_value = middle_value.next();
// First element is a list representing the mandatory value. let mandatory_value = match mandatory_value {
if let Some(val_e) = kw_e.next() { Some(mandatory_value) => mandatory_value,
let el = val_e.as_list()?; None => {
if el.len() != kw_r.1.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in el.into_iter().zip(kw_r.1.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
} else {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r emacs_field, value, rust_value
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
// Remaining elements are the optional value. };
if kw_e.len() != or.len() {
// Compare optional value
if let Some(rust_optional) = rust_optional {
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
if rust_optional.len() != middle_value.len() {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}", "{} optional value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field, kw_e, kw_r emacs_field,
middle_value.len(),
rust_optional.len(),
rust_optional
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
for (e, r) in kw_e.zip(or.iter()) { 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())?);
} }
} else { if !child_status.is_empty() {
// if no optional value let diff_scope = artificial_diff_scope("optional value", child_status)?;
if !kw_e.len() == 1 { full_status.push(diff_scope);
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
}
let e = kw_e // Compare mandatory value
.first() let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
.map(Token::as_list) let mandatory_value = mandatory_value.as_list()?;
.map_or(Ok(None), |r| r.map(Some))? if rust_value.len() != mandatory_value.len() {
.expect("The above if-statement proves this will be Some.") let this_status = DiffStatus::Bad;
.iter(); let message = Some(format!(
let r = kw_r.1.iter(); "{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
if e.len() != r.len() { mandatory_value.len(),
let this_status = DiffStatus::Bad; rust_value.len(),
let message = Some(format!( rust_value
"{} mismatch (emacs != rust) {:?} != {:?}", ));
emacs_field, kw_e, kw_r return Ok(ComparePropertiesResult::SelfChange(this_status, message));
)); }
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())?);
for (e, r) in e.zip(r) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
} }
if !child_status.is_empty() { if !child_status.is_empty() {
let diff_scope = artificial_diff_scope("mandatory value", child_status)?; let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
full_status.push(diff_scope); full_status.push(diff_scope);
} }
} }
if full_status.is_empty() { if full_status.is_empty() {
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} else { } else {

View File

@ -1,8 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use super::compare_field::compare_property_list_of_list_of_list_of_ast_nodes;
use super::compare_field::compare_property_list_of_quoted_string; use super::compare_field::compare_property_list_of_quoted_string;
use super::compare_field::compare_property_object_tree;
use super::compare_field::compare_property_optional_pair;
use super::compare_field::compare_property_quoted_string; use super::compare_field::compare_property_quoted_string;
use super::compare_field::ComparePropertiesResult; use super::compare_field::ComparePropertiesResult;
use super::diff::DiffEntry; use super::diff::DiffEntry;
@ -377,23 +376,13 @@ where
)?; )?;
ret.push(diff); ret.push(diff);
} }
AffiliatedKeywordValue::OptionalPair { optval, val } => { AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => {
let diff = compare_property_optional_pair( let diff = compare_property_list_of_list_of_list_of_ast_nodes(
source, source,
emacs, emacs,
rust, rust,
emacs_property_name.as_str(), emacs_property_name.as_str(),
|_| Some((*optval, *val)), |_| Some(rust_value),
)?;
ret.push(diff);
}
AffiliatedKeywordValue::ObjectTree(rust_value) => {
let diff = compare_property_object_tree(
source,
emacs,
rust,
emacs_property_name.as_str(),
|_| Some(rust_value.iter()),
)?; )?;
ret.push(diff); ret.push(diff);
} }

View File

@ -35,86 +35,75 @@ where
let mut ret = BTreeMap::new(); let mut ret = BTreeMap::new();
for kw in input { for kw in input {
let translated_name = translate_name(global_settings, kw.key); let translated_name = translate_name(global_settings, kw.key);
let keyword_type = identify_keyword_type(global_settings, translated_name.as_str()); if is_single_string_keyword(global_settings, translated_name.as_str()) {
match keyword_type { ret.insert(
AffiliatedKeywordType::SingleString => { translated_name,
ret.insert( AffiliatedKeywordValue::SingleString(kw.value),
translated_name, );
AffiliatedKeywordValue::SingleString(kw.value), } else if is_list_of_single_string_keyword(global_settings, translated_name.as_str()) {
); let list_of_strings = ret
} .entry(translated_name)
AffiliatedKeywordType::ListOfStrings => { .or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
let list_of_strings = ret.entry(translated_name).or_insert_with(|| { match list_of_strings {
AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)) AffiliatedKeywordValue::ListOfStrings(list_of_strings)
}); if list_of_strings.is_empty() =>
match list_of_strings { {
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => { list_of_strings.push(kw.value);
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
} }
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.clear();
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
} }
AffiliatedKeywordType::OptionalPair => { } else if is_list_of_objects_keyword(global_settings, translated_name.as_str()) {
let (_remaining, optional_string) = opt(all_consuming(map( let initial_context = ContextElement::document_context();
tuple(( let initial_context = Context::new(global_settings, List::new(&initial_context));
take_until::<_, &str, nom::error::Error<_>>("["),
tag("["), let (_remaining, optional_objects) = opt(all_consuming(map(
tuple((
take_until("["),
tag("["),
map_parser(
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))), recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
tag("]"), confine_context(|i| {
eof, all_consuming(many0(parser_with_context!(standard_set_object)(
)), &initial_context,
|(_, _, objects, _, _)| objects, )))(i)
)))(kw.key.into()) }),
.expect("Parser should always succeed."); ),
ret.insert( tag("]"),
translated_name, eof,
AffiliatedKeywordValue::OptionalPair { )),
optval: optional_string, |(_, _, objects, _, _)| objects,
val: kw.value, )))(kw.key.into())
}, .expect("Object parser should always succeed.");
);
}
AffiliatedKeywordType::ObjectTree => {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
let (_remaining, optional_objects) = opt(all_consuming(map( // TODO: This should be omitting footnote references
tuple(( let (_remaining, objects) = all_consuming(many0(parser_with_context!(
take_until("["), standard_set_object
tag("["), )(&initial_context)))(kw.value.into())
map_parser( .expect("Object parser should always succeed.");
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))), let list_of_lists = ret.entry(translated_name).or_insert_with(|| {
confine_context(|i| { AffiliatedKeywordValue::ListOfListsOfObjects(Vec::with_capacity(1))
all_consuming(many0(parser_with_context!(standard_set_object)( });
&initial_context, match list_of_lists {
)))(i) AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => {
}), list_of_lists.push((optional_objects, objects));
),
tag("]"),
eof,
)),
|(_, _, objects, _, _)| objects,
)))(kw.key.into())
.expect("Object parser should always succeed.");
// TODO: This should be omitting footnote references
let (_remaining, objects) =
all_consuming(many0(parser_with_context!(standard_set_object)(
&initial_context,
)))(kw.value.into())
.expect("Object parser should always succeed.");
let entry_per_keyword_list = ret
.entry(translated_name)
.or_insert_with(|| AffiliatedKeywordValue::ObjectTree(Vec::with_capacity(1)));
match entry_per_keyword_list {
AffiliatedKeywordValue::ObjectTree(entry_per_keyword_list) => {
entry_per_keyword_list.push((optional_objects, objects));
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
} }
_ => panic!("Invalid AffiliatedKeywordValue type."),
} }
}; } else {
let list_of_strings = ret
.entry(translated_name)
.or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
match list_of_strings {
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
}
} }
AffiliatedKeywords { keywords: ret } AffiliatedKeywords { keywords: ret }
} }
@ -132,37 +121,40 @@ fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s
name_until_optval.to_lowercase() name_until_optval.to_lowercase()
} }
enum AffiliatedKeywordType { fn is_single_string_keyword<'g, 's>(
SingleString, _global_settings: &'g GlobalSettings<'g, 's>,
ListOfStrings, name: &'s str,
OptionalPair, ) -> bool {
ObjectTree, // TODO: Is this defined by an elisp variable?
for single_string_name in ["plot", "name"] {
if name.eq_ignore_ascii_case(single_string_name) {
return true;
}
}
false
} }
fn identify_keyword_type<'g, 's>( fn is_list_of_single_string_keyword<'g, 's>(
_global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> bool {
// TODO: Is this defined by an elisp variable?
for single_string_name in ["results"] {
if name.eq_ignore_ascii_case(single_string_name) {
return true;
}
}
false
}
fn is_list_of_objects_keyword<'g, 's>(
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str, name: &'s str,
) -> AffiliatedKeywordType { ) -> bool {
let is_multiple = ["CAPTION", "HEADER"] for parsed_keyword in global_settings.element_parsed_keywords {
.into_iter() if name.eq_ignore_ascii_case(parsed_keyword) {
.any(|candidate| name.eq_ignore_ascii_case(candidate)) return true;
|| name.to_lowercase().starts_with("attr_"); }
let is_parsed = global_settings
.element_parsed_keywords
.iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate));
let can_have_optval = global_settings
.element_dual_keywords
.iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate));
match (is_multiple, is_parsed, can_have_optval) {
(true, true, true) => AffiliatedKeywordType::ObjectTree,
(true, true, false) => unreachable!("Nothing like this exists in upstream org-mode."),
(true, false, true) => unreachable!("Nothing like this exists in upstream org-mode."),
(true, false, false) => AffiliatedKeywordType::ListOfStrings,
(false, true, true) => unreachable!("Nothing like this exists in upstream org-mode."),
(false, true, false) => unreachable!("Nothing like this exists in upstream org-mode."),
(false, false, true) => AffiliatedKeywordType::OptionalPair,
(false, false, false) => AffiliatedKeywordType::SingleString,
} }
false
} }

View File

@ -9,7 +9,6 @@ use super::keyword::affiliated_keyword;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending; use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@ -51,11 +50,8 @@ where
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_diary_sexp<'b, 'g, 'r, 's>( pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
context: RefContext<'b, 'g, 'r, 's>, let (input, _) = many0(affiliated_keyword)(input)?;
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(parser_with_context!(affiliated_keyword)(context))(input)?;
tuple((start_of_line, tag("%%(")))(input)?; tuple((start_of_line, tag("%%(")))(input)?;
Ok((input, ())) Ok((input, ()))
} }

View File

@ -56,8 +56,7 @@ fn _element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>> {
let (post_affiliated_keywords_input, affiliated_keywords) = let (post_affiliated_keywords_input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
many0(parser_with_context!(affiliated_keyword)(context))(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@ -273,14 +272,13 @@ fn _detect_element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
// TODO: unify parsing of affiliated keywords like we did for the element parser.
if alt(( if alt((
parser_with_context!(detect_plain_list)(context), parser_with_context!(detect_plain_list)(context),
parser_with_context!(detect_footnote_definition)(context), detect_footnote_definition,
parser_with_context!(detect_diary_sexp)(context), detect_diary_sexp,
detect_comment, detect_comment,
parser_with_context!(detect_fixed_width_area)(context), detect_fixed_width_area,
parser_with_context!(detect_table)(context), detect_table,
))(input) ))(input)
.is_ok() .is_ok()
{ {

View File

@ -90,11 +90,8 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'b, 'g, 'r, 's>( pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
context: RefContext<'b, 'g, 'r, 's>, let (input, _) = many0(affiliated_keyword)(input)?;
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(parser_with_context!(affiliated_keyword)(context))(input)?;
tuple(( tuple((
start_of_line, start_of_line,
space0, space0,

View File

@ -125,7 +125,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
let (remaining, source) = alt(( let (remaining, source) = alt((
recognize(tuple(( recognize(tuple((
parser_with_context!(maybe_consume_trailing_whitespace)(context), parser_with_context!(maybe_consume_trailing_whitespace)(context),
parser_with_context!(detect_footnote_definition)(context), detect_footnote_definition,
))), ))),
recognize(tuple(( recognize(tuple((
start_of_line, start_of_line,
@ -139,11 +139,8 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_footnote_definition<'b, 'g, 'r, 's>( pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
context: RefContext<'b, 'g, 'r, 's>, let (input, _) = many0(affiliated_keyword)(input)?;
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(parser_with_context!(affiliated_keyword)(context))(input)?;
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?; tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ())) Ok((input, ()))
} }

View File

@ -9,7 +9,6 @@ use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::consumed; use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
@ -22,7 +21,7 @@ use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@ -31,9 +30,15 @@ use crate::parser::util::start_of_line;
use crate::types::AffiliatedKeywords; use crate::types::AffiliatedKeywords;
use crate::types::Keyword; use crate::types::Keyword;
pub(crate) fn filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>( const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
"caption", "data", "headers", "header", "label", "name", "plot", "resname", "results",
"result", "source", "srcname", "tblname",
];
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
pub(crate) fn filtered_keyword<F: Matcher>(
key_parser: F, key_parser: F,
) -> impl Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
move |input| _filtered_keyword(&key_parser, input) move |input| _filtered_keyword(&key_parser, input)
} }
@ -41,7 +46,7 @@ pub(crate) fn filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Or
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(key_parser)) tracing::instrument(ret, level = "debug", skip(key_parser))
)] )]
fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>( fn _filtered_keyword<'s, F: Matcher>(
key_parser: F, key_parser: F,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
@ -108,11 +113,8 @@ where
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>( pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
context: RefContext<'b, 'g, 'r, 's>, filtered_keyword(affiliated_key)(input)
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(parser_with_context!(affiliated_key)(context))(input)
} }
#[cfg_attr( #[cfg_attr(
@ -147,30 +149,18 @@ fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn affiliated_key<'b, 'g, 'r, 's>( fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
parser_with_context!(dual_affiliated_key)(context), recognize(tuple((dual_affiliated_key, tag("["), optval, tag("]")))),
parser_with_context!(plain_affiliated_key)(context), plain_affiliated_key,
export_keyword, export_keyword,
))(input) ))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_affiliated_key<'b, 'g, 'r, 's>( fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
input: OrgSource<'s>, let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in context.get_global_settings().element_affiliated_keywords {
let result = map(
tuple((
tag_no_case::<_, _, CustomError<_>>(*keyword),
peek(tag(":")),
)),
|(key, _)| key,
)(input);
match result { match result {
Ok((remaining, ent)) => { Ok((remaining, ent)) => {
return Ok((remaining, ent)); return Ok((remaining, ent));
@ -185,18 +175,9 @@ fn plain_affiliated_key<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dual_affiliated_key<'b, 'g, 'r, 's>( fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
input: OrgSource<'s>, let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in context.get_global_settings().element_dual_keywords {
let result = recognize(tuple((
tag_no_case::<_, _, CustomError<_>>(*keyword),
tag("["),
optval,
tag("]"),
peek(tag(":")),
)))(input);
match result { match result {
Ok((remaining, ent)) => { Ok((remaining, ent)) => {
return Ok((remaining, ent)); return Ok((remaining, ent));

View File

@ -59,7 +59,7 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(parser_with_context!(affiliated_keyword)(context))(input)?; let (input, _) = many0(affiliated_keyword)(input)?;
if verify( if verify(
tuple(( tuple((
start_of_line, start_of_line,

View File

@ -93,11 +93,8 @@ where
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_table<'b, 'g, 'r, 's>( pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
context: RefContext<'b, 'g, 'r, 's>, let (input, _) = many0(affiliated_keyword)(input)?;
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(parser_with_context!(affiliated_keyword)(context))(input)?;
tuple((start_of_line, space0, tag("|")))(input)?; tuple((start_of_line, space0, tag("|")))(input)?;
Ok((input, ())) Ok((input, ()))
} }

View File

@ -6,11 +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>),
OptionalPair { ListOfListsOfObjects(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
optval: Option<&'s str>,
val: &'s str,
},
ObjectTree(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
} }
#[derive(Debug)] #[derive(Debug)]