22 Commits

Author SHA1 Message Date
Tom Alexander
9f1671658d Merge branch 'object_parser_perf'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 15:04:04 -04:00
Tom Alexander
18d0676fad Clean up. 2023-10-16 15:03:23 -04:00
Tom Alexander
7833a58461 Apply a similar optimization to the detect element parser but also unify detection of affiliated keywords. 2023-10-16 14:55:40 -04:00
Tom Alexander
0020d71089 Extend that optimization to more object parsers. 2023-10-16 14:41:12 -04:00
Tom Alexander
cfdf39d1fa Significantly reduce the use of closures in the object parsers. 2023-10-16 14:25:02 -04:00
Tom Alexander
26f1eae9a1 Merge branch 'planning_before_property_drawer'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 13:48:03 -04:00
Tom Alexander
3eff85059a Add support for planning before property drawer when calculating additional properties for headlines. 2023-10-16 13:35:03 -04:00
Tom Alexander
d2d0e9e5dd Merge branch 'optval_affiliated_keywords'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-16 13:16:44 -04:00
Tom Alexander
c86d1000c0 Do not clear values in lists of strings.
Some checks failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
This is a hold-over from when I had list of single string which was a misunderstanding of the optional pair type.
2023-10-16 12:58:20 -04:00
Tom Alexander
911634cb42 Attr_ affiliated keywords should be lists of strings. 2023-10-16 12:55:18 -04:00
Tom Alexander
0aa746fb1e Implement comparison for object tree. 2023-10-16 12:50:53 -04:00
Tom Alexander
33800c4a88 Implement comparison for optional pair. 2023-10-16 12:05:36 -04:00
Tom Alexander
909ccadfa1 Beginning update to compare_affiliated_keywords. 2023-10-16 11:45:54 -04:00
Tom Alexander
e352deb989 Update parse_affiliated_keywords for handling optional pairs. 2023-10-16 11:42:20 -04:00
Tom Alexander
f5a6a26c43 Disable the existing handling of affiliated keywords. 2023-10-15 20:31:14 -04:00
Tom Alexander
dd7184da54 Add analysis from test. 2023-10-15 20:22:46 -04:00
Tom Alexander
1168ddb1fe Start an investigation into affiliated keyword behavior. 2023-10-15 17:38:56 -04:00
Tom Alexander
77ab636e6a Merge branch 'unify_keyword_constants' 2023-10-15 15:59:21 -04:00
Tom Alexander
f5dcacc79d Do not match keyword name if a longer keyword name would match. 2023-10-15 15:55:19 -04:00
Tom Alexander
e7c3c7aab6 Switch the keyword parsers over to using the settings from GlobalSettings. 2023-10-15 15:17:08 -04:00
Tom Alexander
7603b0a1cc Add a test showing we are not handling optval properly. 2023-10-15 15:16:23 -04:00
Tom Alexander
dea3721b1c Fix reporting errors in tests.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-15 15:16:06 -04:00
20 changed files with 702 additions and 388 deletions

View File

@@ -0,0 +1,42 @@
* 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

@@ -0,0 +1,31 @@
#!/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

@@ -0,0 +1,25 @@
# 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

@@ -0,0 +1,7 @@
** Foo
DEADLINE: <2023-10-16 Mon>
:PROPERTIES:
:foo: *a*
:Bar: *b*
:BAZ: *c*
:END:

View File

