aboutsummaryrefslogtreecommitdiff
path: root/mingling_picker_macros/src/arg.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-17 11:08:07 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-17 11:11:28 +0800
commit79ec6878877f0fd9246d67d3cd4f8cc2d1200150 (patch)
treee3cd8cfc7ef5cd2a6a7ceb93d9a4b1764fa21b61 /mingling_picker_macros/src/arg.rs
parente6136f22cff446b16dbebf3b26a6fdea6dbc0e83 (diff)
refactor: rename `mingling_picker` to `arg_picker`
Diffstat (limited to 'mingling_picker_macros/src/arg.rs')
-rw-r--r--mingling_picker_macros/src/arg.rs205
1 files changed, 0 insertions, 205 deletions
diff --git a/mingling_picker_macros/src/arg.rs b/mingling_picker_macros/src/arg.rs
deleted file mode 100644
index 70b27b0..0000000
--- a/mingling_picker_macros/src/arg.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-use proc_macro::{TokenStream, TokenTree};
-use proc_macro2::TokenStream as TS2;
-use quote::quote;
-
-pub(crate) fn arg(input: TokenStream) -> TokenStream {
- let tokens: Vec<TokenTree> = input.into_iter().collect();
- let args = split_at_commas(&tokens);
- if args.is_empty() {
- return quote! { compile_error!("arg! 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!("arg! only supports at most one short name") }.into();
- }
-
- // Extract short char and string aliases
- let short_char: Option<char> = rest
- .iter()
- .find(|a| is_char_literal(a))
- .and_then(|a| extract_char(a));
- let aliases: Vec<String> = rest
- .iter()
- .filter(|a| is_string_literal(a))
- .filter_map(|a| extract_string(a))
- .collect();
-
- // Parse first argument
- // arg![name: Type] → name, with explicit type
- // arg![Type] → positional type, no name
- let colon_pos = first
- .iter()
- .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':'));
- let has_named_colon = colon_pos.is_some_and(|pos| pos > 0 && !is_double_colon(first, pos));
-
- let first_slice = first.as_slice();
- let (name, ty, is_named) = if has_named_colon {
- let pos = colon_pos.unwrap();
- // `name : Type`
- (
- Some(&first_slice[..pos]),
- Some(&first_slice[pos + 1..]),
- true,
- )
- } else {
- // No `name:` prefix → the entire first argument is the type
- (None, Some(first_slice), false)
- };
-
- // Build full names
- let full_names: Vec<String> = 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();
- TS2::from(ts)
- })
- .unwrap_or(TS2::new());
-
- #[cfg(feature = "mingling_support")]
- let import = quote! { ::mingling::picker::PickerArg };
-
- #[cfg(not(feature = "mingling_support"))]
- let import = quote! { ::mingling_picker::PickerArg };
-
- 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<proc_macro2::Literal> = 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<Vec<TokenTree>> {
- 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_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 extract_char(tokens: &[TokenTree]) -> Option<char> {
- match &tokens[0] {
- TokenTree::Literal(l) => {
- let s = l.to_string();
- let cs: Vec<char> = s.chars().collect();
- if cs.len() >= 3 && cs[0] == '\'' && cs[cs.len() - 1] == '\'' {
- let inner: String = cs[1..cs.len() - 1].iter().collect();
- // inner is the character between the quotes of a char literal.
- // For a literal `'n'`, inner is `"n"` (the character n).
- // For an escape `'\n'`, inner is the actual newline character.
- // The catch-all handles both literal single chars and escape
- // sequences (the escaped char IS the actual control character).
- match inner.as_str() {
- "\\\\" => Some('\\'),
- "\\'" => Some('\''),
- _ => inner.chars().next(),
- }
- } else {
- None
- }
- }
- _ => None,
- }
-}
-
-fn extract_string(tokens: &[TokenTree]) -> Option<String> {
- match &tokens[0] {
- TokenTree::Literal(l) => {
- let s = l.to_string();
- let cs: Vec<char> = 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()
-}