From 3f90ae7c987b16d485a2fb4e9f9bcb3774d37530 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 14 Jul 2026 02:24:11 +0800 Subject: refactor: rename PickerArguments to PickerArgs and PickerRequirement to PickerFlag --- mingling_picker_macros/src/flag.rs | 215 +++++++++++++++++++++++++++++++++++++ mingling_picker_macros/src/lib.rs | 20 ++-- mingling_picker_macros/src/req.rs | 215 ------------------------------------- 3 files changed, 225 insertions(+), 225 deletions(-) create mode 100644 mingling_picker_macros/src/flag.rs delete mode 100644 mingling_picker_macros/src/req.rs (limited to 'mingling_picker_macros') diff --git a/mingling_picker_macros/src/flag.rs b/mingling_picker_macros/src/flag.rs new file mode 100644 index 0000000..b59b04c --- /dev/null +++ b/mingling_picker_macros/src/flag.rs @@ -0,0 +1,215 @@ +use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::TokenStream as TS2; +use quote::quote; + +pub(crate) fn flag(input: TokenStream) -> TokenStream { + let tokens: Vec = input.into_iter().collect(); + let args = split_at_commas(&tokens); + if args.is_empty() { + return quote! { compile_error!("flag! flaguires at least one argument") }.into(); + } + + let first = &args[0]; + let rest = &args[1..]; + + // Validate: at most one char literal + let char_count = rest.iter().filter(|a| is_char_literal(a)).count(); + if char_count > 1 { + return quote! { compile_error!("flag! only supports at most one short name") }.into(); + } + + // Extract short char and string aliases + let short_char: Option = rest + .iter() + .find(|a| is_char_literal(a)) + .and_then(|a| extract_char(a)); + let aliases: Vec = rest + .iter() + .filter(|a| is_string_literal(a)) + .filter_map(|a| extract_string(a)) + .collect(); + + // Parse first argument + let colon_pos = first + .iter() + .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':')); + let has_generics = first + .iter() + .any(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '<')); + + let first_slice = first.as_slice(); + let (name, ty, is_named) = match colon_pos { + Some(pos) if pos > 0 && !is_double_colon(first, pos) => { + // `name : Type` + ( + Some(&first_slice[..pos]), + Some(&first_slice[pos + 1..]), + true, + ) + } + _ => { + if has_generics || !is_single_ident(first) || !has_char_or_string(rest) { + // Type path, generic type, or bare type (no more args) + (None, Some(first_slice), false) + } else { + // Single ident followed by char/string → bare name + (Some(first_slice), None, true) + } + } + }; + + // Build full names + let full_names: Vec = match name { + Some(n) => { + let mut v = vec![join_idents(n)]; + v.extend(aliases); + v + } + None => aliases, + }; + + let path: TS2 = { + let ty_ts: TS2 = ty + .map(|t| { + let ts: TokenStream = t.iter().cloned().collect(); + ts.to_string().parse().unwrap() + }) + .unwrap_or(TS2::new()); + + let import = quote! { ::mingling::picker::PickerFlag }; + if ty.is_some() { + quote! { #import::<#ty_ts> } + } else { + quote! { #import::<_> } + } + }; + + // full: &["name", "alias", ...] or &[] + let full_value: TS2 = if !full_names.is_empty() { + let strs: Vec = full_names + .iter() + .map(|s| proc_macro2::Literal::string(s)) + .collect(); + quote! { &[#(#strs),*] } + } else { + quote! { &[] } + }; + + // short: Some('c') or None + let short_value: TS2 = match short_char { + Some(c) => { + let lit = proc_macro2::Literal::character(c); + quote! { ::std::option::Option::Some(#lit) } + } + None => quote! { ::std::option::Option::None }, + }; + + let positional_value: bool = !is_named; + + let result = quote! { + #path { + full: #full_value, + short: #short_value, + positional: #positional_value, + internal_type: ::std::marker::PhantomData, + } + }; + + result.into() +} + +fn split_at_commas(tokens: &[TokenTree]) -> Vec> { + let mut result = vec![Vec::new()]; + let mut depth = 0u32; + for t in tokens { + match t { + TokenTree::Group(_g) => { + depth += 1; + result.last_mut().unwrap().push(t.clone()); + depth -= 1; + } + TokenTree::Punct(p) if p.as_char() == ',' && depth == 0 => { + result.push(Vec::new()); + } + _ => result.last_mut().unwrap().push(t.clone()), + } + } + result +} + +fn is_double_colon(tokens: &[TokenTree], pos: usize) -> bool { + pos > 0 && matches!(&tokens[pos - 1], TokenTree::Punct(p) if p.as_char() == ':') +} + +fn is_single_ident(tokens: &[TokenTree]) -> bool { + tokens.len() == 1 && matches!(&tokens[0], TokenTree::Ident(_)) +} + +fn is_char_literal(tokens: &[TokenTree]) -> bool { + tokens.len() == 1 + && matches!(&tokens[0], TokenTree::Literal(l) if { + let s = l.to_string(); + s.starts_with('\'') && s.len() >= 3 + }) +} + +fn is_string_literal(tokens: &[TokenTree]) -> bool { + tokens.len() == 1 + && matches!(&tokens[0], TokenTree::Literal(l) if { + l.to_string().starts_with('"') + }) +} + +fn has_char_or_string(args: &[Vec]) -> bool { + args.iter() + .any(|a| is_char_literal(a) || is_string_literal(a)) +} + +fn extract_char(tokens: &[TokenTree]) -> Option { + match &tokens[0] { + TokenTree::Literal(l) => { + let s = l.to_string(); + let cs: Vec = s.chars().collect(); + if cs.len() >= 3 && cs[0] == '\'' && cs[cs.len() - 1] == '\'' { + let inner: String = cs[1..cs.len() - 1].iter().collect(); + match inner.as_str() { + "n" => Some('\n'), + "t" => Some('\t'), + "r" => Some('\r'), + "0" => Some('\0'), + "\\\\" => Some('\\'), + "\\'" => Some('\''), + _ => inner.chars().next(), + } + } else { + None + } + } + _ => None, + } +} + +fn extract_string(tokens: &[TokenTree]) -> Option { + match &tokens[0] { + TokenTree::Literal(l) => { + let s = l.to_string(); + let cs: Vec = s.chars().collect(); + if cs.len() >= 2 && cs[0] == '"' && cs[cs.len() - 1] == '"' { + Some(cs[1..cs.len() - 1].iter().collect()) + } else { + None + } + } + _ => None, + } +} + +fn join_idents(tokens: &[TokenTree]) -> String { + tokens + .iter() + .map(|t| match t { + TokenTree::Ident(id) => id.to_string(), + _ => String::new(), + }) + .collect() +} diff --git a/mingling_picker_macros/src/lib.rs b/mingling_picker_macros/src/lib.rs index a3c93eb..fe1b53c 100644 --- a/mingling_picker_macros/src/lib.rs +++ b/mingling_picker_macros/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; +mod flag; mod internal_repeat; -mod req; /// Core proc-macro: repeats a template body `count` times. /// @@ -11,20 +11,20 @@ pub fn internal_repeat(input: TokenStream) -> TokenStream { internal_repeat::internal_repeat(input) } -/// Quick builder for `PickerRequirement`. +/// Quick builder for `PickerFlag`. /// /// # Syntax /// /// ```ignore -/// use mingling_picker_macros::req; +/// use mingling_picker_macros::flag; /// -/// let basic = req![name: String]; -/// let with_short_name = req![name: String, 'n']; -/// let with_short_alias = req![name: String, 'n', "alias"]; -/// let positional = req![String]; -/// let positional_with_name = req![String, 'n', "alias"]; +/// let basic = flag![name: String]; +/// let with_short_name = flag![name: String, 'n']; +/// let with_short_alias = flag![name: String, 'n', "alias"]; +/// let positional = flag![String]; +/// let positional_with_name = flag![String, 'n', "alias"]; /// ``` #[proc_macro] -pub fn req(input: TokenStream) -> TokenStream { - req::req(input) +pub fn flag(input: TokenStream) -> TokenStream { + flag::flag(input) } diff --git a/mingling_picker_macros/src/req.rs b/mingling_picker_macros/src/req.rs deleted file mode 100644 index 35c9027..0000000 --- a/mingling_picker_macros/src/req.rs +++ /dev/null @@ -1,215 +0,0 @@ -use proc_macro::{TokenStream, TokenTree}; -use proc_macro2::TokenStream as TS2; -use quote::quote; - -pub(crate) fn req(input: TokenStream) -> TokenStream { - let tokens: Vec = input.into_iter().collect(); - let args = split_at_commas(&tokens); - if args.is_empty() { - return quote! { compile_error!("req! requires at least one argument") }.into(); - } - - let first = &args[0]; - let rest = &args[1..]; - - // Validate: at most one char literal - let char_count = rest.iter().filter(|a| is_char_literal(a)).count(); - if char_count > 1 { - return quote! { compile_error!("req! only supports at most one short name") }.into(); - } - - // Extract short char and string aliases - let short_char: Option = rest - .iter() - .find(|a| is_char_literal(a)) - .and_then(|a| extract_char(a)); - let aliases: Vec = rest - .iter() - .filter(|a| is_string_literal(a)) - .filter_map(|a| extract_string(a)) - .collect(); - - // Parse first argument - let colon_pos = first - .iter() - .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':')); - let has_generics = first - .iter() - .any(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '<')); - - let first_slice = first.as_slice(); - let (name, ty, is_named) = match colon_pos { - Some(pos) if pos > 0 && !is_double_colon(first, pos) => { - // `name : Type` - ( - Some(&first_slice[..pos]), - Some(&first_slice[pos + 1..]), - true, - ) - } - _ => { - if has_generics || !is_single_ident(first) || !has_char_or_string(rest) { - // Type path, generic type, or bare type (no more args) - (None, Some(first_slice), false) - } else { - // Single ident followed by char/string → bare name - (Some(first_slice), None, true) - } - } - }; - - // Build full names - let full_names: Vec = match name { - Some(n) => { - let mut v = vec![join_idents(n)]; - v.extend(aliases); - v - } - None => aliases, - }; - - let path: TS2 = { - let ty_ts: TS2 = ty - .map(|t| { - let ts: TokenStream = t.iter().cloned().collect(); - ts.to_string().parse().unwrap() - }) - .unwrap_or(TS2::new()); - - let import = quote! { ::mingling::picker::PickerRequirement }; - if ty.is_some() { - quote! { #import::<#ty_ts> } - } else { - quote! { #import::<_> } - } - }; - - // full: &["name", "alias", ...] or &[] - let full_value: TS2 = if !full_names.is_empty() { - let strs: Vec = full_names - .iter() - .map(|s| proc_macro2::Literal::string(s)) - .collect(); - quote! { &[#(#strs),*] } - } else { - quote! { &[] } - }; - - // short: Some('c') or None - let short_value: TS2 = match short_char { - Some(c) => { - let lit = proc_macro2::Literal::character(c); - quote! { ::std::option::Option::Some(#lit) } - } - None => quote! { ::std::option::Option::None }, - }; - - let positional_value: bool = !is_named; - - let result = quote! { - #path { - full: #full_value, - short: #short_value, - positional: #positional_value, - internal_type: ::std::marker::PhantomData, - } - }; - - result.into() -} - -fn split_at_commas(tokens: &[TokenTree]) -> Vec> { - let mut result = vec![Vec::new()]; - let mut depth = 0u32; - for t in tokens { - match t { - TokenTree::Group(_g) => { - depth += 1; - result.last_mut().unwrap().push(t.clone()); - depth -= 1; - } - TokenTree::Punct(p) if p.as_char() == ',' && depth == 0 => { - result.push(Vec::new()); - } - _ => result.last_mut().unwrap().push(t.clone()), - } - } - result -} - -fn is_double_colon(tokens: &[TokenTree], pos: usize) -> bool { - pos > 0 && matches!(&tokens[pos - 1], TokenTree::Punct(p) if p.as_char() == ':') -} - -fn is_single_ident(tokens: &[TokenTree]) -> bool { - tokens.len() == 1 && matches!(&tokens[0], TokenTree::Ident(_)) -} - -fn is_char_literal(tokens: &[TokenTree]) -> bool { - tokens.len() == 1 - && matches!(&tokens[0], TokenTree::Literal(l) if { - let s = l.to_string(); - s.starts_with('\'') && s.len() >= 3 - }) -} - -fn is_string_literal(tokens: &[TokenTree]) -> bool { - tokens.len() == 1 - && matches!(&tokens[0], TokenTree::Literal(l) if { - l.to_string().starts_with('"') - }) -} - -fn has_char_or_string(args: &[Vec]) -> bool { - args.iter() - .any(|a| is_char_literal(a) || is_string_literal(a)) -} - -fn extract_char(tokens: &[TokenTree]) -> Option { - match &tokens[0] { - TokenTree::Literal(l) => { - let s = l.to_string(); - let cs: Vec = s.chars().collect(); - if cs.len() >= 3 && cs[0] == '\'' && cs[cs.len() - 1] == '\'' { - let inner: String = cs[1..cs.len() - 1].iter().collect(); - match inner.as_str() { - "n" => Some('\n'), - "t" => Some('\t'), - "r" => Some('\r'), - "0" => Some('\0'), - "\\\\" => Some('\\'), - "\\'" => Some('\''), - _ => inner.chars().next(), - } - } else { - None - } - } - _ => None, - } -} - -fn extract_string(tokens: &[TokenTree]) -> Option { - match &tokens[0] { - TokenTree::Literal(l) => { - let s = l.to_string(); - let cs: Vec = s.chars().collect(); - if cs.len() >= 2 && cs[0] == '"' && cs[cs.len() - 1] == '"' { - Some(cs[1..cs.len() - 1].iter().collect()) - } else { - None - } - } - _ => None, - } -} - -fn join_idents(tokens: &[TokenTree]) -> String { - tokens - .iter() - .map(|t| match t { - TokenTree::Ident(id) => id.to_string(), - _ => String::new(), - }) - .collect() -} -- cgit