@@ -0,0 +1,16 @@
#+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,7 +16,6 @@ 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;
@@ -288,6 +287,107 @@ 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>,
@@ -412,118 +512,133 @@ where
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} }
/// Special compare used for affiliate keywords that are parsed as objects. pub(crate) fn compare_property_object_tree<
///
/// 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,
RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>, RV: std::fmt::Debug + 'b,
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>>
// TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier. where
let rust_value = rust_value_getter(rust_node); AstNode<'b, 's>: From<&'b RV>,
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 (value, rust_value) = match (value, rust_value) { let rust_value = rust_value_getter(rust_node);
let (outer_emacs_list, outer_rust_list) = match (value, rust_value) {
(None, None) => { (None, None) => {
return Ok(ComparePropertiesResult::NoChange); return Ok(ComparePropertiesResult::NoChange);
} }
(None, Some(_)) | (Some(_), None) => { (None, rv @ Some(_)) | (Some(_), rv @ 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, rust_value emacs_field, value, rv
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
(Some(value), Some(rust_value)) if value.len() != rust_value.len() => { (Some(el), Some(rl)) if el.len() != rl.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, value, rust_value emacs_field, el, rl
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
(Some(value), Some(rust_value)) => (value, rust_value), (Some(el), Some(rl)) => (el, rl),
}; };
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len()); for (kw_e, kw_r) in outer_emacs_list.into_iter().zip(outer_rust_list) {
let kw_e = kw_e.as_list()?;
// Iterate the outer lists let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) { let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
let mut middle_value = value.as_list()?.iter(); if let Some(or) = &kw_r.0 {
// First element of middle list is the mandatory value (the value past the colon). // if optional value
let mandatory_value = middle_value.next(); let mut kw_e = kw_e.into_iter();
let mandatory_value = match mandatory_value { // First element is a list representing the mandatory value.
Some(mandatory_value) => mandatory_value, if let Some(val_e) = kw_e.next() {
None => { let el = val_e.as_list()?;
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, value, rust_value emacs_field, kw_e, kw_r
)); ));
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!(
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, emacs_field, kw_e, kw_r
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 middle_value.zip(rust_optional) { for (e, r) in kw_e.zip(or.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?); child_status.push(compare_ast_node(source, e, r.into())?);
} }
if !child_status.is_empty() { } else {
let diff_scope = artificial_diff_scope("optional value", child_status)?; // if no optional value
full_status.push(diff_scope); if !kw_e.len() == 1 {
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));
} }
}
// Compare mandatory value let e = kw_e
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len()); .first()
let mandatory_value = mandatory_value.as_list()?; .map(Token::as_list)
if rust_value.len() != mandatory_value.len() { .map_or(Ok(None), |r| r.map(Some))?
let this_status = DiffStatus::Bad; .expect("The above if-statement proves this will be Some.")
let message = Some(format!( .iter();
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}", let r = kw_r.1.iter();
emacs_field,
mandatory_value.len(), if e.len() != r.len() {
rust_value.len(), let this_status = DiffStatus::Bad;
rust_value let message = Some(format!(
)); "{} mismatch (emacs != rust) {:?} != {:?}",
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); emacs_field, kw_e, kw_r
} ));
for (e, r) in mandatory_value.iter().zip(rust_value) { return Ok(ComparePropertiesResult::SelfChange(this_status, message));
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,7 +1,8 @@
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;
@@ -376,13 +377,23 @@ where
)?; )?;
ret.push(diff); ret.push(diff);
} }
AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => { AffiliatedKeywordValue::OptionalPair { optval, val } => {
let diff = compare_property_list_of_list_of_list_of_ast_nodes( let diff = compare_property_optional_pair(
source, source,
emacs, emacs,
rust, rust,
emacs_property_name.as_str(), emacs_property_name.as_str(),
|_| Some(rust_value), |_| Some((*optval, *val)),
)?;
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,75 +35,86 @@ 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);
if is_single_string_keyword(global_settings, translated_name.as_str()) { let keyword_type = identify_keyword_type(global_settings, translated_name.as_str());
ret.insert( match keyword_type {
translated_name, AffiliatedKeywordType::SingleString => {
AffiliatedKeywordValue::SingleString(kw.value), ret.insert(
); translated_name,
} else if is_list_of_single_string_keyword(global_settings, translated_name.as_str()) { AffiliatedKeywordValue::SingleString(kw.value),
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)
if list_of_strings.is_empty() =>
{
list_of_strings.push(kw.value);
}
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.clear();
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
} }
} else if is_list_of_objects_keyword(global_settings, translated_name.as_str()) { AffiliatedKeywordType::ListOfStrings => {
let initial_context = ContextElement::document_context(); let list_of_strings = ret.entry(translated_name).or_insert_with(|| {
let initial_context = Context::new(global_settings, List::new(&initial_context)); AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1))
});
let (_remaining, optional_objects) = opt(all_consuming(map( match list_of_strings {
tuple(( AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
take_until("["), list_of_strings.push(kw.value);
tag("["), }
map_parser( _ => panic!("Invalid AffiliatedKeywordValue type."),
}
}
AffiliatedKeywordType::OptionalPair => {
let (_remaining, optional_string) = opt(all_consuming(map(
tuple((
take_until::<_, &str, nom::error::Error<_>>("["),
tag("["),
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))), recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
confine_context(|i| { tag("]"),
all_consuming(many0(parser_with_context!(standard_set_object)( eof,
&initial_context, )),
)))(i) |(_, _, objects, _, _)| objects,
}), )))(kw.key.into())
), .expect("Parser should always succeed.");
tag("]"), ret.insert(
eof, translated_name,
)), AffiliatedKeywordValue::OptionalPair {
|(_, _, objects, _, _)| objects, optval: optional_string,
)))(kw.key.into()) val: kw.value,
.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));
// TODO: This should be omitting footnote references let (_remaining, optional_objects) = opt(all_consuming(map(
let (_remaining, objects) = all_consuming(many0(parser_with_context!( tuple((
standard_set_object take_until("["),
)(&initial_context)))(kw.value.into()) tag("["),
.expect("Object parser should always succeed."); map_parser(
let list_of_lists = ret.entry(translated_name).or_insert_with(|| { recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
AffiliatedKeywordValue::ListOfListsOfObjects(Vec::with_capacity(1)) confine_context(|i| {
}); all_consuming(many0(parser_with_context!(standard_set_object)(
match list_of_lists { &initial_context,
AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => { )))(i)
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 }
} }
@@ -121,40 +132,37 @@ fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s
name_until_optval.to_lowercase() name_until_optval.to_lowercase()
} }
fn is_single_string_keyword<'g, 's>( enum AffiliatedKeywordType {
_global_settings: &'g GlobalSettings<'g, 's>, SingleString,
name: &'s str, ListOfStrings,
) -> bool { OptionalPair,
// TODO: Is this defined by an elisp variable? ObjectTree,
for single_string_name in ["plot", "name"] {
if name.eq_ignore_ascii_case(single_string_name) {
return true;
}
}
false
} }
fn is_list_of_single_string_keyword<'g, 's>( fn identify_keyword_type<'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,
) -> bool { ) -> AffiliatedKeywordType {
for parsed_keyword in global_settings.element_parsed_keywords { let is_multiple = ["CAPTION", "HEADER"]
if name.eq_ignore_ascii_case(parsed_keyword) { .into_iter()
return true; .any(|candidate| name.eq_ignore_ascii_case(candidate))
} || 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

@@ -1,11 +1,9 @@
use nom::bytes::complete::is_not; use nom::bytes::complete::is_not;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many0;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
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;
@@ -49,9 +47,19 @@ where
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(
pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { feature = "tracing",
let (input, _) = many0(affiliated_keyword)(input)?; tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
tuple((start_of_line, tag("%%(")))(input)?; )]
pub(crate) fn detect_diary_sexp<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple((start_of_line, tag("%%(")))(remaining)?;
Ok((input, ())) Ok((input, ()))
} }

View File

@@ -1,4 +1,4 @@
use nom::branch::alt;
use nom::multi::many0; use nom::multi::many0;
use super::babel_call::babel_call; use super::babel_call::babel_call;
@@ -56,7 +56,8 @@ 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) = many0(affiliated_keyword)(input)?; let (post_affiliated_keywords_input, affiliated_keywords) =
many0(parser_with_context!(affiliated_keyword)(context))(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@@ -272,22 +273,60 @@ 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>, ()> {
if alt(( let (post_affiliated_keywords_input, affiliated_keywords) =
parser_with_context!(detect_plain_list)(context), many0(parser_with_context!(affiliated_keyword)(context))(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter();
ak_element!(
detect_plain_list,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
ak_element!(
detect_footnote_definition, detect_footnote_definition,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
ak_element!(
detect_diary_sexp, detect_diary_sexp,
detect_comment, &mut affiliated_keywords,
detect_fixed_width_area, post_affiliated_keywords_input,
detect_table, context,
))(input) input
.is_ok() );
{
if let Ok((_, _)) = detect_comment(input) {
return Ok((input, ())); return Ok((input, ()));
} }
ak_element!(
detect_fixed_width_area,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
ak_element!(
detect_table,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError(
Err(nom::Err::Error(CustomError::MyError(MyError(
"No element detected.".into(), "No element detected.".into(),
)))); ))))
} }

View File

@@ -10,7 +10,6 @@ use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
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;
@@ -89,14 +88,24 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
Ok((remaining, value)) Ok((remaining, value))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { feature = "tracing",
let (input, _) = many0(affiliated_keyword)(input)?; tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
)]
pub(crate) fn detect_fixed_width_area<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple(( tuple((
start_of_line, start_of_line,
space0, space0,
tag(":"), tag(":"),
alt((tag(" "), org_line_ending)), alt((tag(" "), org_line_ending)),
))(input)?; ))(remaining)?;
Ok((input, ())) Ok((input, ()))
} }

