diff --git a/build.rs b/build.rs index 0d45c7cd..79aee4e3 100644 --- a/build.rs +++ b/build.rs @@ -58,7 +58,7 @@ fn write_header(test_file: &mut File) { write!( test_file, r#" -#[feature(exit_status_error)] +#![feature(exit_status_error)] use organic::compare_document; use organic::parser::document; use organic::emacs_parse_org_document; diff --git a/src/parser/org_macro.rs b/src/parser/org_macro.rs index 05d43a8d..43335e7a 100644 --- a/src/parser/org_macro.rs +++ b/src/parser/org_macro.rs @@ -1,10 +1,78 @@ +use nom::bytes::complete::tag; +use nom::character::complete::anychar; +use nom::combinator::not; +use nom::combinator::opt; +use nom::combinator::peek; +use nom::combinator::verify; +use nom::multi::many0; +use nom::multi::separated_list0; + use super::Context; use crate::error::Res; use crate::parser::object::OrgMacro; -use crate::parser::util::not_yet_implemented; +use crate::parser::parser_with_context::parser_with_context; +use crate::parser::util::exit_matcher_parser; +use crate::parser::util::get_consumed; #[tracing::instrument(ret, level = "debug")] pub fn org_macro<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, OrgMacro<'s>> { - not_yet_implemented()?; - todo!(); + let (remaining, _) = tag("{{{")(input)?; + let (remaining, macro_name) = org_macro_name(context, remaining)?; + let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?; + let (remaining, _) = tag("}}}")(remaining)?; + + let source = get_consumed(input, remaining); + Ok(( + remaining, + OrgMacro { + source, + macro_name, + macro_args: macro_args.unwrap_or_else(|| Vec::with_capacity(0)), + }, + )) +} + +#[tracing::instrument(ret, level = "debug")] +fn org_macro_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + let (remaining, _) = verify(anychar, |c| c.is_alphabetic())(input)?; + let (remaining, _) = many0(verify(anychar, |c| { + c.is_alphanumeric() || *c == '-' || *c == '_' + }))(remaining)?; + let source = get_consumed(input, remaining); + Ok((remaining, source)) +} + +#[tracing::instrument(ret, level = "debug")] +fn org_macro_args<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<&'s str>> { + let (remaining, _) = tag("(")(input)?; + let (remaining, args) = + separated_list0(tag(","), parser_with_context!(org_macro_arg)(context))(remaining)?; + let (remaining, _) = tag(")")(remaining)?; + + Ok((remaining, args)) +} + +#[tracing::instrument(ret, level = "debug")] +fn org_macro_arg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + let mut remaining = input; + let mut escaping: bool = false; + loop { + not(parser_with_context!(exit_matcher_parser)(context))(remaining)?; + not(peek(tag("}}}")))(remaining)?; + let (new_remaining, next_char) = anychar(remaining)?; + if escaping { + remaining = new_remaining; + escaping = false; + continue; + } + if next_char == '\\' { + escaping = true; + } + if next_char == ',' || next_char == ')' { + break; + } + remaining = new_remaining; + } + let source = get_consumed(input, remaining); + Ok((remaining, source)) }