Add =~ and !~ operators on strings

`left =~ right` return true if left contains right, using Rust's
`String::contains`. `!~` is the negated version.

A new `apply_operator` function is added which decouples evaluation from
`Value::compare`. This returns a `Value` and opens the door to
implementing `+` for example, though it wouldn't be useful immediately.

The `operator!` macro had to be changed slightly as it would choke on
`~` in arguments.
This commit is contained in:
Belhorma Bendebiche 2019-11-21 12:18:00 -05:00
parent 06857fbc52
commit fbc6f01cfb
6 changed files with 176 additions and 11 deletions

View File

@ -1,5 +1,6 @@
use crate::data::base::Block; use crate::data::base::Block;
use crate::errors::ArgumentError; use crate::errors::ArgumentError;
use crate::evaluate::operator::apply_operator;
use crate::parser::hir::path::{ColumnPath, RawPathMember}; use crate::parser::hir::path::{ColumnPath, RawPathMember};
use crate::parser::{ use crate::parser::{
hir::{self, Expression, RawExpression}, hir::{self, Expression, RawExpression},
@ -79,8 +80,8 @@ pub(crate) fn evaluate_baseline_expr(
trace!("left={:?} right={:?}", left.item, right.item); trace!("left={:?} right={:?}", left.item, right.item);
match left.compare(binary.op(), &*right) { match apply_operator(binary.op(), &*left, &*right) {
Ok(result) => Ok(Value::boolean(result).tagged(tag)), Ok(result) => Ok(result.tagged(tag)),
Err((left_type, right_type)) => Err(ShellError::coerce_error( Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(binary.left().span), left_type.spanned(binary.left().span),
right_type.spanned(binary.right().span), right_type.spanned(binary.right().span),

View File

@ -1,3 +1,4 @@
pub(crate) mod evaluator; pub(crate) mod evaluator;
pub(crate) mod operator;
pub(crate) use evaluator::{evaluate_baseline_expr, Scope}; pub(crate) use evaluator::{evaluate_baseline_expr, Scope};

33
src/evaluate/operator.rs Normal file
View File

@ -0,0 +1,33 @@
use crate::data::Primitive;
use crate::data::Value;
use crate::parser::Operator;
use crate::traits::ShellTypeName;
use std::ops::Not;
pub fn apply_operator(
op: &Operator,
left: &Value,
right: &Value,
) -> Result<Value, (&'static str, &'static str)> {
match *op {
Operator::Equal
| Operator::NotEqual
| Operator::LessThan
| Operator::GreaterThan
| Operator::LessThanOrEqual
| Operator::GreaterThanOrEqual => left.compare(op, right).map(Value::boolean),
Operator::Dot => Ok(Value::boolean(false)),
Operator::Contains => contains(left, right).map(Value::boolean),
Operator::NotContains => contains(left, right).map(Not::not).map(Value::boolean),
}
}
fn contains(left: &Value, right: &Value) -> Result<bool, (&'static str, &'static str)> {
if let (Value::Primitive(Primitive::String(l)), Value::Primitive(Primitive::String(r))) =
(left, right)
{
Ok(l.contains(r))
} else {
Err((left.type_name(), right.type_name()))
}
}

View File

@ -12,6 +12,8 @@ pub enum Operator {
LessThanOrEqual, LessThanOrEqual,
GreaterThanOrEqual, GreaterThanOrEqual,
Dot, Dot,
Contains,
NotContains,
} }
impl FormatDebug for Operator { impl FormatDebug for Operator {
@ -34,6 +36,8 @@ impl Operator {
Operator::LessThanOrEqual => "<=", Operator::LessThanOrEqual => "<=",
Operator::GreaterThanOrEqual => ">=", Operator::GreaterThanOrEqual => ">=",
Operator::Dot => ".", Operator::Dot => ".",
Operator::Contains => "=~",
Operator::NotContains => "!~",
} }
} }
} }
@ -55,6 +59,8 @@ impl FromStr for Operator {
"<=" => Ok(Operator::LessThanOrEqual), "<=" => Ok(Operator::LessThanOrEqual),
">=" => Ok(Operator::GreaterThanOrEqual), ">=" => Ok(Operator::GreaterThanOrEqual),
"." => Ok(Operator::Dot), "." => Ok(Operator::Dot),
"=~" => Ok(Operator::Contains),
"!~" => Ok(Operator::NotContains),
_ => Err(()), _ => Err(()),
} }
} }

