Parse the babel call.

This commit is contained in:
Tom Alexander 2023-10-05 16:32:40 -04:00
parent 343af41f78
commit 68e392811e
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
8 changed files with 176 additions and 12 deletions

View File

@ -0,0 +1,3 @@
#+call:
#+call:

View File

@ -0,0 +1,3 @@
#+call: foo[inside](bar="baz")[outside]
#+call: foo[](bar="baz")[]

View File

@ -0,0 +1 @@
#+call: foo(bar="baz"

View File

@ -1 +1,7 @@
#+call: foo(bar="baz") #+call: foo(bar="baz")
#+call: lorem ipsum
#+call: dolar cat(dog)
#+call: (bat)

View File

@ -0,0 +1,3 @@
#+call: foo [inside] (bar="baz") [outside]
#+call: foo (bar="baz") [outside]

View File

@ -2403,7 +2403,7 @@ fn compare_babel_call<'b, 's>(
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut message = None; let mut message = None;
// TODO: Compare :call :inside-header :arguments :end-header // TODO: Compare :inside-header :end-header
// TODO: Compare :caption // TODO: Compare :caption
// Compare name // Compare name
@ -2417,19 +2417,55 @@ fn compare_babel_call<'b, 's>(
} }
// Compare value // Compare value
let value = unquote( let value = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new());
get_property(emacs, ":value")?
.ok_or("Emacs keywords should have a :value")?
.as_atom()?,
)?;
if value != rust.value { if value != rust.value {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
"Mismatchs keyword values (emacs != rust) {:?} != {:?}", "Value mismatch (emacs != rust) {:?} != {:?}",
value, rust.value value, rust.value
)) ))
} }
// Compare call
let call = get_property_quoted_string(emacs, ":call")?;
if call.as_ref().map(String::as_str) != rust.call {
this_status = DiffStatus::Bad;
message = Some(format!(
"Call mismatch (emacs != rust) {:?} != {:?}",
call, rust.call
))
}
// Compare arguments
let arguments = get_property_quoted_string(emacs, ":arguments")?;
if arguments.as_ref().map(String::as_str) != rust.arguments {
this_status = DiffStatus::Bad;
message = Some(format!(
"Arguments mismatch (emacs != rust) {:?} != {:?}",
arguments, rust.arguments
))
}
// Compare inside header
let inside_header = get_property_quoted_string(emacs, ":inside-header")?;
if inside_header.as_ref().map(String::as_str) != rust.inside_header {
this_status = DiffStatus::Bad;
message = Some(format!(
"Inside header mismatch (emacs != rust) {:?} != {:?}",
inside_header, rust.inside_header
))
}
// Compare end header
let end_header = get_property_quoted_string(emacs, ":end-header")?;
if end_header.as_ref().map(String::as_str) != rust.end_header {
this_status = DiffStatus::Bad;
message = Some(format!(
"End header mismatch (emacs != rust) {:?} != {:?}",
end_header, rust.end_header
))
}
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: rust.get_elisp_name(), name: rust.get_elisp_name(),

View File

@ -1,10 +1,14 @@
use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
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::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
@ -12,6 +16,7 @@ use nom::InputTake;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
use super::util::get_name; use super::util::get_name;
use super::util::start_of_line;
use super::OrgSource; use super::OrgSource;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
@ -26,8 +31,8 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, BabelCall<'s>> { ) -> Res<OrgSource<'s>, BabelCall<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?; let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, (consumed_input, (_, _, parsed_key, _))) = start_of_line(input)?;
consumed(tuple((space0, tag("#+"), tag_no_case("call"), tag(":"))))(input)?; let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(input)?;
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) { if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@ -37,13 +42,17 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>(
source: Into::<&str>::into(source), source: Into::<&str>::into(source),
name: get_name(&affiliated_keywords), name: get_name(&affiliated_keywords),
value: Into::<&str>::into(line_break.take(0)), value: Into::<&str>::into(line_break.take(0)),
call: None,
inside_header: None,
arguments: None,
end_header: None,
}, },
)); ));
} }
let (remaining, _ws) = space0(remaining)?; let (remaining, _ws) = space0(remaining)?;
let (remaining, parsed_value) = let (remaining, (value, (call, inside_header, arguments, end_header))) =
recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?; consumed(babel_call_value)(remaining)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?; let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@ -53,7 +62,106 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>(
BabelCall { BabelCall {
source: Into::<&str>::into(source), source: Into::<&str>::into(source),
name: get_name(&affiliated_keywords), name: get_name(&affiliated_keywords),
value: Into::<&str>::into(parsed_value), value: Into::<&str>::into(value).trim_end(),
call: call.map(Into::<&str>::into),
inside_header: inside_header.map(Into::<&str>::into),
arguments: arguments.map(Into::<&str>::into),
end_header: end_header.map(Into::<&str>::into),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value<'s>(
input: OrgSource<'s>,
) -> Res<
OrgSource<'s>,
(
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
),
> {
let (remaining, call) = opt(babel_call_call)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
Ok((
remaining,
(call, inside_header, arguments.flatten(), end_header),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(
recognize(many_till(
anychar,
alt((
peek(recognize(one_of("[("))),
recognize(tuple((space0, org_line_ending))),
)),
)),
|s| s.len() > 0,
)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inside_header<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?;
let contents_start = remaining;
let (remaining, contents) = opt(recognize(many_till(
anychar,
alt((
peek(recognize(one_of("]"))),
recognize(tuple((space0, org_line_ending))),
)),
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
Ok((remaining, contents.unwrap_or(contents_start.take(0))))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn arguments<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
let (remaining, _) = tag("(")(input)?;
let (remaining, contents) = opt(verify(
recognize(many_till(
anychar,
alt((
peek(recognize(one_of(")"))),
recognize(tuple((space0, org_line_ending))),
)),
)),
|s| s.len() > 0,
))(remaining)?;
let (remaining, _) = tag(")")(remaining)?;
Ok((remaining, contents))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn end_header<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = space0(input)?;
verify(
recognize(many_till(anychar, peek(tuple((space0, org_line_ending))))),
|s| s.len() > 0,
)(remaining)
}
#[cfg(test)]
mod tests {
use nom::combinator::opt;
use super::*;
#[test]
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("()");
let (remaining, call) = opt(babel_call_call)(input)?;
assert_eq!(Into::<&str>::into(remaining), "()");
assert_eq!(call, None);
Ok(())
}
}

View File

@ -139,6 +139,10 @@ pub struct BabelCall<'s> {
pub source: &'s str, pub source: &'s str,
pub name: Option<&'s str>, pub name: Option<&'s str>,
pub value: &'s str, pub value: &'s str,
pub call: Option<&'s str>,
pub inside_header: Option<&'s str>,
pub arguments: Option<&'s str>,
pub end_header: Option<&'s str>,
} }
#[derive(Debug)] #[derive(Debug)]