use {
super::{Ingredient, Method, Recipe, RecipeError, RecipeUtilities},
crate::{types::Row, Result, Value},
};
#[derive(Clone)]
pub enum SimplifyBy<'a> {
Basic,
OptRow(&'a Vec<Option<Value>>),
Row(&'a Row),
CompletedAggregate(Vec<Value>),
}
pub trait Resolve
where
Self: Sized,
{
fn simplify(self, component: SimplifyBy) -> Result<Self>;
}
impl Resolve for Recipe {
fn simplify(self, component: SimplifyBy) -> Result<Self> {
match self {
Recipe::Ingredient(ingredient) => {
ingredient.simplify(component).map(Recipe::Ingredient)
}
Recipe::Method(method) => method.simplify(component).map(|method| {
if let Method::Value(value) = method {
Recipe::Ingredient(Ingredient::Value(value))
} else {
Recipe::Method(Box::new(method))
}
}),
}
}
}
impl Resolve for Ingredient {
fn simplify(self, component: SimplifyBy) -> Result<Self> {
Ok(match self {
Ingredient::Column(index) => {
if let SimplifyBy::Row(row) = component {
Ingredient::Value(row.get(index).ok_or(RecipeError::UnreachableNoRow)?.clone())
} else if let SimplifyBy::OptRow(row) = component {
row.get(index)
.and_then(Clone::clone)
.map(Ingredient::Value)
.unwrap_or(self)
} else {
self
}
}
Ingredient::Aggregate(index) => {
if let SimplifyBy::CompletedAggregate(values) = component {
Ingredient::Value(values.get(index).ok_or(RecipeError::Unreachable)?.clone())
} else {
self
}
}
Ingredient::Value(..) => self, })
}
}
#[allow(clippy::if_same_then_else)] #[allow(clippy::collapsible_else_if)] impl Resolve for Method {
fn simplify(self, component: SimplifyBy) -> Result<Self> {
Ok(match self {
Method::UnaryOperation(operator, recipe) => {
let recipe = recipe.simplify(component)?;
if let Some(value) = recipe.as_solution() {
Method::Value(operator(value)?)
} else {
Method::UnaryOperation(operator, recipe)
}
}
Method::BinaryOperation(operator, left, right) => {
let left = left.simplify(component.clone())?;
if let Some(Value::Bool(value)) = left.as_solution() {
if !value {
if operator as usize == Value::and as usize {
return Ok(Method::Value(Value::Bool(false)));
}
} else {
if operator as usize == Value::or as usize {
return Ok(Method::Value(Value::Bool(true)));
}
}
}
let right = right.simplify(component)?;
if let (Some(left), Some(right)) = (left.as_solution(), right.as_solution()) {
Method::Value(operator(left, right)?)
} else {
Method::BinaryOperation(operator, left, right)
}
}
Method::Function(function, arguments) => {
let arguments = arguments
.into_iter()
.map(|argument| argument.simplify(component.clone()))
.collect::<Result<Vec<Recipe>>>()?;
if let Some(arguments) = arguments
.iter()
.map(|argument| argument.as_solution())
.collect::<Option<Vec<Value>>>()
{
Method::Value(function(arguments)?)
} else {
Method::Function(function, arguments)
}
}
Method::Cast(data_type, recipe) => {
let recipe = recipe.simplify(component)?;
if let Some(value) = recipe.as_solution() {
Method::Value(value.cast_datatype(&data_type)?)
} else {
Method::Cast(data_type, recipe)
}
}
Method::Case {
operand,
cases,
else_result,
} => {
let operand = operand
.map(|operand| operand.simplify(component.clone()))
.transpose()?;
let else_result = else_result
.map(|else_result| else_result.simplify(component.clone()))
.transpose()?;
let cases = cases
.into_iter()
.map(|(condition, result)| {
Ok((
condition.simplify(component.clone())?,
result.simplify(component.clone())?,
))
})
.collect::<Result<Vec<(Recipe, Recipe)>>>()?;
if let Some(None) = operand.clone().map(|operand| operand.as_solution()) {
Method::Case {
operand,
cases,
else_result,
}
} else if let Some(None) = else_result
.clone()
.map(|else_result| else_result.as_solution())
{
Method::Case {
operand,
cases,
else_result,
}
} else if let Some(cases) = cases
.iter()
.map(|(condition, result)| {
Some((condition.as_solution()?, result.as_solution()?))
})
.collect::<Option<Vec<(Value, Value)>>>()
{
let operand = operand.map(|operand| operand.as_solution());
let else_result = else_result
.map(|else_result| else_result.as_solution())
.unwrap_or(Some(Value::Null))
.unwrap();
if let Some(operand) = operand {
let operand = operand.unwrap();
Method::Value(
cases
.into_iter()
.find_map(|(condition, result)| {
if operand == condition {
Some(result)
} else {
None
}
})
.unwrap_or(else_result),
)
} else {
Method::Value(
cases
.into_iter()
.find_map(|(condition, result)| {
if matches!(condition, Value::Bool(true)) {
Some(result)
} else {
None
}
})
.unwrap_or(else_result),
)
}
} else {
Method::Case {
operand,
cases,
else_result,
}
}
}
Method::Value(..) => return Err(RecipeError::Unreachable.into()),
Method::Aggregate(operator, recipe) => {
Method::Aggregate(operator, recipe.simplify(component)?)
}
})
}
}