use crate:: {
Dimension, Dim, DynDim, PhysicalQuantity,
dimension,
traits::Real,
error::Error,
predefined::dim,
};
use core:: {
fmt:: { self, Debug, Display },
ops:: { Mul, Div },
};
use const_frac::Frac;
#[cfg(feature = "parser")]
use core::marker::PhantomData;
#[cfg(feature = "parser")]
use combine:: { Parser as _, stream::position };
#[cfg(feature = "default-units")]
use core::str::FromStr;
#[cfg(feature = "default-units")]
use crate::predefined::unit::DEFAULT_UNIT_DEF;
#[cfg(feature = "parser")]
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
mod parser;
const ZERO: Frac = Frac::from_int(0);
const ONE: Frac = Frac::from_int(1);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Conv(pub Frac, pub Frac);
impl Conv {
pub const fn add_prefix(mut self, exp10: i8) -> Self {
if exp10 == 0 { return self }
let exp = Frac::from_exp10(exp10 as i16);
self.0 = if exp10 < 0 {
self.0.div(exp)
} else {
self.0.mul(exp)
};
self
}
pub const fn then(mut self, rhs: Self) -> Self {
self.0 = self.0.mul(rhs.0);
self.1 = self.1.mul(rhs.0).add(rhs.1);
self
}
pub const fn mul(mut self, rhs: Self) -> Self {
self.0 = self.0.mul(rhs.0);
self.1 = ZERO;
self
}
pub const fn div(mut self, rhs: Self) -> Self {
self.0 = self.0.div(rhs.0);
self.1 = ZERO;
self
}
pub const fn powi(mut self, exp: i8) -> Self {
self.0 = self.0.powi(exp as i16);
self.1 = ZERO;
self
}
}
impl Mul for Conv {
type Output = Self;
fn mul(self, rhs: Self) -> Self { Conv::mul(self, rhs) }
}
impl Div for Conv {
type Output = Self;
fn div(self, rhs: Self) -> Self { Conv::div(self, rhs) }
}
impl Default for Conv {
fn default() -> Self {
Self(ONE, ONE)
}
}
pub struct Unit<R, D> {
pub a: R,
pub b: R,
pub dim: D,
}
impl<R, D> Unit<R, D>
where
D: Dimension, R: Real
{
pub fn pq(&self, r: R) -> PhysicalQuantity<R, D> {
PhysicalQuantity {
value: r.mul_ref(&self.a).add_ref(&self.b),
dim: self.dim,
}
}
}
impl<R> Unit<R, DynDim>
where
R: Real,
{
pub fn value<D>(&self, pq: PhysicalQuantity<R, D>) -> Result<R, Error>
where
D: Dimension
{
if !dimension::is_equal(&self.dim, &pq.dim) {
let inv = DynDim::from(dim::DIMENSIONLESS) / pq.dim;
if !dimension::is_equal(&self.dim, &inv) {
Err(Error::DimensionMismatch)
} else {
Ok(self.a.clone().div_ref(&pq.value.sub_ref(&self.b)))
}
} else {
Ok(pq.value.sub_ref(&self.b).div_ref(&self.a))
}
}
pub fn powi(self, n: i8) -> Self {
Self {
a: self.a.poweri(n as i16),
b: R::from_frac(ZERO),
dim: self.dim.powi(n),
}
}
}
#[allow(non_camel_case_types)]
impl<R, L, M, T, θ, N, I, J> Unit<R, Dim<L, M, T, θ, N, I, J>>
where
R: Real,
{
pub fn value(&self, pq: PhysicalQuantity<R, Dim<L, M, T, θ, N, I, J>>) -> R {
pq.value.sub_ref(&self.b).div_ref(&self.a)
}
}
impl<R, D> Clone for Unit<R, D>
where
R: Clone, D: Clone,
{
fn clone(&self) -> Self {
Self {
a: self.a.clone(),
b: self.b.clone(),
dim: self.dim.clone(),
}
}
}
impl<R, D> Copy for Unit<R, D>
where
R: Copy, D: Copy,
{}
macro_rules! binop_impl {
($trait:ident, $method:ident, $ref_method:ident) => {
impl<'a, D0, D1, R> $trait<&'a Unit<R, D1>> for Unit<R, D0>
where
PhysicalQuantity<R, D0>: $trait<PhysicalQuantity<R, D1>>,
D0: Dimension + $trait<D1>, <D0 as $trait<D1>>::Output: Dimension,
D1: Dimension,
R: Real,
{
type Output = Unit<R, <D0 as $trait<D1>>::Output>;
fn $method(self, rhs: &'a Unit<R, D1>) -> Self::Output {
Unit {
a: self.a.$ref_method(&rhs.a),
b: R::from_frac(ZERO),
dim: self.dim.$method(rhs.dim),
}
}
}
};
}
binop_impl! { Mul, mul, mul_ref }
binop_impl! { Div, div, div_ref }
impl<R, D> Default for Unit<R, D>
where
D: Dimension,
R: Real,
{
fn default() -> Self {
Self {
a: R::from_frac(ONE),
b: R::from_frac(ZERO),
dim: Default::default(),
}
}
}
impl<D0, D1, R> PartialEq<Unit<R, D1>> for Unit<R, D0>
where
D0: Dimension, D1: Dimension, R: Real,
PhysicalQuantity<R, D0>: PartialEq<PhysicalQuantity<R, D1>>
{
fn eq(&self, other: &Unit<R, D1>) -> bool {
self.a == other.a && self.b == other.b
}
}
impl<R, D> Debug for Unit<R, D>
where
D: Dimension,
R: Real,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("Unit")
.field("a", &self.a)
.field("b", &self.b)
.field("dim", &self.dim)
.finish()
}
}
impl<R, D> Display for Unit<R, D>
where
D: Dimension, R: Real,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
Debug::fmt(self, f)
}
}
impl<R, D> From<(Conv, D)> for Unit<R, D>
where
R: Real,
D: Dimension,
{
fn from((conv, dim): (Conv, D)) -> Self {
Self {
a: R::from_frac(conv.0),
b: R::from_frac(conv.1),
dim,
}
}
}
#[cfg(feature = "default-units")]
#[cfg_attr(docsrc, doc(cfg(feature = "default-units")))]
impl<R> FromStr for Unit<R, DynDim>
where
R: Real,
{
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Parser::new(&DEFAULT_UNIT_DEF[..]).parse(s)
}
}
#[cfg(feature = "parser")]
#[cfg_attr(docsrc, doc(cfg(feature = "parser")))]
pub struct Parser<K, T>
where
K: AsRef<str>,
T: AsRef<[(K, (Conv, DynDim))]>
{
tbl: T,
_key: PhantomData<K>,
}
#[cfg(feature = "parser")]
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
impl<K, T> Parser<K, T>
where
K: AsRef<str>,
T: AsRef<[(K, (Conv, DynDim))]> + Copy
{
pub fn new(tbl: T) -> Self {
Self {
tbl,
_key: PhantomData,
}
}
pub fn parse<R: Real>(&self, s: &str)
-> Result<Unit<R, DynDim>,Error>
{
let input = position::Stream::new(s);
match parser::unit(self.tbl).parse(input) {
Ok((output, _remaining_input)) => Ok(Unit {
a: R::from_frac(output.a),
b: R::from_frac(output.b),
dim: output.dim,
}),
_ => Err(Error::InvalidUnitString),
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use crate:: {
Dim,
predefined::dim,
};
use typenum::Z0;
use alloc::string::ToString;
#[test]
fn test_parse_unit() {
let u: Result<Unit<_, _>, _> = "kg*m/s2".parse();
assert_eq!(
u.unwrap(),
Unit { a: 1000.0, b: 0.0, dim: DynDim::from(dim::FORCE) }
);
}
#[test]
fn test_unit_creation() {
let u = Unit {
a: 1f64,
b: 0f64,
dim: Dim::<Z0, Z0, Z0, Z0, Z0, Z0, Z0>::new(),
};
assert_eq!(
u.to_string(),
"Unit { a: 1.0, b: 0.0, dim: Dim { L: 0, M: 0, T: 0, θ: 0, N: 0, I: 0, J: 0 } }"
);
}
}