xmacro_lib 0.5.1

macro engine for producing multiple expansions
Documentation
/// the different expansion functions.
/// These are top level functions and the main API for this crate.
use self::xmacro::Definition;
use self::xtoken::XToken;
#[allow(clippy::wildcard_imports)]
use crate::*;

use std::collections::HashMap;
use std::vec::IntoIter;

use crate::xmacro::Definitions;

#[derive(Debug)]
pub(crate) struct ExpansionGroup {
    current_index: usize,
    len: usize,
}

pub(crate) fn expand_tokens(
    output: &mut Vec<XToken>,
    input: &[XToken],
    definitions: &[Definition],
    indices: &[usize],
    groups: &[ExpansionGroup],
) {
    let mut token_iter = input.iter();

    let definition_at =
        |index: usize| definitions[index].def[groups[indices[index]].current_index].clone();

    loop {
        match token_iter.next() {
            Some(XToken::IndexedSubstitution(_, index)) => {
                output.push(XToken::TokenStream(definition_at(*index)));
            }

            Some(XToken::IndexOfIndexed(_, index)) => {
                output.push(XToken::TokenTree(TokenTree::Literal(
                    Literal::usize_unsuffixed(groups[indices[*index]].current_index),
                )));
            }

            Some(XToken::LengthOfIndexed(_, index)) => {
                output.push(XToken::TokenTree(TokenTree::Literal(
                    Literal::usize_unsuffixed(definitions[*index].def.len()),
                )));
            }

            Some(XToken::EscapedDollar(_)) => {
                output.push(XToken::TokenTree(PunctAlone::<'$'>::new().into()));
            }

            Some(XToken::BreakSemicolon(_)) => {
                output.push(XToken::TokenTree(PunctAlone::<';'>::new().into()));
            }

            Some(XToken::BreakPunct(p, _)) => {
                output.push(XToken::TokenTree(TokenTree::Punct(Punct::new(
                    p.as_char(),
                    Spacing::Alone,
                ))));
            }

            Some(XToken::ConcatIdent(_, ident, _, def)) => {
                let Either::First(index) = def else {
                    unreachable!()
                };
                let s = ident.to_string() + definition_at(*index).to_string().as_str();
                output.push(XToken::TokenTree(TokenTree::Ident(Ident::new(
                    &s,
                    ident.span(),
                ))));
            }

            // expand nested groups
            Some(XToken::XGroup(group)) => {
                let mut expanded = Vec::<XToken>::new();
                expand_tokens(&mut expanded, &group.content, definitions, indices, groups);
                output.push(XToken::XGroup(GroupContaining {
                    delimiter: group.delimiter,
                    content: expanded,
                }));
            }

            Some(XToken::TokenTree(tt)) => {
                output.push(XToken::TokenTree(tt.clone()));
            }

            Some(XToken::TokenStream(ts)) => {
                output.push(XToken::TokenStream(ts.clone()));
            }

            None | Some(XToken::EndOfStream(_)) => break,

            // The cases that never happen when expanding because the parser already
            // substituted these
            Some(
                XToken::Scope(..)
                | XToken::Definition(..)
                | XToken::DefinitionError(..)
                | XToken::Directive(..)
                | XToken::LengthOfNamed(..)
                | XToken::NamedSubstitution(..)
                | XToken::IndexOfNamed(..)
                | XToken::Group(_),
            ) => unreachable!(),
        }
    }
}

impl XMacro<'_> {
    pub fn expand(self) -> IntoIter<XToken> {
        let XMacro {
            xtokens,
            definitions: Definitions { definitions, .. },
            ..
        } = self;

        // substitution indices are per group, sorted by appearance
        let mut groups: Vec<ExpansionGroup> = Vec::new();
        // group_id:group
        let mut group_lookup: HashMap<u32, usize> = HashMap::new();
        // substitution -> index in groups
        let mut indices: Vec<usize> = Vec::new();

        // collect groups
        for def in &definitions {
            if def.do_expand {
                let len = groups.len();
                let group_index = *group_lookup.entry(def.group_id).or_insert(len);
                if group_index == len {
                    groups.push(ExpansionGroup {
                        current_index: 0,
                        len: def.def.len(),
                    });
                }
                indices.push(group_lookup[&def.group_id]);
            } else {
                // This is a unused slot, we just to fill it with an invalid  dummy value
                indices.push(usize::MAX);
            }
        }

        // next permutation
        #[allow(clippy::items_after_statements)]
        fn permute_group_indices(group_indices: &mut [ExpansionGroup]) -> bool {
            for i in (0..group_indices.len()).rev() {
                group_indices[i].current_index += 1;
                if group_indices[i].current_index < group_indices[i].len {
                    return true;
                }
                group_indices[i].current_index = 0;
            }
            false
        }

        let mut output = Vec::new();

        while {
            expand_tokens(
                &mut output,
                xtokens.as_slice(),
                &definitions,
                indices.as_slice(),
                groups.as_slice(),
            );
            permute_group_indices(groups.as_mut_slice())
        } {}

        output.into_iter()
    }
}