From 828df17ad5d05d72f285ea5d05a49dab86ec3535 Mon Sep 17 00:00:00 2001 From: Andrey Kutejko Date: Mon, 12 Nov 2018 16:21:01 +0100 Subject: [PATCH] add support for 'is not' operator --- src/parser/ast.rs | 2 ++ src/parser/mod.rs | 8 +++++++- src/parser/tera.pest | 3 ++- src/parser/tests/lexer.rs | 1 + src/parser/tests/parser.rs | 16 ++++++++++++++++ src/renderer/processor.rs | 7 ++++++- src/renderer/tests/basic.rs | 2 ++ 7 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/parser/ast.rs b/src/parser/ast.rs index a60f2b064..1888c4199 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -188,6 +188,8 @@ impl Expr { pub struct Test { /// Which expression is evaluated pub ident: String, + /// Is it using `not`? + pub negated: bool, /// Name of the test pub name: String, /// Any optional arg given to the test diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c8f04609f..1e8d303bd 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -152,7 +152,7 @@ fn parse_test(pair: Pair) -> Test { }; } - Test { ident: ident.unwrap(), name: name.unwrap(), args } + Test { ident: ident.unwrap(), negated: false, name: name.unwrap(), args } } fn parse_string_concat(pair: Pair) -> ExprVal { @@ -238,6 +238,11 @@ fn parse_basic_expression(pair: Pair) -> ExprVal { _ => unreachable!(), }, Rule::test => ExprVal::Test(parse_test(pair)), + Rule::test_not => { + let mut test = parse_test(pair); + test.negated = true; + ExprVal::Test(test) + }, Rule::fn_call => ExprVal::FunctionCall(parse_fn_call(pair)), Rule::macro_call => ExprVal::MacroCall(parse_macro_call(pair)), Rule::string => ExprVal::String(replace_string_markers(pair.as_str())), @@ -899,6 +904,7 @@ pub fn parse(input: &str) -> TeraResult> { Rule::op_modulo => "`%`".to_string(), Rule::filter => "a filter".to_string(), Rule::test => "a test".to_string(), + Rule::test_not => "a negated test".to_string(), Rule::test_call => "a test call".to_string(), Rule::test_arg => "a test argument (any expressions)".to_string(), Rule::test_args => "a list of test arguments (any expressions)".to_string(), diff --git a/src/parser/tera.pest b/src/parser/tera.pest index 6063fba57..753bac9c5 100644 --- a/src/parser/tera.pest +++ b/src/parser/tera.pest @@ -79,7 +79,7 @@ string_concat = { (fn_call | float | int | string | dotted_square_bracket_ident) /// We'll use precedence climbing on those in the parser phase // boolean first so they are not caught as identifiers -basic_val = _{ boolean | string_concat | test | macro_call | fn_call | dotted_square_bracket_ident | float | int | string } +basic_val = _{ boolean | string_concat | test_not | test | macro_call | fn_call | dotted_square_bracket_ident | float | int | string } basic_op = _{ op_plus | op_minus | op_times | op_slash | op_modulo } basic_expr = { ("(" ~ basic_expr ~ ")" | basic_val) ~ (basic_op ~ basic_val)* } basic_expr_filter = { basic_expr ~ filter* } @@ -124,6 +124,7 @@ macro_call = { ident ~ "::" ~ ident ~ "(" ~ kwargs? ~ ")" } test_arg = { logic_expr } test_args = !{ test_arg ~ ("," ~ test_arg)* } test_call = !{ ident ~ ("(" ~ test_args ~ ")")? } +test_not = { dotted_ident ~ "is" ~ "not" ~ test_call } test = { dotted_ident ~ "is" ~ test_call } // ------------------------------------------------------- diff --git a/src/parser/tests/lexer.rs b/src/parser/tests/lexer.rs index f2b09dc7c..d5d438e70 100644 --- a/src/parser/tests/lexer.rs +++ b/src/parser/tests/lexer.rs @@ -261,6 +261,7 @@ fn lex_logic_val() { r#""hey""#, "a is defined", "a is defined(2)", + "a is not defined", "1 + 1", "1 + counts", "1 + counts.first", diff --git a/src/parser/tests/parser.rs b/src/parser/tests/parser.rs index 2c5428fab..a0fa8c304 100644 --- a/src/parser/tests/parser.rs +++ b/src/parser/tests/parser.rs @@ -248,6 +248,21 @@ fn parse_variable_tag_simple_test() { ast[0], Node::VariableBlock(Expr::new(ExprVal::Test(Test { ident: "id".to_string(), + negated: false, + name: "defined".to_string(), + args: vec![], + },))) + ); +} + +#[test] +fn parse_variable_tag_simple_negated_test() { + let ast = parse("{{ id is not defined }}").unwrap(); + assert_eq!( + ast[0], + Node::VariableBlock(Expr::new(ExprVal::Test(Test { + ident: "id".to_string(), + negated: true, name: "defined".to_string(), args: vec![], },))) @@ -262,6 +277,7 @@ fn parse_variable_tag_test_as_expression() { Node::VariableBlock(Expr::new(ExprVal::Logic(LogicExpr { lhs: Box::new(Expr::new(ExprVal::Test(Test { ident: "user".to_string(), + negated: false, name: "defined".to_string(), args: vec![], },))), diff --git a/src/renderer/processor.rs b/src/renderer/processor.rs index 2080bb852..73e0db4a6 100644 --- a/src/renderer/processor.rs +++ b/src/renderer/processor.rs @@ -416,7 +416,12 @@ impl<'a> Processor<'a> { let found = self.lookup_ident(&test.ident).map(|found| found.clone().into_owned()).ok(); - Ok(tester_fn.test(found.as_ref(), &tester_args)?) + let result = tester_fn.test(found.as_ref(), &tester_args)?; + if test.negated { + Ok(!result) + } else { + Ok(result) + } } fn eval_tera_fn_call(self: &mut Self, function_call: &'a FunctionCall) -> Result> { diff --git a/src/renderer/tests/basic.rs b/src/renderer/tests/basic.rs index 3dfbdbe7c..e6ac63b46 100644 --- a/src/renderer/tests/basic.rs +++ b/src/renderer/tests/basic.rs @@ -99,6 +99,8 @@ fn render_variable_block_ident() { ("{{ name | length }}", "4"), ("{{ name is defined }}", "true"), ("{{ not name is defined }}", "false"), + ("{{ name is not defined }}", "false"), + ("{{ not name is not defined }}", "true"), ("{{ a is odd }}", "false"), ("{{ a is odd or b is odd }}", "true"), ("{{ range(start=1, end=4) }}", "[1, 2, 3]"),