use proc_macro2::{Ident, TokenStream};
use quote::quote;
pub(crate) const FRESH_PREFIX: &str = "_Param_";
pub(crate) const FRESH_SUFFIX: &str = "_BatchGen_";
pub(crate) fn fresh_param(g: usize, i: usize) -> TokenStream {
let name = format!("{}{}_{}{}", FRESH_PREFIX, g, i, FRESH_SUFFIX);
let ident = Ident::new(&name, proc_macro2::Span::call_site());
quote!(#ident)
}
pub(crate) fn at_ref_name(lit: &str) -> Option<String> {
if let Ok(n) = lit.parse::<usize>() {
return Some(format!("{}{}{}", FRESH_PREFIX, n, FRESH_SUFFIX));
}
if let Some((g, i)) = lit.split_once('_')
&& let (Ok(g), Ok(i)) = (g.parse::<usize>(), i.parse::<usize>())
{
return Some(format!("{}{}_{}{}", FRESH_PREFIX, g, i, FRESH_SUFFIX));
}
None
}
pub(crate) fn parse_grouped_fresh(s: &str) -> Option<(usize, usize)> {
let rest = s.strip_prefix(FRESH_PREFIX)?.strip_suffix(FRESH_SUFFIX)?;
let (g, i) = rest.split_once('_')?;
Some((g.parse().ok()?, i.parse().ok()?))
}
pub(crate) fn parse_numbered_fresh(s: &str) -> Option<usize> {
let rest = s.strip_prefix(FRESH_PREFIX)?.strip_suffix(FRESH_SUFFIX)?;
rest.parse().ok()
}
pub(crate) const RANGE_WITH_INFIX: &str = "_With_";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct FreshRange {
pub(crate) group: Option<usize>,
pub(crate) start: usize,
pub(crate) end: Option<usize>,
}
pub(crate) fn range_fresh_name(range: FreshRange) -> String {
let head = match range.group {
Some(l) => format!("{}{}_{}", FRESH_PREFIX, l, range.start),
None => format!("{}{}", FRESH_PREFIX, range.start),
};
match range.end {
Some(end) => format!("{}{}{}{}", head, RANGE_WITH_INFIX, end, FRESH_SUFFIX),
None => format!("{}_With{}", head, FRESH_SUFFIX),
}
}
pub(crate) fn parse_range_fresh(s: &str) -> Option<FreshRange> {
let rest = s.strip_prefix(FRESH_PREFIX)?.strip_suffix(FRESH_SUFFIX)?;
let (pos_str, end) = if let Some((pos_str, tail)) = rest.split_once("_With_") {
(pos_str, Some(tail.parse::<usize>().ok()?))
} else {
let (pos_str, marker) = rest.rsplit_once("_With")?;
(pos_str, if marker.is_empty() { None } else { return None })
};
let (group, start) = match pos_str.split_once('_') {
Some((l, n)) => (Some(l.parse::<usize>().ok()?), n.parse::<usize>().ok()?),
None => (None, pos_str.parse::<usize>().ok()?),
};
let range = FreshRange { group, start, end };
(range.end.is_none_or(|e| range.start <= e)).then_some(range)
}
pub(crate) fn is_fresh_name(s: &str) -> bool {
s.starts_with(FRESH_PREFIX) && s.ends_with(FRESH_SUFFIX)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn range_placeholder_roundtrip() {
for (range, expect) in [
(FreshRange { group: None, start: 0, end: None }, "_Param_0_With_BatchGen_"),
(FreshRange { group: None, start: 1, end: None }, "_Param_1_With_BatchGen_"),
(FreshRange { group: None, start: 0, end: Some(2) }, "_Param_0_With_2_BatchGen_"),
(FreshRange { group: None, start: 1, end: Some(3) }, "_Param_1_With_3_BatchGen_"),
(FreshRange { group: Some(0), start: 0, end: None }, "_Param_0_0_With_BatchGen_"),
(FreshRange { group: Some(0), start: 2, end: None }, "_Param_0_2_With_BatchGen_"),
(FreshRange { group: Some(1), start: 0, end: Some(1) }, "_Param_1_0_With_1_BatchGen_"),
(FreshRange { group: Some(2), start: 1, end: Some(3) }, "_Param_2_1_With_3_BatchGen_"),
] {
let name = range_fresh_name(range);
assert_eq!(name, expect);
assert_eq!(parse_range_fresh(&name), Some(range), "{name}");
}
}
#[test]
fn range_placeholder_not_confused_with_plain_fresh() {
for name in
["_Param_0_With_BatchGen_", "_Param_1_With_2_BatchGen_", "_Param_0_1_With_BatchGen_"]
{
assert_eq!(parse_grouped_fresh(name), None, "{name}");
assert_eq!(parse_numbered_fresh(name), None, "{name}");
}
for name in ["_Param_0_BatchGen_", "_Param_0_1_BatchGen_"] {
assert_eq!(parse_range_fresh(name), None, "{name}");
}
}
#[test]
fn range_placeholder_invalid_forms() {
assert_eq!(parse_range_fresh("_Param_x_With_BatchGen_"), None);
assert_eq!(parse_range_fresh("_Param_2_With_1_BatchGen_"), None); assert_eq!(parse_range_fresh("_Param_0_With_x_BatchGen_"), None);
assert_eq!(parse_range_fresh("_Param_0_2_With_1_BatchGen_"), None); assert_eq!(parse_range_fresh("_Param_x_0_With_BatchGen_"), None); assert_eq!(parse_range_fresh("plain"), None);
}
}