diff --git a/docsite/site/content/reference/grammar.md b/docsite/site/content/reference/grammar.md index db3817e..86acd72 100644 --- a/docsite/site/content/reference/grammar.md +++ b/docsite/site/content/reference/grammar.md @@ -52,6 +52,7 @@ mod_keyword: "mod" ; out_keyword: "out" ; assert_keyword: "assert" ; null_keyword: "NULL" ; +in_keyword: "in" ; escaped: "\", VISIBLE_CHAR ; str: quot, { escaped | UTF8_CHAR }, quot ; ``` @@ -142,8 +143,8 @@ non_operator_expr: literal ``` sum_op: plus | minus ; product_op: start | slash ; -compare_op: equalequal | gtequal | ltequal | gt | lt ; -binary_op: sum_op | product_op | dot | compare_op; +compare_op: equalequal | gtequal | ltequal | gt | lt | in_keyword ; +binary_op: sum_op | product_op | dot | compare_op ; binary_expr: non_operator_expr, binary_op, expr ; ``` diff --git a/integration_tests/comparisons_test.ucg b/integration_tests/comparisons_test.ucg index b8983af..daa277d 100644 --- a/integration_tests/comparisons_test.ucg +++ b/integration_tests/comparisons_test.ucg @@ -87,4 +87,28 @@ assert |(1+1) == (1+1);|; let want = "foo"; assert | select want, 1, { foo=2, } == 2; +|; + +// Contains comparison operators. +assert | + "foo" in { + foo = "bar", + }; +|; +assert | + foo in { + foo = "bar", + }; +|; +assert | + "foo" in ["foo", "bar"]; +|; +assert | + "foo" in ["bar", "foo", "bar"]; +|; +assert | + { foo = 1 } in ["foo", { foo = 1 }]; +|; +assert | + true in [ "foo" in {foo = 1}, false ]; |; \ No newline at end of file diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a2bf36c..a8e921a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -495,6 +495,7 @@ pub enum BinaryExprType { NotEqual, GTEqual, LTEqual, + IN, // Selector operator DOT, } @@ -512,6 +513,7 @@ impl BinaryExprType { BinaryExprType::LTEqual => 1, BinaryExprType::GT => 1, BinaryExprType::LT => 1, + BinaryExprType::IN => 1, // Sum operators are next least tightly bound BinaryExprType::Add => 2, BinaryExprType::Sub => 2, diff --git a/src/build/mod.rs b/src/build/mod.rs index 05d2717..fc88849 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -729,8 +729,58 @@ impl<'a> FileBuilder<'a> { } } + fn do_element_check( + &mut self, + left: &Expression, + right: &Expression, + scope: &Scope, + ) -> Result, Box> { + // First we evaluate our right hand side so we have a something to search + // inside for our left hand expression. + let right_pos = right.pos().clone(); + let right = self.eval_expr(right, scope)?; + // presence checks are only valid for tuples and lists. + if !(right.is_tuple() || right.is_list()) { + return Err(Box::new(error::BuildError::new( + format!( + "Invalid righthand type for in operator {}", + right.type_name() + ), + error::ErrorType::TypeFail, + right_pos, + ))); + } + if let &Val::List(ref els) = right.as_ref() { + let left_pos = left.pos().clone(); + let left = self.eval_expr(left, scope)?; + for val in els.iter() { + if let Ok(b) = self.do_deep_equal(&left_pos, left.clone(), val.clone()) { + if let &Val::Boolean(b) = b.as_ref() { + if b { + // We found a match + return Ok(Rc::new(Val::Boolean(true))); + } + } + } + } + // We didn't find a match anywhere so return false. + return Ok(Rc::new(Val::Boolean(false))); + } else { + // Handle our tuple case since this isn't a list. + let mut child_scope = scope.spawn_child(); + child_scope.set_curr_val(right.clone()); + // Search for the field in our tuple or list. + let maybe_val = self.do_dot_lookup(left, &child_scope); + // Return the result of the search. + return Ok(Rc::new(Val::Boolean(maybe_val.is_ok()))); + } + } + fn eval_binary(&mut self, def: &BinaryOpDef, scope: &Scope) -> Result, Box> { let kind = &def.kind; + if let &BinaryExprType::IN = kind { + return self.do_element_check(&def.left, &def.right, scope); + }; let left = self.eval_expr(&def.left, scope)?; let mut child_scope = scope.spawn_child(); child_scope.set_curr_val(left.clone()); @@ -756,8 +806,7 @@ impl<'a> FileBuilder<'a> { &BinaryExprType::GTEqual => self.do_gtequal(&def.pos, left, right), &BinaryExprType::LTEqual => self.do_ltequal(&def.pos, left, right), &BinaryExprType::NotEqual => self.do_not_deep_equal(&def.pos, left, right), - // TODO Handle the whole selector lookup logic here. - &BinaryExprType::DOT => panic!("Unraeachable"), + &BinaryExprType::IN | &BinaryExprType::DOT => panic!("Unreachable"), } } diff --git a/src/parse/precedence.rs b/src/parse/precedence.rs index e5d461b..d28af85 100644 --- a/src/parse/precedence.rs +++ b/src/parse/precedence.rs @@ -171,7 +171,8 @@ make_fn!( do_each!(_ => punct!("<="), (Element::Op(BinaryExprType::LTEqual))), do_each!(_ => punct!(">="), (Element::Op(BinaryExprType::GTEqual))), do_each!(_ => punct!("<"), (Element::Op(BinaryExprType::LT))), - do_each!(_ => punct!(">"), (Element::Op(BinaryExprType::GT))) + do_each!(_ => punct!(">"), (Element::Op(BinaryExprType::GT))), + do_each!(_ => word!("in"), (Element::Op(BinaryExprType::IN))) ) ); @@ -191,7 +192,8 @@ fn parse_compare_operator(i: SliceIter) -> Result, B | &BinaryExprType::LT | &BinaryExprType::LTEqual | &BinaryExprType::NotEqual - | &BinaryExprType::Equal => { + | &BinaryExprType::Equal + | &BinaryExprType::IN => { return Result::Complete(i_.clone(), op.clone()); } _other => { diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index 45f989d..87108c7 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -264,6 +264,10 @@ make_fn!(selecttok, do_text_token_tok!(TokenType::BAREWORD, "select", WS) ); +make_fn!(intok, + do_text_token_tok!(TokenType::BAREWORD, "in", WS) +); + make_fn!(macrotok, do_text_token_tok!(TokenType::BAREWORD, "macro", WS) ); @@ -385,6 +389,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result, Token> { leftsquarebracket, rightsquarebracket, booleantok, + intok, lettok, outtok, selecttok,