expanda-macros 0.4.4

macros for expanda crate
Documentation
use std::{collections::HashSet, slice};

use proc_macro::{Ident, Literal, Span, TokenStream, TokenTree as Tt};

use crate::{error::error, literal::{LitKind, StringType, make_ident, parse_string, unescape}};

mod display;

#[derive(Clone, Default, Debug)]
pub struct Metaval(Vec<MetavalToken>);

#[derive(Clone, Debug)]
pub struct MetavalToken {
    order: u32,
    token: Tt,
}

impl MetavalToken {
    pub fn order_fn(order: u32) -> impl Fn(Tt) -> Self {
        move |token| Self { order, token }
    }

    pub fn new(order: u32, token: Tt) -> Self {
        Self { order, token }
    }

    pub fn zero(token: Tt) -> Self {
        Self {
            order: 0,
            token,
        }
    }

    pub fn raise(&self) -> Self {
        Self {
            order: self.order.saturating_sub(1),
            token: self.token.clone()
        }
    }
}

impl Metaval {
    pub fn empty() -> Self {
        Self(Vec::new())
    }

    pub fn single(token: Tt) -> Self {
        Self(vec![MetavalToken::zero(token)])
    }

    pub fn flat(tokens: impl IntoIterator<Item = Tt>) -> Self {
        Self(tokens.into_iter().map(MetavalToken::zero).collect())
    }

    pub fn len_flat(&self) -> usize {
        self.0.len()
    }

    pub fn is_single(&self) -> bool {
        self.len_flat() == 1
    }

    pub fn as_single(&self) -> Option<&Tt> {
        if !self.is_single() { return None }
        Some(&self.0[0].token)
    }

    pub fn into_single(self) -> Option<Tt> {
        let [token] = self.0.try_into().ok()?;
        Some(token.token)
    }

    pub fn to_stream(&self) -> TokenStream {
        self
            .0
            .iter()
            .map(
                |MetavalToken { token, .. }| token.clone()
            ).collect()
    }

    pub fn simplify(mut self) -> Self {
        if self.0.is_empty() {
            return self;
        }
        let mut max_order = 0;
        let mut orders = HashSet::new();
        for token in &self.0[..self.0.len() - 1] {
            if token.order > max_order {
                max_order = token.order;
            }
            orders.insert(token.order);
        }

        let mut missing_orders = HashSet::new();
        for o in 0..=max_order {
            if !orders.contains(&o) {
                missing_orders.insert(o);
            }
        }

        for token in &mut self.0 {
            let order = token.order;
            for &m in &missing_orders {
                if m < order {
                    token.order = token.order.saturating_sub(1);
                }
            }
        }

        self
    }
    
    pub fn first_span(&self) -> Option<Span> {
        Some(self.0.first()?.token.span())
    }

    pub fn last_span(&self) -> Option<Span> {
        Some(self.0.last()?.token.span())
    }

    pub fn raise_final(&mut self) {
        if let Some(token) = self.0.last_mut() {
            token.order = token.order.saturating_sub(1);
        }
    }

    pub fn split(&self) -> impl Iterator<Item = Self> {
        Split { iter: self.0.iter() }
    }

    pub fn concatenate(self, rhs: &Self) -> Result<Self, ConcatenateError> {
        fn unraw(maybe_raw: &str) -> &str {
            maybe_raw.strip_prefix("r#").unwrap_or(maybe_raw)
        }
        match (self.into_single(), rhs.as_single()) {
            (Some(lhs), Some(rhs)) => {
                match (lhs, rhs) {
                    (Tt::Ident(lhs), Tt::Ident(rhs)) => {
                        let cat = &(unraw(&lhs.to_string()).to_owned() + unraw(&rhs.to_string()));

                        Ok(Self::single(Tt::Ident(make_ident(cat, lhs.span()))))
                    }
                    (Tt::Ident(lhs), Tt::Literal(rhs)) => {
                        match LitKind::of(rhs) {
                            LitKind::Number => {
                                let r = rhs.to_string();
                                if r.chars().any(|c| !(c.is_alphanumeric() || c == '_')) {
                                    return Err(ConcatenateError::RhsBadNumberForIdent);
                                }
                                let cat = &(unraw(&lhs.to_string()).to_owned() + &r);

                                Ok(Self::single(Tt::Ident(make_ident(cat, lhs.span()))))
                            },
                            _ => Err(ConcatenateError::RhsBadLiteralForIdent),
                        }
                    }
                    (Tt::Literal(lhs), Tt::Literal(rhs)) => {
                        let lkind = LitKind::of(&lhs);
                        let rkind = LitKind::of(rhs);
                        if let Some(ltype) = lkind.string_type() && let Some(rtype) = rkind.string_type() && ltype == rtype {
                            let l = lhs.to_string();
                            let r = rhs.to_string();
                            let (l, l_suffix) = parse_string(&l);
                            let (r, r_suffix) = parse_string(&r);
                            let l = if lkind.is_raw() {l.to_owned()} else {unescape(l)};
                            let r = if rkind.is_raw() {r.to_owned()} else {unescape(r)};
                            if !l_suffix.is_empty() {return Err(ConcatenateError::LhsSuffix)}
                            let mut result = ltype.make_literal(&(l.to_owned() + &r));
                            result.set_span(lhs.span());
                            if !r_suffix.is_empty() {
                                result = (result.to_string() + r_suffix).parse().unwrap();
                            }
                            Ok(Self::single(Tt::Literal(result)))
                        } else {
                            Err(ConcatenateError::BadUnknown)
                        }
                    }
                    _ => Err(ConcatenateError::BadUnknown)
                }
            },
            (Some(_), None) => Err(ConcatenateError::RhsMulti),
            _ => Err(ConcatenateError::LhsMulti),
        }
    }

