aboutsummaryrefslogtreecommitdiff
path: root/arg_picker_macros/src/internal_repeat.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 /arg_picker_macros/src/internal_repeat.rs
parente6136f22cff446b16dbebf3b26a6fdea6dbc0e83 (diff)
refactor: rename `mingling_picker` to `arg_picker`
Diffstat (limited to 'arg_picker_macros/src/internal_repeat.rs')
-rw-r--r--arg_picker_macros/src/internal_repeat.rs315
1 files changed, 315 insertions, 0 deletions
diff --git a/arg_picker_macros/src/internal_repeat.rs b/arg_picker_macros/src/internal_repeat.rs
new file mode 100644
index 0000000..4b2242b
--- /dev/null
+++ b/arg_picker_macros/src/internal_repeat.rs
@@ -0,0 +1,315 @@
+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))
+}