Compare commits

...

10 Commits

Author SHA1 Message Date
Tom Alexander
81c0b7079f
Do not include leading slash in citation style.
Some checks failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-10-09 15:48:43 -04:00
Tom Alexander
4a367dd7e0
Include closing semicolon in citation reference. 2023-10-09 15:45:10 -04:00
Tom Alexander
8a0f9d4540
Fix comparing key and mark prefix/suffix as optional. 2023-10-09 15:41:21 -04:00
Tom Alexander
f6155ecf93
Switch to returning ComparePropertiesResult.
This is to support returning lists of child results for properties that contain lists of ast nodes.
2023-10-09 15:33:33 -04:00
Tom Alexander
c077d34933
Populate citation reference properties. 2023-10-09 15:33:33 -04:00
Tom Alexander
1ecc3ecf9d
Fill citation fields. 2023-10-09 15:33:33 -04:00
Tom Alexander
ced35e1694
Merge branch 'footnote_reference_properties' 2023-10-09 15:33:13 -04:00
Tom Alexander
840dc0a750
Support text markup at the start of a regular link description.
Some checks failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-09 14:02:27 -04:00
Tom Alexander
adc5a383c3
Allow text markup at the start of a radio target. 2023-10-09 13:47:36 -04:00
Tom Alexander
5ac12229f4
Fix footnote reference type. 2023-10-09 13:23:08 -04:00
11 changed files with 517 additions and 200 deletions

View File