View File

@@ -12,7 +12,6 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::include_input; use super::util::include_input;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
@@ -125,7 +124,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),
detect_footnote_definition, |i| detect_footnote_definition(std::iter::empty(), i, context, i),
))), ))),
recognize(tuple(( recognize(tuple((
start_of_line, start_of_line,
@@ -138,10 +137,20 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { feature = "tracing",
let (input, _) = many0(affiliated_keyword)(input)?; tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?; )]
pub(crate) fn detect_footnote_definition<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(remaining)?;
Ok((input, ())) Ok((input, ()))
} }

View File

@@ -9,6 +9,7 @@ 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;
@@ -21,7 +22,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::Matcher; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -30,15 +31,9 @@ use crate::parser::util::start_of_line;
use crate::types::AffiliatedKeywords; use crate::types::AffiliatedKeywords;
use crate::types::Keyword; use crate::types::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [ pub(crate) fn filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>(
"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 for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> impl Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
move |input| _filtered_keyword(&key_parser, input) move |input| _filtered_keyword(&key_parser, input)
} }
@@ -46,7 +41,7 @@ pub(crate) fn filtered_keyword<F: Matcher>(
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: Matcher>( fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>(
key_parser: F, key_parser: F,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
@@ -113,8 +108,11 @@ where
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> { pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
filtered_keyword(affiliated_key)(input) context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(parser_with_context!(affiliated_key)(context))(input)
} }
#[cfg_attr( #[cfg_attr(
@@ -149,18 +147,30 @@ 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<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn affiliated_key<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
recognize(tuple((dual_affiliated_key, tag("["), optval, tag("]")))), parser_with_context!(dual_affiliated_key)(context),
plain_affiliated_key, parser_with_context!(plain_affiliated_key)(context),
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<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn plain_affiliated_key<'b, 'g, 'r, 's>(
for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS { context: RefContext<'b, 'g, 'r, 's>,
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input); input: OrgSource<'s>,
) -> 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));
@@ -175,9 +185,18 @@ fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSourc
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn dual_affiliated_key<'b, 'g, 'r, 's>(
for keyword in ORG_ELEMENT_DUAL_KEYWORDS { context: RefContext<'b, 'g, 'r, 's>,
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input); input: OrgSource<'s>,
) -> 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

@@ -1,6 +1,6 @@
/// Parse an element that has affiliated keywords. /// Parse an element that has affiliated keywords.
macro_rules! ak_element { macro_rules! ak_element {
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => { ($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => {
if let Ok((remaining, ele)) = $parser( if let Ok((remaining, ele)) = $parser(
$affiliated_keywords, $affiliated_keywords,
$post_affiliated_keywords_input, $post_affiliated_keywords_input,
@@ -10,7 +10,7 @@ macro_rules! ak_element {
return Ok((remaining, $wrapper(ele))); return Ok((remaining, $wrapper(ele)));
} }
}; };
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => { ($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => {
if let Ok((remaining, ele)) = $parser( if let Ok((remaining, ele)) = $parser(
$affiliated_keywords, $affiliated_keywords,
$post_affiliated_keywords_input, $post_affiliated_keywords_input,
@@ -25,12 +25,12 @@ macro_rules! ak_element {
pub(crate) use ak_element; pub(crate) use ak_element;
macro_rules! element { macro_rules! element {
($parser:ident, $context: expr, $input: expr, $wrapper: expr) => { ($parser:expr, $context: expr, $input: expr, $wrapper: expr) => {
if let Ok((remaining, ele)) = $parser($context, $input) { if let Ok((remaining, ele)) = $parser($context, $input) {
return Ok((remaining, $wrapper(ele))); return Ok((remaining, $wrapper(ele)));
} }
}; };
($parser:ident, $context: expr, $input: expr) => { ($parser:expr, $context: expr, $input: expr) => {
if let Ok((remaining, ele)) = $parser($context, $input) { if let Ok((remaining, ele)) = $parser($context, $input) {
return Ok((remaining, ele)); return Ok((remaining, ele));
} }

View File

@@ -1,11 +1,7 @@
use nom::branch::alt;
use nom::combinator::map;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::plain_text::plain_text; use super::plain_text::plain_text;
use super::regular_link::regular_link; use super::regular_link::regular_link;
use super::subscript_and_superscript::detect_subscript_or_superscript; use super::subscript_and_superscript::detect_subscript_or_superscript;
use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -19,6 +15,7 @@ use crate::parser::inline_babel_call::inline_babel_call;
use crate::parser::inline_source_block::inline_source_block; use crate::parser::inline_source_block::inline_source_block;
use crate::parser::latex_fragment::latex_fragment; use crate::parser::latex_fragment::latex_fragment;
use crate::parser::line_break::line_break; use crate::parser::line_break::line_break;
use crate::parser::macros::element;
use crate::parser::org_macro::org_macro; use crate::parser::org_macro::org_macro;
use crate::parser::plain_link::plain_link; use crate::parser::plain_link::plain_link;
use crate::parser::radio_link::radio_link; use crate::parser::radio_link::radio_link;
@@ -39,14 +36,14 @@ pub(crate) fn standard_set_object<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( element!(standard_set_object_sans_plain_text, context, input);
parser_with_context!(standard_set_object_sans_plain_text)(context), element!(
map( plain_text(detect_standard_set_object_sans_plain_text),
parser_with_context!(plain_text(detect_standard_set_object_sans_plain_text))(context), context,
Object::PlainText, input,
), Object::PlainText
))(input)?; );
Ok((remaining, object)) Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
} }
#[cfg_attr( #[cfg_attr(
@@ -57,14 +54,14 @@ pub(crate) fn minimal_set_object<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( element!(minimal_set_object_sans_plain_text, context, input);
parser_with_context!(minimal_set_object_sans_plain_text)(context), element!(
map( plain_text(detect_minimal_set_object_sans_plain_text),
parser_with_context!(plain_text(detect_minimal_set_object_sans_plain_text))(context), context,
Object::PlainText, input,
), Object::PlainText
))(input)?; );
Ok((remaining, object)) Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
} }
#[cfg_attr( #[cfg_attr(
@@ -75,56 +72,38 @@ fn standard_set_object_sans_plain_text<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( element!(timestamp, context, input, Object::Timestamp);
map(parser_with_context!(timestamp)(context), Object::Timestamp), element!(subscript, context, input, Object::Subscript);
map(parser_with_context!(subscript)(context), Object::Subscript), element!(superscript, context, input, Object::Superscript);
map( element!(statistics_cookie, context, input, Object::StatisticsCookie);
parser_with_context!(superscript)(context), element!(target, context, input, Object::Target);
Object::Superscript, element!(line_break, context, input, Object::LineBreak);
), element!(
map( inline_source_block,
parser_with_context!(statistics_cookie)(context), context,
Object::StatisticsCookie, input,
), Object::InlineSourceBlock
map(parser_with_context!(target)(context), Object::Target), );
map(parser_with_context!(line_break)(context), Object::LineBreak), element!(inline_babel_call, context, input, Object::InlineBabelCall);
map( element!(citation, context, input, Object::Citation);
parser_with_context!(inline_source_block)(context), element!(
Object::InlineSourceBlock, footnote_reference,
), context,
map( input,
parser_with_context!(inline_babel_call)(context), Object::FootnoteReference
Object::InlineBabelCall, );
), element!(export_snippet, context, input, Object::ExportSnippet);
map(parser_with_context!(citation)(context), Object::Citation), element!(entity, context, input, Object::Entity);
map( element!(latex_fragment, context, input, Object::LatexFragment);
parser_with_context!(footnote_reference)(context), element!(radio_link, context, input, Object::RadioLink);
Object::FootnoteReference, element!(radio_target, context, input, Object::RadioTarget);
), element!(text_markup, context, input);
map( element!(regular_link, context, input, Object::RegularLink);
parser_with_context!(export_snippet)(context), element!(plain_link, context, input, Object::PlainLink);
Object::ExportSnippet, element!(angle_link, context, input, Object::AngleLink);
), element!(org_macro, context, input, Object::OrgMacro);
map(parser_with_context!(entity)(context), Object::Entity),
map( Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink),
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
parser_with_context!(text_markup)(context),
map(
parser_with_context!(regular_link)(context),
Object::RegularLink,
),
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
))(input)?;
Ok((remaining, object))
} }
#[cfg_attr( #[cfg_attr(
@@ -135,20 +114,12 @@ fn minimal_set_object_sans_plain_text<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( element!(subscript, context, input, Object::Subscript);
map(parser_with_context!(subscript)(context), Object::Subscript), element!(superscript, context, input, Object::Superscript);
map( element!(entity, context, input, Object::Entity);
parser_with_context!(superscript)(context), element!(latex_fragment, context, input, Object::LatexFragment);
Object::Superscript, element!(text_markup, context, input);
), Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
))(input)?;
Ok((remaining, object))
} }
#[cfg_attr( #[cfg_attr(
@@ -200,16 +171,18 @@ pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]] // TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
let (remaining, object) = alt(( element!(
parser_with_context!(regular_link_description_set_object_sans_plain_text)(context), regular_link_description_set_object_sans_plain_text,
map( context,
parser_with_context!(plain_text( input
detect_regular_link_description_set_object_sans_plain_text );
))(context), element!(
Object::PlainText, plain_text(detect_regular_link_description_set_object_sans_plain_text),
), context,
))(input)?; input,
Ok((remaining, object)) Object::PlainText
);
Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
} }
#[cfg_attr( #[cfg_attr(
@@ -221,27 +194,18 @@ fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]] // TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
let (remaining, object) = alt(( element!(export_snippet, context, input, Object::ExportSnippet);
map( element!(statistics_cookie, context, input, Object::StatisticsCookie);
parser_with_context!(export_snippet)(context), element!(
Object::ExportSnippet, inline_source_block,
), context,
map( input,
parser_with_context!(statistics_cookie)(context), Object::InlineSourceBlock
Object::StatisticsCookie, );
), element!(inline_babel_call, context, input, Object::InlineBabelCall);
map( element!(org_macro, context, input, Object::OrgMacro);
parser_with_context!(inline_source_block)(context), element!(minimal_set_object_sans_plain_text, context, input);
Object::InlineSourceBlock, Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?;
Ok((remaining, object))
} }
#[cfg_attr( #[cfg_attr(
@@ -272,14 +236,14 @@ pub(crate) fn table_cell_set_object<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( element!(table_cell_set_object_sans_plain_text, context, input);
parser_with_context!(table_cell_set_object_sans_plain_text)(context), element!(
map( plain_text(detect_table_cell_set_object_sans_plain_text),
parser_with_context!(plain_text(detect_table_cell_set_object_sans_plain_text))(context), context,
Object::PlainText, input,
), Object::PlainText
))(input)?; );
Ok((remaining, object)) Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
} }
#[cfg_attr( #[cfg_attr(
@@ -290,33 +254,24 @@ fn table_cell_set_object_sans_plain_text<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( element!(citation, context, input, Object::Citation);
map(parser_with_context!(citation)(context), Object::Citation), element!(export_snippet, context, input, Object::ExportSnippet);
map( element!(
parser_with_context!(export_snippet)(context), footnote_reference,
Object::ExportSnippet, context,
), input,
map( Object::FootnoteReference
parser_with_context!(footnote_reference)(context), );
Object::FootnoteReference, element!(radio_link, context, input, Object::RadioLink);
), element!(regular_link, context, input, Object::RegularLink);
map(parser_with_context!(radio_link)(context), Object::RadioLink), element!(plain_link, context, input, Object::PlainLink);
map( element!(angle_link, context, input, Object::AngleLink);
parser_with_context!(regular_link)(context), element!(org_macro, context, input, Object::OrgMacro);
Object::RegularLink, element!(radio_target, context, input, Object::RadioTarget);
), element!(target, context, input, Object::Target);
map(parser_with_context!(plain_link)(context), Object::PlainLink), element!(timestamp, context, input, Object::Timestamp);
map(parser_with_context!(angle_link)(context), Object::AngleLink), element!(minimal_set_object_sans_plain_text, context, input);
map(parser_with_context!(org_macro)(context), Object::OrgMacro), Err(nom::Err::Error(CustomError::MyError(MyError("No object."))))
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(timestamp)(context), Object::Timestamp),
parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?;
Ok((remaining, object))
} }
#[cfg_attr( #[cfg_attr(

