use crate::{
error::{
parse::validate::{ExpressionKind, InvalidVariableExpression, ValidationError},
utils::MetaData,
},
knot::Address,
line::{Expression, Variable},
process::check_condition,
story::validate::{ValidateContent, ValidationData},
};
use std::{cmp::Ordering, error::Error};
#[cfg(feature = "serde_support")]
use crate::utils::OrderingDerive;
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub struct Condition {
pub root: ConditionItem,
pub items: Vec<AndOr>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub struct ConditionItem {
pub negate: bool,
pub kind: ConditionKind,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub enum ConditionKind {
True,
False,
Nested(Box<Condition>),
Single(StoryCondition),
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub enum StoryCondition {
Comparison {
lhs_variable: Expression,
rhs_variable: Expression,
#[cfg_attr(feature = "serde_support", serde(with = "OrderingDerive"))]
ordering: Ordering,
},
IsTrueLike { variable: Variable },
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
pub enum AndOr {
And(ConditionItem),
Or(ConditionItem),
}
impl Condition {
pub fn evaluate<F, E>(&self, evaluator: &F) -> Result<bool, E>
where
F: Fn(&StoryCondition) -> Result<bool, E>,
E: Error,
{
self.items
.iter()
.fold(inner_eval(&self.root, evaluator), |acc, next_condition| {
acc.and_then(|current| match next_condition {
AndOr::And(item) => inner_eval(item, evaluator).map(|next| current && next),
AndOr::Or(item) => inner_eval(item, evaluator).map(|next| current || next),
})
})
}
}
fn inner_eval<F, E>(item: &ConditionItem, evaluator: &F) -> Result<bool, E>
where
F: Fn(&StoryCondition) -> Result<bool, E>,
E: Error,
{
let mut result = match &item.kind {
ConditionKind::True => Ok(true),
ConditionKind::False => Ok(false),
ConditionKind::Nested(condition) => condition.evaluate(evaluator),
ConditionKind::Single(ref kind) => evaluator(kind),
}?;
if item.negate {
result = !result;
}
Ok(result)
}
pub struct ConditionBuilder {
root: ConditionItem,
items: Vec<AndOr>,
}
impl ConditionBuilder {
pub fn from_kind(kind: &ConditionKind, negate: bool) -> Self {
let root = ConditionItem {
kind: kind.clone(),
negate,
};
ConditionBuilder {
root,
items: Vec::new(),
}
}
pub fn build(self) -> Condition {
Condition {
root: self.root,
items: self.items,
}
}
pub fn and(&mut self, kind: &ConditionKind, negate: bool) {
self.items.push(AndOr::And(ConditionItem {
kind: kind.clone(),
negate,
}));
}
pub fn or(&mut self, kind: &ConditionKind, negate: bool) {
self.items.push(AndOr::Or(ConditionItem {
kind: kind.clone(),
negate,
}));
}
pub fn extend(&mut self, items: &[AndOr]) {
self.items.extend_from_slice(items);
}
}
impl ValidateContent for Condition {
fn validate(
&mut self,
error: &mut ValidationError,
current_location: &Address,
meta_data: &MetaData,
data: &ValidationData,
) {
let num_errors = error.num_errors();
self.root
.kind
.validate(error, current_location, meta_data, data);
self.items.iter_mut().for_each(|item| match item {
AndOr::And(item) | AndOr::Or(item) => {
item.kind.validate(error, current_location, meta_data, data)
}
});
if num_errors == error.num_errors() {
if let Err(err) = check_condition(self, &data.follow_data) {
error.variable_errors.push(InvalidVariableExpression {
expression_kind: ExpressionKind::Condition,
kind: err.into(),
meta_data: meta_data.clone(),
});
}
}
}
}
impl ValidateContent for ConditionKind {
fn validate(
&mut self,
error: &mut ValidationError,
current_location: &Address,
meta_data: &MetaData,
data: &ValidationData,
) {
match self {
ConditionKind::True | ConditionKind::False => (),
ConditionKind::Nested(condition) => {
condition.validate(error, current_location, meta_data, data)
}
ConditionKind::Single(kind) => kind.validate(error, current_location, meta_data, data),
}
}
}
impl ValidateContent for StoryCondition {
fn validate(
&mut self,
error: &mut ValidationError,
current_location: &Address,
meta_data: &MetaData,
data: &ValidationData,
) {
match self {
StoryCondition::Comparison {
ref mut lhs_variable,
ref mut rhs_variable,
..
} => {
lhs_variable.validate(error, current_location, meta_data, data);
rhs_variable.validate(error, current_location, meta_data, data);
}
StoryCondition::IsTrueLike { variable } => {
variable.validate(error, current_location, meta_data, data)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt;
use ConditionKind::{False, True};
impl From<StoryCondition> for Condition {
fn from(kind: StoryCondition) -> Self {
ConditionBuilder::from_kind(&kind.into(), false).build()
}
}
impl From<StoryCondition> for ConditionKind {
fn from(kind: StoryCondition) -> Self {
ConditionKind::Single(kind)
}
}
impl From<&StoryCondition> for ConditionKind {
fn from(kind: &StoryCondition) -> Self {
ConditionKind::Single(kind.clone())
}
}
impl Condition {
pub fn story_condition(&self) -> &StoryCondition {
&self.root.kind.story_condition()
}
pub fn with_and(mut self, kind: ConditionKind) -> Self {
let item = ConditionItem {
kind,
negate: false,
};
self.items.push(AndOr::And(item));
self
}
pub fn with_or(mut self, kind: ConditionKind) -> Self {
let item = ConditionItem {
kind,
negate: false,
};
self.items.push(AndOr::Or(item));
self
}
}
impl ConditionKind {
pub fn nested(&self) -> &Condition {
match self {
ConditionKind::Nested(condition) => condition,
other => panic!(
"tried to extract nested `Condition`, but item was not `ConditionKind::Nested` \
(was: {:?})",
other
),
}
}
pub fn story_condition(&self) -> &StoryCondition {
match self {
ConditionKind::Single(story_condition) => story_condition,
other => panic!(
"tried to extract `StoryCondition`, but item was not `ConditionKind::Single` \
(was: {:?})",
other
),
}
}
}
impl AndOr {
pub fn nested(&self) -> &Condition {
match self {
AndOr::And(item) | AndOr::Or(item) => item.kind.nested(),
}
}
pub fn story_condition(&self) -> &StoryCondition {
match self {
AndOr::And(item) | AndOr::Or(item) => item.kind.story_condition(),
}
}
pub fn is_and(&self) -> bool {
match self {
AndOr::And(..) => true,
_ => false,
}
}
pub fn is_or(&self) -> bool {
match self {
AndOr::Or(..) => true,
_ => false,
}
}
}
#[derive(Debug)]
struct MockError;
impl fmt::Display for MockError {
fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
unreachable!();
}
}
impl Error for MockError {}
#[test]
fn condition_links_from_left_to_right() {
let f = |kind: &StoryCondition| match kind {
_ => Err(MockError),
};
assert!(ConditionBuilder::from_kind(&True.into(), false)
.build()
.evaluate(&f)
.unwrap());
assert!(!ConditionBuilder::from_kind(&False.into(), false)
.build()
.evaluate(&f)
.unwrap());
assert!(ConditionBuilder::from_kind(&True.into(), false)
.build()
.with_and(True.into())
.evaluate(&f)
.unwrap());
assert!(!ConditionBuilder::from_kind(&True.into(), false)
.build()
.with_and(False.into())
.evaluate(&f)
.unwrap());
assert!(ConditionBuilder::from_kind(&False.into(), false)
.build()
.with_and(False.into())
.with_or(True)
.evaluate(&f)
.unwrap());
assert!(!ConditionBuilder::from_kind(&False.into(), false)
.build()
.with_and(False)
.with_or(True)
.with_and(False)
.evaluate(&f)
.unwrap());
}
#[test]
fn conditions_can_be_negated() {
let f = |kind: &StoryCondition| match kind {
_ => Err(MockError),
};
assert!(ConditionBuilder::from_kind(&False.into(), true)
.build()
.evaluate(&f)
.unwrap());
}
}