diff options
-rw-r--r-- | src/makefile/mod.rs | 5 | ||||
-rw-r--r-- | src/makefile/token.rs | 125 |
2 files changed, 92 insertions, 38 deletions
diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs index 00c5051..c118eb9 100644 --- a/src/makefile/mod.rs +++ b/src/makefile/mod.rs @@ -714,10 +714,13 @@ mod test { #[test] fn basic_conditionals() -> R { let file = " -ifeq (1,1) +ifneq (,$(foo bar, $(baz))) +else ifeq (1,1) worked = yes else ifeq (2,2) worked = no +else +worked = perhaps endif "; let args = Args::empty(); diff --git a/src/makefile/token.rs b/src/makefile/token.rs index 076ac60..0ee3e4c 100644 --- a/src/makefile/token.rs +++ b/src/makefile/token.rs @@ -7,11 +7,15 @@ use nom::{ bytes::complete::{tag, take_till1, take_while1}, character::complete::{anychar, space1}, combinator::{all_consuming, map, opt, verify}, + error::{context, convert_error, ContextError, ParseError, VerboseError}, multi::{many1, separated_list1}, sequence::{delimited, pair, preceded, separated_pair}, Finish, IResult, }; +trait Err<'a>: 'a + ParseError<&'a str> + ContextError<&'a str> {} +impl<'a, T: 'a + ParseError<&'a str> + ContextError<&'a str>> Err<'a> for T {} + #[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Eq, Clone, Debug)] pub struct TokenString(Vec<Token>); @@ -150,13 +154,15 @@ impl fmt::Display for Token { } } -fn macro_function_name(input: &str) -> IResult<&str, &str> { +fn macro_function_name<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, &'a str, E> { // POSIX says "periods, underscores, digits, and alphabetics from the portable character set" // one GNUism is a function with a - in the name take_while1(|c: char| c == '.' || c == '_' || c.is_alphanumeric() || c == '-')(input) } -fn macro_expansion_body<'a>(end: char) -> impl FnMut(&'a str) -> IResult<&'a str, Token> + 'a { +fn macro_expansion_body<'a, E: Err<'a>>( + end: char, +) -> impl FnMut(&'a str) -> IResult<&'a str, Token, E> + 'a { let subst = preceded( tag(":"), separated_pair( @@ -165,30 +171,38 @@ fn macro_expansion_body<'a>(end: char) -> impl FnMut(&'a str) -> IResult<&'a str tokens_but_not(vec![end]), ), ); - map( - pair(macro_function_name, opt(subst)), - |(name, replacement)| Token::MacroExpansion { - name: name.into(), - replacement, - }, + context( + "macro_expansion_body", + map( + pair(macro_function_name, opt(subst)), + |(name, replacement)| Token::MacroExpansion { + name: name.into(), + replacement, + }, + ), ) } -fn function_call_body<'a>(end: char) -> impl FnMut(&'a str) -> IResult<&'a str, Token> { - map( - separated_pair( - macro_function_name, - space1, - separated_list1(tag(","), tokens_but_not(vec![',', end])), +fn function_call_body<'a, E: Err<'a>>( + end: char, +) -> impl FnMut(&'a str) -> IResult<&'a str, Token, E> { + context( + "function_call_body", + map( + separated_pair( + macro_function_name, + space1, + separated_list1(tag(","), tokens_but_not(vec![',', end])), + ), + |(name, args)| Token::FunctionCall { + name: name.into(), + args, + }, ), - |(name, args)| Token::FunctionCall { - name: name.into(), - args, - }, ) } -fn parens_macro_expansion(input: &str) -> IResult<&str, Token> { +fn parens_macro_expansion<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, Token, E> { delimited( tag("$("), alt((macro_expansion_body(')'), function_call_body(')'))), @@ -196,15 +210,15 @@ fn parens_macro_expansion(input: &str) -> IResult<&str, Token> { )(input) } -fn braces_macro_expansion(input: &str) -> IResult<&str, Token> { +fn braces_macro_expansion<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, Token, E> { delimited( tag("${"), - alt((macro_expansion_body('}'), function_call_body(')'))), + alt((macro_expansion_body('}'), function_call_body('}'))), tag("}"), )(input) } -fn tiny_macro_expansion(input: &str) -> IResult<&str, Token> { +fn tiny_macro_expansion<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, Token, E> { let raw = preceded(tag("$"), verify(anychar, |&c| c != '(' && c != '{')); map(raw, |c| { if c == '$' { @@ -218,44 +232,56 @@ fn tiny_macro_expansion(input: &str) -> IResult<&str, Token> { })(input) } -fn macro_expansion(input: &str) -> IResult<&str, Token> { - alt(( - tiny_macro_expansion, - parens_macro_expansion, - braces_macro_expansion, - ))(input) +fn macro_expansion<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, Token, E> { + context( + "macro_expansion", + alt(( + tiny_macro_expansion, + parens_macro_expansion, + braces_macro_expansion, + )), + )(input) } -fn text_but_not<'a>(ends: Vec<char>) -> impl FnMut(&'a str) -> IResult<&'a str, Token> { +fn text_but_not<'a, E: Err<'a>>( + ends: Vec<char>, +) -> impl FnMut(&'a str) -> IResult<&'a str, Token, E> { map( take_till1(move |c| c == '$' || ends.contains(&c)), |x: &str| Token::Text(x.into()), ) } -fn single_token_but_not<'a>(ends: Vec<char>) -> impl FnMut(&'a str) -> IResult<&'a str, Token> { +fn single_token_but_not<'a, E: Err<'a>>( + ends: Vec<char>, +) -> impl FnMut(&'a str) -> IResult<&'a str, Token, E> { alt((text_but_not(ends), macro_expansion)) } -fn empty_tokens(input: &str) -> IResult<&str, TokenString> { - map(tag(""), |_| TokenString(vec![Token::Text(String::new())]))(input) +fn empty_tokens<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, TokenString, E> { + context( + "empty_tokens", + map(tag(""), |_| TokenString(vec![Token::Text(String::new())])), + )(input) } -fn tokens_but_not<'a>(ends: Vec<char>) -> impl FnMut(&'a str) -> IResult<&'a str, TokenString> { +fn tokens_but_not<'a, E: Err<'a>>( + ends: Vec<char>, +) -> impl FnMut(&'a str) -> IResult<&'a str, TokenString, E> { alt(( map(many1(single_token_but_not(ends)), TokenString), empty_tokens, )) } -fn full_text_tokens(input: &str) -> IResult<&str, TokenString> { +fn full_text_tokens<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, TokenString, E> { all_consuming(tokens_but_not(vec![]))(input) } pub fn tokenize(input: &str) -> eyre::Result<TokenString> { let (_, result) = full_text_tokens(input) .finish() - .map_err(|err| eyre::eyre!(err.to_string())) + .map_err(|err: VerboseError<&str>| eyre::eyre!(convert_error(input, err))) .with_context(|| format!("couldn't parse {:?}", input))?; Ok(result) } @@ -270,9 +296,9 @@ impl FromStr for TokenString { #[cfg(test)] mod test { - use super::{tokenize, Token, TokenString}; + use super::*; - type R = anyhow::Result<()>; + type R = eyre::Result<()>; impl From<Vec<Token>> for TokenString { fn from(x: Vec<Token>) -> Self { @@ -302,6 +328,13 @@ mod test { } } + fn token_function_call(name: impl Into<String>, args: Vec<impl Into<TokenString>>) -> Token { + Token::FunctionCall { + name: name.into(), + args: args.into_iter().map(|x| x.into()).collect(), + } + } + #[test] fn no_macros() -> R { let text = "This is an example sentence! There aren't macros in it at all!"; @@ -396,4 +429,22 @@ mod test { ); Ok(()) } + + #[test] + fn function_hell() -> R { + dbg!(alt::<_, _, VerboseError<_>, _>(( + macro_expansion_body(')'), + function_call_body(')') + ))("$(foo bar,$(baz))")); + let text = "$(foo bar, $(baz))"; + let tokens = tokenize(text)?; + assert_eq!( + tokens, + TokenString(vec![token_function_call( + "foo", + vec![TokenString::text("bar"), TokenString::r#macro("baz")] + )]) + ); + Ok(()) + } } |