use quote::{quote, ToTokens}; use syn::{ braced, bracketed, parse::{discouraged::Speculative, Parse}, punctuated::Punctuated, token, Token, }; pub struct Command { program: Value, args: Option>, } impl Parse for Command { fn parse(input: syn::parse::ParseStream) -> syn::Result { let program = input.parse()?; if input.is_empty() { Ok(Self { program, args: None, }) } else { _ = input.parse::()?; Ok(Self { program, args: Some(Punctuated::parse_terminated(input)?), }) } } } impl ToTokens for Command { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { program, args } = self; let program = quote! { ::std::process::Command::new(#program) }; let args = args.as_ref().map(Punctuated::iter); tokens.extend(args.map_or_else( || quote! { #program }, |args| { quote! { { let mut _c = #program; #(#args)* _c } } }, )); } } enum LogicArg { Expr(SingleArg), ForIter(ForIter), ForIn(ForIn), IfLet(IfLet), If(If), Match(Match), Closure(Closure), } impl Parse for LogicArg { fn parse(input: syn::parse::ParseStream) -> syn::Result { if input.peek(Token![for]) { let pat_fork = input.fork(); _ = pat_fork.parse::()?; if pat_fork.call(syn::Pat::parse_single).is_ok() && pat_fork.peek(Token![in]) { input.parse().map(Self::ForIn) } else { input.parse().map(Self::ForIter) } } else if input.peek(Token![if]) { if input.peek2(Token![let]) { input.parse().map(Self::IfLet) } else { input.parse().map(Self::If) } } else if input.peek(Token![match]) { input.parse().map(Self::Match) } else if input.peek(Token![||]) { input.parse().map(Self::Closure) } else { input.parse().map(Self::Expr) } } } impl ToTokens for LogicArg { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { Self::Expr(expr) => quote! { #expr }, Self::ForIter(for_iter) => quote! { #for_iter }, Self::ForIn(for_in) => quote! { #for_in }, Self::IfLet(if_let) => quote! { #if_let }, Self::If(if_) => quote! { #if_ }, Self::Match(match_) => quote! { #match_ }, Self::Closure(closure) => quote! { #closure }, }); } } struct ForIter { expr: Value, } impl Parse for ForIter { fn parse(input: syn::parse::ParseStream) -> syn::Result { _ = input.parse::()?; let expr = input.parse()?; Ok(Self { expr }) } } impl ToTokens for ForIter { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let expr = &self.expr; tokens.extend(quote! { for _a in #expr { _c.arg(_a); } }); } } struct ForIn { pattern: syn::Pat, iter: Value, args: Arguments, } impl Parse for ForIn { fn parse(input: syn::parse::ParseStream) -> syn::Result { _ = input.parse::()?; let pattern = input.call(syn::Pat::parse_single)?; _ = input.parse::()?; let iter = input.parse()?; _ = input.parse::]>()?; let args = input.parse()?; Ok(Self { pattern, iter, args, }) } } impl ToTokens for ForIn { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { pattern, iter, args, } = self; tokens.extend(quote! { for #pattern in #iter { #args } }); } } struct IfLet { pattern: syn::Pat, expr: Value, args: Arguments, } impl Parse for IfLet { fn parse(input: syn::parse::ParseStream) -> syn::Result { _ = input.parse::()?; _ = input.parse::()?; let pattern = input.call(syn::Pat::parse_single)?; _ = input.parse::()?; let expr = input.parse()?; _ = input.parse::]>()?; let args = input.parse()?; Ok(Self { pattern, expr, args, }) } } impl ToTokens for IfLet { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { pattern, expr, args, } = &self; tokens.extend(quote! { if let #pattern = #expr { #args } }); } } struct If { expr: Value, args: Arguments, } impl Parse for If { fn parse(input: syn::parse::ParseStream) -> syn::Result { _ = input.parse::()?; let expr = input.parse()?; _ = input.parse::]>()?; let args = input.parse()?; Ok(Self { expr, args }) } } impl ToTokens for If { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { expr, args } = self; tokens.extend(quote! { if #expr { #args } }); } } struct Match { expr: Value, match_arms: Punctuated, } impl Parse for Match { fn parse(input: syn::parse::ParseStream) -> syn::Result { _ = input.parse::()?; let expr = input.parse()?; let arms; braced!(arms in input); let match_arms = Punctuated::parse_terminated(&arms)?; Ok(Self { expr, match_arms }) } } impl ToTokens for Match { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { expr, match_arms } = self; let match_arms = match_arms.iter(); tokens.extend(quote! { match #expr { #(#match_arms)* } }); } } struct MatchArm { pattern: syn::Pat, if_expr: Option, args: Arguments, } impl Parse for MatchArm { fn parse(input: syn::parse::ParseStream) -> syn::Result { let pattern = input.call(syn::Pat::parse_multi)?; let if_expr = if input.peek(Token![if]) { _ = input.parse::()?; Some(input.parse()?) } else { None }; _ = input.parse::]>()?; let args = input.parse()?; Ok(Self { pattern, if_expr, args, }) } } impl ToTokens for MatchArm { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { pattern, if_expr, args, } = self; tokens.extend(if_expr.as_ref().map_or_else( || { quote! { #pattern => { #args } } }, |if_expr| { quote! { #pattern if #if_expr => { #args } } }, )); } } struct Closure(syn::Expr); impl Parse for Closure { fn parse(input: syn::parse::ParseStream) -> syn::Result { _ = input.parse::()?; input.parse().map(Self) } } impl ToTokens for Closure { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self(block) = self; tokens.extend(quote! { let _fn = || #block; for _a in _fn() { _c.arg(_a); } }); } } enum Arguments { Single(SingleArg), Multi(MultiArg), } impl Parse for Arguments { fn parse(input: syn::parse::ParseStream) -> syn::Result { if input.peek(token::Bracket) { input.parse().map(Self::Multi) } else { input.parse().map(Self::Single) } } } impl ToTokens for Arguments { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { Self::Single(arg) => quote! { #arg }, Self::Multi(args) => quote! { #args }, }); } } struct MultiArg(Punctuated); impl Parse for MultiArg { fn parse(input: syn::parse::ParseStream) -> syn::Result { let args; bracketed!(args in input); Punctuated::parse_terminated(&args).map(Self) } } impl ToTokens for MultiArg { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let args = self.0.iter().collect::>(); tokens.extend(quote! { #(#args)* }); } } struct SingleArg(Value); impl Parse for SingleArg { fn parse(input: syn::parse::ParseStream) -> syn::Result { input.parse().map(Self) } } impl ToTokens for SingleArg { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let arg = &self.0; tokens.extend(quote! { _c.arg(#arg); }); } } enum Value { Lit(syn::Lit), Ident(syn::Ident), Expr(syn::Expr), } impl Parse for Value { fn parse(input: syn::parse::ParseStream) -> syn::Result { let expr_fork = input.fork(); expr_fork .parse() .map(|expr| { input.advance_to(&expr_fork); Self::Expr(expr) }) .or_else(|_| { if input.peek(syn::Ident) { input.parse().map(Self::Ident) } else if input.peek(syn::Lit) { input.parse().map(Self::Lit) } else { Err(syn::Error::new( input.span(), "Expected an expression, ident, or literal", )) } }) } } impl ToTokens for Value { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { Self::Lit(lit) => quote! { #lit }, Self::Ident(ident) => quote! { #ident }, Self::Expr(expr) => quote! { #expr }, }); } }