Skip to content

Commit

Permalink
feat(if_block): support multiple conditions with and and or
Browse files Browse the repository at this point in the history
Implement disjunction and conjunction operators to if and unless blocks.
  • Loading branch information
Goncalerta committed Oct 21, 2018
1 parent 5f213e1 commit fb16a06
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 18 deletions.
1 change: 1 addition & 0 deletions liquid-compiler/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ pub fn granularize(block: &str) -> Result<Vec<Token>> {
"?" => Token::Question,
"-" => Token::Dash,
"=" => Token::Assignment,
"and" => Token::And,
"or" => Token::Or,

"==" => Token::Comparison(ComparisonOperator::Equals),
Expand Down
2 changes: 2 additions & 0 deletions liquid-compiler/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub enum Token {
BooleanLiteral(bool),
DotDot,
Comparison(ComparisonOperator),
And,
Or,
}

Expand Down Expand Up @@ -108,6 +109,7 @@ impl fmt::Display for Token {
Token::Dash => write!(f, "-"),
Token::DotDot => write!(f, ".."),
Token::Assignment => write!(f, "="),
Token::And => write!(f, "and"),
Token::Or => write!(f, "or"),

Token::Comparison(ref x) => write!(f, "{}", x),
Expand Down
142 changes: 124 additions & 18 deletions src/tags/if_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,21 @@ impl fmt::Display for ExistenceCondition {
enum Condition {
Binary(BinaryCondition),
Existence(ExistenceCondition),
Conjunction(Box<Condition>, Box<Condition>),
Disjunction(Box<Condition>, Box<Condition>),
}

impl Condition {
pub fn evaluate(&self, context: &Context) -> Result<bool> {
match *self {
Condition::Binary(ref c) => c.evaluate(context),
Condition::Existence(ref c) => c.evaluate(context),
Condition::Conjunction(ref left, ref right) => {
Ok(left.evaluate(context)? && right.evaluate(context)?)
}
Condition::Disjunction(ref left, ref right) => {
Ok(left.evaluate(context)? || right.evaluate(context)?)
}
}
}
}
Expand All @@ -84,6 +92,8 @@ impl fmt::Display for Condition {
match *self {
Condition::Binary(ref c) => write!(f, "{}", c),
Condition::Existence(ref c) => write!(f, "{}", c),
Condition::Conjunction(ref left, ref right) => write!(f, "{} and {}", left, right),
Condition::Disjunction(ref left, ref right) => write!(f, "{} or {}", left, right),
}
}
}
Expand Down Expand Up @@ -151,24 +161,61 @@ impl Renderable for Conditional {

/// Common parsing for "if" and "unless" condition
fn parse_condition(arguments: &[Token]) -> Result<Condition> {
let mut args = arguments.iter();

let lh = consume_value_token(&mut args)?.to_arg()?;

let cond = match args.next() {
Some(&Token::Comparison(x)) => {
let rh = consume_value_token(&mut args)?.to_arg()?;
Condition::Binary(BinaryCondition {
lh,
comparison: x,
rh,
})
}
None => Condition::Existence(ExistenceCondition { lh }),
x @ Some(_) => return Err(unexpected_token_error("comparison operator", x)),
};

Ok(cond)
// Iterator over conditions linked with `or`
let mut or_iter = arguments
.split(|t| if let Token::Or = t { true } else { false })
.map(|args| {
// Iterator over conditions linked with `and`
let mut and_iter = args
.split(|t| if let Token::And = t { true } else { false })
.map(|args| {
// Iterator over tokens that form a condition
let mut args = args.iter();

let lh = consume_value_token(&mut args)?.to_arg()?;

let cond = match args.next() {
Some(&Token::Comparison(x)) => {
let rh = consume_value_token(&mut args)?.to_arg()?;
Condition::Binary(BinaryCondition {
lh,
comparison: x,
rh,
})
}
None => Condition::Existence(ExistenceCondition { lh }),
x @ Some(_) => return Err(unexpected_token_error("comparison operator", x)),
};

// There shouldn't exist any more tokens before next `and` or `or`
match args.next() {
None => Ok(cond),
x @ Some(_) => Err(unexpected_token_error("logic operator", x)),
}
});
let mut lh = and_iter
.next()
.expect("There will be always at least one condition.")?;
for rh in and_iter {
let rh = match rh {
Ok(rh) => rh,
err @ Err(_) => return err,
};
lh = Condition::Conjunction(Box::new(lh), Box::new(rh));
}
Ok(lh)
});
let mut lh = or_iter
.next()
.expect("There will be always at least one condition.")?;
for rh in or_iter {
let rh = match rh {
Ok(rh) => rh,
err @ Err(_) => return err,
};
lh = Condition::Disjunction(Box::new(lh), Box::new(rh));
}
Ok(lh)
}

pub fn unless_block(
Expand Down Expand Up @@ -572,4 +619,63 @@ mod test {
let output = template.render(&mut context).unwrap();
assert_eq!(output, "if false");
}

#[test]
fn multiple_conditions_and() {
let text = "{% if 1 == 1 and 2 == 2 %}if true{% else %}if false{% endif %}";
let tokens = compiler::tokenize(&text).unwrap();
let template = compiler::parse(&tokens, &options())
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
let output = template.render(&mut context).unwrap();
assert_eq!(output, "if true");

let text = "{% if 1 == 1 and 2 != 2 %}if true{% else %}if false{% endif %}";
let tokens = compiler::tokenize(&text).unwrap();
let template = compiler::parse(&tokens, &options())
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
let output = template.render(&mut context).unwrap();
assert_eq!(output, "if false");
}

#[test]
fn multiple_conditions_or() {
let text = "{% if 1 == 1 or 2 != 2 %}if true{% else %}if false{% endif %}";
let tokens = compiler::tokenize(&text).unwrap();
let template = compiler::parse(&tokens, &options())
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
let output = template.render(&mut context).unwrap();
assert_eq!(output, "if true");

let text = "{% if 1 != 1 or 2 != 2 %}if true{% else %}if false{% endif %}";
let tokens = compiler::tokenize(&text).unwrap();
let template = compiler::parse(&tokens, &options())
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
let output = template.render(&mut context).unwrap();
assert_eq!(output, "if false");
}

#[test]
fn multiple_conditions_and_or() {
let text = "{% if 1 == 1 or 2 == 2 and 3 != 3 %}if true{% else %}if false{% endif %}";
let tokens = compiler::tokenize(&text).unwrap();
let template = compiler::parse(&tokens, &options())
.map(interpreter::Template::new)
.unwrap();

let mut context = Context::new();
let output = template.render(&mut context).unwrap();
assert_eq!(output, "if true");
}
}

0 comments on commit fb16a06

Please sign in to comment.