use proc_macro2::{TokenStream, TokenTree};
use crate::preprocess::consts::ctx::ConstCtx;
use crate::preprocess::{
builtin_named, builtin_range, render_list, split_range_endpoint,
};
use crate::util::{
compile_err, compile_err_at, compile_error_str, is_joint_punct_at, is_punct_at,
};
pub(crate) fn try_expand_at(
tokens: &[TokenTree], ctx: ConstCtx,
) -> Result<Option<(Vec<TokenTree>, usize)>, TokenStream> {
let Some(TokenTree::Ident(name)) = tokens.get(1) else {
if matches!(tokens.get(1), Some(TokenTree::Literal(_))) {
return Ok(None);
}
return Err(compile_error_str(
"batch-impl: `@` must be followed by a constant name (e.g. `@u*`, \
`@u8..u128`)",
tokens[0].span(),
));
};
let name_str = name.to_string();
if let Some(TokenTree::Punct(eq)) = tokens.get(2)
&& eq.as_char() == '='
{
let msg = if ctx.user_table().is_some() {
format!(
"batch-impl: constant definition `@{}=...` must appear before all \
`batch_trait!` trait segments (only the leading position can \
define)",
name_str
)
} else {
"batch-impl: `#[batch_impl]` / `#[batch_impl_only]` do not support \
custom constant definitions; custom constants are supported only \
by `batch_trait!` (leading `@name=value;` segment)"
.to_string()
};
return Err(compile_error_str(&msg, tokens[0].span()));
}
if is_joint_punct_at(tokens, 2, '.') && is_punct_at(tokens, 3, '.') {
let end_idx = if let Some(TokenTree::Punct(eq)) = tokens.get(4)
&& eq.as_char() == '='
{
5
} else {
4
};
let Some(TokenTree::Ident(end)) = tokens.get(end_idx) else {
return Err(compile_err!(
"batch-impl: range constant `@{}{}..` is missing an end point \
(e.g. `@u8..u128`)",
name_str,
".."
));
};
let types = builtin_range(&name_str, &end.to_string())
.map_err(|msg| compile_err!("batch-impl: {}", msg))?;
return Ok((
vec![render_list(types.iter().map(|s| s.as_str()))],
end_idx + 1,
)
.into());
}
if name_str == "trait" {
return match ctx.trait_full_path() {
Some(path) => Ok((path.clone().into_iter().collect(), 2).into()),
None => Ok(None),
};
}
if let Some((kinds, default, receiver)) =
crate::preprocess::resolve_all_marker(&name_str)
{
return match ctx.trait_def() {
Some(td) => {
let ids = crate::preprocess::get_trait_item_names(
td, kinds.0, kinds.1, kinds.2, default, receiver,
);
Ok((vec![render_list(ids.iter())], 2).into())
}
None => Err(compile_err!(
"batch-impl: `@{}` is supported only by `#[batch_impl]` / \
`#[batch_impl_only]` (needs a trait definition to select \
items; `batch_trait!` is a function-like macro without one)",
name_str
)),
};
}
if let Some(gf) = crate::preprocess::resolve_generic_marker(&name_str) {
return match ctx.trait_def() {
Some(td) => match crate::preprocess::get_trait_generic_decl(td, gf) {
Some(decl) => {
let decl = decl.into_iter().collect();
Ok((decl, 2).into())
}
None => Err(compile_err!(
"batch-impl: `@{}` cannot expand — trait `{}` has no {} \
parameters",
name_str,
td.ident,
match gf {
crate::preprocess::GenericFilter::Type => "type",
crate::preprocess::GenericFilter::Const => "const",
crate::preprocess::GenericFilter::Lifetime => "lifetime",
}
)),
},
None => Err(compile_err!(
"batch-impl: `@{}` is supported only by `#[batch_impl]` / \
`#[batch_impl_only]` (needs a trait definition to read its \
generic parameters; `batch_trait!` is a function-like macro \
without one)",
name_str
)),
};
}
if let Some(expanded) = ctx.user_table().and_then(|t| t.get(&name_str)) {
return Ok((expanded.clone(), 2).into());
}
let star = is_punct_at(tokens, 2, '*');
let lookup = if star { format!("{}*", name_str) } else { name_str.clone() };
match builtin_named(&lookup) {
Some(types) => Ok((
vec![render_list(types.iter().copied())],
if star { 3 } else { 2 },
)
.into()),
None => {
if name_str == "all_fresh" {
return Ok(None);
}
Err(compile_err_at!(
tokens[0].span(),
"batch-impl: unknown @ constant `@{}`; built-ins: `@u*` `@i*` `@f*` \
`@num` `@scalar` and ranges `@u8..u128` `@i8..i128` `@f32..f64`\
{}",
lookup,
if ctx.user_table().is_some() {
"; batch_trait! user constants must be defined before the \
reference (defining them later has no effect)"
} else {
""
}
))
}
}
}
pub(crate) fn check_value_refs(
tokens: &[TokenTree], table: &std::collections::HashMap<String, Vec<TokenTree>>,
def_name: &str,
) -> Result<(), TokenStream> {
check_value_refs_at(tokens, table, def_name, 0)
}
fn check_value_refs_at(
tokens: &[TokenTree], table: &std::collections::HashMap<String, Vec<TokenTree>>,
def_name: &str, depth: usize,
) -> Result<(), TokenStream> {
if depth > crate::util::MAX_NEST_DEPTH {
return Err(crate::util::depth_err(tokens, " in a constant value"));
}
let mut i = 0;
while i < tokens.len() {
match &tokens[i] {
TokenTree::Punct(p) if p.as_char() == '@' => {
let Some(TokenTree::Ident(name)) = tokens.get(i + 1) else {
return Err(compile_error_str(
"batch-impl: inside a constant value, `@` must be followed \
by a constant name (e.g. `@u*`, `@u8..u128`)",
tokens[i].span(),
));
};
let name_str = name.to_string();
let star = is_punct_at(tokens, i + 2, '*');
let lookup =
if star { format!("{}*", name_str) } else { name_str.clone() };
let is_range = is_punct_at(tokens, i + 2, '.');
let known = name_str == "trait"
|| builtin_named(&lookup).is_some()
|| (is_range && split_range_endpoint(&name_str).is_some())
|| table.contains_key(&name_str);
if !known {
return Err(compile_err!(
"batch-impl: constant `@{}` references unknown `@{}` \
(undefined or defined later; inside a constant \
definition, only built-in constants or previously \
defined constants can be referenced)",
def_name,
name_str
));
}
i += if star { 3 } else { 2 };
}
TokenTree::Group(g) => {
if depth + 1 > crate::util::MAX_NEST_DEPTH {
return Err(crate::util::depth_err(
&tokens[i..i + 1],
" in a constant value",
));
}
check_value_refs_at(
&g.stream().into_iter().collect::<Vec<_>>(),
table,
def_name,
depth + 1,
)?;
i += 1;
}
_ => i += 1,
}
}
Ok(())
}