const-array-attrs 0.0.3

Attribute macros for const array.
Documentation
use proc_macro2::TokenStream;
use syn:: {
    Expr, ExprArray, ExprLit, ExprTuple, Ident, Index, ItemConst, Lit,
    Type, TypeArray, TypeTuple, Visibility,
    parse:: { self, Parse, ParseStream },
    punctuated::Punctuated,
    token::Comma,
};
use quote::{ quote, ToTokens, format_ident };
use disuse::Disuse;
use transition_table:: { Entry, Transition };

pub struct TransitionTableInfo {
    vis: Visibility,
    name: Ident,
}

impl Parse for TransitionTableInfo {
    fn parse(input: ParseStream) -> parse::Result<Self> {
        Ok(Self {
            vis: input.parse()?,
            name: input.parse()?,
        })
    }
}

impl ToTokens for TransitionTableInfo {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let Self { vis, name } = self;

        tokens.extend(quote! {
            #vis const #name:
        })
    }
}

pub struct TransitionTableBody {
    transitions: Vec<(u8, usize, usize, usize)>,
    src_name: Ident,
    v_type: Type,
}

impl TransitionTableBody {
    fn idx_type(&self) -> Ident {
        uint_size(self.transitions.len())
    }
}

impl Parse for TransitionTableBody {
    fn parse(input: ParseStream) -> parse::Result<Self> {
        let item_const: ItemConst = input.parse()?;
        let v_type = match item_const.ty.as_ref() {
            Type::Array(TypeArray { ref elem, .. }) => match elem.as_ref() {
                Type::Tuple(TypeTuple { ref elems, .. }) => elems.iter().nth(1).ok_or_else(
                    || input.error("need value type.")
                )?.clone(),
                _ => return Err(input.error("only (&str, V) type supported for array element.")),
            },
            _ => return Err(input.error("not const array.")),
        };
        let (transitions, _): (_, Disuse) = match item_const.expr.as_ref() {
            Expr::Array(ExprArray { ref elems, .. }) => gen_transition_table(elems),
            _ => return Err(input.error("not const array.")),
        };

        Ok(Self {
            transitions,
            src_name: item_const.ident.clone(),
            v_type
        })
    }
}

impl ToTokens for TransitionTableBody {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let idx_type = self.idx_type();
        let src_name = &self.src_name;
        let v_type = &self.v_type;
        let n = self.transitions.len();
        let idx: Index = 1usize.into();
        let mut entries = TokenStream::new();

        for e in self.transitions.iter() {
            let (key, beg, end, iv) = e;
            let value = if *iv == !0 {
                quote! { None }
            } else {
                quote! { Some(&#src_name[#iv].#idx) }
            };

            entries.extend(quote! { (
                #key,
                #beg as #idx_type,
                #end as #idx_type,
                #value,
            ), })
        }
        tokens.extend(quote! {
            [(u8, #idx_type, #idx_type, Option<&'static #v_type>); #n] = [ #entries ]; });
    }
}

fn gen_transition_table<'a, V>(exprs: &'a Punctuated<Expr, Comma>) -> (Vec<Transition<u8, usize, usize>>, V)
where
    V: From<(Vec<&'a Expr>,)>,
{
    let mut values = Vec::new();
    let mut entry = Entry::default();

    for (i, expr) in exprs.iter().enumerate() {
        match expr {
            Expr::Tuple(ExprTuple { ref elems, .. }) => {
                let mut it = elems.iter();

                match it.next() {
                    Some(Expr::Lit(ExprLit { lit: Lit::Str(ref key), .. })) => match it.next() {
                        Some(value) => {
                            entry.push(key.value().bytes(), i);
                            values.push(value);
                        },
                        None => break,
                    },
                    _ => break,
                }
            },
            _ => break,
        }
    }
    (entry.into(), (values,).into())
}

fn uint_size(n: usize) -> Ident {
    format_ident!("u{}", match n {
        0..=0xFE => 8usize,
        0xFF..=0xFFFE => 16usize,
        0xFFFF..=0xFFFF_FFFE => 32usize,
        _ => 64usize,
    })
}