Get macro functional with tests

This commit is contained in:
2025-01-10 18:47:52 -05:00
parent 85dce7f39f
commit 5602c66b11
5 changed files with 667 additions and 86 deletions

View File

@@ -1,7 +1,8 @@
use syn::{bracketed, parse::Parse, punctuated::Punctuated, token, Token};
use quote::{quote, ToTokens};
use syn::{bracketed, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Token};
struct Command {
program: Program,
program: Value,
args: Option<Punctuated<Arg, Token![,]>>,
}
@@ -10,119 +11,257 @@ impl Parse for Command {
let program = input.parse()?;
if input.is_empty() {
Ok(Command {
Ok(Self {
program,
args: None,
})
} else {
_ = input.parse::<Token![,]>()?;
Ok(Command {
Ok(Self {
program,
args: Some(input.parse_terminated(Arg::parse, Token![,])?),
args: Some(Punctuated::parse_terminated(input)?),
})
}
}
}
struct Program(syn::LitStr);
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(|args| args.iter());
impl Parse for Program {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse().map(Self)
tokens.extend(args.map_or_else(
|| quote! { #program },
|args| {
quote! {
{
let mut command = #program;
#(#args)*
command
}
}
},
));
}
}
enum Arg {
Expression(syn::Expr),
ForIter {
for_key: Token![for],
expr: syn::Expr,
},
ForInIterMulti {
for_iter: ForIter,
arrow_token: Token![=>],
args: MultiArg,
},
ForInIterSingle {
for_iter: ForIter,
arrow_token: Token![=>],
arg: SingleArg,
},
IfLetMulti {
if_let: IfLet,
arrow_token: Token![=>],
args: MultiArg,
},
IfLetSingle {
if_let: IfLet,
arrow_token: Token![=>],
arg: SingleArg,
},
IfMulti {
expr: syn::Expr,
arrow_token: Token![=>],
args: MultiArg,
},
IfSingle {
expr: syn::Expr,
arrow_token: Token![=>],
arg: SingleArg,
},
Expr(SingleArg),
ForIter(ForIter),
ForIn(ForIn),
IfLet(IfLet),
If(If),
}
impl Parse for Arg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse()
if input.peek(Token![for]) {
if input.peek3(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 {
input.parse().map(Self::Expr)
}
}
}
struct Pattern(syn::Pat);
impl Parse for Pattern {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.call(syn::Pat::parse_single).map(Self)
impl ToTokens for Arg {
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_ },
});
}
}
struct ForIter {
for_token: Token![for],
pattern: Pattern,
in_token: Token![in],
iter: syn::Expr,
expr: Value,
}
impl Parse for ForIter {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(ForIter {
for_token: input.parse()?,
pattern: input.parse()?,
in_token: input.parse()?,
iter: input.parse()?,
_ = input.parse::<Token![for]>()?;
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 arg in #expr {
command.arg(arg);
}
});
}
}
struct ForIn {
pattern: syn::Pat,
iter: Value,
args: SingleMultiArg,
}
impl Parse for ForIn {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![for]>()?;
let pattern = input.call(syn::Pat::parse_single)?;
_ = input.parse::<Token![in]>()?;
let iter = input.parse()?;
_ = input.parse::<Token![=>]>()?;
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 {
if_token: Token![if],
let_token: Token![let],
pattern: Pattern,
eq_token: Token![=],
expr: syn::Expr,
pattern: syn::Pat,
expr: Value,
args: SingleMultiArg,
}
impl Parse for IfLet {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(IfLet {
if_token: input.parse()?,
let_token: input.parse()?,
pattern: input.parse()?,
eq_token: input.parse()?,
expr: input.parse()?,
_ = input.parse::<Token![if]>()?;
_ = input.parse::<Token![let]>()?;
let pattern = input.call(syn::Pat::parse_single)?;
_ = input.parse::<Token![=]>()?;
let expr = input.parse()?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self {
pattern,
expr,
args,
})
}
}
struct SingleArg(syn::Expr);
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: SingleMultiArg,
}
impl Parse for If {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![if]>()?;
let expr = input.parse()?;
_ = input.parse::<Token![=>]>()?;
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
}
});
}
}
enum SingleMultiArg {
Single(SingleArg),
Multi(MultiArg),
}
impl Parse for SingleMultiArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(token::Bracket) {
input.parse().map(Self::Multi)
} else {
input.parse().map(Self::Single)
}
}
}
impl ToTokens for SingleMultiArg {
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<SingleArg, Token![,]>);
impl Parse for MultiArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
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::<Vec<_>>();
tokens.extend(quote! { #(#args)* });
}
}
struct SingleArg(Value);
impl Parse for SingleArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
@@ -130,17 +269,45 @@ impl Parse for SingleArg {
}
}
struct MultiArg {
bracket: token::Bracket,
args: Punctuated<SingleArg, Token![,]>,
}
impl Parse for MultiArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args;
Ok(MultiArg {
bracket: bracketed!(args in input),
args: input.parse_terminated(SingleArg::parse, Token![,])?,
})
impl ToTokens for SingleArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let arg = &self.0;
tokens.extend(quote! {
command.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<Self> {
if input.peek(syn::Lit) {
input.parse().map(Self::Lit)
} else if input.peek(syn::Ident) {
input.parse().map(Self::Ident)
} else {
input.parse().map(Self::Expr)
}
}
}
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 },
});
}
}
#[proc_macro]
pub fn cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let command = parse_macro_input!(input as Command);
quote! { #command }.into()
}