diff --git a/crates/api/src/measurement/ops/mul_div.rs b/crates/api/src/measurement/ops/mul_div.rs index 9175147..bd0b6cc 100644 --- a/crates/api/src/measurement/ops/mul_div.rs +++ b/crates/api/src/measurement/ops/mul_div.rs @@ -971,5 +971,16 @@ mod tests { term!(Meter, exponent: 2) ) ); + + // VolumePerVolume * VolumePerArea + validate_ok!( + volume_per_volume_and_volume_per_area, + parse_unit!("m3/m3"), parse_unit!("m3/m2"), + mul_values: 2.0, 2.0, + mul_unit: parse_unit!("m"), + div_values: 0.5, 2.0, + div_unit1: parse_unit!("/m"), + div_unit2: parse_unit!("m") + ); } } diff --git a/crates/api/src/unit/ops.rs b/crates/api/src/unit/ops.rs index 410ad5c..aa69425 100644 --- a/crates/api/src/unit/ops.rs +++ b/crates/api/src/unit/ops.rs @@ -264,5 +264,7 @@ mod tests { METER, METER_PER_SECOND => parse_unit!["m2/s"]); test_mul!(test_annotatable_mul_different_annotatable: parse_unit!("42m{foo}"), parse_unit!("42m-1{bar}") => parse_unit!("42m{foo}/42m{bar}")); + test_mul!(test_volume_per_volume_mul_volume_per_area: + parse_unit!("m3/m3"), parse_unit!("m3/m2") => parse_unit!("m")); } } diff --git a/crates/api/src/unit/parser/terms/mapper/basic_component.rs b/crates/api/src/unit/parser/terms/mapper/basic_component.rs index bb46bb6..e707c28 100644 --- a/crates/api/src/unit/parser/terms/mapper/basic_component.rs +++ b/crates/api/src/unit/parser/terms/mapper/basic_component.rs @@ -1,7 +1,7 @@ use pest::iterators::Pair; use crate::{ - term::{variants::FactorAnnotation, Factor}, + term::{variants::FactorAnnotation, Factor, UNITY}, unit::parser::{terms::term_parser::Rule, Error, Visit}, Annotation, }; @@ -82,7 +82,7 @@ impl Finishable for BasicComponent<'_> { let mut terms: Vec = Vec::with_capacity(self.terms.len() + 1); let self_term = match (self.factor, self.annotatable, self.annotation) { - (None, None, None) => unreachable!("Parsed empty unit string for Term"), + (None, None, None) => UNITY, (None, None, Some(annotation)) => Term::Annotation(Annotation::from(annotation)), (None, Some(Annotatable(term)), None) => term, (None, Some(Annotatable(mut term)), Some(annotation)) => { diff --git a/crates/api/src/unit/term_reducing.rs b/crates/api/src/unit/term_reducing.rs index fbe72ca..bcc164b 100644 --- a/crates/api/src/unit/term_reducing.rs +++ b/crates/api/src/unit/term_reducing.rs @@ -51,16 +51,31 @@ pub(super) fn compare_and_cancel(lhs_terms: &[Term], rhs_terms: Vec) -> Co output.extend(what_to_keep.new_terms); remaining_rhs = what_to_keep.kept_terms; - - // Break if there is nothing left on the RHS to compare the LHS to. - if remaining_rhs.is_empty() { - break; - } } // `remaining_rhs` are the RHS Terms that couldn't be combined with any LHS ones. output.extend_from_slice(&remaining_rhs); + let output = if output.len() > 1 { + let what_to_keep = simplify_one_to_many(&output[0], &output[1..]); + + match (what_to_keep.keep_lhs, what_to_keep.made_new_terms()) { + (true, true) => { + let mut new_output = vec![output[0].clone()]; + new_output.extend(what_to_keep.into_terms()); + new_output + } + (true, false) => output, + (false, true) => what_to_keep.into_terms(), + (false, false) => { + // NOTE: Shouldn't be able to get here... + what_to_keep.into_terms() + } + } + } else { + output + }; + cleanup(output) } @@ -84,6 +99,19 @@ pub(super) struct WhatToKeep { pub(super) kept_terms: Vec, } +impl WhatToKeep { + pub(super) fn made_new_terms(&self) -> bool { + !self.new_terms.is_empty() + } + + pub(super) fn into_terms(self) -> Vec { + let mut output = self.kept_terms; + output.extend(self.new_terms); + + output + } +} + impl Default for WhatToKeep { fn default() -> Self { Self { @@ -103,8 +131,8 @@ pub(super) fn simplify_one_to_many(lhs: &Term, rhs_terms: &[Term]) -> WhatToKeep what_to_keep.keep_lhs = false; match rhs_terms.get((i + 1)..) { - Some(things) if !things.is_empty() => { - let wtk = simplify_one_to_many(&new_term, things); + Some(remaining_rhs) if !remaining_rhs.is_empty() => { + let wtk = simplify_one_to_many(&new_term, remaining_rhs); if wtk.keep_lhs { what_to_keep.new_terms.push(new_term);