units/
lib.rs

1#![cfg_attr(feature="unstable", feature(unboxed_closures, core, zero_one))]
2
3extern crate tylar;
4
5#[doc(no_inline)]
6pub use tylar::{NumType,Zero,P1};
7#[doc(no_inline,hidden)]
8pub use tylar::Sub as TSub;
9#[doc(no_inline,hidden)]
10pub use tylar::Add as TAdd;
11#[doc(no_inline,hidden)]
12pub use tylar::Halve as THalve;
13
14use std::fmt::{Formatter,Result};
15
16// TODO: support documentation comments/attributes for modules and dimensions/units using `$(#[$attr:meta])*` (blocked on rust/#23812)
17
18pub trait UnitFormat { fn fmt(&mut Formatter) -> Result; }
19pub trait UnitZero {}
20pub trait UnitAdd<RHS> { type Out; }
21pub trait UnitSub<RHS> { type Out; }
22pub trait UnitMul<RHS> { type Out; }
23pub trait UnitDiv<RHS> { type Out; }
24pub trait UnitSqrt { type Out; }
25
26#[macro_export]
27macro_rules! units {( $name:ident { $( $dim:ident[$unit:ident]),+ } ) => {
28    use $crate::{UnitZero,UnitAdd,UnitSub,UnitMul,UnitDiv,UnitSqrt,UnitFormat};
29    use std::marker::PhantomData;
30    use std::fmt::{Debug,Formatter,Result};
31    use std::ops::{Add,Sub,Mul,Div,Deref};
32    use $crate::{NumType,Zero,P1,TAdd,TSub,THalve};
33    
34    // TODO: move as much as possible of the following impls out of the macro (using a helper trait)
35    #[derive(Copy,Clone,PartialEq,PartialOrd,Eq,Ord)]    
36    pub struct $name<D,N=f64> {
37        amount: N,
38        phantom: PhantomData<D>
39    }
40
41    impl<D,N> $name<D,N> {
42        fn new(v: N) -> Self {
43            $name { amount: v, phantom: PhantomData }
44        }
45    }
46
47    impl<D:UnitFormat,N> Debug for $name<D,N> where N:Debug {
48        fn fmt(&self, formatter: &mut Formatter) -> Result {
49            try!(self.amount.fmt(formatter));
50            D::fmt(formatter)
51        }
52    }
53
54    // TODO: Add operators for reference types?
55
56    impl<D1,D2,N1,N2> Add<$name<D2,N2>> for $name<D1,N1> where D1:UnitAdd<D2>, N1:Add<N2> {
57        type Output = $name<D1::Out, N1::Output>;
58        
59        fn add(self, rhs: $name<D2,N2>) -> Self::Output {
60            $name::new(self.amount + rhs.amount)
61        }
62    }
63
64    impl<D1,D2,N1,N2> Sub<$name<D2,N2>> for $name<D1,N1> where D1:UnitSub<D2>, N1:Sub<N2> {
65        type Output = $name<D1::Out, N1::Output>;
66        
67        fn sub(self, rhs: $name<D2,N2>) -> Self::Output {
68            $name::new(self.amount - rhs.amount)
69        }
70    }
71
72    impl<D1,D2,N1,N2> Mul<$name<D2,N2>> for $name<D1,N1> where D1:UnitMul<D2>, N1:Mul<N2> {
73        type Output = $name<D1::Out, N1::Output>;
74        
75        fn mul(self, rhs: $name<D2,N2>) -> Self::Output {
76            $name::new(self.amount * rhs.amount)
77        }
78    }
79
80    impl<D1,D2,N1,N2> Div<$name<D2,N2>> for $name<D1,N1> where D1:UnitDiv<D2>, N1: Div<N2> {
81        type Output = $name<D1::Out, N1::Output>;
82        
83        fn div(self, rhs: $name<D2,N2>) -> Self::Output {
84            $name::new(self.amount / rhs.amount)
85        }
86    }
87
88    // Implement sqrt function if underlying numeric type is f32 or f64
89    // TODO: other functions that could be implemented: min, max, abs(?)
90
91    #[allow(dead_code)]
92    impl<D1> $name<D1,f64> where D1:UnitSqrt {
93        pub fn sqrt(self) -> $name<D1::Out,f64> {
94            $name::new(self.amount.sqrt())
95        }
96    }
97
98    #[allow(dead_code)]
99    impl<D1> $name<D1,f32> where D1:UnitSqrt {
100        pub fn sqrt(self) -> $name<D1::Out,f32> {
101            $name::new(self.amount.sqrt())
102        }
103    }
104
105    // Implementation of Deref that is only valid for dimensionless quantities (all exponents Zero)
106    impl<D:UnitZero,N> Deref for $name<D,N> {
107        type Target = N;
108        fn deref(&self) -> &Self::Target { &self.amount }
109    }
110
111    // Implementation of Zero (unstable), which is valid for all units, since zero is the only
112    // value that is polymorphic in its unit. std::num::One is deliberately NOT implemented.
113    #[cfg(feature = "unstable")]
114    impl <D,N> ::std::num::Zero for $name<D,N> where N: ::std::num::Zero {
115        fn zero() -> Self {
116            $name::new(N::zero())
117        }
118    }
119
120    // This was moved to a helper macro, because conditional compilation did
121    // not work correctly ("rust-call" not available in stable Rust)
122    __dim_fn_call_helper!($name);
123
124    // One operand is a (dimensionless) float
125    #[cfg(not(feature = "unstable"))]
126    impl<D> Mul<$name<D,f64>> for f64 {
127        type Output = $name<D,f64>;
128        fn mul(self, rhs: $name<D,f64>) -> Self::Output {
129            $name::new(self * rhs.amount)
130        }
131    }
132
133    #[cfg(not(feature = "unstable"))]
134    impl<D> Mul<$name<D,f32>> for f32 {
135        type Output = $name<D,f32>;
136        fn mul(self, rhs: $name<D,f32>) -> Self::Output {
137            $name::new(self * rhs.amount)
138        }
139    }
140    
141    #[derive(Copy,Clone,PartialEq,PartialOrd,Eq,Ord)]
142    #[allow(non_snake_case)]
143    pub struct Unit<$($dim:NumType=Zero),+> {
144        $($dim: PhantomData<$dim>),+
145    }
146    
147    // Debug formatting (printing units)
148    // TODO: maybe implement Display?
149    impl<$($dim:NumType),+> UnitFormat for Unit<$($dim),+> {
150        fn fmt(formatter: &mut Formatter) -> Result {
151            let mut exp: i32;
152            $(
153                exp = $dim::new().into();
154                match exp {
155                    0 => (),
156                    1 => try!(write!(formatter, " {}", stringify!($unit))),
157                    _ => try!(write!(formatter, " {}^{:?}", stringify!($unit), exp))
158                }
159            )+
160            Ok(())
161        }
162    }
163    
164    impl<$($dim:NumType),+> UnitAdd<Unit<$($dim),+>> for Unit<$($dim),+> { type Out = Unit<$($dim),+>; }
165
166    impl<$($dim:NumType),+> UnitSub<Unit<$($dim),+>> for Unit<$($dim),+> { type Out = Unit<$($dim),+>; }
167
168    // In Mul and Div implementations we abuse $unit for the RHS type parameter name
169    
170    #[allow(non_camel_case_types)]
171    impl<$($dim:NumType),+ , $($unit:NumType),+> UnitMul<Unit<$($unit),+>> for Unit<$($dim),+>
172        where $($dim:TAdd<$unit>),+ { type Out = Unit<$(<$dim as TAdd<$unit>>::Out),+>; }
173    
174    #[allow(non_camel_case_types)]    
175    impl<$($dim:NumType),+ , $($unit:NumType),+> UnitDiv<Unit<$($unit),+>> for Unit<$($dim),+>
176        where $($dim:TSub<$unit>),+ { type Out = Unit<$(<$dim as TSub<$unit>>::Out),+>; }
177        
178    impl<$($dim:NumType),+> UnitSqrt for Unit<$($dim),+>
179        where $($dim:THalve),+ { type Out = Unit<$(<$dim as THalve>::Out),+>; }
180        
181    // type alias and `UnitZero` impl for the dimensionless type (all exponents are zero)
182    pub type One = Unit;
183    impl UnitZero for One {}
184    
185    // generate aliases of the form `pub type $dim1 = Unit<Pos1, Zero, Zero, ...>`
186    __dim_type_alias_helper! { $($dim),+ -> P1 }
187    
188    pub mod f64 {
189        use std::marker::PhantomData;
190        use super::{$name,One,$($dim),+};
191        
192        #[allow(non_upper_case_globals, dead_code)]
193        pub const one: $name<One, f64> = $name {
194            amount: 1.0,
195            phantom: PhantomData
196        };
197        
198        $(
199        #[allow(non_upper_case_globals, dead_code)]
200        pub const $unit: $name<$dim, f64> = $name {
201            amount: 1.0,
202            phantom: PhantomData
203        };
204        )+
205    }
206    
207    pub mod f32 {
208        use std::marker::PhantomData;
209        use super::{$name,One,$($dim),+};
210        
211        #[allow(non_upper_case_globals, dead_code)]
212        pub const one: $name<One, f32> = $name {
213            amount: 1.0,
214            phantom: PhantomData
215        };
216        
217        $(
218        #[allow(non_upper_case_globals, dead_code)]
219        pub const $unit: $name<$dim, f32> = $name {
220            amount: 1.0,
221            phantom: PhantomData
222        };
223        )+
224    }
225}}
226
227#[macro_export]
228#[doc(hidden)]
229macro_rules! __dim_type_alias_helper {
230    ( $dim:ident -> $($types:ty),+ ) => (
231        pub type $dim = Unit<$($types),+>;
232    );
233    
234    ( $dim:ident, $($dims:ident),* -> $($types:ty),+ ) => (
235        pub type $dim = Unit<$($types),+>; __dim_type_alias_helper!( $($dims),* -> Zero, $($types),+);
236    )
237}
238
239#[macro_export]
240#[doc(hidden)]
241#[cfg(feature = "unstable")]
242macro_rules! __dim_fn_call_helper {
243    ($name:ident) => (
244       impl<D,N> FnOnce<(N,)> for $name<D,N> where N:Mul<N,Output=N> {
245            type Output = $name<D,N>;
246            extern "rust-call" fn call_once(self, args: (N,)) -> Self::Output {
247                $name::new(self.amount * args.0)
248            }
249        }
250    );
251}
252
253#[macro_export]
254#[doc(hidden)]
255#[cfg(not(feature = "unstable"))]
256macro_rules! __dim_fn_call_helper {
257    // Just return nothing (because function call overloading isn't supported in stable Rust)
258    ($name:ident) => ()
259}
260
261// Macro invocation inside private module to make sure that the macro works as intended
262mod smoke_test {
263    units! {
264        Units {
265            Meter[m],
266            Second[s]
267        }
268    }
269}