aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-14 03:10:57 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-14 03:10:57 +0800
commit132b065e53e53b588a496d8649bd60872ab88a5c (patch)
tree11f2785649d73d85441501c0dd67476bd4d80c09
parent3f90ae7c987b16d485a2fb4e9f9bcb3774d37530 (diff)
feat(mingling_picker_macros): add $+ $- $^ special tokens to
internal_repeat Extend the `internal_repeat` macro with new special tokens: - `$+`/`ident$+` for current + 1 - `$-`/`ident$-` for current - 1 (clamped at 0) - `$^`/`ident$^` for min (aka start) value Move the repetition marker from outside the paren to the last token inside, supporting `(group,+)` syntax where `+` is the final token. A trailing `+` or `-` after `)` shifts the repetition count. Add `PickerPattern::pick` method using new syntax to allow incremental composition of typed picks.
-rw-r--r--mingling_picker/src/picker/patterns.rs37
-rw-r--r--mingling_picker_macros/src/internal_repeat.rs197
2 files changed, 188 insertions, 46 deletions
diff --git a/mingling_picker/src/picker/patterns.rs b/mingling_picker/src/picker/patterns.rs
index bd0eb12..2242a70 100644
--- a/mingling_picker/src/picker/patterns.rs
+++ b/mingling_picker/src/picker/patterns.rs
@@ -1,17 +1,46 @@
+use mingling_picker_macros::internal_repeat;
+
use crate::{Pickable, Picker, PickerArgs, PickerFlag, PickerResult};
-mingling_picker_macros::internal_repeat!(1..=32 => {
- pub struct PickerPattern$<'a, (T$,)+>
- where (T$: Pickable + Default,)+
+internal_repeat!(1..=32 => {
+ pub struct PickerPattern$<'a, (T$,+)>
+ where (T$: Pickable + Default,+)
{
pub args: PickerArgs<'a>,
(
pub flag_$: &'a PickerFlag<'a, T$>,
pub result_$: PickerResult<T$>,
- )+
+ +)
}
});
+internal_repeat!(1..32 => {
+ impl<'a, (T$,+)> PickerPattern$<'a, (T$,+)>
+ where (T$: Pickable + Default,+)
+ {
+ #[allow(clippy::type_complexity)]
+ pub fn pick<N>(self, flag: &'a PickerFlag<'a, N>) -> PickerPattern$+<'a, (T$,+), N>
+ where
+ N: Pickable + Default,
+ {
+ PickerPattern$+ {
+ // Args
+ args: self.args,
+
+ // Current
+ flag_$+: flag,
+ result_$+: PickerResult::Unparsed,
+
+ // Prev
+ (
+ flag_$: self.flag_$,
+ result_$: self.result_$,
+ +)
+ }
+ }
+ }
+});
+
impl<'a> Picker<'a> {
pub fn pick<N>(self, flag: &'a PickerFlag<'a, N>) -> PickerPattern1<'a, N>
where
diff --git a/mingling_picker_macros/src/internal_repeat.rs b/mingling_picker_macros/src/internal_repeat.rs
index 5832cca..4b2242b 100644
--- a/mingling_picker_macros/src/internal_repeat.rs
+++ b/mingling_picker_macros/src/internal_repeat.rs
@@ -14,7 +14,7 @@ pub(crate) fn internal_repeat(input: TokenStream) -> TokenStream {
let mut result = Vec::new();
for i in range_start..=range_end {
- result.extend(expand_body(&body, i));
+ result.extend(expand_body(&body, i, range_start, range_end));
}
result.into_iter().collect()
}
@@ -92,27 +92,74 @@ fn parse_usize_tokens(tokens: &[&TokenTree]) -> usize {
s.parse().unwrap_or(12)
}
-/// Walk tokens, replacing `$` in identifier tails, and expanding
-/// `( … )+` / `( … ,)+` / `( … ;)+` groups.
-fn expand_body(tokens: &[TokenTree], outer: usize) -> Vec<TokenTree> {
+/// 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, outer) {
+ if let Some(exp) = try_expand_paren_group(tokens, i, current, min, max) {
let (items, consumed) = exp;
out.extend(items);
i += consumed;
continue;
}
- // Identifier followed by `$` → combined ident + number
+ // `^$` — 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() == '$'
{
- let name = format!("{}{outer}", id);
+ // 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;
@@ -124,10 +171,38 @@ fn expand_body(tokens: &[TokenTree], outer: usize) -> Vec<TokenTree> {
match &tokens[i] {
TokenTree::Punct(p) if p.as_char() == '$' => {
- out.push(TokenTree::Literal(Literal::usize_suffixed(outer)));
+ // 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(), outer);
+ let inner = expand_body_vec(&g.stream(), current, min, max);
out.push(TokenTree::Group(Group::new(
g.delimiter(),
inner.into_iter().collect(),
@@ -140,62 +215,100 @@ fn expand_body(tokens: &[TokenTree], outer: usize) -> Vec<TokenTree> {
out
}
-fn expand_body_vec(stream: &TokenStream, outer: usize) -> Vec<TokenTree> {
+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, outer)
+ expand_body(&v, current, min, max)
}
-/// Try to expand `( … )+` / `( … ,)+` / `( … ;)+` at position `i`.
+/// 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 AFTER the closing paren. An optional separator (`,` or `;`)
-/// may appear between `)` and `+`.
-/// Returns `(expanded_tokens, consumed_count)` or `None`.
+/// 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,
- outer: 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,
};
- // Check tokens AFTER the group for `+` (optionally preceded by `,` or `;`)
- let rest = &tokens[i + 1..];
- let sep: Option<&str> = match rest.first() {
- Some(TokenTree::Punct(p)) if p.as_char() == ',' => {
- if matches!(rest.get(1), Some(TokenTree::Punct(p)) if p.as_char() == '+') {
- Some(",")
- } else {
- return None;
- }
- }
- Some(TokenTree::Punct(p)) if p.as_char() == ';' => {
- if matches!(rest.get(1), Some(TokenTree::Punct(p)) if p.as_char() == '+') {
- Some(";")
- } else {
- 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![], "")
}
- Some(TokenTree::Punct(p)) if p.as_char() == '+' => None,
- _ => return None,
};
- let consumed = if sep.is_some() { 3 } else { 2 }; // group + sep? + +
-
- let inner: Vec<TokenTree> = group.stream().into_iter().collect();
+ // 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..=outer {
- if n > 1
- && let Some(s) = sep
- {
+ for n in 1..=repeat_count {
+ if n > 1 && !sep_str.is_empty() {
out.push(TokenTree::Punct(proc_macro::Punct::new(
- s.chars().next().unwrap(),
+ sep_str.chars().next().unwrap(),
proc_macro::Spacing::Alone,
)));
}
- out.extend(expand_body(&inner, n));
+ out.extend(expand_body(&inner, n, 1, repeat_count));
}
Some((out, consumed))