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,
})
}