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::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),
|
||||||
|
|
|
@ -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
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,
|
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(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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