View File

@@ -20,7 +20,6 @@ use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::element_parser::element; use super::element_parser::element;
use super::keyword::affiliated_keyword;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::include_input; use super::util::include_input;
@@ -53,13 +52,17 @@ use crate::types::PlainListType;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, _affiliated_keywords))
)] )]
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>( pub(crate) fn detect_plain_list<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'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(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
if verify( if verify(
tuple(( tuple((
start_of_line, start_of_line,
@@ -70,7 +73,7 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|(_start, (indent_level, _), (_bullet_type, bull), _after_whitespace)| { |(_start, (indent_level, _), (_bullet_type, bull), _after_whitespace)| {
!Into::<&str>::into(bull).starts_with("*") || *indent_level > 0 !Into::<&str>::into(bull).starts_with("*") || *indent_level > 0
}, },
)(input) )(remaining)
.is_ok() .is_ok()
{ {
return Ok((input, ())); return Ok((input, ()));
@@ -730,7 +733,7 @@ dolar"#,
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
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 result = detect_plain_list(&initial_context, input); let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@@ -740,7 +743,7 @@ dolar"#,
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
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 result = detect_plain_list(&initial_context, input); let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@@ -750,7 +753,7 @@ dolar"#,
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
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 result = detect_plain_list(&initial_context, input); let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
// Since there is no whitespace after the '+' this is a paragraph, not a plain list. // Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -761,7 +764,7 @@ dolar"#,
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
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 result = detect_plain_list(&initial_context, input); let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
} }

View File

@@ -14,7 +14,6 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::keyword::table_formula_keyword; use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object; use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@@ -92,10 +91,20 @@ where
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { feature = "tracing",
let (input, _) = many0(affiliated_keyword)(input)?; tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
tuple((start_of_line, space0, tag("|")))(input)?; )]
pub(crate) fn detect_table<'b, 'g, 'r, 's, AK>(
_affiliated_keywords: AK,
remaining: OrgSource<'s>,
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
tuple((start_of_line, space0, tag("|")))(remaining)?;
Ok((input, ())) Ok((input, ()))
} }

View File

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

View File

@@ -101,11 +101,15 @@ impl<'s> Heading<'s> {
_ => None, _ => None,
}) })
.flat_map(|section| section.children.iter()) .flat_map(|section| section.children.iter())
.take(1) .take_while(|element| match element {
.filter_map(|element| match element { Element::Planning(_) | Element::PropertyDrawer(_) => true,
_ => false,
})
.find_map(|element| match element {
Element::PropertyDrawer(property_drawer) => Some(property_drawer), Element::PropertyDrawer(property_drawer) => Some(property_drawer),
_ => None, _ => None,
}) })
.into_iter()
.flat_map(|property_drawer| property_drawer.children.iter()) .flat_map(|property_drawer| property_drawer.children.iter())
} }
} }

View File

@@ -5,7 +5,7 @@
async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{ async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}"; let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
organic::compare::run_anonymous_compare(org_contents.as_str()).await?; assert!(organic::compare::run_anonymous_compare(org_contents.as_str()).await?);
Ok(()) Ok(())
}} }}
@@ -19,7 +19,7 @@ async fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.list_allow_alphabetical = true; global_settings.list_allow_alphabetical = true;
global_settings global_settings
}}; }};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
Ok(()) Ok(())
}} }}
@@ -33,7 +33,7 @@ async fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.tab_width = 1; global_settings.tab_width = 1;
global_settings global_settings
}}; }};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
Ok(()) Ok(())
}} }}
@@ -47,7 +47,7 @@ async fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.tab_width = 16; global_settings.tab_width = 16;
global_settings global_settings
}}; }};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
Ok(()) Ok(())
}} }}
@@ -61,6 +61,6 @@ async fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd; global_settings.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd;
global_settings global_settings
}}; }};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
Ok(()) Ok(())
}} }}