@ -1,36 +1,46 @@
use std::fmt::Debug;
use super::diff::artificial_diff_scope;
use super::diff::compare_ast_node;
use super::diff::DiffEntry;
use super::diff::DiffStatus;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::get_property;
use super::util::get_property_quoted_string;
use super::util::get_property_unquoted_atom;
use crate::types::AstNode;
#[derive(Debug)]
pub(crate) enum EmacsField<'s> {
Required(&'s str),
#[allow(dead_code)]
Optional(&'s str),
}
#[derive(Debug)]
pub(crate) enum ComparePropertiesResult<'b, 's> {
NoChange,
/// Return when you want the status for "this" node to change (as opposed to collecting child status).
SelfChange(DiffStatus, Option<String>),
DiffEntry(DiffEntry<'b, 's>),
}
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
#[allow(dead_code)]
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
_source: &'s str,
_emacs: &'b Token<'s>,
_rust_node: R,
_emacs_field: &'x str,
_rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
Ok(None)
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
Ok(ComparePropertiesResult::NoChange)
}
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
#[allow(dead_code)]
pub(crate) fn compare_identity() -> () {
()
}
@ -39,11 +49,12 @@ pub(crate) fn compare_identity() -> () {
///
/// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property.
pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
_source: &'s str,
emacs: &'b Token<'s>,
_rust_node: R,
emacs_field: &'x str,
_rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?;
if value.is_some() {
let this_status = DiffStatus::Bad;
@ -51,9 +62,9 @@ pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
"{} was expected to always be nil: {:?}",
emacs_field, value
));
Ok(Some((this_status, message)))
Ok(ComparePropertiesResult::SelfChange(this_status, message))
} else {
Ok(None)
Ok(ComparePropertiesResult::NoChange)
}
}
@ -65,11 +76,12 @@ pub(crate) fn compare_property_quoted_string<
RV: AsRef<str> + std::fmt::Debug,
RG: Fn(R) -> Option<RV>,
>(
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property_quoted_string(emacs, emacs_field)?;
let rust_value = rust_value_getter(rust_node);
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_ref().map(String::as_str) {
@ -78,18 +90,19 @@ pub(crate) fn compare_property_quoted_string<
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
Ok(Some((this_status, message)))
Ok(ComparePropertiesResult::SelfChange(this_status, message))
} else {
Ok(None)
Ok(ComparePropertiesResult::NoChange)
}
}
pub(crate) fn compare_property_unquoted_atom<'b, 's, 'x, R, RG: Fn(R) -> Option<&'s str>>(
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
let value = get_property_unquoted_atom(emacs, emacs_field)?;
let rust_value = rust_value_getter(rust_node);
if rust_value != value {
@ -98,9 +111,9 @@ pub(crate) fn compare_property_unquoted_atom<'b, 's, 'x, R, RG: Fn(R) -> Option<
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
Ok(Some((this_status, message)))
Ok(ComparePropertiesResult::SelfChange(this_status, message))
} else {
Ok(None)
Ok(ComparePropertiesResult::NoChange)
}
}
@ -113,11 +126,12 @@ pub(crate) fn compare_property_list_of_quoted_string<
RI: Iterator<Item = RV>,
RG: Fn(R) -> Option<RI>,
>(
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
) -> 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))?;
@ -132,7 +146,7 @@ pub(crate) fn compare_property_list_of_quoted_string<
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(Some((this_status, message)));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some(rl)) if el.len() != rl.len() => {
let this_status = DiffStatus::Bad;
@ -140,7 +154,7 @@ pub(crate) fn compare_property_list_of_quoted_string<
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(Some((this_status, message)));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some(rl)) => {
for (e, r) in el.iter().zip(rl) {
@ -152,20 +166,21 @@ pub(crate) fn compare_property_list_of_quoted_string<
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
emacs_field, e, r, value, rust_value
));
return Ok(Some((this_status, message)));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
}
}
}
Ok(None)
Ok(ComparePropertiesResult::NoChange)
}
pub(crate) fn compare_property_boolean<'b, 's, 'x, R, RG: Fn(R) -> bool>(
_source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
// get_property already converts nil to None.
let value = get_property(emacs, emacs_field)?.is_some();
let rust_value = rust_value_getter(rust_node);
@ -175,8 +190,62 @@ pub(crate) fn compare_property_boolean<'b, 's, 'x, R, RG: Fn(R) -> bool>(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
Ok(Some((this_status, message)))
Ok(ComparePropertiesResult::SelfChange(this_status, message))
} else {
Ok(None)
Ok(ComparePropertiesResult::NoChange)
}
}
pub(crate) fn compare_property_list_of_ast_nodes<
'b,
's,
'x: 'b + 's,
R,
RV: std::fmt::Debug,
RI: Iterator<Item = RV>,
RG: Fn(R) -> Option<RI>,
>(
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>>
where
AstNode<'b, 's>: From<RV>,
{
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);
// TODO: Seems we are needlessly coverting to a vec here.
let rust_value: Option<Vec<RV>> = rust_value.map(|it| it.collect());
match (value, rust_value) {
(None, None) => {}
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rv
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some(rl)) if el.len() != rl.len() => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, el, rl
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(el), Some(rl)) => {
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rl.len());
for (e, r) in el.iter().zip(rl) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
let diff_scope = artificial_diff_scope(emacs_field, child_status)?;
return Ok(ComparePropertiesResult::DiffEntry(diff_scope));
}
}
Ok(ComparePropertiesResult::NoChange)
}

View File

