FEATURE: add a conatins operator

Adds the `in` operator that checks for fields in tuples and
elements in a list.

Fixes: #12
This commit is contained in:
Jeremy Wall 2019-01-03 11:42:11 -06:00
parent d13556b4cd
commit 59343d71d5
6 changed files with 89 additions and 6 deletions

View File

@ -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 ;
```

View File

@ -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 ];
|;

View File

@ -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,

View File

@ -729,8 +729,58 @@ impl<'a> FileBuilder<'a> {
}
}
fn do_element_check(
&mut self,
left: &Expression,
right: &Expression,
scope: &Scope,
) -> Result<Rc<Val>, Box<Error>> {
// 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<Rc<Val>, Box<Error>> {
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"),
}
}

View File

@ -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<Element>) -> Result<SliceIter<Element>, B
| &BinaryExprType::LT
| &BinaryExprType::LTEqual
| &BinaryExprType::NotEqual
| &BinaryExprType::Equal => {
| &BinaryExprType::Equal
| &BinaryExprType::IN => {
return Result::Complete(i_.clone(), op.clone());
}
_other => {

View File

@ -264,6 +264,10 @@ make_fn!(selecttok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "select", WS)
);
make_fn!(intok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "in", WS)
);
make_fn!(macrotok<OffsetStrIter, Token>,
do_text_token_tok!(TokenType::BAREWORD, "macro", WS)
);
@ -385,6 +389,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
leftsquarebracket,
rightsquarebracket,
booleantok,
intok,
lettok,
outtok,
selecttok,