diff --git a/src/tags/if_block.rs b/src/tags/if_block.rs index f0bd454bf..b35e568ba 100644 --- a/src/tags/if_block.rs +++ b/src/tags/if_block.rs @@ -14,18 +14,79 @@ use interpreter::Template; use value::Value; #[derive(Clone, Debug)] -struct Condition { +struct BinaryCondition { lh: Argument, comparison: ComparisonOperator, rh: Argument, } -impl fmt::Display for Condition { +impl BinaryCondition { + pub fn evaluate(&self, context: &Context) -> Result { + let a = self.lh.evaluate(context)?; + let b = self.rh.evaluate(context)?; + + let result = match self.comparison { + ComparisonOperator::Equals => a == b, + ComparisonOperator::NotEquals => a != b, + ComparisonOperator::LessThan => a < b, + ComparisonOperator::GreaterThan => a > b, + ComparisonOperator::LessThanEquals => a <= b, + ComparisonOperator::GreaterThanEquals => a >= b, + ComparisonOperator::Contains => contains_check(&a, &b)?, + }; + + Ok(result) + } +} + +impl fmt::Display for BinaryCondition { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {} {}", self.lh, self.comparison, self.rh) } } +#[derive(Clone, Debug)] +struct ExistenceCondition { + lh: Argument, +} + +impl ExistenceCondition { + pub fn evaluate(&self, context: &Context) -> Result { + let a = self.lh.evaluate(context).unwrap_or_default(); + Ok(a.is_truthy()) + } +} + +impl fmt::Display for ExistenceCondition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.lh) + } +} + +#[derive(Clone, Debug)] +enum Condition { + Binary(BinaryCondition), + Existence(ExistenceCondition), +} + +impl Condition { + pub fn evaluate(&self, context: &Context) -> Result { + match *self { + Condition::Binary(ref c) => c.evaluate(context), + Condition::Existence(ref c) => c.evaluate(context), + } + } +} + +impl fmt::Display for Condition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Condition::Binary(ref c) => write!(f, "{}", c), + Condition::Existence(ref c) => write!(f, "{}", c), + } + } +} + #[derive(Debug)] struct Conditional { tag_name: &'static str, @@ -59,18 +120,7 @@ fn contains_check(a: &Value, b: &Value) -> Result { impl Conditional { fn compare(&self, context: &Context) -> Result { - let a = self.condition.lh.evaluate(context)?; - let b = self.condition.rh.evaluate(context)?; - - let result = match self.condition.comparison { - ComparisonOperator::Equals => a == b, - ComparisonOperator::NotEquals => a != b, - ComparisonOperator::LessThan => a < b, - ComparisonOperator::GreaterThan => a > b, - ComparisonOperator::LessThanEquals => a <= b, - ComparisonOperator::GreaterThanEquals => a >= b, - ComparisonOperator::Contains => contains_check(&a, &b)?, - }; + let result = self.condition.evaluate(context)?; Ok(result == self.mode) } @@ -105,26 +155,20 @@ fn parse_condition(arguments: &[Token]) -> Result { let lh = consume_value_token(&mut args)?.to_arg()?; - let (comp, rh) = match args.next() { + let cond = match args.next() { Some(&Token::Comparison(x)) => { - let rhs = consume_value_token(&mut args)?.to_arg()?; - (x, rhs) - } - None => { - // no trailing operator or RHS value implies "== true" - ( - ComparisonOperator::Equals, - Token::BooleanLiteral(true).to_arg()?, - ) + let rh = consume_value_token(&mut args)?.to_arg()?; + Condition::Binary(BinaryCondition { + lh: lh, + comparison: x, + rh: rh, + }) } + None => Condition::Existence(ExistenceCondition { lh }), x @ Some(_) => return Err(unexpected_token_error("comparison operator", x)), }; - Ok(Condition { - lh: lh, - comparison: comp, - rh: rh, - }) + Ok(cond) } pub fn unless_block( @@ -266,26 +310,24 @@ mod test { .map(interpreter::Template::new) .unwrap(); + // Non-existence let mut context = Context::new(); - context.set_global_val("truthy", Value::Nil); let output = template.render(&mut context).unwrap(); assert_eq!(output, Some("nope".to_owned())); - let tokens = compiler::tokenize(&text).unwrap(); - let template = compiler::parse(&tokens, &options()) - .map(interpreter::Template::new) - .unwrap(); + // Explicit nil + let mut context = Context::new(); + context.set_global_val("truthy", Value::Nil); + let output = template.render(&mut context).unwrap(); + assert_eq!(output, Some("nope".to_owned())); + // false let mut context = Context::new(); context.set_global_val("truthy", Value::scalar(false)); let output = template.render(&mut context).unwrap(); assert_eq!(output, Some("nope".to_owned())); - let tokens = compiler::tokenize(&text).unwrap(); - let template = compiler::parse(&tokens, &options()) - .map(interpreter::Template::new) - .unwrap(); - + // true let mut context = Context::new(); context.set_global_val("truthy", Value::scalar(true)); let output = template.render(&mut context).unwrap(); diff --git a/src/value/values.rs b/src/value/values.rs index cf6fdcc89..1eae50f36 100644 --- a/src/value/values.rs +++ b/src/value/values.rs @@ -187,6 +187,12 @@ impl Value { } } +impl Default for Value { + fn default() -> Self { + Self::nil() + } +} + impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match (self, other) {