use crate::{
substitute::{duplicate_and_substitute, Substitution},
token_iter::{Token, TokenIter},
DuplicationDefinition, Result, SubstitutionGroup,
};
use proc_macro::{Delimiter, Ident, Span, TokenStream, TokenTree};
use std::collections::HashSet;
pub(crate) fn parse_invocation(attr: TokenStream) -> Result<DuplicationDefinition>
{
let mut iter = attr.into();
let global_substitutions = validate_global_substitutions(&mut iter)?;
match validate_verbose_invocation(&mut iter, global_substitutions.substitutions.is_empty())
{
Err(_) =>
{
let substitutions = validate_short_attr(iter)?;
let mut reorder = Vec::new();
for _ in 0..substitutions[0].2.len()
{
reorder.push(SubstitutionGroup::new());
}
for (ident, args, subs) in substitutions
{
for (idx, sub) in subs.into_iter().enumerate()
{
let substitution = Substitution::new(&args, sub.into());
if let Ok(substitution) = substitution
{
reorder[idx].add_substitution(
Ident::new(&ident.clone(), Span::call_site()),
substitution,
)?;
}
else
{
return Err((
Span::call_site(),
"Duplicate internal error: Failed at creating substitution".into(),
));
}
}
}
Ok(DuplicationDefinition {
global_substitutions,
duplications: reorder,
})
},
verbose_result =>
{
verbose_result.map(|dups| {
DuplicationDefinition {
global_substitutions,
duplications: dups,
}
})
},
}
}
fn validate_global_substitutions(iter: &mut TokenIter) -> Result<SubstitutionGroup>
{
let mut sub_group = SubstitutionGroup::new();
while let Ok((ident, sub)) = extract_inline_substitution(iter)
{
sub_group.add_substitution(ident, sub)?;
if iter.has_next()?
{
iter.expect_semicolon()?;
}
}
Ok(sub_group)
}
fn validate_verbose_invocation(
iter: &mut TokenIter,
err_on_no_subs: bool,
) -> Result<Vec<SubstitutionGroup>>
{
if err_on_no_subs && !iter.has_next()?
{
return Err((Span::call_site(), "No substitutions found.".into()));
}
let mut sub_groups = Vec::new();
let mut substitution_ids = None;
while iter.has_next()?
{
let (body, span) = iter.next_group(
Some(Delimiter::Bracket),
"Hint: When using verbose syntax, a substitutions must be enclosed in a \
group.\nExample:\n..\n[\n\tidentifier1 [ substitution1 ]\n\tidentifier2 [ \
substitution2 ]\n]",
)?;
sub_groups.push(extract_verbose_substitutions(
body,
span,
&substitution_ids,
)?);
if None == substitution_ids
{
substitution_ids = Some(
sub_groups[0]
.identifiers_with_args()
.map(|(ident, count)| (ident.clone(), count))
.collect(),
)
}
}
Ok(sub_groups)
}
fn extract_inline_substitution(stream: &mut TokenIter) -> Result<(Ident, Substitution)>
{
let ident = stream.extract_identifier(Some("substitution identifier"))?;
let param_group = stream.next_group(Some(Delimiter::Parenthesis), "");
let substitution = stream.next_group(
Some(Delimiter::Bracket),
"Hint: A substitution identifier should be followed by a group containing the code to be \
inserted instead of any occurrence of the identifier.",
);
if let Ok((params, span)) = param_group
{
substitution
.and_then(|(sub, _)| {
extract_argument_list(params.clone())
.map(|args| Substitution::new(&args, sub).unwrap())
.or_else(|err| Err(err))
})
.or_else(|err| {
stream.push_front(Token::Group(Delimiter::Parenthesis, params, span));
Err(err)
})
}
else
{
substitution.map(|(sub, _)| Substitution::new_simple(sub.to_token_stream()))
}
.or_else(|err| {
stream.push_front(Token::Simple(TokenTree::Ident(ident.clone())));
Err(err)
})
.map(|result| (ident, result))
}
fn extract_verbose_substitutions(
mut iter: TokenIter,
iter_span: Span,
existing: &Option<HashSet<(String, usize)>>,
) -> Result<SubstitutionGroup>
{
if !iter.has_next()?
{
return Err((iter_span, "No substitution groups found.".into()));
}
let mut substitutions = SubstitutionGroup::new();
let mut stream = iter;
while let Ok((ident, substitution)) = extract_inline_substitution(&mut stream)
{
substitutions.add_substitution(ident, substitution)?;
}
if let Some(idents) = existing
{
let sub_idents: HashSet<_> = substitutions.identifiers_with_args().collect();
let idents = idents
.iter()
.map(|(ident, count)| (ident, count.clone()))
.collect();
let diff: Vec<_> = sub_idents.difference(&idents).collect();
if diff.len() > 0
{
let mut msg: String = "Invalid substitutions.\nThe following identifiers were not \
found in previous substitution groups or had different \
arguments:\n"
.into();
for ident in diff
{
msg.push_str(&ident.0.to_string());
msg.push_str("(");
if ident.1 > 0
{
msg.push_str("_");
}
for _ in 1..(ident.1)
{
msg.push_str(",_")
}
msg.push_str(")");
}
return Err((iter_span, msg));
}
}
Ok(substitutions)
}
fn validate_short_attr(mut iter: TokenIter)
-> Result<Vec<(String, Vec<String>, Vec<TokenStream>)>>
{
let idents = validate_short_get_identifiers(&mut iter)?;
let mut result: Vec<_> = idents
.into_iter()
.map(|(ident, args)| (ident, args, Vec::new()))
.collect();
validate_short_get_all_substitution_goups(iter, &mut result)?;
Ok(result)
}
fn validate_short_get_identifiers(mut iter: &mut TokenIter) -> Result<Vec<(String, Vec<String>)>>
{
let mut result = Vec::new();
while let Some(ident) = iter.extract_simple(
|t| Token::is_ident(t, None) || Token::is_semicolon(t),
|t| Token::get_ident(t),
Some("substitution identifier or ';'"),
)?
{
result.push((
ident.to_string(),
validate_short_get_identifier_arguments(&mut iter)?,
));
}
Ok(result)
}
fn validate_short_get_identifier_arguments(iter: &mut TokenIter) -> Result<Vec<String>>
{
if let Ok((group, _)) = iter.next_group(Some(Delimiter::Parenthesis), "")
{
let result = extract_argument_list(group)?;
return Ok(result);
}
Ok(Vec::new())
}
fn validate_short_get_all_substitution_goups<'a>(
mut iter: TokenIter,
result: &mut Vec<(String, Vec<String>, Vec<TokenStream>)>,
) -> Result<()>
{
while iter.has_next()?
{
for (_, _, streams) in result.iter_mut()
{
let (group, _) = iter.next_group(Some(Delimiter::Bracket), "")?;
streams.push(group.to_token_stream());
}
if iter.has_next()?
{
iter.expect_semicolon()?;
}
}
Ok(())
}
pub fn invoke_nested(iter: &mut TokenIter) -> Result<TokenStream>
{
let (mut nested_body_iter, _) = iter.next_group(None, "")?;
let (nested_invocation, _) = nested_body_iter.next_group(Some(Delimiter::Bracket), "")?;
let nested_dup_def = parse_invocation(nested_invocation.to_token_stream())?;
duplicate_and_substitute(
nested_body_iter.to_token_stream(),
&nested_dup_def.global_substitutions,
nested_dup_def.duplications.iter(),
)
}
pub fn extract_argument_list(mut args: TokenIter) -> Result<Vec<String>>
{
let mut result = Vec::new();
while args.has_next()?
{
let ident =
args.extract_identifier(Some("substitution identifier argument as identifier"))?;
result.push(ident.to_string());
if args.has_next()?
{
args.expect_comma()?;
}
}
Ok(result)
}