unit-proc 0.0.3

Procedual macros for physical quantities.
Documentation
use syn:: {
    Expr, ExprPath, Ident, Lit, Token,
    parse:: { self, Parse, ParseStream },
    punctuated::Punctuated,
};
use quote:: { quote, ToTokens, format_ident };
use proc_macro2::TokenStream;
use physical_quantity:: {
    Dimension, DynDim, Unit,
};
use const_frac:: {
    Frac,
    frac::syn::TokenFrac,
};
use std::borrow::Borrow;

pub struct TokenUnit {
    unit: Unit<Frac, DynDim>,
    init: Option<ExprPath>,
    path: Option<ExprPath>,
}

impl TokenUnit {
    fn init_real<T>(&self, path: Option<&ExprPath>, frac: TokenFrac<T>) -> TokenStream
    where
        T: Borrow<ExprPath>
    {
        match path {
            Some(path) => match path.path.segments.last() {
                Some(seg) => match seg.ident.to_string() {
                    s if s == "Frac" => quote! {
                        #frac
                    },
                    s if s == "f64" => quote! {
                        #frac.to_f64()
                    },
                    s if s == "Ratio<u128>" => quote! {
                        #frac.into_rational128()
                    },
                    _ => quote! {
                        #path(#frac)
                    },
                },
                _ => quote! { #frac.into() },
            },
            _ => quote! { #frac.into() },
        }
    }
}

impl Parse for TokenUnit {
    fn parse(input: ParseStream) -> parse::Result<Self> {
        use Lit::*;

        let p = match Punctuated::<Expr, Token![,]>::parse_separated_nonempty(input) {
            Ok(p) => p,
            Err(e) => return Err(input.error(format!("{:?}", e))),
        };
        let mut path = None;
        let mut lit= None;
        let mut init = None;

        for expr in p.into_iter() {
            match expr {
                Expr::Path(ref expr) => if path == None {
                        path = Some(expr.clone());
                    } else {
                        init = Some(expr.clone());
                    },
                Expr::Group(ref expr) => match &*expr.expr {
                    &Expr::Path(ref expr) => if path == None {
                        path = Some(expr.clone());
                    } else {
                        init = Some(expr.clone());
                    },
                    &Expr::Lit(ref expr) => lit = Some(expr.lit.clone()),
                    _ => (),
                },
                Expr::Lit(ref expr) => lit = Some(expr.lit.clone()),
                _ => (),
            }
        }

        let s = match lit {
            Some(Str(s)) => s.value(),
            Some(ByteStr(s)) => String::from_utf8(s.value()).unwrap(),
            Some(Byte(s)) => {
                let ch: char = s.value().into();
                let mut s = String::new();

                s.extend(Some(ch));
                s
            },
            Some(Char(ch)) => {
                let mut s = String::new();

                s.extend(Some(ch.value()));
                s
            },
            Some(Verbatim(s)) => s.to_string(),
            Some(_) => return Err(input.error("Only string literal is allowed.")),
            None => return Err(input.error("Provide at least one unit string.")),
        };
        let unit = s.parse().map_err(|_| input.error("invalid unit string."))?;

        Ok(Self { unit, init, path })
    }
}

impl ToTokens for TokenUnit {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let a = self.init_real(
            self.init.as_ref(),
            TokenFrac {
                frac: self.unit.a,
                path: self.path.as_ref(),
            }
        );
        let b = self.init_real(
            self.init.as_ref(),
            TokenFrac {
                frac: self.unit.b,
                path: self.path.as_ref(),
            }
        );
        let dim = TokenDim {
            dim: self.unit.dim,
            path: self.path.as_ref(),
        };

        tokens.extend(match self.path.as_ref() {
            Some(path) => quote! {
                #path::Unit {
                    a: #a,
                    b: #b,
                    dim: #dim::new(),
                }
            },
            None => quote! {
                Unit {
                    a: #a,
                    b: #b,
                    dim: #dim::new(),
                }
            },
        })
    }
}

fn ident_dim(i: i8) -> Ident {
    match i.signum() {
        -1 => format_ident!("N{}", -i as usize),
        1 => format_ident!("P{}", i as usize),
        _ => format_ident!("Z0"),
    }
}

pub struct TokenDim<T>
where
    T: Borrow<ExprPath>
{
    dim: DynDim,
    path: Option<T>,
}

impl Parse for TokenDim<ExprPath> {
    fn parse(input: ParseStream) -> parse::Result<Self> {
        let u: TokenUnit = input.parse()?;

        Ok(Self {
            dim: u.unit.dim,
            path: u.path,
        })
    }
}

impl<T> ToTokens for TokenDim<T>
where
    T: Borrow<ExprPath>
{
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let l = ident_dim(self.dim.length());
        let m = ident_dim(self.dim.mass());
        let t = ident_dim(self.dim.time());
        let th = ident_dim(self.dim.temperature());
        let n = ident_dim(self.dim.amount());
        let i = ident_dim(self.dim.current());
        let j = ident_dim(self.dim.luminous());

        tokens.extend(match self.path.borrow() {
            Some(path) => {
                let path = path.borrow();

                quote! {
                    #path::Dim::<
                        #path::typenum::#l,
                        #path::typenum::#m,
                        #path::typenum::#t,
                        #path::typenum::#th,
                        #path::typenum::#n,
                        #path::typenum::#i,
                        #path::typenum::#j
                    >
                }
            },
            None => quote! {
                Dim::<
                    typenum::#l,
                    typenum::#m,
                    typenum::#t,
                    typenum::#th,
                    typenum::#n,
                    typenum::#i,
                    typenum::#j
                >
            },
        })
    }
}