diff --git a/integration_tests/comparisons_test.ucg b/integration_tests/comparisons_test.ucg index c7dac8c..2e1605e 100644 --- a/integration_tests/comparisons_test.ucg +++ b/integration_tests/comparisons_test.ucg @@ -183,4 +183,34 @@ assert { assert { ok = (foo in {foo = NULL}), desc = "Null valued fields are still present in tuple", +}; + +assert { + ok = true && true == true, + desc = "&&: truth", +}; + +assert { + ok = true && false == false, + desc = "&&: propagates false", +}; + +assert { + ok = false && true == false, + desc = "&&: propagates false part 2", +}; + +assert { + ok = true || false == true, + desc = "||: propagates true", +}; + +assert { + ok = false || true == true, + desc = "||: propagates true part 2", +}; + +assert { + ok = true || true == true, + desc = "||: likes truth", }; \ No newline at end of file diff --git a/integration_tests/operator_precedence_test.ucg b/integration_tests/operator_precedence_test.ucg index 4bdc46c..6ef09bb 100644 --- a/integration_tests/operator_precedence_test.ucg +++ b/integration_tests/operator_precedence_test.ucg @@ -51,4 +51,14 @@ let tpl = { assert { ok = 1 + tpl.one.two * 2 + 3 == 28, desc = "1 + tpl.one.two * 2 + 3 == 28", +}; + +assert { + ok = (1 == 1) && (1 != 1) == false, + desc = "(1 == 1) && (1 != 1) == true", +}; + +assert { + ok = (1 == 1) || (1 != 1) == true, + desc = "(1 == 1) && (1 != 1) == true", }; \ No newline at end of file diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 558de82..a7486f0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -392,6 +392,9 @@ pub enum BinaryExprType { Sub, Mul, Div, + // Boolean + AND, + OR, // Comparison Equal, GT, @@ -428,8 +431,11 @@ impl BinaryExprType { // Product operators are next tightly bound BinaryExprType::Mul => 3, BinaryExprType::Div => 3, + // Boolean operators bind tighter than math + BinaryExprType::AND => 4, + BinaryExprType::OR => 4, // Dot operators are most tightly bound. - BinaryExprType::DOT => 4, + BinaryExprType::DOT => 5, } } } diff --git a/src/build/ir.rs b/src/build/ir.rs index b7c3c43..c867cf1 100644 --- a/src/build/ir.rs +++ b/src/build/ir.rs @@ -178,6 +178,13 @@ impl Val { return false; } + pub fn is_bool(&self) -> bool { + if let &Val::Boolean(_) = self { + return true; + } + return false; + } + pub fn is_macro(&self) -> bool { if let &Val::Macro(_) = self { return true; diff --git a/src/build/mod.rs b/src/build/mod.rs index a02c1f1..50456d5 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -815,6 +815,57 @@ impl<'a> FileBuilder<'a> { } } + fn do_bool_operator( + &self, + kind: &BinaryExprType, + left: &Expression, + right: &Expression, + scope: &Scope, + ) -> Result, Box> { + let left_pos = left.pos(); + let left = self.eval_expr(left, scope)?; + if let Val::Boolean(b) = left.as_ref() { + let right_pos = right.pos(); + let b = *b; + if kind == &BinaryExprType::AND { + if !b { + // short circuit + return Ok(Rc::new(Val::Boolean(b))); + } + let right = self.eval_expr(right, scope)?; + if right.is_bool() { + return Ok(right); + } + } else { + if b { + // short circuit + return Ok(Rc::new(Val::Boolean(b))); + } + let right = self.eval_expr(right, scope)?; + if right.is_bool() { + return Ok(right); + } + } + return Err(Box::new(error::BuildError::new( + format!( + "Expected boolean value for operator but got {}", + left.type_name() + ), + error::ErrorType::TypeFail, + right_pos.clone(), + ))); + } else { + return Err(Box::new(error::BuildError::new( + format!( + "Expected boolean value for operator but got {}", + left.type_name() + ), + error::ErrorType::TypeFail, + left_pos.clone(), + ))); + } + } + fn do_element_check( &self, left: &Expression, @@ -901,6 +952,15 @@ impl<'a> FileBuilder<'a> { // TODO Should we support this operation on strings too? return self.do_element_check(&def.left, &def.right, scope); }; + match kind { + // We special case the boolean operators because we want them to short circuit. + &BinaryExprType::AND | &BinaryExprType::OR => { + return self.do_bool_operator(kind, &def.left, &def.right, scope); + } + _ => { + // noop + } + } let left = self.eval_expr(&def.left, scope)?; let mut child_scope = scope.spawn_child(); child_scope.set_curr_val(left.clone()); @@ -932,7 +992,10 @@ impl<'a> FileBuilder<'a> { &BinaryExprType::NotREMatch => { self.eval_re_match(left, def.left.pos(), right, def.right.pos(), true) } - &BinaryExprType::IN | &BinaryExprType::DOT => panic!("Unreachable"), + &BinaryExprType::IN + | &BinaryExprType::DOT + | &BinaryExprType::AND + | &BinaryExprType::OR => panic!("Unreachable"), } } diff --git a/src/parse/precedence.rs b/src/parse/precedence.rs index dea52ce..2aae458 100644 --- a/src/parse/precedence.rs +++ b/src/parse/precedence.rs @@ -52,6 +52,18 @@ make_fn!( ) ); +make_fn!( + bool_op_type, Element>, + either!( + do_each!( + _ => punct!("&&"), + (Element::Op(BinaryExprType::AND))), + do_each!( + _ => punct!("||"), + (Element::Op(BinaryExprType::OR))) + ) +); + fn parse_expression(i: SliceIter) -> Result, Expression> { let mut i_ = i.clone(); if eoi(i_.clone()).is_complete() { @@ -73,6 +85,34 @@ fn parse_expression(i: SliceIter) -> Result, Express )); } +fn parse_bool_operator(i: SliceIter) -> Result, BinaryExprType> { + let mut i_ = i.clone(); + if eoi(i_.clone()).is_complete() { + return Result::Fail(Error::new( + format!("Expected Expression found End Of Input"), + Box::new(i_), + )); + } + let el = i_.next(); + if let Some(&Element::Op(ref op)) = el { + match op { + BinaryExprType::AND | BinaryExprType::OR => { + return Result::Complete(i_.clone(), op.clone()); + } + _other => { + // noop + } + }; + } + return Result::Fail(Error::new( + format!( + "Error while parsing Binary Expression Unexpected Operator {:?}", + el + ), + Box::new(i_), + )); +} + fn parse_dot_operator(i: SliceIter) -> Result, BinaryExprType> { let mut i_ = i.clone(); if eoi(i_.clone()).is_complete() { @@ -243,7 +283,13 @@ fn parse_operand_list<'a>(i: SliceIter<'a, Token>) -> ParseResult<'a, Vec { if firstrun { // If we don't find an operator in our first @@ -278,7 +324,8 @@ make_fn!( parse_dot_operator, parse_sum_operator, parse_product_operator, - parse_compare_operator + parse_compare_operator, + parse_bool_operator ) ); diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index ccc6c3d..728f666 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -258,6 +258,14 @@ make_fn!(fatcommatok, do_text_token_tok!(TokenType::PUNCT, "=>") ); +make_fn!(andtok, + do_text_token_tok!(TokenType::PUNCT, "&&") +); + +make_fn!(ortok, + do_text_token_tok!(TokenType::PUNCT, "||") +); + make_fn!(selecttok, do_text_token_tok!(TokenType::BAREWORD, "select", WS) ); @@ -376,6 +384,8 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result, Token> { lparentok, rparentok, dottok, + andtok, + ortok, plustok, dashtok, startok,