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