use crate::computation::rational::{
checked_div, checked_mul, convert_quantity_magnitude_rational, rational_one, RationalInteger,
};
use crate::evaluation::operations::OperationResult;
use crate::parsing::ast::PrimitiveKind;
use crate::planning::semantics::{
calendar_unit_factor, primitive_number_arc, primitive_text_arc, LiteralValue,
SemanticCalendarUnit, SemanticConversionTarget, ValueKind,
};
use std::sync::Arc;
#[derive(Copy, Clone)]
pub enum UnitResolutionContext<'a> {
WithIndex(
&'a std::collections::HashMap<
String,
std::sync::Arc<crate::planning::semantics::LemmaType>,
>,
),
NamedQuantityOnly,
}
pub fn convert_unit(
value: &LiteralValue,
target: &SemanticConversionTarget,
resolution_context: UnitResolutionContext<'_>,
) -> OperationResult {
match target {
SemanticConversionTarget::Type(PrimitiveKind::Number) => cast_to_number(value),
SemanticConversionTarget::Type(PrimitiveKind::Text) => OperationResult::Value(Box::new(
LiteralValue::text_with_type(value.display_value(), primitive_text_arc().clone()),
)),
SemanticConversionTarget::Type(PrimitiveKind::Boolean) => {
if value.lemma_type.is_boolean() {
OperationResult::Value(Box::new(value.clone()))
} else {
unreachable!(
"BUG: boolean cast on non-boolean; planning should have rejected {:?}",
value.lemma_type.name()
);
}
}
SemanticConversionTarget::Type(target_kind) => {
if same_primitive_kind(value, *target_kind) {
OperationResult::Value(Box::new(value.clone()))
} else {
unreachable!(
"BUG: invalid identity cast {:?} -> {:?} reached runtime",
value.lemma_type.name(),
target_kind
);
}
}
SemanticConversionTarget::Unit { unit_name } => {
cast_to_unit(value, unit_name, resolution_context)
}
}
}
fn same_primitive_kind(value: &LiteralValue, target: PrimitiveKind) -> bool {
use crate::planning::semantics::TypeSpecification;
matches!(
(target, &value.lemma_type.specifications),
(PrimitiveKind::Number, TypeSpecification::Number { .. })
| (PrimitiveKind::Text, TypeSpecification::Text { .. })
| (PrimitiveKind::Boolean, TypeSpecification::Boolean { .. })
| (PrimitiveKind::Date, TypeSpecification::Date { .. })
| (PrimitiveKind::Time, TypeSpecification::Time { .. })
| (PrimitiveKind::Ratio, TypeSpecification::Ratio { .. })
| (PrimitiveKind::Quantity, TypeSpecification::Quantity { .. })
)
}
fn cast_to_unit(
value: &LiteralValue,
unit_name: &str,
resolution_context: UnitResolutionContext<'_>,
) -> OperationResult {
match &value.value {
ValueKind::Number(magnitude) => {
cast_number_to_unit(*magnitude, unit_name, resolution_context)
}
ValueKind::Quantity(magnitude, _) => {
cast_quantity_to_unit(*magnitude, unit_name, resolution_context)
}
ValueKind::Range(left, right) => {
cast_range_span_to_unit(left, right, unit_name, resolution_context)
}
ValueKind::Ratio(magnitude, from_unit) => cast_ratio_to_unit(
value,
*magnitude,
from_unit.as_deref(),
unit_name,
resolution_context,
),
other => unreachable!(
"BUG: unit cast from {:?} should be rejected at planning",
other
),
}
}
fn cast_number_to_unit(
magnitude: RationalInteger,
unit_name: &str,
resolution_context: UnitResolutionContext<'_>,
) -> OperationResult {
let UnitResolutionContext::WithIndex(unit_index) = resolution_context else {
unreachable!("BUG: unit cast requires unit index at evaluation");
};
let target_type = unit_index.get(unit_name).unwrap_or_else(|| {
unreachable!(
"BUG: unit '{}' missing from expression unit index after planning",
unit_name
)
});
if target_type.is_ratio() {
return OperationResult::Value(Box::new(LiteralValue::ratio_with_type(
magnitude,
Some(unit_name.to_string()),
Arc::clone(target_type),
)));
}
let factor = target_type.quantity_unit_factor(unit_name);
let canonical = match checked_mul(&magnitude, factor) {
Ok(v) => v,
Err(failure) => {
return OperationResult::Veto(crate::evaluation::operations::VetoType::computation(
failure.to_string(),
))
}
};
OperationResult::Value(Box::new(LiteralValue::quantity_with_type(
canonical,
unit_name.to_string(),
Arc::clone(target_type),
)))
}
fn cast_quantity_to_unit(
magnitude: RationalInteger,
unit_name: &str,
resolution_context: UnitResolutionContext<'_>,
) -> OperationResult {
let UnitResolutionContext::WithIndex(unit_index) = resolution_context else {
unreachable!("BUG: quantity unit cast requires unit index at evaluation");
};
let target_type = unit_index.get(unit_name).unwrap_or_else(|| {
unreachable!(
"BUG: unit '{}' missing from expression unit index after planning",
unit_name
)
});
OperationResult::Value(Box::new(LiteralValue::quantity_with_type(
magnitude,
unit_name.to_string(),
Arc::clone(target_type),
)))
}
fn cast_ratio_to_unit(
value: &LiteralValue,
magnitude: RationalInteger,
from_unit: Option<&str>,
unit_name: &str,
resolution_context: UnitResolutionContext<'_>,
) -> OperationResult {
let UnitResolutionContext::WithIndex(unit_index) = resolution_context else {
unreachable!("BUG: ratio unit cast requires unit index at evaluation");
};
let target_type = unit_index.get(unit_name).unwrap_or_else(|| {
unreachable!(
"BUG: unit '{}' missing from expression unit index after planning",
unit_name
)
});
if value.lemma_type.same_quantity_family(target_type.as_ref()) {
let from_factor = from_unit
.map(|u| *value.lemma_type.quantity_unit_factor(u))
.unwrap_or(rational_one());
let to_factor = *target_type.quantity_unit_factor(unit_name);
let converted =
match convert_quantity_magnitude_rational(magnitude, &from_factor, &to_factor) {
Ok(m) => m,
Err(failure) => {
return OperationResult::Veto(
crate::evaluation::operations::VetoType::computation(failure.to_string()),
);
}
};
OperationResult::Value(Box::new(LiteralValue::ratio_with_type(
converted,
Some(unit_name.to_string()),
Arc::clone(target_type),
)))
} else {
OperationResult::Value(Box::new(LiteralValue::ratio_with_type(
magnitude,
Some(unit_name.to_string()),
Arc::clone(target_type),
)))
}
}
fn cast_range_span_to_unit(
left: &LiteralValue,
right: &LiteralValue,
unit_name: &str,
resolution_context: UnitResolutionContext<'_>,
) -> OperationResult {
if let (ValueKind::Date(left_date), ValueKind::Date(right_date)) = (&left.value, &right.value) {
if calendar_unit_factor(unit_name).is_some() {
let UnitResolutionContext::WithIndex(unit_index) = resolution_context else {
unreachable!("BUG: calendar date span requires unit index after planning");
};
let calendar_type = Arc::clone(
unit_index
.get(unit_name)
.expect("BUG: calendar unit must be in index after planning"),
);
return super::datetime::compute_date_calendar_difference(
left_date,
right_date,
&semantic_calendar_unit(unit_name),
calendar_type,
);
}
let OperationResult::Value(span) = super::range::compute_span(left, right) else {
return super::range::compute_span(left, right);
};
return convert_span_quantity_to_unit(span.as_ref(), unit_name, resolution_context);
}
let OperationResult::Value(span) = super::range::compute_span(left, right) else {
return super::range::compute_span(left, right);
};
let span = span.as_ref();
match &span.value {
ValueKind::Quantity(_, _) => {
convert_span_quantity_to_unit(span, unit_name, resolution_context)
}
ValueKind::Ratio(_, _) => {
let ValueKind::Ratio(magnitude, from_unit) = &span.value else {
unreachable!("BUG: ratio span expected");
};
cast_ratio_to_unit(
span,
*magnitude,
from_unit.as_deref(),
unit_name,
resolution_context,
)
}
ValueKind::Number(magnitude) => {
cast_number_to_unit(*magnitude, unit_name, resolution_context)
}
other => unreachable!("BUG: unexpected range span value kind for unit cast: {other:?}"),
}
}
fn convert_span_quantity_to_unit(
span: &LiteralValue,
unit_name: &str,
resolution_context: UnitResolutionContext<'_>,
) -> OperationResult {
let ValueKind::Quantity(magnitude, _) = &span.value else {
unreachable!("BUG: span quantity expected");
};
cast_quantity_to_unit(*magnitude, unit_name, resolution_context)
}
fn semantic_calendar_unit(unit_name: &str) -> SemanticCalendarUnit {
match unit_name {
"month" | "months" => SemanticCalendarUnit::Month,
"year" | "years" => SemanticCalendarUnit::Year,
other => unreachable!("BUG: unknown calendar unit '{other}' after planning"),
}
}
fn cast_to_number(value: &LiteralValue) -> OperationResult {
match &value.value {
ValueKind::Range(left, right) => {
let span = super::range::compute_span(left, right);
let OperationResult::Value(span_value) = span else {
return span;
};
cast_to_number(span_value.as_ref())
}
ValueKind::Quantity(magnitude, signature) if value.lemma_type.is_calendar_like() => {
let unit_name = signature
.first()
.map(|(name, _)| name.as_str())
.expect("BUG: calendar quantity must carry a unit signature");
let factor = *value.lemma_type.quantity_unit_factor(unit_name);
let in_unit = checked_div(magnitude, &factor)
.expect("BUG: calendar de-canonicalization by unit factor must not fail");
OperationResult::Value(Box::new(LiteralValue::number_with_type(
in_unit,
primitive_number_arc().clone(),
)))
}
ValueKind::Number(number) => OperationResult::Value(Box::new(
LiteralValue::number_with_type(*number, primitive_number_arc().clone()),
)),
ValueKind::Boolean(b) => {
let n = if *b {
RationalInteger::new(1, 1)
} else {
RationalInteger::new(0, 1)
};
OperationResult::Value(Box::new(LiteralValue::number_with_type(
n,
primitive_number_arc().clone(),
)))
}
ValueKind::Ratio(rational_value, _) => OperationResult::Value(Box::new(
LiteralValue::number_with_type(*rational_value, primitive_number_arc().clone()),
)),
ValueKind::Quantity(magnitude, signature) => {
let factor = match signature.as_slice() {
[(unit_name, 1)] => *value.lemma_type.quantity_unit_factor(unit_name),
[] => rational_one(),
_ => panic!(
"BUG: cast_to_number with compound signature must be rejected at planning"
),
};
let in_unit = checked_div(magnitude, &factor)
.expect("BUG: de-canonicalization by unit factor must not fail");
OperationResult::Value(Box::new(LiteralValue::number_with_type(
in_unit,
primitive_number_arc().clone(),
)))
}
ValueKind::Text(_) | ValueKind::Date(_) | ValueKind::Time(_) => unreachable!(
"BUG: cast to number from {:?} should be rejected at planning",
value.lemma_type.name()
),
}
}
pub(crate) fn convert_calendar_magnitude(
value: crate::computation::rational::RationalInteger,
from: &crate::planning::semantics::SemanticCalendarUnit,
to: &crate::planning::semantics::SemanticCalendarUnit,
) -> crate::computation::rational::RationalInteger {
convert_calendar(value, from, to)
}
fn convert_calendar(
value: crate::computation::rational::RationalInteger,
from: &crate::planning::semantics::SemanticCalendarUnit,
to: &crate::planning::semantics::SemanticCalendarUnit,
) -> crate::computation::rational::RationalInteger {
use crate::computation::rational::{convert_quantity_magnitude_rational, rational_one};
if from == to {
return value;
}
let from_factor = match from {
crate::planning::semantics::SemanticCalendarUnit::Month => rational_one(),
crate::planning::semantics::SemanticCalendarUnit::Year => RationalInteger::new(12, 1),
};
let to_factor = match to {
crate::planning::semantics::SemanticCalendarUnit::Month => rational_one(),
crate::planning::semantics::SemanticCalendarUnit::Year => RationalInteger::new(12, 1),
};
convert_quantity_magnitude_rational(value, &from_factor, &to_factor)
.expect("BUG: calendar unit conversion failed after planning")
}