    pub fn stringify(&self, r#type: StringType) -> Self {
        let string = self.to_stream().to_string();
        let lit = r#type.make_literal(&string);
        Self::single(Tt::Literal(lit))
    }

    pub fn recase(mut self, case: Case) -> Result<Self, TokenStream> {
        for token in &mut self.0 {
            let tt = &mut token.token;
            let Tt::Ident(ident) = tt else {
                return Err(error(tt, &format!("`{}` can only be applied to identifiers", case.operator_name())))
            };

            let mut words = Vec::new();
            let string = ident.to_string();


            for w in string.split("_") {
                let mut i = 0;
                let mut j = w.ceil_char_boundary(1);

                while let Some(slice) = w.get(j..) && let Some(char) = slice.chars().next() {
                    if char.is_uppercase() && w.get(w.ceil_char_boundary(j + 1)..).is_some_and(|slice| slice.chars().next().is_some_and(|c| !c.is_uppercase())) {
                        words.push(&w[i..j]);
                        i = j;
                    }
                    j += char.len_utf8();
                }

                words.push(&w[i..j]);
            }

            let mut text = String::new();

            let mut words = words.into_iter();

            if let Some(word) = words.next() {
                match case {
                    Case::Snake | Case::Camel => text += &word.to_lowercase(),
                    Case::UpperCamel | Case::UpperSnake => {
                        let i = word.ceil_char_boundary(1);
                        text += &format!("{}{}", word[..i].to_uppercase(), word[i..].to_lowercase())
                    }
                    Case::ScreamingSnake => text += &word.to_uppercase(),
                }
            }

            for word in words {
                if matches!(case, Case::Snake | Case::ScreamingSnake | Case::UpperSnake) {
                    text += "_";
                }
                match case {
                    Case::Snake => text += &word.to_lowercase(),
                    Case::UpperCamel | Case::Camel | Case::UpperSnake => {
                        let i = word.ceil_char_boundary(1);
                        text += &format!("{}{}", word[..i].to_uppercase(), word[i..].to_lowercase())
                    },
                    Case::ScreamingSnake => text += &word.to_uppercase(),
                }
            }

            if text.is_empty() || text.chars().any(|c| !(c.is_alphanumeric() || c == '_')) {
                return Err(error(&ident.span(), &format!("`{text}` is not a valid identifier")));
            }

            *ident = Ident::new(&text, ident.span());
        }

        Ok(self)
    }

    pub fn to_dashes(mut self) -> Result<Self, TokenStream> {
        for token in &mut self.0 {
            match &mut token.token {
                Tt::Literal(literal) => {
                    if LitKind::of(literal).string_type().is_some() {
                        let string = literal.to_string();
                        let (text, _) = parse_string(&string);
                        let text = text.replace("_", "-");
                        *literal = Literal::string(&text);
                    }
                },
                _ => continue,
            }
        }

        Ok(self)
    }
}

#[derive(Copy, Clone)]
pub enum Case {
    Snake,
    UpperCamel,
    ScreamingSnake,
    Camel,
    UpperSnake,
}

impl Case {
    pub fn operator_name(self) -> &'static str {
        match self {
            Case::Snake => "snake_case",
            Case::UpperCamel => "upper_camel_case",
            Case::ScreamingSnake => "screaming_snake_case",
            Case::Camel => "camel_case",
            Case::UpperSnake => "upper_snake_case",
        }
    }
}

pub enum ConcatenateError {
    LhsMulti,
    RhsMulti,
    RhsBadLiteralForIdent,
    RhsBadNumberForIdent,
    LhsSuffix,
    BadUnknown,
}

impl From<Tt> for Metaval {
    fn from(value: Tt) -> Self {
        Self::single(value)
    }
}

impl From<TokenStream> for Metaval {
    fn from(value: TokenStream) -> Self {
        Self::flat(value)
    }
}

impl IntoIterator for Metaval {
    type Item = MetavalToken;

    type IntoIter = <Vec<MetavalToken> as IntoIterator>::IntoIter;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl Extend<MetavalToken> for Metaval {
    fn extend<T: IntoIterator<Item = MetavalToken>>(&mut self, iter: T) {
        self.0.extend(iter)
    }
}

pub struct Split<'a> {
    iter: slice::Iter<'a, MetavalToken>,
}

impl<'a> Iterator for Split<'a> {
    type Item = Metaval;

    fn next(&mut self) -> Option<Self::Item> {
        let mut vals = Vec::new();
        for val in self.iter.by_ref() {
            vals.push(val.raise());
            if val.order == 0 {
                return Some(Metaval(vals))
            }
        }
        None
    }
}