feat: Add more support for patterns and conditional match

This commit is contained in:
2025-01-11 15:52:13 -05:00
parent 0858e04c41
commit 820afa2171
3 changed files with 111 additions and 8 deletions

View File

@@ -64,7 +64,10 @@ enum LogicArg {
impl Parse for LogicArg { impl Parse for LogicArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(Token![for]) { if input.peek(Token![for]) {
if input.peek3(Token![in]) { let pat_fork = input.fork();
_ = pat_fork.parse::<Token![for]>()?;
if pat_fork.call(syn::Pat::parse_single).is_ok() && pat_fork.peek(Token![in]) {
input.parse().map(Self::ForIn) input.parse().map(Self::ForIn)
} else { } else {
input.parse().map(Self::ForIter) input.parse().map(Self::ForIter)
@@ -263,28 +266,54 @@ impl ToTokens for Match {
struct MatchArm { struct MatchArm {
pattern: syn::Pat, pattern: syn::Pat,
if_expr: Option<Value>,
args: Arguments, args: Arguments,
} }
impl Parse for MatchArm { impl Parse for MatchArm {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let pattern = input.call(syn::Pat::parse_multi)?; let pattern = input.call(syn::Pat::parse_multi)?;
let if_expr = if input.peek(Token![if]) {
_ = input.parse::<Token![if]>()?;
Some(input.parse()?)
} else {
None
};
_ = input.parse::<Token![=>]>()?; _ = input.parse::<Token![=>]>()?;
let args = input.parse()?; let args = input.parse()?;
Ok(Self { pattern, args }) Ok(Self {
pattern,
if_expr,
args,
})
} }
} }
impl ToTokens for MatchArm { impl ToTokens for MatchArm {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self { pattern, args } = self; let Self {
pattern,
if_expr,
args,
} = self;
tokens.extend(quote! { tokens.extend(if_expr.as_ref().map_or_else(
|| {
quote! {
#pattern => { #pattern => {
#args #args
} }
}); }
},
|if_expr| {
quote! {
#pattern if #if_expr => {
#args
}
}
},
));
} }
} }

View File

@@ -86,6 +86,29 @@ mod cmd;
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "arg1" "arg2" "multi" "multi1" "multi" "multi2""#.to_string()); /// assert_eq!(format!("{command:?}"), r#""echo" "test" "arg1" "arg2" "multi" "multi1" "multi" "multi2""#.to_string());
/// ``` /// ```
/// ///
/// ## Match Statements
/// ```
/// use comlexr::cmd;
///
/// enum TestArgs {
/// Arg1,
/// Arg2,
/// Arg3,
/// }
///
/// let match_arg = TestArgs::Arg2;
/// let command = cmd!(
/// "echo",
/// "test",
/// match match_arg {
/// TestArgs::Arg1 => "arg1",
/// TestArgs::Arg2 => ["arg1", "arg2"],
/// TestArgs::Arg3 => ["arg1", "arg2", "arg3"],
/// }
/// );
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "arg1" "arg2""#.to_string());
/// ```
///
/// ## Dynamic Closures /// ## Dynamic Closures
/// ``` /// ```
/// use comlexr::cmd; /// use comlexr::cmd;

View File

@@ -62,6 +62,31 @@ fn for_in(#[case] single_iter: &[&str], #[case] multi_iter: &[&str], #[case] exp
assert_eq!(format!("{command:?}"), expected.to_string()); assert_eq!(format!("{command:?}"), expected.to_string());
} }
struct TestStruct(&'static str);
#[rstest]
#[case(&[], &[], r#""echo" "test""#)]
#[case(&[TestStruct("1"), TestStruct("2")], &[], r#""echo" "test" "1" "2""#)]
#[case(&[], &[TestStruct("3"), TestStruct("4")], r#""echo" "test" "multi" "3" "multi" "4""#)]
#[case(&[TestStruct("1"), TestStruct("2")], &[TestStruct("3"), TestStruct("4")], r#""echo" "test" "1" "2" "multi" "3" "multi" "4""#)]
fn for_in_pat(
#[case] single_iter: &[TestStruct],
#[case] multi_iter: &[TestStruct],
#[case] expected: &str,
) {
let command = cmd!(
"echo",
"test",
for TestStruct(arg) in single_iter => arg,
for TestStruct(arg) in multi_iter => [
"multi",
arg,
],
);
assert_eq!(format!("{command:?}"), expected.to_string());
}
#[rstest] #[rstest]
#[case(None, None, r#""echo" "test""#)] #[case(None, None, r#""echo" "test""#)]
#[case(Some("arg"), None, r#""echo" "test" "arg""#)] #[case(Some("arg"), None, r#""echo" "test" "arg""#)]
@@ -105,6 +130,32 @@ fn match_statement(#[case] match_arg: TestArgs, #[case] expected: &str) {
assert_eq!(format!("{command:?}"), expected.to_string()); assert_eq!(format!("{command:?}"), expected.to_string());
} }
#[rstest]
#[case(TestArgs::Arg1, true, r#""echo" "test" "arg1""#)]
#[case(TestArgs::Arg1, false, r#""echo" "test""#)]
#[case(TestArgs::Arg2, true, r#""echo" "test" "arg1" "arg2""#)]
#[case(TestArgs::Arg2, false, r#""echo" "test""#)]
#[case(TestArgs::Arg3, true, r#""echo" "test" "arg1" "arg2" "arg3""#)]
#[case(TestArgs::Arg3, false, r#""echo" "test""#)]
fn match_statement_conditional(
#[case] match_arg: TestArgs,
#[case] flag: bool,
#[case] expected: &str,
) {
let command = cmd!(
"echo",
"test",
match match_arg {
TestArgs::Arg1 if flag => "arg1",
TestArgs::Arg2 if flag => ["arg1", "arg2"],
TestArgs::Arg3 if flag => ["arg1", "arg2", "arg3"],
_ => [],
}
);
assert_eq!(format!("{command:?}"), expected.to_string());
}
#[rstest] #[rstest]
#[case(None, None, r#""echo" "test""#)] #[case(None, None, r#""echo" "test""#)]
#[case(Some("arg"), None, r#""echo" "test" "arg""#)] #[case(Some("arg"), None, r#""echo" "test" "arg""#)]