use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{GenericArgument, PathArguments, Type, TypeArray, TypePath, TypeTuple};
pub(crate) enum FieldKind<'a> {
VecOfU8,
VecOfNumeric { elem_ty: &'a Type, dt: TokenStream },
VecOfOther { elem_ty: &'a Type },
NumericTensor {
elem_ty: &'a Type,
dt: TokenStream,
dims: Vec<usize>,
tuple_paths: Option<Vec<String>>,
original_ty: &'a Type,
},
TupleHetero { tup: &'a TypeTuple },
ArrayHetero { arr: &'a TypeArray, len: usize },
Other,
}
pub(crate) fn classify(ty: &Type) -> FieldKind<'_> {
if let Some(elem) = vec_inner(ty) {
return classify_vec(elem);
}
if let Type::Array(arr) = ty {
if let Some(len) = array_len(&arr.len) {
if let Some(tensor) = numeric_tensor_of(ty) {
return tensor;
}
return FieldKind::ArrayHetero { arr, len };
}
}
if let Type::Tuple(tup) = ty {
if !tup.elems.is_empty() {
if let Some(tensor) = numeric_tensor_of(ty) {
return tensor;
}
return FieldKind::TupleHetero { tup };
}
}
FieldKind::Other
}
fn classify_vec(elem: &Type) -> FieldKind<'_> {
if let Some(prim) = primitive_ident_of(elem) {
if prim == "u8" {
return FieldKind::VecOfU8;
}
if let Some(dt) = numeric_dt_for(&prim) {
return FieldKind::VecOfNumeric { elem_ty: elem, dt };
}
}
FieldKind::VecOfOther { elem_ty: elem }
}
fn numeric_tensor_of(ty: &Type) -> Option<FieldKind<'_>> {
if let Type::Array(_) = ty {
let (leaf, dims) = peel_array_nest(ty)?;
let prim = primitive_ident_of(leaf)?;
let dt = numeric_dt_for(&prim)?;
return Some(FieldKind::NumericTensor {
elem_ty: leaf,
dt,
dims,
tuple_paths: None,
original_ty: ty,
});
}
if let Type::Tuple(_) = ty {
let (leaf, dims) = peel_tuple_nest(ty)?;
let prim = primitive_ident_of(leaf)?;
let dt = numeric_dt_for(&prim)?;
let paths = generate_tuple_paths(ty);
return Some(FieldKind::NumericTensor {
elem_ty: leaf,
dt,
dims,
tuple_paths: Some(paths),
original_ty: ty,
});
}
None
}
fn peel_array_nest(ty: &Type) -> Option<(&Type, Vec<usize>)> {
match ty {
Type::Array(arr) => {
let n = array_len(&arr.len)?;
let (leaf, mut inner_dims) = peel_array_nest(&arr.elem)?;
let mut dims = vec![n];
dims.append(&mut inner_dims);
Some((leaf, dims))
},
Type::Tuple(_) => None,
_ => Some((ty, Vec::new())),
}
}
fn peel_tuple_nest(ty: &Type) -> Option<(&Type, Vec<usize>)> {
match ty {
Type::Tuple(tup) => {
let n = tup.elems.len();
if n == 0 {
return None;
}
let mut iter = tup.elems.iter();
let first = iter.next().unwrap();
let (leaf, first_inner_dims) = peel_tuple_nest(first)?;
for next in iter {
let (other_leaf, other_inner_dims) = peel_tuple_nest(next)?;
if !same_type(leaf, other_leaf) || other_inner_dims != first_inner_dims {
return None;
}
}
let mut dims = vec![n];
dims.extend(first_inner_dims);
Some((leaf, dims))
},
Type::Array(_) => None,
_ => Some((ty, Vec::new())),
}
}
fn generate_tuple_paths(ty: &Type) -> Vec<String> {
fn walk(ty: &Type, prefix: &str, out: &mut Vec<String>) {
match ty {
Type::Tuple(tup) => {
for (i, elem) in tup.elems.iter().enumerate() {
let next = if prefix.is_empty() {
i.to_string()
} else {
format!("{}.{}", prefix, i)
};
walk(elem, &next, out);
}
},
_ => {
if !prefix.is_empty() {
out.push(prefix.to_string());
}
},
}
}
let mut out = Vec::new();
walk(ty, "", &mut out);
out
}
fn vec_inner(ty: &Type) -> Option<&Type> {
let path = match ty {
Type::Path(TypePath { qself: None, path }) => path,
_ => return None,
};
let last = path.segments.last()?;
if last.ident != "Vec" {
return None;
}
let args = match &last.arguments {
PathArguments::AngleBracketed(a) => a,
_ => return None,
};
if args.args.len() != 1 {
return None;
}
match args.args.first()? {
GenericArgument::Type(t) => Some(t),
_ => None,
}
}
pub(crate) fn numeric_primitive_name(ty: &Type) -> Option<String> {
let prim = primitive_ident_of(ty)?;
match prim.as_str() {
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "f32" | "f64" => {
Some(prim)
},
_ => None,
}
}
fn primitive_ident_of(ty: &Type) -> Option<String> {
let path = match ty {
Type::Path(TypePath { qself: None, path }) => path,
_ => return None,
};
if path.segments.len() != 1 {
return None;
}
Some(path.segments[0].ident.to_string())
}
fn numeric_dt_for(prim: &str) -> Option<TokenStream> {
let variant = match prim {
"i8" => "Integer8",
"i16" => "Integer16",
"i32" => "Integer32",
"i64" => "Integer64",
"u8" => "UnsignedInteger8",
"u16" => "UnsignedInteger16",
"u32" => "UnsignedInteger32",
"u64" => "UnsignedInteger64",
"f32" => "Real32",
"f64" => "Real64",
_ => return None,
};
let ident = syn::Ident::new(variant, proc_macro2::Span::call_site());
Some(quote! { ::wolfram_serialize::NumericArrayEnum::#ident })
}
fn array_len(expr: &syn::Expr) -> Option<usize> {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(li),
..
}) = expr
{
li.base10_parse::<usize>().ok()
} else {
None
}
}
fn same_type(a: &Type, b: &Type) -> bool {
a.to_token_stream().to_string() == b.to_token_stream().to_string()
}
pub(crate) fn is_option_type(ty: &Type) -> bool {
let path = match ty {
Type::Path(TypePath { qself: None, path }) => path,
_ => return false,
};
let last = match path.segments.last() {
Some(s) => s,
None => return false,
};
if last.ident != "Option" {
return false;
}
matches!(&last.arguments, PathArguments::AngleBracketed(args) if args.args.len() == 1)
}