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/src/picker.rs | 48 ++++---- mingling_picker/src/picker/patterns.rs | 12 +- mingling_picker/src/requirement.rs | 6 +- mingling_picker_macros/src/flag.rs | 215 +++++++++++++++++++++++++++++++++ mingling_picker_macros/src/lib.rs | 20 +-- mingling_picker_macros/src/req.rs | 215 --------------------------------- 6 files changed, 258 insertions(+), 258 deletions(-) create mode 100644 mingling_picker_macros/src/flag.rs delete mode 100644 mingling_picker_macros/src/req.rs diff --git a/mingling_picker/src/picker.rs b/mingling_picker/src/picker.rs index 01bebc7..ecf59b0 100644 --- a/mingling_picker/src/picker.rs +++ b/mingling_picker/src/picker.rs @@ -12,7 +12,7 @@ pub use patterns::*; /// - Parsing results pub struct Picker<'a> { /// Internal arguments of Picker - args: PickerArguments<'a>, + args: PickerArgs<'a>, } /// Internal arguments of Picker @@ -20,7 +20,7 @@ pub struct Picker<'a> { /// - `Slice` - borrowed slice of string slices /// - `Vec` - owned vector of borrowed string slices /// - `Owned` - owned vector of owned strings -pub enum PickerArguments<'a> { +pub enum PickerArgs<'a> { /// Borrowed slice of string slices Slice(&'a [&'a str]), /// Owned vector of borrowed string slices @@ -32,7 +32,7 @@ pub enum PickerArguments<'a> { impl<'a> From<&'a [&'a str]> for Picker<'a> { fn from(value: &'a [&'a str]) -> Self { Picker { - args: PickerArguments::Slice(value), + args: PickerArgs::Slice(value), } } } @@ -40,7 +40,7 @@ impl<'a> From<&'a [&'a str]> for Picker<'a> { impl<'a> From> for Picker<'a> { fn from(value: Vec<&'a str>) -> Self { Picker { - args: PickerArguments::Vec(value), + args: PickerArgs::Vec(value), } } } @@ -48,7 +48,7 @@ impl<'a> From> for Picker<'a> { impl<'a> From> for Picker<'a> { fn from(value: Vec) -> Self { Picker { - args: PickerArguments::Owned(value), + args: PickerArgs::Owned(value), } } } @@ -75,9 +75,9 @@ impl<'a> Picker<'a> { /// ``` pub fn len(&self) -> usize { match &self.args { - PickerArguments::Slice(items) => items.len(), - PickerArguments::Vec(items) => items.len(), - PickerArguments::Owned(items) => items.len(), + PickerArgs::Slice(items) => items.len(), + PickerArgs::Vec(items) => items.len(), + PickerArgs::Owned(items) => items.len(), } } @@ -119,9 +119,9 @@ impl<'a> Picker<'a> { /// ``` pub fn iter(&'a self) -> PickerIter<'a> { match &self.args { - PickerArguments::Slice(items) => PickerIter::Slice(items.iter()), - PickerArguments::Vec(items) => PickerIter::Vec(items.iter()), - PickerArguments::Owned(items) => PickerIter::Owned(items.iter()), + PickerArgs::Slice(items) => PickerIter::Slice(items.iter()), + PickerArgs::Vec(items) => PickerIter::Vec(items.iter()), + PickerArgs::Owned(items) => PickerIter::Owned(items.iter()), } } } @@ -131,9 +131,9 @@ impl<'a> Index for Picker<'a> { fn index(&self, index: usize) -> &Self::Output { match &self.args { - PickerArguments::Slice(items) => items[index], - PickerArguments::Vec(items) => items[index], - PickerArguments::Owned(items) => &items[index], + PickerArgs::Slice(items) => items[index], + PickerArgs::Vec(items) => items[index], + PickerArgs::Owned(items) => &items[index], } } } @@ -143,9 +143,9 @@ impl<'a> Index for &Picker<'a> { fn index(&self, index: usize) -> &Self::Output { match &self.args { - PickerArguments::Slice(items) => items[index], - PickerArguments::Vec(items) => items[index], - PickerArguments::Owned(items) => &items[index], + PickerArgs::Slice(items) => items[index], + PickerArgs::Vec(items) => items[index], + PickerArgs::Owned(items) => &items[index], } } } @@ -156,9 +156,9 @@ impl<'a> IntoIterator for &'a Picker<'a> { fn into_iter(self) -> Self::IntoIter { match &self.args { - PickerArguments::Slice(items) => PickerIter::Slice(items.iter()), - PickerArguments::Vec(items) => PickerIter::Vec(items.iter()), - PickerArguments::Owned(items) => PickerIter::Owned(items.iter()), + PickerArgs::Slice(items) => PickerIter::Slice(items.iter()), + PickerArgs::Vec(items) => PickerIter::Vec(items.iter()), + PickerArgs::Owned(items) => PickerIter::Owned(items.iter()), } } } @@ -166,7 +166,7 @@ impl<'a> IntoIterator for &'a Picker<'a> { /// Iterator for `Picker`, yielding owned `String` values. /// /// This enum wraps the underlying slice iterators for each variant of -/// `PickerArguments`, allowing uniform iteration over borrowed or owned data. +/// `PickerArgs`, allowing uniform iteration over borrowed or owned data. pub enum PickerIter<'a> { /// Iterates over a borrowed slice (`&[&str]`) Slice(std::slice::Iter<'a, &'a str>), @@ -227,7 +227,7 @@ pub trait IntoPicker<'a> { impl<'a> IntoPicker<'a> for &'a [&'a str] { fn to_picker(self) -> Picker<'a> { Picker { - args: PickerArguments::Slice(self), + args: PickerArgs::Slice(self), } } } @@ -235,7 +235,7 @@ impl<'a> IntoPicker<'a> for &'a [&'a str] { impl<'a> IntoPicker<'a> for Vec<&'a str> { fn to_picker(self) -> Picker<'a> { Picker { - args: PickerArguments::Vec(self), + args: PickerArgs::Vec(self), } } } @@ -243,7 +243,7 @@ impl<'a> IntoPicker<'a> for Vec<&'a str> { impl<'a> IntoPicker<'a> for Vec { fn to_picker(self) -> Picker<'a> { Picker { - args: PickerArguments::Owned(self), + args: PickerArgs::Owned(self), } } } diff --git a/mingling_picker/src/picker/patterns.rs b/mingling_picker/src/picker/patterns.rs index 80632d5..bd0eb12 100644 --- a/mingling_picker/src/picker/patterns.rs +++ b/mingling_picker/src/picker/patterns.rs @@ -1,25 +1,25 @@ -use crate::{Pickable, Picker, PickerArguments, PickerRequirement, PickerResult}; +use crate::{Pickable, Picker, PickerArgs, PickerFlag, PickerResult}; -mingling_picker_macros::internal_repeat! (1..=32 => { +mingling_picker_macros::internal_repeat!(1..=32 => { pub struct PickerPattern$<'a, (T$,)+> where (T$: Pickable + Default,)+ { - pub args: PickerArguments<'a>, + pub args: PickerArgs<'a>, ( - pub require_$: &'a PickerRequirement<'a, T$>, + pub flag_$: &'a PickerFlag<'a, T$>, pub result_$: PickerResult, )+ } }); impl<'a> Picker<'a> { - pub fn pick(self, req: &'a PickerRequirement<'a, N>) -> PickerPattern1<'a, N> + pub fn pick(self, flag: &'a PickerFlag<'a, N>) -> PickerPattern1<'a, N> where N: Pickable + Default, { PickerPattern1 { args: self.args, - require_1: req, + flag_1: flag, result_1: PickerResult::Unparsed, } } diff --git a/mingling_picker/src/requirement.rs b/mingling_picker/src/requirement.rs index 564ae45..0b351f8 100644 --- a/mingling_picker/src/requirement.rs +++ b/mingling_picker/src/requirement.rs @@ -22,7 +22,7 @@ use std::marker::PhantomData; /// /// - `_type`: PhantomData to hold the type parameter. #[derive(Default, Clone, Copy)] -pub struct PickerRequirement<'a, Type> +pub struct PickerFlag<'a, Type> where Type: Default + Pickable, { @@ -39,11 +39,11 @@ where pub internal_type: PhantomData, } -impl<'a, Type> PickerRequirement<'a, Type> +impl<'a, Type> PickerFlag<'a, Type> where Type: Default + Pickable, { - /// Creates a new `PickerRequirement` with the provided parameters. + /// Creates a new `PickerFlag` with the provided parameters. pub fn new(full: &'a [&'a str], short: Option, positional: bool) -> Self { Self { full, 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