View File

@ -59,7 +59,7 @@ macro_rules! operator {
#[tracable_parser] #[tracable_parser]
pub fn $name(input: NomSpan) -> IResult<NomSpan, TokenNode> { pub fn $name(input: NomSpan) -> IResult<NomSpan, TokenNode> {
let start = input.offset; let start = input.offset;
let (input, tag) = tag(stringify!($token))(input)?; let (input, tag) = tag($token)(input)?;
let end = input.offset; let end = input.offset;
Ok(( Ok((
@ -70,13 +70,15 @@ macro_rules! operator {
}; };
} }
operator! { gt: > } operator! { gt: ">" }
operator! { lt: < } operator! { lt: "<" }
operator! { gte: >= } operator! { gte: ">=" }
operator! { lte: <= } operator! { lte: "<=" }
operator! { eq: == } operator! { eq: "==" }
operator! { neq: != } operator! { neq: "!=" }
operator! { dot: . } operator! { dot: "." }
operator! { cont: "=~" }
operator! { ncont: "!~" }
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
pub enum Number { pub enum Number {
@ -228,7 +230,7 @@ pub fn raw_number(input: NomSpan) -> IResult<NomSpan, Spanned<RawNumber>> {
#[tracable_parser] #[tracable_parser]
pub fn operator(input: NomSpan) -> IResult<NomSpan, TokenNode> { pub fn operator(input: NomSpan) -> IResult<NomSpan, TokenNode> {
let (input, operator) = alt((gte, lte, neq, gt, lt, eq))(input)?; let (input, operator) = alt((gte, lte, neq, gt, lt, eq, cont, ncont))(input)?;
Ok((input, operator)) Ok((input, operator))
} }
@ -830,6 +832,16 @@ mod tests {
<nodes> <nodes>
"!=" -> b::token_list(vec![b::op("!=")]) "!=" -> b::token_list(vec![b::op("!=")])
} }
equal_tokens! {
<nodes>
"=~" -> b::token_list(vec![b::op("=~")])
}
equal_tokens! {
<nodes>
"!~" -> b::token_list(vec![b::op("!~")])
}
} }
#[test] #[test]

112
tests/filter_where_tests.rs Normal file
View File

@ -0,0 +1,112 @@
mod helpers;
use helpers as h;
#[test]
fn test_compare() {
let actual = nu!(
cwd: "tests/fixtures/formats", h::pipeline(
r#"
open sample.db
| where table_name == ints
| get table_values
| first 4
| where z > 4200
| get z
| echo $it
"#
));
assert_eq!(actual, "4253");
let actual = nu!(
cwd: "tests/fixtures/formats", h::pipeline(
r#"
open sample.db
| where table_name == ints
| get table_values
| first 4
| where z >= 4253
| get z
| echo $it
"#
));
assert_eq!(actual, "4253");
let actual = nu!(
cwd: "tests/fixtures/formats", h::pipeline(
r#"
open sample.db
| where table_name == ints
| get table_values
| first 4
| where z < 10
| get z
| echo $it
"#
));
assert_eq!(actual, "1");
let actual = nu!(
cwd: "tests/fixtures/formats", h::pipeline(
r#"
open sample.db
| where table_name == ints
| get table_values
| first 4
| where z <= 1
| get z
| echo $it
"#
));
assert_eq!(actual, "1");
let actual = nu!(
cwd: "tests/fixtures/formats", h::pipeline(
r#"
open sample.db
| where table_name == ints
| get table_values
| where z != 1
| first 1
| get z
| echo $it
"#
));
assert_eq!(actual, "42");
}
#[test]
fn test_contains() {
let actual = nu!(
cwd: "tests/fixtures/formats", h::pipeline(
r#"
open sample.db
| where table_name == strings
| get table_values
| where x =~ ell
| count
| echo $it
"#
));
assert_eq!(actual, "4");
let actual = nu!(
cwd: "tests/fixtures/formats", h::pipeline(
r#"
open sample.db
| where table_name == strings
| get table_values
| where x !~ ell
| count
| echo $it
"#
));
assert_eq!(actual, "2");
}