diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-17 11:08:07 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-17 11:11:28 +0800 |
| commit | 79ec6878877f0fd9246d67d3cd4f8cc2d1200150 (patch) | |
| tree | e3cd8cfc7ef5cd2a6a7ceb93d9a4b1764fa21b61 /mingling_picker_macros/src | |
| parent | e6136f22cff446b16dbebf3b26a6fdea6dbc0e83 (diff) | |
refactor: rename `mingling_picker` to `arg_picker`
Diffstat (limited to 'mingling_picker_macros/src')
| -rw-r--r-- | mingling_picker_macros/src/arg.rs | 205 | ||||
| -rw-r--r-- | mingling_picker_macros/src/internal_repeat.rs | 315 | ||||
| -rw-r--r-- | mingling_picker_macros/src/lib.rs | 32 |
3 files changed, 0 insertions, 552 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() -} diff --git a/mingling_picker_macros/src/internal_repeat.rs b/mingling_picker_macros/src/internal_repeat.rs deleted file mode 100644 index 4b2242b..0000000 --- a/mingling_picker_macros/src/internal_repeat.rs +++ /dev/null @@ -1,315 +0,0 @@ -use proc_macro::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree}; - -pub(crate) fn internal_repeat(input: TokenStream) -> TokenStream { - let tokens: Vec<TokenTree> = input.into_iter().collect(); - let (range_start, range_end, body_start) = parse_range(&tokens); - - let mut body: Vec<TokenTree> = tokens[body_start..].to_vec(); - if body.len() == 1 - && let TokenTree::Group(g) = &body[0] - && g.delimiter() == Delimiter::Brace - { - body = g.stream().into_iter().collect(); - } - - let mut result = Vec::new(); - for i in range_start..=range_end { - result.extend(expand_body(&body, i, range_start, range_end)); - } - result.into_iter().collect() -} - -/// Parse `start .. end =>` or `start ..= end =>` or `count =>` (backward compat). -/// Returns `(start, end_inclusive, body_start_index)`. -fn parse_range(tokens: &[TokenTree]) -> (usize, usize, usize) { - // Find => separator - let arrow_pos = tokens.windows(2).position(|w| { - matches!(&w[0], TokenTree::Punct(p) if p.as_char() == '=') - && matches!(&w[1], TokenTree::Punct(p) if p.as_char() == '>') - }); - - let (arrow_pos, body_start) = match arrow_pos { - Some(p) => (p, p + 2), - None => return (1, 12, 0), // fallback - }; - - let before: Vec<&TokenTree> = tokens[..arrow_pos].iter().collect(); - - // Try to find `..` or `..=` pattern - // `..` is two Punct('.') tokens - let dotdot = before.windows(2).position(|w| { - matches!(w[0], TokenTree::Punct(p) if p.as_char() == '.') - && matches!(w[1], TokenTree::Punct(p) if p.as_char() == '.') - }); - - if let Some(dd) = dotdot { - // Start value: tokens before `..` - let start = parse_usize_tokens(&before[..dd]); - let after_dd = &before[dd + 2..]; - - // Check for `..=` (inclusive range) - let (inclusive, end_tokens) = if after_dd - .first() - .is_some_and(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '=')) - { - (true, &after_dd[1..]) - } else { - (false, after_dd) - }; - - let end = parse_usize_tokens(end_tokens); - - if inclusive { - (start, end, body_start) - } else { - // Exclusive end: if end >= start, iterate start..end, so end_inclusive = end - 1 - if end > start { - (start, end - 1, body_start) - } else { - (1, 12, body_start) // fallback - } - } - } else { - // No `..` found — fallback to simple count - let count = parse_usize_tokens(&before); - (1, count, body_start) - } -} - -/// Parse a sequence of tokens as a single usize value. -fn parse_usize_tokens(tokens: &[&TokenTree]) -> usize { - let s: String = tokens - .iter() - .map(|t| match t { - TokenTree::Literal(l) => l.to_string(), - TokenTree::Ident(id) => id.to_string(), - _ => String::new(), - }) - .collect::<Vec<_>>() - .join("") - .replace(' ', ""); - - s.parse().unwrap_or(12) -} - -/// Walk tokens, replacing: -/// `$` → current -/// `^$` → max -/// `$^` → min -/// `$+` → current + 1 (clamped) -/// `$-` → current - 1 (clamped) -/// `ident$` → ident{current} -/// and expanding `( … )+` / `( … ,)+` / `( … ;)+` groups. -fn expand_body(tokens: &[TokenTree], current: usize, min: usize, max: usize) -> Vec<TokenTree> { - let mut out = Vec::new(); - let mut i = 0; - while i < tokens.len() { - // Check for a parenthesized repetition group: ( ... ) sep? + - if let Some(exp) = try_expand_paren_group(tokens, i, current, min, max) { - let (items, consumed) = exp; - out.extend(items); - i += consumed; - continue; - } - - // `^$` — max value - if let TokenTree::Punct(p) = &tokens[i] - && p.as_char() == '^' - && i + 1 < tokens.len() - && let TokenTree::Punct(p2) = &tokens[i + 1] - && p2.as_char() == '$' - { - out.push(TokenTree::Literal(Literal::usize_suffixed(max))); - i += 2; - continue; - } - - // `ident$` / `ident$+` / `ident$-` / `ident$^` - // → {ident}{current} / {ident}{current+1} / {ident}{current-1} / {ident}{min} - if let TokenTree::Ident(id) = &tokens[i] { - if i + 1 < tokens.len() - && let TokenTree::Punct(p) = &tokens[i + 1] - && p.as_char() == '$' - { - // Check for ident$+ / ident$- / ident$^ - if i + 2 < tokens.len() { - match &tokens[i + 2] { - TokenTree::Punct(p2) if p2.as_char() == '+' => { - // ident$+ → {ident}{current+1} - let name = format!("{}{}", id, current + 1); - out.push(TokenTree::Ident(Ident::new(&name, id.span()))); - i += 3; - continue; - } - TokenTree::Punct(p2) if p2.as_char() == '-' => { - // ident$- → {ident}{current-1} - let val = current.saturating_sub(1); - let name = format!("{}{}", id, val); - out.push(TokenTree::Ident(Ident::new(&name, id.span()))); - i += 3; - continue; - } - TokenTree::Punct(p2) if p2.as_char() == '^' => { - let name = format!("{}{}", id, min); - out.push(TokenTree::Ident(Ident::new(&name, id.span()))); - i += 3; - continue; - } - _ => {} - } - } - // ident$ alone → {ident}{current} - let name = format!("{}{}", id, current); - out.push(TokenTree::Ident(Ident::new(&name, id.span()))); - i += 2; - continue; - } - out.push(tokens[i].clone()); - i += 1; - continue; - } - - match &tokens[i] { - TokenTree::Punct(p) if p.as_char() == '$' => { - // lookahead for $^, $+, $- - if i + 1 < tokens.len() { - match &tokens[i + 1] { - TokenTree::Punct(p2) if p2.as_char() == '^' => { - // $^ → min - out.push(TokenTree::Literal(Literal::usize_suffixed(min))); - i += 2; - continue; - } - TokenTree::Punct(p2) if p2.as_char() == '+' => { - // $+ → current + 1 - out.push(TokenTree::Literal(Literal::usize_suffixed(current + 1))); - i += 2; - continue; - } - TokenTree::Punct(p2) if p2.as_char() == '-' => { - // $- → current - 1 - let val = current.saturating_sub(1); - out.push(TokenTree::Literal(Literal::usize_suffixed(val))); - i += 2; - continue; - } - _ => {} - } - } - // `$` alone → current - out.push(TokenTree::Literal(Literal::usize_suffixed(current))); - i += 1; - continue; - } - TokenTree::Group(g) => { - let inner = expand_body_vec(&g.stream(), current, min, max); - out.push(TokenTree::Group(Group::new( - g.delimiter(), - inner.into_iter().collect(), - ))); - } - other => out.push(other.clone()), - } - i += 1; - } - out -} - -fn expand_body_vec(stream: &TokenStream, current: usize, min: usize, max: usize) -> Vec<TokenTree> { - let v: Vec<TokenTree> = stream.clone().into_iter().collect(); - expand_body(&v, current, min, max) -} - -/// Try to expand a repetition group. -/// -/// New syntax (repetition marker `+` is the LAST token INSIDE the parens): -/// - `(group,+)` — repeat `*` times (current), `,` is the separator -/// - `(group,+)` — repeat `*` times, `,` is the separator -/// - `(group,+)` — repeat `*` times, `;` is the separator -/// - `(group +)` — repeat `*` times, no separator -/// - `(group,+)+` — repeat `*+1` times (with separator) -/// - `(group,+)--` — repeat `*-1` times (with separator) [not yet used] -/// -/// The `*` is the current counter value. An optional `+` or `-` immediately -/// after the closing paren shifts the repeat count up or down by one. -fn try_expand_paren_group( - tokens: &[TokenTree], - i: usize, - current: usize, - _min: usize, - _max: usize, -) -> Option<(Vec<TokenTree>, usize)> { - let group = match tokens.get(i)? { - TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => g, - _ => return None, - }; - - let stream: Vec<TokenTree> = group.stream().into_iter().collect(); - - // Repetition syntax: the LAST token inside the parens MUST be `+`. - // `(content,+)` — repeat * times, `,` separator - // `(content;+)` — repeat * times, `;` separator - // `(content,+)` — repeat * times, no separator - // `(content,+)+` — repeat *+1 times - // `(content,+)-` — repeat *-1 times - // An optional `+` / `-` right after `)` shifts the count by ±1. - // - // Any `+` tokens inside `content` are treated as regular Rust syntax - // (trait bounds, etc.) — the repetition marker is ONLY the final `+`. - - let last_is_plus = stream - .last() - .is_some_and(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '+')); - if !last_is_plus { - return None; - } - - // Determine separator and inner content. - let (inner, sep_str): (Vec<TokenTree>, &str) = { - if stream.len() >= 2 { - let sep_idx = stream.len() - 2; - match &stream[sep_idx] { - TokenTree::Punct(p) if p.as_char() == ',' => { - // (...,content,+) — inner is everything before the last `,` - let content: Vec<TokenTree> = stream[..sep_idx].into(); - (content, ",") - } - TokenTree::Punct(p) if p.as_char() == ';' => { - let content: Vec<TokenTree> = stream[..sep_idx].into(); - (content, ";") - } - _ => { - // (content+) — no separator, the `+` is the only special token - let content: Vec<TokenTree> = stream[..stream.len() - 1].into(); - (content, "") - } - } - } else { - // (+) — bare repetition marker, empty content - (vec![], "") - } - }; - - // Handle modifier after `)` - let rest = &tokens[i + 1..]; - let modifier: isize = match rest.first() { - Some(TokenTree::Punct(p)) if p.as_char() == '+' => 1, - Some(TokenTree::Punct(p)) if p.as_char() == '-' => -1, - _ => 0, - }; - let consumed = if modifier != 0 { 2 } else { 1 }; - let repeat_count = (current as isize + modifier) as usize; - - let mut out = Vec::new(); - for n in 1..=repeat_count { - if n > 1 && !sep_str.is_empty() { - out.push(TokenTree::Punct(proc_macro::Punct::new( - sep_str.chars().next().unwrap(), - proc_macro::Spacing::Alone, - ))); - } - out.extend(expand_body(&inner, n, 1, repeat_count)); - } - - Some((out, consumed)) -} diff --git a/mingling_picker_macros/src/lib.rs b/mingling_picker_macros/src/lib.rs deleted file mode 100644 index a12e3ed..0000000 --- a/mingling_picker_macros/src/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -#![doc = include_str!("../README.md")] - -use proc_macro::TokenStream; - -mod arg; -mod internal_repeat; - -/// Core proc-macro: repeats a template body `count` times. -/// -/// Internal call signature: `internal_repeat!(count => { template })` -#[proc_macro] -pub fn internal_repeat(input: TokenStream) -> TokenStream { - internal_repeat::internal_repeat(input) -} - -/// Quick builder for `PickerArg`. -/// -/// # Syntax -/// -/// ```ignore -/// use mingling_picker_macros::flag; -/// -/// let basic = arg![name: String]; -/// let with_short_name = arg![name: String, 'n']; -/// let with_short_alias = arg![name: String, 'n', "alias"]; -/// let positional = arg![String]; -/// let positional_with_name = arg![String, 'n', "alias"]; -/// ``` -#[proc_macro] -pub fn arg(input: TokenStream) -> TokenStream { - arg::arg(input) -} |
