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 +++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 mingling_picker_macros/src/flag.rs (limited to 'mingling_picker_macros/src/flag.rs') 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() +} -- cgit