use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree};
use quote::quote;
use crate::preprocess::consts::ctx::{ConstCtx, UserConsts};
use crate::util::{bracket_is_passthrough, compile_err, is_punct};
pub(crate) fn builtin_named(name: &str) -> Option<Vec<&'static str>> {
match name {
"u*" => vec!["u8", "u16", "u32", "u64", "u128", "usize"].into(),
"i*" => vec!["i8", "i16", "i32", "i64", "i128", "isize"].into(),
"f*" => vec!["f32", "f64"].into(),
"num" => vec![
"u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64",
"i128", "isize", "f32", "f64",
]
.into(),
"scalar" => vec![
"u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64",
"i128", "isize", "f32", "f64", "bool", "char",
]
.into(),
_ => None,
}
}
pub(crate) fn split_range_endpoint(s: &str) -> Option<(char, u32)> {
let (fam, width_str) = s.split_at(1);
let fam = fam.chars().next()?;
let width = width_str.parse().ok()?;
let legal: &[_] = match fam {
'u' | 'i' => &[8, 16, 32, 64, 128],
'f' => &[32, 64],
_ => return None,
};
legal.contains(&width).then_some((fam, width))
}
pub(crate) fn builtin_range(start: &str, end: &str) -> Result<Vec<String>, String> {
let Some((fam1, w1)) = split_range_endpoint(start) else {
return Err(format!(
"`@{}` has an invalid width (legal: u/i are 8/16/32/64/128, \
f is 32/64)",
start
));
};
let Some((fam2, w2)) = split_range_endpoint(end) else {
return Err(format!(
"`@{}` has an invalid width (legal: u/i are 8/16/32/64/128, \
f is 32/64)",
end
));
};
if fam1 != fam2 {
return Err(format!(
"range endpoint families mismatch: `{}` and `{}`",
start, end
));
}
if w1 > w2 {
return Err(format!(
"range start is greater than end: `{}..{}`",
start, end
));
}
let widths: &[_] = match fam1 {
'u' | 'i' => &[8, 16, 32, 64, 128],
_ => &[32, 64],
};
Ok(widths
.iter()
.filter(|&&w| w >= w1 && w <= w2)
.map(|w| format!("{}{}", fam1, w))
.collect())
}
pub(crate) fn render_list<S: ToString>(
names: impl IntoIterator<Item = S>,
) -> TokenTree {
let idents = names
.into_iter()
.map(|s| Ident::new(&s.to_string(), Span::call_site()))
.collect::<Vec<_>>();
Group::new(delimiter![[]], quote!(#(#idents),*)).into()
}
pub(crate) fn expand_consts(
tokens: &[TokenTree], ctx: ConstCtx,
) -> Result<Vec<TokenTree>, TokenStream> {
expand_consts_at(tokens, ctx, 0)
}
fn expand_consts_at(
tokens: &[TokenTree], ctx: ConstCtx, depth: usize,
) -> Result<Vec<TokenTree>, TokenStream> {
if depth > crate::util::MAX_NEST_DEPTH {
return Err(crate::util::depth_err(tokens, ""));
}
let mut result = vec![];
let mut i = 0;
while i < tokens.len() {
match &tokens[i] {
TokenTree::Group(g)
if g.delimiter() == delimiter![()]
|| g.delimiter() == delimiter![[]]
|| g.delimiter() == delimiter![none] =>
{
if bracket_is_passthrough(tokens, i) {
result.push(tokens[i].clone());
} else {
if depth + 1 > crate::util::MAX_NEST_DEPTH {
return Err(crate::util::depth_err(&tokens[i..i + 1], ""));
}
let inner = g.stream().into_iter().collect::<Vec<_>>();
result.push(
Group::new(
g.delimiter(),
expand_consts_at(&inner, ctx, depth + 1)?
.into_iter()
.collect(),
)
.into(),
);
}
i += 1;
}
TokenTree::Punct(p) if p.as_char() == '@' => {
match crate::preprocess::try_expand_at(&tokens[i..], ctx)? {
Some((expanded, consumed)) => {
let expanded = expand_consts_at(&expanded, ctx, depth + 1)?;
result.extend(expanded);
i += consumed;
}
None => {
result.push(tokens[i].clone());
i += 1;
}
}
}
TokenTree::Ident(id) if id == "where" => {
if let Some(TokenTree::Group(g)) = tokens.get(i + 1)
&& g.delimiter() == delimiter![{}]
{
let inner = g.stream().into_iter().collect::<Vec<_>>();
let expanded = expand_consts_at(&inner, ctx, depth + 1)?
.into_iter()
.collect();
result.push(tokens[i].clone());
result.push(Group::new(delimiter![{}], expanded).into());
i += 2;
} else {
result.push(tokens[i].clone());
i += 1;
}
}
_ => {
result.push(tokens[i].clone());
i += 1;
}
}
}
Ok(result)
}
pub(crate) fn collect_user_consts(
tokens: &[TokenTree],
) -> Result<(Vec<TokenTree>, UserConsts), TokenStream> {
let mut i = 0;
let mut table = UserConsts::new();
while let Some(TokenTree::Punct(at)) = tokens.get(i) {
if at.as_char() != '@' {
break;
}
let Some(TokenTree::Ident(name)) = tokens.get(i + 1) else { break };
let Some(TokenTree::Punct(eq)) = tokens.get(i + 2) else { break };
if eq.as_char() != '=' {
break;
}
let name_str = name.to_string();
if name_str == "trait" {
return Err(compile_err!(
"batch-impl: constant name `@trait` is a reserved marker \
(segment-level substitution into a trait path); please rename"
));
}
if name_str == "all" || name_str.starts_with("all_") {
return Err(compile_err!(
"batch-impl: constant name `@{}` is a reserved `@all` \
selector; please rename",
name_str
));
}
if builtin_named(&name_str).is_some() {
return Err(compile_err!(
"batch-impl: user constant `@{}` collides with a built-in \
constant name; please rename",
name_str
));
}
let mut j = i + 3;
let mut end = None;
while j < tokens.len() {
if is_punct(&tokens[j], ';') {
end = Some(j);
break;
}
j += 1;
}
let Some(end) = end else {
return Err(compile_err!(
"batch-impl: constant definition `@{}=...` is missing the \
trailing `;`",
name_str
));
};
let value = tokens[i + 3..end].to_vec();
crate::preprocess::check_value_refs(&value, &table, &name_str)?;
table.insert(name_str, value);
i = end + 1;
}
Ok((tokens[i..].to_vec(), table))
}