@ -8,6 +8,7 @@ use super::compare_field::compare_identity;
use super::compare_field::compare_noop;
use super::compare_field::compare_property_always_nil;
use super::compare_field::compare_property_boolean;
use super::compare_field::compare_property_list_of_ast_nodes;
use super::compare_field::compare_property_list_of_quoted_string;
use super::compare_field::compare_property_quoted_string;
use super::compare_field::compare_property_unquoted_atom;
@ -23,6 +24,7 @@ use super::util::get_property_boolean;
use super::util::get_property_numeric;
use super::util::get_property_quoted_string;
use super::util::get_property_unquoted_atom;
use crate::compare::compare_field::ComparePropertiesResult;
use crate::compare::compare_field::EmacsField;
use crate::compare::macros::compare_properties;
use crate::types::AngleLink;
@ -54,6 +56,7 @@ use crate::types::ExportSnippet;
use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::FootnoteReferenceType;
use crate::types::GetStandardProperties;
use crate::types::Heading;
use crate::types::HorizontalRule;
@ -327,8 +330,8 @@ impl<'b, 's> DiffLayer<'b, 's> {
}
}
fn artificial_diff_scope<'b, 's>(
name: &'static str,
pub(crate) fn artificial_diff_scope<'b, 's>(
name: &'s str,
children: Vec<DiffEntry<'b, 's>>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
Ok(DiffLayer {
@ -2597,16 +2600,22 @@ fn compare_bold<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(emacs)? {
this_status = new_status;
message = new_message;
for diff in compare_properties!(emacs) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2631,16 +2640,22 @@ fn compare_italic<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(emacs)? {
this_status = new_status;
message = new_message;
for diff in compare_properties!(emacs) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2665,16 +2680,22 @@ fn compare_underline<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(emacs)? {
this_status = new_status;
message = new_message;
for diff in compare_properties!(emacs) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2682,16 +2703,18 @@ fn compare_underline<'b, 's>(
}
fn compare_verbatim<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b Verbatim<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -2699,16 +2722,22 @@ fn compare_verbatim<'b, 's>(
|r| Some(r.contents),
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2716,16 +2745,18 @@ fn compare_verbatim<'b, 's>(
}
fn compare_code<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b Code<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -2733,16 +2764,22 @@ fn compare_code<'b, 's>(
|r| Some(r.contents),
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2767,16 +2804,22 @@ fn compare_strike_through<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(emacs)? {
this_status = new_status;
message = new_message;
for diff in compare_properties!(emacs) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2801,7 +2844,8 @@ fn compare_regular_link<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -2843,16 +2887,22 @@ fn compare_regular_link<'b, 's>(
|r| r.get_search_option(),
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2877,7 +2927,8 @@ fn compare_radio_link<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -2910,16 +2961,22 @@ fn compare_radio_link<'b, 's>(
compare_identity,
compare_property_always_nil
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2944,7 +3001,8 @@ fn compare_radio_target<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -2952,16 +3010,22 @@ fn compare_radio_target<'b, 's>(
|r| Some(r.value),
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -2969,16 +3033,18 @@ fn compare_radio_target<'b, 's>(
}
fn compare_plain_link<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b PlainLink<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -3020,16 +3086,22 @@ fn compare_plain_link<'b, 's>(
|r| r.search_option,
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -3037,16 +3109,18 @@ fn compare_plain_link<'b, 's>(
}
fn compare_angle_link<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b AngleLink<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -3088,16 +3162,22 @@ fn compare_angle_link<'b, 's>(
|r| r.get_search_option(),
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -3105,16 +3185,18 @@ fn compare_angle_link<'b, 's>(
}
fn compare_org_macro<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b OrgMacro<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -3136,16 +3218,22 @@ fn compare_org_macro<'b, 's>(
},
compare_property_list_of_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -3153,16 +3241,18 @@ fn compare_org_macro<'b, 's>(
}
fn compare_entity<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b Entity<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -3206,16 +3296,22 @@ fn compare_entity<'b, 's>(
|r| r.use_brackets,
compare_property_boolean
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -3223,16 +3319,18 @@ fn compare_entity<'b, 's>(
}
fn compare_latex_fragment<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b LatexFragment<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -3240,16 +3338,22 @@ fn compare_latex_fragment<'b, 's>(
|r| Some(r.value),
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -3257,16 +3361,18 @@ fn compare_latex_fragment<'b, 's>(
}
fn compare_export_snippet<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b ExportSnippet<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
assert_no_children(emacs, &mut this_status, &mut message)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -3279,16 +3385,22 @@ fn compare_export_snippet<'b, 's>(
|r| r.contents,
compare_property_quoted_string
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -3313,7 +3425,8 @@ fn compare_footnote_reference<'b, 's>(
&mut message,
)?;
if let Some((new_status, new_message)) = compare_properties!(
for diff in compare_properties!(
source,
emacs,
rust,
(
@ -3323,12 +3436,21 @@ fn compare_footnote_reference<'b, 's>(
),
(
EmacsField::Required(":type"),
|_| Some("inline"),
|r| Some(match r.get_type() {
FootnoteReferenceType::Standard => "standard",
FootnoteReferenceType::Inline => "inline",
}),
compare_property_unquoted_atom
)
)? {
this_status = new_status;
message = new_message;
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
@ -3343,20 +3465,66 @@ fn compare_footnote_reference<'b, 's>(
}
fn compare_citation<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b Citation<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let this_status = DiffStatus::Good;
let message = None;
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :style :prefix :suffix
compare_children(
source,
emacs,
&rust.children,
&mut child_status,
&mut this_status,
&mut message,
)?;
for diff in compare_properties!(
source,
emacs,
rust,
(
EmacsField::Required(":style"),
|r| r.style,
compare_property_quoted_string
),
(
EmacsField::Optional(":prefix"),
|r| if r.prefix.is_empty() {
None
} else {
Some(r.prefix.iter())
},
compare_property_list_of_ast_nodes
),
(
EmacsField::Optional(":suffix"),
|r| if r.suffix.is_empty() {
None
} else {
Some(r.suffix.iter())
},
compare_property_list_of_ast_nodes
)
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
@ -3364,20 +3532,59 @@ fn compare_citation<'b, 's>(
}
fn compare_citation_reference<'b, 's>(
_source: &'s str,
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b CitationReference<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None;
// TODO: Compare :key :prefix :suffix
assert_no_children(emacs, &mut this_status, &mut message)?;
for diff in compare_properties!(
source,
emacs,
rust,
(
EmacsField::Required(":key"),
|r| Some(r.key),
compare_property_quoted_string
),
(
EmacsField::Optional(":prefix"),
|r| if r.prefix.is_empty() {
None
} else {
Some(r.prefix.iter())
},
compare_property_list_of_ast_nodes
),
(
EmacsField::Optional(":suffix"),
|r| if r.suffix.is_empty() {
None
} else {
Some(r.suffix.iter())
},
compare_property_list_of_ast_nodes
)
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: Vec::new(),
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}

View File

@ -30,10 +30,9 @@
/// }
/// ```
macro_rules! compare_properties {
($emacs:expr, $rust:expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),+) => {
($source:expr, $emacs:expr, $rust:expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),+) => {
{
let mut this_status = DiffStatus::Good;
let mut message: Option<String> = None;
let mut new_status = Vec::new();
let children = $emacs.as_list()?;
let attributes_child = children
.iter()
@ -44,10 +43,9 @@ macro_rules! compare_properties {
if emacs_keys.contains(":standard-properties") {
emacs_keys.remove(":standard-properties");
} else {
this_status = DiffStatus::Bad;
message = Some(format!(
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks :standard-properties field.",
));
))));
}
$(
match $emacs_field {
@ -58,11 +56,10 @@ macro_rules! compare_properties {
emacs_keys.remove(name);
},
EmacsField::Required(name) => {
this_status = DiffStatus::Bad;
message = Some(format!(
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks required field: {}",
name
));
))));
},
EmacsField::Optional(_name) => {},
}
@ -71,11 +68,10 @@ macro_rules! compare_properties {
if !emacs_keys.is_empty() {
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
let unexpected_keys = unexpected_keys.join(", ");
this_status = DiffStatus::Bad;
message = Some(format!(
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token had extra field(s): {}",
unexpected_keys
));
))));
}
$(
@ -87,33 +83,23 @@ macro_rules! compare_properties {
name
},
};
let result = $compare_fn($emacs, $rust, emacs_name, $rust_value_getter)?;
let result = $compare_fn($source, $emacs, $rust, emacs_name, $rust_value_getter)?;
match result {
Some((DiffStatus::Good, _)) => unreachable!("No comparison functions should return Some() when DiffStatus is good."),
Some((status, msg)) => {
this_status = status;
message = msg;
},
_ => {}
ComparePropertiesResult::SelfChange(DiffStatus::Good, _) => unreachable!("No comparison functions should return SelfChange() when DiffStatus is good."),
ComparePropertiesResult::NoChange => {},
result => {
new_status.push(result);
}
}
)+
match this_status {
DiffStatus::Good => {
let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
result
},
_ => {
Ok(Some((this_status, message)))
}
}
new_status
}
};
// Default case for when there are no expected properties except for :standard-properties
($emacs:expr) => {
{
let mut this_status = DiffStatus::Good;
let mut message: Option<String> = None;
let mut new_status = Vec::new();
let children = $emacs.as_list()?;
let attributes_child = children
.iter()
@ -124,30 +110,20 @@ macro_rules! compare_properties {
if emacs_keys.contains(":standard-properties") {
emacs_keys.remove(":standard-properties");
} else {
this_status = DiffStatus::Bad;
message = Some(format!(
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks :standard-properties field.",
));
))));
}
if !emacs_keys.is_empty() {
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
let unexpected_keys = unexpected_keys.join(", ");
this_status = DiffStatus::Bad;
message = Some(format!(
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token had extra field(s): {}",
unexpected_keys
));
}
match this_status {
DiffStatus::Good => {
let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
result
},
_ => {
Ok(Some((this_status, message)))
}
))));
}
new_status
}
};
}

View File

@ -33,6 +33,11 @@ pub(crate) enum ContextElement<'r, 's> {
/// The value stored is the start of the element after the affiliated keywords. In this way, we can ensure that we do not exit an element immediately after the affiliated keyword had been consumed.
HasAffiliatedKeyword(HasAffiliatedKeywordInner<'r, 's>),
/// Indicate the position that we started parsing a text section.
///
/// This value is stored because "<<<" is not a valid prefix for text markup UNLESS it is starting a radio target. Likewise "[" is not a valid prefix for text markup UNLESS it is the start of a regular link description.
StartTextSection(OrgSource<'s>),
/// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement.
#[allow(dead_code)]
Placeholder(PhantomData<&'s str>),

View File

@ -2,6 +2,7 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
@ -37,17 +38,17 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, Citation<'s>> {
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
let (remaining, _) = tag_no_case("[cite")(input)?;
let (remaining, _) = opt(citestyle)(remaining)?;
let (remaining, style) = opt(citestyle)(remaining)?;
let (remaining, _) = tag(":")(remaining)?;
let (remaining, _prefix) =
let (remaining, prefix) =
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let (remaining, _references) =
let (remaining, references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let (remaining, _suffix) = must_balance_bracket(opt(tuple((
tag(";"),
parser_with_context!(global_suffix)(context),
))))(remaining)?;
let (remaining, suffix) = must_balance_bracket(opt(map(
tuple((tag(";"), parser_with_context!(global_suffix)(context))),
|(_, suffix)| suffix,
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@ -56,16 +57,23 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
remaining,
Citation {
source: source.into(),
style: style.map(Into::<&str>::into),
prefix: prefix.unwrap_or(Vec::new()),
suffix: suffix.unwrap_or(Vec::new()),
children: references,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn citestyle<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tuple((tag("/"), style))(input)?;
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
map(
tuple((
tag("/"),
recognize(tuple((style, opt(tuple((tag("/"), variant)))))),
)),
|(_, style)| style,
)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@ -1,6 +1,7 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
@ -33,17 +34,22 @@ pub(crate) fn citation_reference<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CitationReference<'s>> {
let (remaining, _prefix) =
let (remaining, prefix) =
must_balance_bracket(opt(parser_with_context!(key_prefix)(context)))(input)?;
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
let (remaining, _suffix) =
let (remaining, key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
let (remaining, suffix) =
must_balance_bracket(opt(parser_with_context!(key_suffix)(context)))(remaining)?;
let without_closing_semi_remaining = remaining;
let (remaining, _closing_semi) = opt(tag(";"))(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
without_closing_semi_remaining,
CitationReference {
source: source.into(),
key: key.into(),
prefix: prefix.unwrap_or(Vec::new()),
suffix: suffix.unwrap_or(Vec::new()),
},
))
}
@ -53,18 +59,22 @@ pub(crate) fn citation_reference_key<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(tuple((
tag("@"),
many1(verify(
preceded(
not(parser_with_context!(exit_matcher_parser)(context)),
anychar,
),
|c| {
WORD_CONSTITUENT_CHARACTERS.contains(*c) || "-.:?~`'/*@+|(){}<>&_^$#%~".contains(*c)
},
let (remaining, source) = map(
tuple((
tag("@"),
recognize(many1(verify(
preceded(
not(parser_with_context!(exit_matcher_parser)(context)),
anychar,
),
|c| {
WORD_CONSTITUENT_CHARACTERS.contains(*c)
|| "-.:?~`'/*@+|(){}<>&_^$#%~".contains(*c)
},
))),
)),
)))(input)?;
|(_, key)| key,
)(input)?;
Ok((remaining, source))
}

View File

@ -103,11 +103,15 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, RadioTarget<'s>> {
let (remaining, _opening) = tag("<<<")(input)?;
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &radio_target_end,
});
let parser_context = context.with_additional_node(&parser_context);
let contexts = [
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &radio_target_end,
}),
ContextElement::StartTextSection(remaining),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, (raw_value, children)) = consumed(verify(
map(

View File

@ -397,11 +397,15 @@ fn description<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &description_end,
});
let parser_context = context.with_additional_node(&parser_context);
let contexts = [
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &description_end,
}),
ContextElement::StartTextSection(input),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(regular_link_description_set_object)(&parser_context),

View File

@ -283,7 +283,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if start_of_line(input).is_ok() {
@ -292,6 +292,16 @@ fn pre<'b, 'g, 'r, 's>(
if preceded_by_whitespace(true)(input).is_ok() {
return Ok((input, ()));
}
let radio_target_start = context
.iter()
.find_map(|c| match c {
ContextElement::StartTextSection(text) => Some(text),
_ => None,
})
.map(|text| text.get_byte_offset());
if Some(input.get_byte_offset()) == radio_target_start {
return Ok((input, ()));
}
let preceding_character = input.get_preceding_character();
match preceding_character {
// If None, we are at the start of the file which is technically the beginning of a line.

View File

@ -69,6 +69,7 @@ pub use object::DayOfMonthInner;
pub use object::Entity;
pub use object::ExportSnippet;
pub use object::FootnoteReference;
pub use object::FootnoteReferenceType;
pub use object::Hour;
pub use object::HourInner;
pub use object::InlineBabelCall;

View File

@ -200,11 +200,18 @@ pub struct FootnoteReference<'s> {
#[derive(Debug, PartialEq)]
pub struct Citation<'s> {
pub source: &'s str,
pub style: Option<&'s str>,
pub prefix: Vec<Object<'s>>,
pub suffix: Vec<Object<'s>>,
pub children: Vec<CitationReference<'s>>,
}
#[derive(Debug, PartialEq)]
pub struct CitationReference<'s> {
pub source: &'s str,
pub key: &'s str,
pub prefix: Vec<Object<'s>>,
pub suffix: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
@ -764,3 +771,19 @@ impl<'s> OrgMacro<'s> {
.map(|arg| coalesce_whitespace_escaped('\\', |c| ",".contains(c))(*arg))
}
}
#[derive(Debug, PartialEq)]
pub enum FootnoteReferenceType {
Standard,
Inline,
}
impl<'s> FootnoteReference<'s> {
pub fn get_type(&self) -> FootnoteReferenceType {
if self.definition.is_empty() {
FootnoteReferenceType::Standard
} else {
FootnoteReferenceType::Inline
}
}
}