use crate::error::ErrorKind;
use crate::powers::Powers;
use crate::rational::Rational;
use crate::syntax::parser::{Parser, Syntax};
use crate::unit::{Conversion, Unit};
use crate::Error;
use serde::{Deserialize, Serialize};
use std::collections::{btree_map, BTreeMap};
use std::fmt;
use std::iter::FromIterator;
use syntree::Span;
#[non_exhaustive]
pub(crate) struct CompoundError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct State {
pub power: i32,
pub prefix: i32,
}
impl From<(i32, i32)> for State {
fn from((power, prefix): (i32, i32)) -> Self {
Self { power, prefix }
}
}
#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Compound {
names: BTreeMap<Unit, State>,
}
impl Compound {
pub fn empty() -> Self {
Self {
names: BTreeMap::new(),
}
}
pub(crate) fn new(names: BTreeMap<Unit, State>) -> Self {
debug_assert!(
names.values().all(|s| s.power != 0),
"all powers of a constructed unit must be non-zero; {:?}",
names
);
Self { names }
}
pub fn update(&mut self, unit: Unit, power: i32, prefix: i32) -> Result<(), i32> {
match self.names.entry(unit) {
btree_map::Entry::Vacant(e) => {
e.insert(State { power, prefix });
Ok(())
}
btree_map::Entry::Occupied(mut e) => {
let state = e.get_mut();
if state.prefix != prefix {
return Err(state.prefix);
}
state.power += power;
if state.power == 0 {
e.remove_entry();
return Ok(());
}
Ok(())
}
}
}
pub fn update_power(&mut self, unit: Unit, power: i32) {
if let Some(state) = self.names.get_mut(&unit) {
state.power = power;
}
}
pub fn has_numerator(&self) -> bool {
self.names.values().any(|s| s.power > 0)
}
pub fn is_empty(&self) -> bool {
self.names.is_empty()
}
pub fn is_acceleration(&self) -> bool {
let (_, bases) = self.base_units();
if bases.len() != 2 {
return false;
}
let meter = bases.get(Unit::Meter);
let second = bases.get(Unit::Second);
meter == Some(1) && second == Some(-2)
}
pub(crate) fn factor(&self, other: &Self, value: &mut Rational) -> Result<bool, CompoundError> {
if self.is_empty() || other.is_empty() {
return Ok(true);
}
let (_, lhs_bases) = self.base_units();
let (_, rhs_bases) = other.base_units();
if lhs_bases.len() != rhs_bases.len() {
return Ok(false);
}
for (name, rhs) in &rhs_bases {
let lhs = match lhs_bases.get(name) {
Some(lhs) => lhs,
None => return Ok(false),
};
if lhs != rhs {
return Ok(false);
}
}
for (name, state) in &other.names {
*value *= Rational::new(10u32, 1u32).pow(state.prefix * state.power);
if let Some(conversion) = name.conversion() {
apply_conversion(state.power, value, conversion)?;
}
}
for (name, state) in &self.names {
if let Some(conversion) = name.conversion() {
apply_conversion(-state.power, value, conversion)?;
}
*value /= Rational::new(10u32, 1u32).pow(state.prefix * state.power);
}
Ok(true)
}
pub(crate) fn mul(
&self,
other: &Self,
n: i32,
lhs: &mut Rational,
rhs: &mut Rational,
) -> Result<Self, CompoundError> {
if self.is_empty() || other.is_empty() {
let unit = if self.is_empty() {
other
.names
.iter()
.map(|(unit, state)| (*unit, (state.power * n, state.prefix)))
.collect()
} else {
self.clone()
};
return Ok(unit);
}
let (lhs_der, lhs_bases) = self.base_units();
let (rhs_der, rhs_bases) = other.base_units();
let mut names = BTreeMap::new();
for (name, power) in lhs_bases {
names.insert(name, State { power, prefix: 0 });
}
for (name, power) in rhs_bases {
match names.entry(name) {
btree_map::Entry::Vacant(e) => {
e.insert(State {
power: power * n,
prefix: 0,
});
}
btree_map::Entry::Occupied(mut e) => {
e.get_mut().power += power * n;
if e.get().power == 0 {
e.remove_entry();
}
}
}
}
for (name, state) in &self.names {
*lhs *= Rational::new(10u32, 1u32).pow(state.prefix * state.power);
if let Some(conversion) = name.conversion() {
apply_conversion(state.power, lhs, conversion)?;
}
}
for (name, state) in &other.names {
*rhs *= Rational::new(10u32, 1u32).pow(state.prefix * state.power);
if let Some(conversion) = name.conversion() {
apply_conversion(state.power, rhs, conversion)?;
}
}
let der = lhs_der
.into_iter()
.map(|(u, p)| (u, p, 1))
.chain(rhs_der.into_iter().map(|(u, p)| (u, p, n)));
reconstruct(der, lhs, &mut names)?;
return Ok(Compound::new(names));
fn reconstruct(
der: impl IntoIterator<Item = (Unit, i32, i32)>,
out: &mut Rational,
names: &mut BTreeMap<Unit, State>,
) -> Result<(), CompoundError> {
let mut powers = Powers::default();
for (unit, power, n) in der {
powers.clear();
if !unit.powers(&mut powers, 1) {
continue;
}
let mod_power = match bases_match(power * n, &powers, names) {
Some(power) => power,
None => continue,
};
for (u, s) in &powers {
if let btree_map::Entry::Occupied(mut e) = names.entry(u) {
e.get_mut().power -= s * mod_power;
if e.get().power == 0 {
e.remove_entry();
}
}
}
match names.entry(unit) {
btree_map::Entry::Vacant(e) => {
e.insert(State {
power: mod_power,
prefix: 0,
});
}
btree_map::Entry::Occupied(mut e) => {
e.get_mut().power += mod_power;
}
};
if let Some(conversion) = unit.conversion() {
apply_conversion(-mod_power, out, conversion)?;
}
}
Ok(())
}
fn bases_match(
mut power: i32,
powers: &Powers,
names: &BTreeMap<Unit, State>,
) -> Option<i32> {
let dec = power.signum();
let m = |(u, p)| inner_match(u, p, &mut power, dec, names);
if powers.iter().all(m) {
return Some(power);
}
None
}
fn inner_match(
unit: Unit,
base: i32,
cur: &mut i32,
dec: i32,
names: &BTreeMap<Unit, State>,
) -> bool {
let s = match names.get(&unit) {
Some(state) => state.power,
None => return false,
};
while *cur != 0 {
let p = base * *cur;
if p.signum() == s.signum() && p * p.signum() <= s * s.signum() {
return true;
}
*cur -= dec;
}
false
}
}
fn base_units(&self) -> (Vec<(Unit, i32)>, Powers) {
let mut powers = Powers::default();
let mut derived = Vec::new();
for (name, state) in &self.names {
if name.powers(&mut powers, state.power) {
derived.push((*name, state.power));
}
}
(derived, powers)
}
pub fn display(&self, pluralize: bool) -> Display<'_> {
Display {
this: self,
pluralize,
}
}
}
impl<S> FromIterator<(Unit, S)> for Compound
where
State: From<S>,
{
fn from_iter<T: IntoIterator<Item = (Unit, S)>>(iter: T) -> Self {
let mut names = BTreeMap::new();
for (unit, state) in iter {
let state = State::from(state);
if state.power != 0 {
names.insert(unit, state);
}
}
Self { names }
}
}
pub struct Display<'a> {
this: &'a Compound,
pluralize: bool,
}
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::Write;
let mut pluralize = if self.this.names.iter().filter(|e| e.1.power >= 0).count() == 1 {
self.pluralize
} else {
false
};
let mut it = self.this.names.iter().filter(|e| e.1.power >= 0).peekable();
while let Some((base, data)) = it.next() {
base.display(data, std::mem::take(&mut pluralize), 1)
.fmt(f)?;
if it.peek().is_some() {
f.write_char('⋅')?;
}
}
if self.this.names.iter().any(|c| c.1.power < 0) {
write!(f, "/")?;
let mut it = self.this.names.iter().filter(|e| e.1.power < 0).peekable();
while let Some((base, data)) = it.next() {
base.display(data, false, -1).fmt(f)?;
if it.peek().is_some() {
f.write_char('⋅')?;
}
}
}
Ok(())
}
}
impl std::str::FromStr for Compound {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let node = match Parser::new(s).parse_unit() {
Ok(node) => node,
Err(error) => {
return Err(Error::new(
Span::new(0, s.len() as u32),
ErrorKind::TreeError { error },
))
}
};
let children = match node.first() {
Some(node) if *node.value() == Syntax::UNIT => node.children(),
Some(node) => {
return Err(Error::new(
*node.span(),
ErrorKind::Expected {
expected: Syntax::UNIT,
actual: *node.value(),
},
))
}
None => Default::default(),
};
crate::eval::unit(s, children, Default::default())
}
}
impl fmt::Debug for Compound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Compound")
.field(&self.display(false).to_string())
.finish()
}
}
impl fmt::Display for Compound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(false).fmt(f)
}
}
fn apply_conversion(
pow: i32,
ratio: &mut Rational,
conversion: Conversion,
) -> Result<(), CompoundError> {
match conversion {
Conversion::Methods(methods) => {
if pow.abs() != 1 {
return Err(CompoundError);
}
for _ in pow..0 {
(methods.from)(ratio);
}
for _ in 0..pow {
(methods.to)(ratio);
}
}
Conversion::Factor(fraction) => {
if pow != 0 {
*ratio *= Rational::new(fraction.numer, fraction.denom).pow(pow);
}
}
Conversion::Offset(fraction) => {
if pow.abs() != 1 {
return Err(CompoundError);
}
if pow != 0 {
*ratio += Rational::new(fraction.numer, fraction.denom) * Rational::new(pow, 1);
}
}
}
Ok(())
}