mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
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:
parent
06857fbc52
commit
fbc6f01cfb
6 changed files with 176 additions and 11 deletions
|
@ -1,5 +1,6 @@
|
|||
use crate::data::base::Block;
|
||||
use crate::errors::ArgumentError;
|
||||
use crate::evaluate::operator::apply_operator;
|
||||
use crate::parser::hir::path::{ColumnPath, RawPathMember};
|
||||
use crate::parser::{
|
||||
hir::{self, Expression, RawExpression},
|
||||
|
@ -79,8 +80,8 @@ pub(crate) fn evaluate_baseline_expr(
|
|||
|
||||
trace!("left={:?} right={:?}", left.item, right.item);
|
||||
|
||||
match left.compare(binary.op(), &*right) {
|
||||
Ok(result) => Ok(Value::boolean(result).tagged(tag)),
|
||||
match apply_operator(binary.op(), &*left, &*right) {
|
||||
Ok(result) => Ok(result.tagged(tag)),
|
||||
Err((left_type, right_type)) => Err(ShellError::coerce_error(
|
||||
left_type.spanned(binary.left().span),
|
||||
right_type.spanned(binary.right().span),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub(crate) mod evaluator;
|
||||
pub(crate) mod operator;
|
||||
|
||||
pub(crate) use evaluator::{evaluate_baseline_expr, Scope};
|
||||
|
|
33
src/evaluate/operator.rs
Normal file
33
src/evaluate/operator.rs
Normal 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()))
|
||||
}
|
||||
}
|
|
@ -12,6 +12,8 @@ pub enum Operator {
|
|||
LessThanOrEqual,
|
||||
GreaterThanOrEqual,
|
||||
Dot,
|
||||
Contains,
|
||||
NotContains,
|
||||
}
|
||||
|
||||
impl FormatDebug for Operator {
|
||||
|
@ -34,6 +36,8 @@ impl Operator {
|
|||
Operator::LessThanOrEqual => "<=",
|
||||
Operator::GreaterThanOrEqual => ">=",
|
||||
Operator::Dot => ".",
|
||||
Operator::Contains => "=~",
|
||||
Operator::NotContains => "!~",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +59,8 @@ impl FromStr for Operator {
|
|||
"<=" => Ok(Operator::LessThanOrEqual),
|
||||
">=" => Ok(Operator::GreaterThanOrEqual),
|
||||
"." => Ok(Operator::Dot),
|
||||
"=~" => Ok(Operator::Contains),
|
||||
"!~" => Ok(Operator::NotContains),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ macro_rules! operator {
|
|||
#[tracable_parser]
|
||||
pub fn $name(input: NomSpan) -> IResult<NomSpan, TokenNode> {
|
||||
let start = input.offset;
|
||||
let (input, tag) = tag(stringify!($token))(input)?;
|
||||
let (input, tag) = tag($token)(input)?;
|
||||
let end = input.offset;
|
||||
|
||||
Ok((
|
||||
|
@ -70,13 +70,15 @@ macro_rules! operator {
|
|||
};
|
||||
}
|
||||
|
||||
operator! { gt: > }
|
||||
operator! { lt: < }
|
||||
operator! { gte: >= }
|
||||
operator! { lte: <= }
|
||||
operator! { eq: == }
|
||||
operator! { neq: != }
|
||||
operator! { dot: . }
|
||||
operator! { gt: ">" }
|
||||
operator! { lt: "<" }
|
||||
operator! { gte: ">=" }
|
||||
operator! { lte: "<=" }
|
||||
operator! { eq: "==" }
|
||||
operator! { neq: "!=" }
|
||||
operator! { dot: "." }
|
||||
operator! { cont: "=~" }
|
||||
operator! { ncont: "!~" }
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum Number {
|
||||
|
@ -228,7 +230,7 @@ pub fn raw_number(input: NomSpan) -> IResult<NomSpan, Spanned<RawNumber>> {
|
|||
|
||||
#[tracable_parser]
|
||||
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))
|
||||
}
|
||||
|
@ -830,6 +832,16 @@ mod tests {
|
|||
<nodes>
|
||||
"!=" -> 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]
|
||||
|
|
112
tests/filter_where_tests.rs
Normal file
112
tests/filter_where_tests.rs
Normal 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");
|
||||
}
|
Loading…
Reference in a new issue